import React from 'react'; import { Document, Page, View, Text, Image, StyleSheet } from '@react-pdf/renderer'; import { PdfTrendChart, PdfHBarChart } from './reportCharts'; import { ReportData, formatCurrency, formatPct, formatPeriodLabel, generateExecutiveSummary } from './reportHelpers'; const S = StyleSheet.create({ page: { fontFamily: 'Helvetica', fontSize: 9, color: '#0f172a', backgroundColor: '#ffffff' }, coverPage: { flexDirection: 'column', padding: 0 }, coverTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', paddingTop: 40, paddingRight: 50, paddingBottom: 0, paddingLeft: 50 }, coverLogoBox: { width: 80, height: 40, justifyContent: 'center' }, coverClientLogo: { width: 80, height: 40, objectFit: 'contain' as const }, coverHiHala: { fontSize: 13, fontFamily: 'Helvetica-Bold', color: '#2563eb', letterSpacing: 0.5 }, coverMiddle: { flex: 1, justifyContent: 'center', paddingHorizontal: 50, paddingTop: 80 }, coverTitle: { fontSize: 28, fontFamily: 'Helvetica-Bold', marginBottom: 16, lineHeight: 1.2 }, coverFor: { fontSize: 11, color: '#334155', marginBottom: 4 }, coverContact: { fontSize: 10, color: '#64748b', marginBottom: 32 }, coverPeriod: { fontSize: 10, color: '#64748b', fontFamily: 'Helvetica-Oblique', marginBottom: 6 }, coverDate: { fontSize: 9, color: '#94a3b8' }, coverBar: { height: 6, flex: 1 }, contentPage: { paddingTop: 32, paddingRight: 44, paddingBottom: 48, paddingLeft: 44 }, pageHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', borderBottomWidth: 1, borderBottomColor: '#e2e8f0', paddingBottom: 8, marginBottom: 24 }, pageHeaderTitle: { fontSize: 8, color: '#94a3b8' }, pageHeaderLogo: { fontSize: 9, fontFamily: 'Helvetica-Bold', color: '#2563eb' }, pageHeaderNum: { fontSize: 8, color: '#94a3b8' }, pageFooter: { position: 'absolute', bottom: 20, left: 44, right: 44, flexDirection: 'row', justifyContent: 'space-between' }, pageFooterText: { fontSize: 7, color: '#94a3b8' }, sectionHeading: { fontSize: 10, fontFamily: 'Helvetica-Bold', color: '#ffffff', paddingTop: 5, paddingRight: 10, paddingBottom: 5, paddingLeft: 10, marginBottom: 14, borderRadius: 3 }, summaryText: { fontSize: 9.5, color: '#334155', lineHeight: 1.6 }, metricsTable: { marginBottom: 8 }, metricsRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: '#f1f5f9', paddingVertical: 6 }, metricsRowAlt: { backgroundColor: '#f8fafc' }, metricsLabel: { flex: 1.5, fontSize: 9, color: '#334155', fontFamily: 'Helvetica-Bold' }, metricsValue: { flex: 1, fontSize: 9, color: '#0f172a', textAlign: 'right' }, metricsChange: { flex: 0.8, fontSize: 8, textAlign: 'right' }, metricsChangeUp: { color: '#059669' }, metricsChangeDown: { color: '#dc2626' }, metricsHeaderRow: { flexDirection: 'row', backgroundColor: '#f1f5f9', paddingTop: 4, paddingBottom: 4, marginBottom: 2 }, metricsHeaderCell: { flex: 1, fontSize: 7.5, fontFamily: 'Helvetica-Bold', color: '#64748b', textAlign: 'right' }, metricsHeaderLabel: { flex: 1.5, fontSize: 7.5, fontFamily: 'Helvetica-Bold', color: '#64748b' }, chartWrap: { marginBottom: 8, backgroundColor: '#f8fafc', padding: 12, borderRadius: 4 }, sectionGap: { marginBottom: 24 }, legendRow: { flexDirection: 'row', marginBottom: 8 }, legendItem: { flexDirection: 'row', alignItems: 'center', marginRight: 16 }, legendDot: { width: 8, height: 8, borderRadius: 4 }, legendLabel: { fontSize: 7.5, color: '#64748b', marginLeft: 4 }, }); function pctChange(curr: number, prev: number): number { if (prev === 0) return 0; return Math.round(((curr - prev) / prev) * 100); } interface PageHeaderProps { title: string; page: number; } function PageHeader({ title, page }: PageHeaderProps) { return ( HiHala Data {title} {page} ); } interface PageFooterProps { confidentiality: string; generatedAt: string; } function PageFooter({ confidentiality, generatedAt }: PageFooterProps) { return ( {confidentiality} Generated {generatedAt} ); } interface SectionProps { title: string; color: string; } function SectionHeading({ title, color }: SectionProps) { return ( {title} ); } interface Props { data: ReportData; } export function ReportDocument({ data }: Props) { const { config: cfg, metrics, prevMetrics, trendLabels, trendCurrent, trendPrevious, museumBreakdown, channelBreakdown, pilgrimCapture, generatedAt } = data; const lang = cfg.language; const color = cfg.accentColor; const period = formatPeriodLabel(cfg.startDate, cfg.endDate, lang); const orientation = cfg.orientation === 'landscape' ? 'landscape' : 'portrait'; const T = lang === 'en' ? LABELS_EN : LABELS_AR; const metricsRows = [ { label: T.revenue, curr: formatCurrency(metrics.revenue, cfg.includeVAT), prev: prevMetrics ? formatCurrency(prevMetrics.revenue, cfg.includeVAT) : null, chg: prevMetrics ? pctChange(metrics.revenue, prevMetrics.revenue) : null }, { label: T.visitors, curr: metrics.visitors.toLocaleString(), prev: prevMetrics ? prevMetrics.visitors.toLocaleString() : null, chg: prevMetrics ? pctChange(metrics.visitors, prevMetrics.visitors) : null }, { label: T.tickets, curr: metrics.tickets.toLocaleString(), prev: prevMetrics ? prevMetrics.tickets.toLocaleString() : null, chg: prevMetrics ? pctChange(metrics.tickets, prevMetrics.tickets) : null }, { label: T.avgRev, curr: formatCurrency(metrics.avgRevPerVisitor, false), prev: prevMetrics ? formatCurrency(prevMetrics.avgRevPerVisitor, false) : null, chg: prevMetrics ? pctChange(metrics.avgRevPerVisitor, prevMetrics.avgRevPerVisitor) : null }, ...(cfg.showPilgrimCapture && pilgrimCapture ? [{ label: T.capture, curr: `${pilgrimCapture.current}%`, prev: pilgrimCapture.previous !== null ? `${pilgrimCapture.previous}%` : null, chg: pilgrimCapture.previous !== null ? pctChange(pilgrimCapture.current, pilgrimCapture.previous) : null, }] : []), ]; const prevYear = parseInt(cfg.startDate.slice(0, 4)) - 1; return ( HiHala Data {cfg.clientLogoBase64 && ( )} {cfg.title || T.defaultTitle} {cfg.clientName && {T.preparedFor}: {cfg.clientName}} {cfg.contactName && {T.attention}: {cfg.contactName}} {period} {T.generated}: {generatedAt} {cfg.showExecutiveSummary && ( {generateExecutiveSummary(data)} )} {cfg.showMetricsTable && ( {period} {prevMetrics && {prevYear}} {prevMetrics && {T.change}} {metricsRows.map((row, i) => ( {row.label} {row.curr} {prevMetrics && {row.prev ?? '—'}} {prevMetrics && row.chg !== null && ( = 0 ? S.metricsChangeUp : S.metricsChangeDown]}> {formatPct(row.chg)} )} ))} )} {cfg.showTrendChart && ( {cfg.includeComparison && ( {period} {prevYear} )} )} {(cfg.showMuseumBreakdown || cfg.showChannelBreakdown) && ( {cfg.showMuseumBreakdown && museumBreakdown.length > 0 && ( )} {cfg.showChannelBreakdown && channelBreakdown.length > 0 && ( )} )} ); } const LABELS_EN = { defaultTitle: 'Performance Report', preparedFor: 'Prepared for', attention: 'Attention', generated: 'Generated', execSummary: 'Executive Summary', keyMetrics: 'Key Metrics', change: 'vs Prior Year', trend: 'Revenue Trend', byMuseum: 'Revenue by Museum', byChannel: 'Visitors by Channel', revenue: 'Revenue', visitors: 'Visitors', tickets: 'Tickets', avgRev: 'Avg Rev / Visitor', capture: 'Pilgrim Capture Rate', }; const LABELS_AR = { defaultTitle: 'تقرير الأداء', preparedFor: 'مُعدّ لـ', attention: 'عناية', generated: 'تاريخ الإصدار', execSummary: 'الملخص التنفيذي', keyMetrics: 'المؤشرات الرئيسية', change: 'مقابل العام السابق', trend: 'اتجاه الإيرادات', byMuseum: 'الإيرادات حسب المتحف', byChannel: 'الزوار حسب القناة', revenue: 'الإيرادات', visitors: 'الزوار', tickets: 'التذاكر', avgRev: 'متوسط الإيراد / زائر', capture: 'معدل استيعاب الحجاج', };