diff --git a/public/fonts/IBMPlexSansArabic-Bold.ttf b/public/fonts/IBMPlexSansArabic-Bold.ttf new file mode 100644 index 0000000..b49cf1f --- /dev/null +++ b/public/fonts/IBMPlexSansArabic-Bold.ttf @@ -0,0 +1,1483 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/public/fonts/IBMPlexSansArabic-Bold.woff2 b/public/fonts/IBMPlexSansArabic-Bold.woff2 new file mode 100644 index 0000000..08e2028 Binary files /dev/null and b/public/fonts/IBMPlexSansArabic-Bold.woff2 differ diff --git a/public/fonts/IBMPlexSansArabic-Latin-Regular.woff2 b/public/fonts/IBMPlexSansArabic-Latin-Regular.woff2 new file mode 100644 index 0000000..6091e04 Binary files /dev/null and b/public/fonts/IBMPlexSansArabic-Latin-Regular.woff2 differ diff --git a/public/fonts/IBMPlexSansArabic-Regular.ttf b/public/fonts/IBMPlexSansArabic-Regular.ttf new file mode 100644 index 0000000..2f58f63 --- /dev/null +++ b/public/fonts/IBMPlexSansArabic-Regular.ttf @@ -0,0 +1,1483 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/public/fonts/IBMPlexSansArabic-Regular.woff2 b/public/fonts/IBMPlexSansArabic-Regular.woff2 new file mode 100644 index 0000000..695d303 Binary files /dev/null and b/public/fonts/IBMPlexSansArabic-Regular.woff2 differ diff --git a/public/fonts/NotoSansArabic-Bold.ttf b/public/fonts/NotoSansArabic-Bold.ttf new file mode 100644 index 0000000..f7b4a76 --- /dev/null +++ b/public/fonts/NotoSansArabic-Bold.ttf @@ -0,0 +1,1483 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/public/fonts/NotoSansArabic-Regular.ttf b/public/fonts/NotoSansArabic-Regular.ttf new file mode 100644 index 0000000..4d97fe8 --- /dev/null +++ b/public/fonts/NotoSansArabic-Regular.ttf @@ -0,0 +1,1483 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/src/components/Comparison.tsx b/src/components/Comparison.tsx index 4bcc487..92d3e44 100644 --- a/src/components/Comparison.tsx +++ b/src/components/Comparison.tsx @@ -6,7 +6,7 @@ import { getUniqueChannels, getUniqueMuseums, getUniqueDistricts, umrahData } from '../services/dataService'; -import { chartColors, chartPalette, createBaseOptions } from '../config/chartConfig'; +import { chartColors, chartPalette, createBaseOptions, TOTAL_COLOR } from '../config/chartConfig'; import type { MuseumRecord, Season } from '../types'; import { useLanguage } from '../contexts/LanguageContext'; import type { LC } from '../lib/locale'; @@ -187,7 +187,7 @@ export default function PeriodSelectorDemo({ data, seasons, includeVAT, allowedM datasets: [ { label:periodLabel(prevStart,prevEnd), data:labels.map((_,i) => pg[i+1]||0), borderColor:chartColors.muted, backgroundColor:'transparent', borderWidth:2, tension:0.4, pointRadius:gran==='week'?3:1, pointBackgroundColor:chartColors.muted }, ...museumDatasets, - { label: multiMuseum ? `Total · ${periodLabel(currStart,currEnd)}` : periodLabel(currStart,currEnd), data:labels.map((_,i) => cg[i+1]||0), borderColor:chartColors.primary, backgroundColor: multiMuseum ? 'transparent' : chartColors.primary+'15', borderWidth:2.5, tension:0.4, fill: !multiMuseum, pointRadius:gran==='week'?4:2, pointBackgroundColor:chartColors.primary }, + { label: multiMuseum ? `Total · ${periodLabel(currStart,currEnd)}` : periodLabel(currStart,currEnd), data:labels.map((_,i) => cg[i+1]||0), borderColor:TOTAL_COLOR, backgroundColor: multiMuseum ? 'transparent' : TOTAL_COLOR+'15', borderWidth:2.5, tension:0.4, fill: !multiMuseum, pointRadius:gran==='week'?4:2, pointBackgroundColor:TOTAL_COLOR }, ] } }; diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index d18a48f..7a2c57d 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -6,7 +6,7 @@ import { groupByMuseum, groupByChannel, groupByDistrict, umrahData, } from '../services/dataService'; -import { chartColors, chartPalette, createBaseOptions } from '../config/chartConfig'; +import { chartColors, chartPalette, createBaseOptions, TOTAL_COLOR } from '../config/chartConfig'; import type { MuseumRecord, Season } from '../types'; import { useLanguage } from '../contexts/LanguageContext'; import { EN, AR } from '../lib/locale'; @@ -148,7 +148,7 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set datasets: [ { label:`${prevYear}`, data:labels.map((_,i) => pg[i+1]||0), borderColor:chartColors.muted, backgroundColor:'transparent', borderWidth:1.5, tension:0.4, pointRadius:0, borderDash:[4,3] }, ...museumDatasets, - { label: multiMuseum ? `Total ${start.slice(0,4)}` : start.slice(0,4), data:labels.map((_,i) => cg[i+1]||0), borderColor:chartColors.primary, backgroundColor: multiMuseum ? 'transparent' : chartColors.primary+'18', borderWidth:2.5, tension:0.4, fill: !multiMuseum, pointRadius:gran==='week'?3:1, pointBackgroundColor:chartColors.primary }, + { label: multiMuseum ? `Total ${start.slice(0,4)}` : start.slice(0,4), data:labels.map((_,i) => cg[i+1]||0), borderColor:TOTAL_COLOR, backgroundColor: multiMuseum ? 'transparent' : TOTAL_COLOR+'18', borderWidth:2.5, tension:0.4, fill: !multiMuseum, pointRadius:gran==='week'?3:1, pointBackgroundColor:TOTAL_COLOR }, ] } }; diff --git a/src/components/Report/ReportDocument.tsx b/src/components/Report/ReportDocument.tsx index f068dc5..f6c6f51 100644 --- a/src/components/Report/ReportDocument.tsx +++ b/src/components/Report/ReportDocument.tsx @@ -1,12 +1,22 @@ import React from 'react'; import { - Document, Page, View, Text, Image, StyleSheet + Document, Page, View, Text, Image, StyleSheet, Font } from '@react-pdf/renderer'; import { PdfTrendChart, PdfHBarChart, CHART_PALETTE } from './reportCharts'; import { ReportData, MuseumDataRow, formatCurrency, formatPct, formatPeriodLabel, generateExecutiveSummary } from './reportHelpers'; +Font.register({ + family: 'IBMPlexArabic', + fonts: [ + { src: '/fonts/IBMPlexSansArabic-Regular.woff2', fontWeight: 400 }, + { src: '/fonts/IBMPlexSansArabic-Bold.woff2', fontWeight: 700 }, + ], +}); + +const TOTAL_LINE_COLOR = '#1e293b'; + // A4 content width minus chart-wrap padding (14×2) // Portrait: 595 - 44 - 44 - 28 = 479 // Landscape: 842 - 44 - 44 - 28 = 726 @@ -17,14 +27,12 @@ const S = StyleSheet.create({ // ── Cover ────────────────────────────────────────────── coverPage: { flexDirection: 'column', padding: 0 }, - // colored header band coverHeader: { paddingTop: 56, paddingRight: 52, paddingBottom: 52, paddingLeft: 52 }, coverHeaderTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 48 }, coverBrand: { fontSize: 12, fontFamily: 'Helvetica-Bold', color: '#ffffff', letterSpacing: 0.8 }, coverLogoBox: { width: 90, height: 44, justifyContent: 'flex-end', alignItems: 'flex-end' }, coverClientLogo: { width: 90, height: 44, objectFit: 'contain' as const }, coverTitle: { fontSize: 36, fontFamily: 'Helvetica-Bold', color: '#ffffff', lineHeight: 1.2 }, - // white body coverBody: { flex: 1, paddingTop: 44, paddingRight: 52, paddingBottom: 44, paddingLeft: 52, flexDirection: 'column' }, coverClientName: { fontSize: 15, color: '#0f172a', fontFamily: 'Helvetica-Bold', marginBottom: 5 }, coverContactName: { fontSize: 11, color: '#64748b', marginBottom: 32 }, @@ -66,8 +74,8 @@ const S = StyleSheet.create({ // ── Trend chart ──────────────────────────────────────── chartWrap: { marginBottom: 8, backgroundColor: '#f8fafc', paddingTop: 14, paddingRight: 14, paddingBottom: 14, paddingLeft: 14, borderRadius: 6, borderWidth: 1, borderColor: '#f1f5f9' }, - legendRow: { flexDirection: 'row', marginBottom: 10 }, - legendItem: { flexDirection: 'row', alignItems: 'center', marginRight: 18 }, + legendRow: { flexDirection: 'row', flexWrap: 'wrap', marginBottom: 10 }, + legendItem: { flexDirection: 'row', alignItems: 'center', marginRight: 18, marginBottom: 4 }, legendDot: { width: 8, height: 8, borderRadius: 4 }, legendLabel: { fontSize: 8, color: '#64748b', marginLeft: 5 }, @@ -122,12 +130,12 @@ function museumIntro(row: MuseumDataRow, lang: 'en' | 'ar', compLabel: string): return `الإيرادات ${revChg >= 0 ? 'ارتفعت' : 'انخفضت'} ${Math.abs(revChg)}%، الزوار ${visChg >= 0 ? 'ارتفعوا' : 'انخفضوا'} ${Math.abs(visChg)}% مقارنةً بـ${compLabel}.`; } -interface PageHeaderProps { title: string; page: number; } -function PageHeader({ title, page }: PageHeaderProps) { +interface PageHeaderProps { title: string; page: number; isAr: boolean; arB: any; } +function PageHeader({ title, page, isAr, arB }: PageHeaderProps) { return ( - HiHala Data - {title} + HiHala Data + {title} {page} ); @@ -143,11 +151,11 @@ function PageFooter({ confidentiality, generatedAt }: PageFooterProps) { ); } -interface SectionProps { title: string; color: string; } -function SectionHeading({ title, color }: SectionProps) { +interface SectionProps { title: string; color: string; arB: any; } +function SectionHeading({ title, color, arB }: SectionProps) { return ( - {title} + {title} ); } @@ -156,18 +164,24 @@ interface Props { data: ReportData; } export function ReportDocument({ data }: Props) { const { config: cfg, metrics, prevMetrics, comparisonPeriodLabel, - trendLabels, trendCurrent, trendPrevious, trendMuseums, + trendCharts, museumData, channelBreakdown, districtBreakdown, pilgrimCapture, generatedAt } = data; const lang = cfg.language; + const isAr = lang === 'ar'; const color = cfg.accentColor; const period = formatPeriodLabel(cfg.startDate, cfg.endDate, lang); const isLandscape = cfg.orientation === 'landscape'; const orientation = isLandscape ? 'landscape' : 'portrait'; const T = lang === 'en' ? LABELS_EN : LABELS_AR; - // Chart width adapts to orientation + // Arabic font overrides — Helvetica has no Arabic glyphs; cast as any so style arrays stay compatible + const arN: any = isAr ? { fontFamily: 'IBMPlexArabic', fontWeight: 400 } : {}; + const arB: any = isAr ? { fontFamily: 'IBMPlexArabic', fontWeight: 700 } : {}; + // direction: 'rtl' flips flex-row children right-to-left; fontFamily cascades to elements without an explicit one + const arPageExtra: any = isAr ? { direction: 'rtl', fontFamily: 'IBMPlexArabic' } : {}; + const chartW = isLandscape ? CHART_W.landscape : CHART_W.portrait; const avgTicketPrice = metrics.tickets > 0 ? metrics.revenue / metrics.tickets : 0; @@ -197,10 +211,6 @@ export function ReportDocument({ data }: Props) { }] : []), ]; - const trendTitle = cfg.trendMetric === 'visitors' ? T.trendVisitors - : cfg.trendMetric === 'tickets' ? T.trendTickets - : T.trendRevenue; - const showMuseumPage = cfg.showMuseumRevenue || cfg.showMuseumVisitors || cfg.showMuseumTickets; const showChannelPage = cfg.showChannelRevenue || cfg.showChannelVisitors || cfg.showChannelTickets; const showDistrictPage = cfg.showDistrictRevenue || cfg.showDistrictVisitors || cfg.showDistrictTickets; @@ -240,32 +250,30 @@ export function ReportDocument({ data }: Props) { {/* ── Cover ─────────────────────────────────────────── */} - - {/* Colored header band */} + - HiHala Data + HiHala Data {cfg.clientLogoBase64 && ( )} - {cfg.title || T.defaultTitle} + {cfg.title || T.defaultTitle} - {/* White body */} {cfg.clientName && ( - {T.preparedFor}: {cfg.clientName} + {T.preparedFor}: {cfg.clientName} )} {cfg.contactName && ( - {T.attention}: {cfg.contactName} + {T.attention}: {cfg.contactName} )} - {period} + {period} {T.generated}: {generatedAt} {cfg.confidentiality !== 'Public' && ( @@ -275,34 +283,34 @@ export function ReportDocument({ data }: Props) { {/* ── Summary + Metrics + Trend ──────────────────────── */} - - + + {cfg.showExecutiveSummary && ( - - {generateExecutiveSummary(data)} + + {generateExecutiveSummary(data)} )} {cfg.showMetricsTable && ( - + - - {period} - {prevMetrics && {comparisonPeriodLabel}} - {prevMetrics && {T.change}} + + {period} + {prevMetrics && {comparisonPeriodLabel}} + {prevMetrics && {T.change}} {metricsRows.map((row, i) => ( - {row.label} - {row.curr} - {prevMetrics && {row.prev ?? '—'}} + {row.label} + {row.curr} + {prevMetrics && {row.prev ?? '—'}} {prevMetrics && row.chg !== null && ( = 0 ? S.metricsChangeUp : S.metricsChangeDown]}> - {row.chg >= 0 ? '+' : '-'}{formatPct(Math.abs(row.chg))} + {formatPct(row.chg)} )} @@ -311,75 +319,85 @@ export function ReportDocument({ data }: Props) { )} - {cfg.showTrendChart && ( - - - - {trendMuseums.length >= 2 && trendMuseums.map((m, i) => ( - - - {m.name} - - ))} - - - {trendMuseums.length >= 2 ? `Total · ${period}` : period} - - {cfg.includeComparison && ( + {cfg.showTrendChart && trendCharts.map((tc, tci) => { + const trendTitle = tc.metric === 'visitors' ? T.trendVisitors + : tc.metric === 'tickets' ? T.trendTickets + : T.trendRevenue; + return ( + + + + {tc.museums.length >= 2 && tc.museums.map((m, i) => ( + + + {m.name} + + ))} - - {comparisonPeriodLabel} + + {tc.museums.length >= 2 ? `Total · ${period}` : period} - )} + {cfg.includeComparison && tc.previous && ( + + + {comparisonPeriodLabel} + + )} + + + = 2 ? tc.museums.map((m, i) => ({ + label: m.name, + color: CHART_PALETTE[i % CHART_PALETTE.length], + data: m.values, + })) : undefined} + /> + - - = 2 ? trendMuseums.map((m, i) => ({ - label: m.name, - color: CHART_PALETTE[i % CHART_PALETTE.length], - data: m.values, - })) : undefined} - /> - - - )} + ); + })} {/* ── Museum Mini-Reports ────────────────────────────── */} {showMuseumPage && museumData.length > 0 && ( - - - + + + {museumData.map((row, mi) => { const mRows = museumMetricRows(row); const hasPrev = row.prev !== null; return ( - {row.name} + {row.name} {hasPrev && ( - + {museumIntro(row, lang, comparisonPeriodLabel)} )} - - {period} - {hasPrev && {comparisonPeriodLabel}} - {hasPrev && {T.change}} + + {period} + {hasPrev && {comparisonPeriodLabel}} + {hasPrev && {T.change}} {mRows.map((mr, ri) => ( - {mr.label} - {mr.curr} - {hasPrev && {mr.prev ?? '—'}} + {mr.label} + {mr.curr} + {hasPrev && {mr.prev ?? '—'}} {hasPrev && mr.chg !== null && ( = 0 ? S.miniChangeUp : S.miniChangeDown]}> - {mr.chg >= 0 ? '+' : '-'}{formatPct(Math.abs(mr.chg))} + {formatPct(mr.chg)} )} @@ -395,12 +413,12 @@ export function ReportDocument({ data }: Props) { {/* ── Channel Breakdowns ─────────────────────────────── */} {showChannelPage && ( - - + + {cfg.showChannelRevenue && channelBreakdown.revenue.length > 0 && ( - + @@ -408,7 +426,7 @@ export function ReportDocument({ data }: Props) { )} {cfg.showChannelVisitors && channelBreakdown.visitors.length > 0 && ( - + @@ -416,7 +434,7 @@ export function ReportDocument({ data }: Props) { )} {cfg.showChannelTickets && channelBreakdown.tickets.length > 0 && ( - + @@ -429,12 +447,12 @@ export function ReportDocument({ data }: Props) { {/* ── District Breakdowns ────────────────────────────── */} {showDistrictPage && ( - - + + {cfg.showDistrictRevenue && districtBreakdown.revenue.length > 0 && ( - + @@ -442,7 +460,7 @@ export function ReportDocument({ data }: Props) { )} {cfg.showDistrictVisitors && districtBreakdown.visitors.length > 0 && ( - + @@ -450,7 +468,7 @@ export function ReportDocument({ data }: Props) { )} {cfg.showDistrictTickets && districtBreakdown.tickets.length > 0 && ( - + @@ -463,27 +481,27 @@ export function ReportDocument({ data }: Props) { {/* ── Global Performance Summary ─────────────────────── */} {showSummaryPage && museumData.length > 0 && ( - - - + + + - + {period} — {T.comparedTo} {comparisonPeriodLabel} - {T.museum} + {T.museum} {cfg.showMuseumRevenue && <> - {T.revenue} - Δ + {T.revenue} + Δ } {cfg.showMuseumVisitors && <> - {T.visitors} - Δ + {T.visitors} + Δ } {cfg.showMuseumTickets && <> - {T.tickets} - Δ + {T.tickets} + Δ } @@ -491,26 +509,26 @@ export function ReportDocument({ data }: Props) { const hasPrev = row.prev !== null; return ( - {row.name.length > 30 ? row.name.slice(0, 30) + '…' : row.name} + {row.name.length > 30 ? row.name.slice(0, 30) + '…' : row.name} {cfg.showMuseumRevenue && <> - {formatCurrency(row.curr.revenue, cfg.includeVAT)} + {formatCurrency(row.curr.revenue, cfg.includeVAT)} {hasPrev && row.prev ? (() => { const c = pctChange(row.curr.revenue, row.prev!.revenue); - return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{c >= 0 ? '+' : '-'}{formatPct(Math.abs(c))}; + return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{formatPct(c)}; })() : } } {cfg.showMuseumVisitors && <> - {row.curr.visitors.toLocaleString()} + {row.curr.visitors.toLocaleString()} {hasPrev && row.prev ? (() => { const c = pctChange(row.curr.visitors, row.prev!.visitors); - return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{c >= 0 ? '+' : '-'}{formatPct(Math.abs(c))}; + return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{formatPct(c)}; })() : } } {cfg.showMuseumTickets && <> - {row.curr.tickets.toLocaleString()} + {row.curr.tickets.toLocaleString()} {hasPrev && row.prev ? (() => { const c = pctChange(row.curr.tickets, row.prev!.tickets); - return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{c >= 0 ? '+' : '-'}{formatPct(Math.abs(c))}; + return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{formatPct(c)}; })() : } } @@ -518,26 +536,26 @@ export function ReportDocument({ data }: Props) { })} - {T.total} + {T.total} {cfg.showMuseumRevenue && <> - {formatCurrency(metrics.revenue, cfg.includeVAT)} + {formatCurrency(metrics.revenue, cfg.includeVAT)} {prevMetrics ? (() => { const c = pctChange(metrics.revenue, prevMetrics.revenue); - return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{c >= 0 ? '+' : '-'}{formatPct(Math.abs(c))}; + return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{formatPct(c)}; })() : } } {cfg.showMuseumVisitors && <> - {metrics.visitors.toLocaleString()} + {metrics.visitors.toLocaleString()} {prevMetrics ? (() => { const c = pctChange(metrics.visitors, prevMetrics.visitors); - return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{c >= 0 ? '+' : '-'}{formatPct(Math.abs(c))}; + return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{formatPct(c)}; })() : } } {cfg.showMuseumTickets && <> - {metrics.tickets.toLocaleString()} + {metrics.tickets.toLocaleString()} {prevMetrics ? (() => { const c = pctChange(metrics.tickets, prevMetrics.tickets); - return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{c >= 0 ? '+' : '-'}{formatPct(Math.abs(c))}; + return = 0 ? S.summaryDeltaUp : S.summaryDeltaDown]}>{formatPct(c)}; })() : } } diff --git a/src/components/Report/ReportForm.tsx b/src/components/Report/ReportForm.tsx index c8d4857..1375b70 100644 --- a/src/components/Report/ReportForm.tsx +++ b/src/components/Report/ReportForm.tsx @@ -336,21 +336,28 @@ export default function ReportForm({ config: cfg, onChange, allMuseums, allChann title="Trend Chart" enabled={cfg.showTrendChart} onToggle={v => onChange({ showTrendChart: v })} - badge={cfg.showTrendChart - ? cfg.trendMetric.charAt(0).toUpperCase() + cfg.trendMetric.slice(1) + badge={cfg.showTrendChart && cfg.trendMetrics.length + ? cfg.trendMetrics.map(m => m.charAt(0).toUpperCase() + m.slice(1)).join(' · ') : undefined} > - {/* H7: PillGroup instead of