From 131868a280c3d303792df18505011294aae0c73b Mon Sep 17 00:00:00 2001 From: fahed Date: Thu, 30 Apr 2026 10:56:26 +0300 Subject: [PATCH] feat(report): per-museum trend lines in PDF report chart When multiple museums are present, the report trend chart now renders one colored line per museum plus a bold Total line, mirroring dashboard behavior. Legend is updated to list each museum with its corresponding color. Co-Authored-By: Claude Sonnet 4.6 --- src/components/Report/ReportDocument.tsx | 30 +++++++++++++++++------- src/components/Report/reportCharts.tsx | 16 +++++++++---- src/components/Report/reportHelpers.ts | 7 ++++++ 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/components/Report/ReportDocument.tsx b/src/components/Report/ReportDocument.tsx index c50c83e..f068dc5 100644 --- a/src/components/Report/ReportDocument.tsx +++ b/src/components/Report/ReportDocument.tsx @@ -156,7 +156,7 @@ interface Props { data: ReportData; } export function ReportDocument({ data }: Props) { const { config: cfg, metrics, prevMetrics, comparisonPeriodLabel, - trendLabels, trendCurrent, trendPrevious, + trendLabels, trendCurrent, trendPrevious, trendMuseums, museumData, channelBreakdown, districtBreakdown, pilgrimCapture, generatedAt } = data; @@ -314,21 +314,33 @@ export function ReportDocument({ data }: Props) { {cfg.showTrendChart && ( - {cfg.includeComparison && ( - - - - {period} + + {trendMuseums.length >= 2 && trendMuseums.map((m, i) => ( + + + {m.name} + ))} + + + {trendMuseums.length >= 2 ? `Total ยท ${period}` : period} + + {cfg.includeComparison && ( {comparisonPeriodLabel} - - )} + )} + + previous={trendPrevious} color={color} width={chartW} height={155} + series={trendMuseums.length >= 2 ? trendMuseums.map((m, i) => ({ + label: m.name, + color: CHART_PALETTE[i % CHART_PALETTE.length], + data: m.values, + })) : undefined} + /> )} diff --git a/src/components/Report/reportCharts.tsx b/src/components/Report/reportCharts.tsx index 2f56c57..b1b347e 100644 --- a/src/components/Report/reportCharts.tsx +++ b/src/components/Report/reportCharts.tsx @@ -19,12 +19,14 @@ interface TrendChartProps { current: number[]; previous: number[] | null; color: string; + series?: Array<{ label: string; color: string; data: number[] }>; width?: number; height?: number; } -export function PdfTrendChart({ labels, current, previous, color, width = 470, height = 155 }: TrendChartProps) { - const allValues = [...current, ...(previous ?? [])].filter(v => v > 0); +export function PdfTrendChart({ labels, current, previous, color, series, width = 470, height = 155 }: TrendChartProps) { + const seriesValues = (series ?? []).flatMap(s => s.data); + const allValues = [...current, ...(previous ?? []), ...seriesValues].filter(v => v > 0); const max = allValues.length > 0 ? Math.max(...allValues) : 1; // padL wide enough for y-axis labels like "1.2M" const padL = 38, padR = 8, padT = 10, padB = 20; @@ -66,10 +68,16 @@ export function PdfTrendChart({ labels, current, previous, color, width = 470, h stroke="#94a3b8" strokeWidth={1.5} strokeDasharray="4 3" fill="none" /> )} - {/* Current period line */} + {/* Per-museum series */} + {(series ?? []).map(s => s.data.some(v => v > 0) && ( + + ))} + + {/* Current period total line */} {current.some(v => v > 0) && ( + stroke={color} strokeWidth={series && series.length >= 2 ? 2 : 2.5} fill="none" /> )} {/* X-axis week labels */} diff --git a/src/components/Report/reportHelpers.ts b/src/components/Report/reportHelpers.ts index 070c0a0..5652308 100644 --- a/src/components/Report/reportHelpers.ts +++ b/src/components/Report/reportHelpers.ts @@ -106,6 +106,7 @@ export interface ReportData { trendLabels: string[]; trendCurrent: number[]; trendPrevious: number[] | null; + trendMuseums: Array<{ name: string; values: number[] }>; museumData: MuseumDataRow[]; museumBreakdown: DimensionBreakdown; channelBreakdown: DimensionBreakdown; @@ -195,6 +196,11 @@ export function computeReportData(allData: MuseumRecord[], cfg: ReportConfig): R ? Array.from({ length: maxLen }, (_, i) => prevTrend.values[i] ?? 0) : null; + const trendMuseums = Object.keys(groupByMuseum(currRows, cfg.includeVAT)).map(name => { + const mt = buildTrend(currRows.filter(r => r.museum_name === name), cfg.startDate, cfg); + return { name, values: Array.from({ length: maxLen }, (_, i) => mt.values[i] ?? 0) }; + }); + const currMuseumGroups = groupByMuseum(currRows, cfg.includeVAT); const prevMuseumGroups = cfg.includeComparison ? groupByMuseum(prevRows, cfg.includeVAT) : {}; const museumData: MuseumDataRow[] = Object.entries(currMuseumGroups) @@ -226,6 +232,7 @@ export function computeReportData(allData: MuseumRecord[], cfg: ReportConfig): R trendLabels, trendCurrent, trendPrevious, + trendMuseums, museumData, museumBreakdown, channelBreakdown,