/** * Pure data + types for the dashboard-PDF report's widget catalogue. * * Lives in its own file so client-side surfaces (the "Export as PDF" * dialog) can import the catalogue without dragging the server-side * resolver - which imports the DB layer - into the client bundle. * Before this split, `export-dashboard-pdf-button.tsx` pulled * `PDF_DASHBOARD_WIDGETS` from `dashboard-report-data.service.ts`, * whose top-level `import { getKpis } from './dashboard.service'` * dragged in `postgres` and crashed the client build with * "Module not found: Can't resolve 'fs'". */ /** * Every selectable widget on the export dialog. Each entry maps 1:1 * to a section in `dashboard-report.tsx` and a fetcher in * `resolveDashboardReportData()`. Categorised so the dialog can * render grouped checkboxes (Charts / Tables / Time-period cohorts / * Activity & people). * * Convention: where a widget has both a chart-style and a * table-style variant of the same underlying data (pipeline funnel * is the canonical case), we expose BOTH ids and let the rep pick. * `*_chart` ids render an SVG chart inline; `*_table` ids render * the same data as a SimpleTable. * * Backwards-compat: the legacy ids `pipeline_funnel`, `berth_status` * and `source_conversion` remain valid (they render as tables - * the original behaviour) so existing saved-templates keep working. */ export const PDF_DASHBOARD_WIDGET_IDS = [ // ─── KPI / summary 'kpi_overview', // ─── Pipeline ──────────────────────────────────────────────────── 'pipeline_funnel', // legacy: table render 'pipeline_funnel_chart', // bar-chart variant 'pipeline_value_breakdown', // $ per stage 'stage_conversion_rates', // % step→step conversion 'avg_sales_cycle', // days from new→won 'revenue_forecast', // weighted forecast snapshot // ─── Berths ────────────────────────────────────────────────────── 'berth_status', // legacy: table render 'berth_status_donut', // donut chart variant 'berth_demand_ranking', // top berths by interest count 'occupancy_timeline_chart', // line over date range // ─── Source / leads 'source_conversion', // legacy: table render 'source_conversion_chart', // bar-chart variant 'lead_source_donut', // donut of source mix 'inquiry_inbox_summary', // inbound inquiry counts // ─── Time-period cohorts (require date-range filter) ───────────── 'new_clients_period', 'new_interests_period', 'signed_documents_period', 'deposits_received_period', 'contracts_signed_period', 'berths_sold_period', // ─── Deals & activity ──────────────────────────────────────────── 'hot_deals', 'deal_pulse_distribution', 'recent_activity', 'reminders_summary', // ─── Geography / clients ───────────────────────────────────────── 'client_country_distribution', ] as const; export type PdfDashboardWidgetId = (typeof PDF_DASHBOARD_WIDGET_IDS)[number]; export type PdfDashboardWidgetCategory = | 'summary' | 'pipeline' | 'berths' | 'sources' | 'period' | 'deals' | 'geography'; export interface PdfDashboardWidgetOption { id: PdfDashboardWidgetId; label: string; description: string; category: PdfDashboardWidgetCategory; /** When true, this widget requires a date-range filter on the * export dialog. The form should grey the option out until the * rep picks a window. */ requiresPeriod?: boolean; /** True when the widget renders as a chart (SVG) not a table. * Used by the export dialog to show a small icon distinguishing * chart variants from their table siblings. */ isChart?: boolean; } /** * Public widget list for the export dialog. New widgets land here; * the resolver picks them up automatically. */ export const PDF_DASHBOARD_WIDGETS: readonly PdfDashboardWidgetOption[] = [ // ─── Summary ───────────────────────────────────────────────────── { id: 'kpi_overview', label: 'Key metrics', description: 'Total clients, active interests, pipeline value, occupancy %.', category: 'summary', }, // ─── Pipeline ──────────────────────────────────────────────────── { id: 'pipeline_funnel_chart', label: 'Pipeline funnel · chart', description: 'Active interests per pipeline stage rendered as a horizontal bar chart.', category: 'pipeline', isChart: true, }, { id: 'pipeline_funnel', label: 'Pipeline funnel · table', description: 'Same data as the chart, rendered as a numbers-first table.', category: 'pipeline', }, { id: 'pipeline_value_breakdown', label: 'Pipeline value breakdown', description: 'Pipeline value per stage with weighted forecast in port currency.', category: 'pipeline', }, { id: 'stage_conversion_rates', label: 'Stage conversion rates', description: '% of interests that advance from each pipeline stage to the next.', category: 'pipeline', }, { id: 'avg_sales_cycle', label: 'Average sales cycle', description: 'Median + mean days from "new enquiry" to "contract signed".', category: 'pipeline', }, { id: 'revenue_forecast', label: 'Revenue forecast snapshot', description: 'Weighted pipeline value over the next 30 / 60 / 90 days.', category: 'pipeline', }, // ─── Berths ────────────────────────────────────────────────────── { id: 'berth_status_donut', label: 'Berth status · donut', description: 'Available / under offer / sold / maintenance as a donut chart.', category: 'berths', isChart: true, }, { id: 'berth_status', label: 'Berth status · table', description: 'Same data as the donut, rendered as counts + percentages.', category: 'berths', }, { id: 'berth_demand_ranking', label: 'Berth demand ranking', description: 'Top 10 berths by active-interest count + heat tier (A/B/C/D).', category: 'berths', }, { id: 'occupancy_timeline_chart', label: 'Occupancy timeline · chart', description: 'Daily berth occupancy rate over the report period, line chart.', category: 'berths', isChart: true, requiresPeriod: true, }, // ─── Sources ───────────────────────────────────────────────────── { id: 'source_conversion_chart', label: 'Source conversion · chart', description: 'Win rate per lead source as a horizontal bar chart.', category: 'sources', isChart: true, }, { id: 'source_conversion', label: 'Source conversion · table', description: 'Same data as the chart, with raw won/lost counts.', category: 'sources', }, { id: 'lead_source_donut', label: 'Lead source mix · donut', description: 'Share of new interests by lead source, donut chart.', category: 'sources', isChart: true, }, { id: 'inquiry_inbox_summary', label: 'Inbound inquiry summary', description: 'Counts + triage outcomes for public-site inquiries over the period.', category: 'sources', requiresPeriod: true, }, // ─── Period cohorts ────────────────────────────────────────────── { id: 'new_clients_period', label: 'New clients (in period)', description: 'Clients added during the report window, with source.', category: 'period', requiresPeriod: true, }, { id: 'new_interests_period', label: 'New interests (in period)', description: 'Interests opened during the window, with stage + value.', category: 'period', requiresPeriod: true, }, { id: 'signed_documents_period', label: 'Documents signed (in period)', description: 'EOIs / reservations / contracts signed during the window.', category: 'period', requiresPeriod: true, }, { id: 'deposits_received_period', label: 'Deposits received (in period)', description: 'Deposit payments received during the window, with $ totals.', category: 'period', requiresPeriod: true, }, { id: 'contracts_signed_period', label: 'Contracts signed (in period)', description: 'Contract documents marked signed during the window.', category: 'period', requiresPeriod: true, }, { id: 'berths_sold_period', label: 'Berths sold (in period)', description: 'Berths transitioned to Sold status during the window.', category: 'period', requiresPeriod: true, }, // ─── Deals & activity ──────────────────────────────────────────── { id: 'hot_deals', label: 'Hot deals', description: 'Top active interests ranked by stage + recent activity (table).', category: 'deals', }, { id: 'deal_pulse_distribution', label: 'Deal pulse distribution', description: 'Counts of interests in each pulse tier (hot / warm / cool / cold).', category: 'deals', }, { id: 'recent_activity', label: 'Recent activity snapshot', description: 'Last 20 audit-log entries against this port (compact log).', category: 'deals', }, { id: 'reminders_summary', label: 'Reminders summary', description: 'Open + completed reminder counts per rep over the window.', category: 'deals', requiresPeriod: true, }, // ─── Geography ─────────────────────────────────────────────────── { id: 'client_country_distribution', label: 'Client country distribution', description: 'Active-client counts grouped by nationality.', category: 'geography', }, ]; /** Buckets the catalog into UI sections for the export dialog. */ export const PDF_DASHBOARD_CATEGORY_LABELS: Record = { summary: 'Summary', pipeline: 'Pipeline', berths: 'Berths', sources: 'Lead sources', period: 'Time-period cohorts', deals: 'Deals & activity', geography: 'Geography', };