diff --git a/src/components/Report/ReportDocument.tsx b/src/components/Report/ReportDocument.tsx
new file mode 100644
index 0000000..e7a50f0
--- /dev/null
+++ b/src/components/Report/ReportDocument.tsx
@@ -0,0 +1,268 @@
+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: 'معدل استيعاب الحجاج',
+};