Files
pn-new-crm/src/lib/services/dashboard-report-widgets.ts

299 lines
11 KiB
TypeScript
Raw Normal View History

/**
* 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<PdfDashboardWidgetCategory, string> = {
summary: 'Summary',
pipeline: 'Pipeline',
berths: 'Berths',
sources: 'Lead sources',
period: 'Time-period cohorts',
deals: 'Deals & activity',
geography: 'Geography',
};