This commit is contained in:
Carl-Gerhard Lindesvärd
2025-11-21 11:21:17 +01:00
parent dd71fd4e11
commit 06fb6c4f3c
40 changed files with 2944 additions and 1972 deletions

View File

@@ -1,62 +1,197 @@
import { ButtonContainer } from '@/components/button-container';
import { Button } from '@/components/ui/button';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { useTRPC } from '@/integrations/trpc/react';
import type { IChartData } from '@/trpc/client';
import type { IChartEvent, IChartInput } from '@openpanel/validation';
import { useQuery } from '@tanstack/react-query';
import { UsersIcon } from 'lucide-react';
import { useMemo, useState } from 'react';
import { popModal } from '.';
import { ModalContent, ModalHeader } from './Modal/Container';
import type { IChartEvent } from '@openpanel/validation';
interface ViewChartUsersProps {
projectId: string;
event: IChartEvent;
chartData: IChartData;
report: IChartInput;
date: string;
breakdowns?: Array<{ id?: string; name: string }>;
interval: string;
startDate: string;
endDate: string;
filters?: Array<{
id?: string;
name: string;
operator: string;
value: Array<string | number | boolean | null>;
}>;
}
export default function ViewChartUsers({
projectId,
event,
chartData,
report,
date,
breakdowns = [],
interval,
startDate,
endDate,
filters = [],
}: ViewChartUsersProps) {
const trpc = useTRPC();
const query = useQuery(
trpc.chart.getProfiles.queryOptions({
projectId,
event,
date,
breakdowns,
interval: interval as any,
startDate,
endDate,
filters,
}),
// Group series by base event/formula (ignoring breakdowns)
const baseSeries = useMemo(() => {
const grouped = new Map<
string,
{
baseName: string;
baseEventId: string;
reportSerie: IChartInput['series'][0] | undefined;
breakdownSeries: Array<{
serie: IChartData['series'][0];
breakdowns: Record<string, string> | undefined;
}>;
}
>();
chartData.series.forEach((serie) => {
const baseEventId = serie.event.id || '';
const baseName = serie.names[0] || 'Unnamed Serie';
if (!grouped.has(baseEventId)) {
const reportSerie = report.series.find((ss) => ss.id === baseEventId);
grouped.set(baseEventId, {
baseName,
baseEventId,
reportSerie,
breakdownSeries: [],
});
}
const group = grouped.get(baseEventId);
if (!group) return;
// Extract breakdowns from serie.event.breakdowns (set in format.ts)
const breakdowns = (serie.event as any).breakdowns;
group.breakdownSeries.push({
serie,
breakdowns,
});
});
return Array.from(grouped.values());
}, [chartData.series, report.series, report.breakdowns]);
const [selectedBaseSerieId, setSelectedBaseSerieId] = useState<string | null>(
null,
);
const [selectedBreakdownIndex, setSelectedBreakdownIndex] = useState<
number | null
>(null);
const selectedBaseSerie = useMemo(
() => baseSeries.find((bs) => bs.baseEventId === selectedBaseSerieId),
[baseSeries, selectedBaseSerieId],
);
const profiles = query.data ?? [];
const selectedBreakdown = useMemo(() => {
if (
!selectedBaseSerie ||
selectedBreakdownIndex === null ||
!selectedBaseSerie.breakdownSeries[selectedBreakdownIndex]
) {
return null;
}
return selectedBaseSerie.breakdownSeries[selectedBreakdownIndex];
}, [selectedBaseSerie, selectedBreakdownIndex]);
// Reset breakdown selection when base serie changes
const handleBaseSerieChange = (value: string) => {
setSelectedBaseSerieId(value);
setSelectedBreakdownIndex(null);
};
const selectedSerie = selectedBreakdown || selectedBaseSerie;
const profilesQuery = useQuery(
trpc.chart.getProfiles.queryOptions(
{
projectId: report.projectId,
date: date,
series:
selectedSerie &&
selectedBaseSerie?.reportSerie &&
selectedBaseSerie.reportSerie.type === 'event'
? [selectedBaseSerie.reportSerie]
: [],
breakdowns: selectedBreakdown?.breakdowns,
interval: report.interval,
},
{
enabled:
!!selectedSerie &&
!!selectedBaseSerie?.reportSerie &&
selectedBaseSerie.reportSerie.type === 'event',
},
),
);
const profiles = profilesQuery.data ?? [];
return (
<ModalContent>
<ModalHeader
title="View Users"
description={`Users who triggered this event on ${new Date(date).toLocaleDateString()}`}
/>
<ModalHeader title="View Users" />
<p className="text-sm text-muted-foreground mb-4">
Users who performed actions on {new Date(date).toLocaleDateString()}
</p>
<div className="flex flex-col gap-4">
{query.isLoading ? (
{baseSeries.length > 0 && (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<label className="text-sm font-medium">Serie:</label>
<Select
value={selectedBaseSerieId || ''}
onValueChange={handleBaseSerieChange}
>
<SelectTrigger className="w-[200px]">
<SelectValue placeholder="Select Serie" />
</SelectTrigger>
<SelectContent>
{baseSeries.map((baseSerie) => (
<SelectItem
key={baseSerie.baseEventId}
value={baseSerie.baseEventId}
>
{baseSerie.baseName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{selectedBaseSerie &&
selectedBaseSerie.breakdownSeries.length > 1 && (
<div className="flex items-center gap-2">
<label className="text-sm font-medium">Breakdown:</label>
<Select
value={selectedBreakdownIndex?.toString() || ''}
onValueChange={(value) =>
setSelectedBreakdownIndex(
value ? Number.parseInt(value, 10) : null,
)
}
>
<SelectTrigger className="w-[200px]">
<SelectValue placeholder="All Breakdowns" />
</SelectTrigger>
<SelectContent>
<SelectItem value="">All Breakdowns</SelectItem>
{selectedBaseSerie.breakdownSeries.map((bdSerie, idx) => (
<SelectItem
key={bdSerie.serie.id}
value={idx.toString()}
>
{bdSerie.serie.names.slice(1).join(' > ') ||
'No Breakdown'}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
</div>
)}
{profilesQuery.isLoading ? (
<div className="flex items-center justify-center py-8">
<div className="text-muted-foreground">Loading users...</div>
</div>
@@ -109,4 +244,3 @@ export default function ViewChartUsers({
</ModalContent>
);
}