From adf4e2ba78b688a79d69b32ec83802e74165140b Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 22 May 2026 13:03:44 +0200 Subject: [PATCH] fix(reports): split PDF widget catalogue out of the DB-touching service export-dashboard-pdf-button.tsx imported PDF_DASHBOARD_WIDGETS + PdfDashboardWidgetId from dashboard-report-data.service.ts. JS modules evaluate their imports eagerly, so the button transitively pulled in that file's top-level `import { getKpis } from './dashboard.service'`, which pulled in `@/lib/db`, which pulls in `postgres`, which crashed the client bundle with: Module not found: Can't resolve 'fs' ./node_modules/.../postgres/src/index.js [Client Component Browser] Split the pure data + types into the new file src/lib/services/dashboard-report-widgets.ts and re-export from the original service for backwards compatibility. The button now imports from the pure file; the server-only route (reports/generate) keeps using the resolver as before. tsc clean, dashboard loads. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../reports/export-dashboard-pdf-button.tsx | 2 +- .../services/dashboard-report-data.service.ts | 65 +++---------------- src/lib/services/dashboard-report-widgets.ts | 61 +++++++++++++++++ 3 files changed, 72 insertions(+), 56 deletions(-) create mode 100644 src/lib/services/dashboard-report-widgets.ts diff --git a/src/components/reports/export-dashboard-pdf-button.tsx b/src/components/reports/export-dashboard-pdf-button.tsx index fa913733..8025e8a6 100644 --- a/src/components/reports/export-dashboard-pdf-button.tsx +++ b/src/components/reports/export-dashboard-pdf-button.tsx @@ -19,7 +19,7 @@ import { import { PDF_DASHBOARD_WIDGETS, type PdfDashboardWidgetId, -} from '@/lib/services/dashboard-report-data.service'; +} from '@/lib/services/dashboard-report-widgets'; import { triggerBlobDownload } from '@/lib/utils/download'; import { usePermissions } from '@/hooks/use-permissions'; import { resolvePortIdFromSlug } from '@/lib/api/client'; diff --git a/src/lib/services/dashboard-report-data.service.ts b/src/lib/services/dashboard-report-data.service.ts index 6d8562d7..03f42b2a 100644 --- a/src/lib/services/dashboard-report-data.service.ts +++ b/src/lib/services/dashboard-report-data.service.ts @@ -21,61 +21,16 @@ import { } from './dashboard.service'; import type { DashboardReportData } from '@/lib/pdf/reports/dashboard-report'; -/** - * Maps widget ids the dashboard PDF understands. The id space is - * intentionally a subset of the on-screen `DASHBOARD_WIDGETS` - * registry — only widgets that have a sensible printable form - * appear here. The dialog's widget picker filters its option list - * by this set. - */ -export const PDF_DASHBOARD_WIDGET_IDS = [ - 'kpi_overview', - 'pipeline_funnel', - 'berth_status', - 'source_conversion', - 'hot_deals', -] as const; - -export type PdfDashboardWidgetId = (typeof PDF_DASHBOARD_WIDGET_IDS)[number]; - -export interface PdfDashboardWidgetOption { - id: PdfDashboardWidgetId; - label: string; - description: string; -} - -/** - * Public widget list (label + description) for the export dialog. - * Mirrored from the on-screen widget-registry but with PDF-friendly - * copy: a "Berth heat" chart is "Berth demand ranking" in print. - */ -export const PDF_DASHBOARD_WIDGETS: readonly PdfDashboardWidgetOption[] = [ - { - id: 'kpi_overview', - label: 'Key metrics', - description: 'Total clients, active interests, pipeline value, occupancy %.', - }, - { - id: 'pipeline_funnel', - label: 'Pipeline funnel', - description: 'Active interests grouped by pipeline stage.', - }, - { - id: 'berth_status', - label: 'Berth status distribution', - description: 'Available / under offer / reserved / sold counts.', - }, - { - id: 'source_conversion', - label: 'Source conversion', - description: 'Inquiries → Clients → Interests → Won, by lead source.', - }, - { - id: 'hot_deals', - label: 'Hot deals', - description: 'Top 5 active interests by deal-health score.', - }, -]; +// Pure data/types now live in `dashboard-report-widgets.ts` so the +// client-side export button can import them without dragging this +// file's DB-touching imports into the browser bundle. Re-exported +// here so existing consumers keep working. +export { + PDF_DASHBOARD_WIDGET_IDS, + PDF_DASHBOARD_WIDGETS, + type PdfDashboardWidgetId, + type PdfDashboardWidgetOption, +} from './dashboard-report-widgets'; export async function resolveDashboardReportData( portId: string, diff --git a/src/lib/services/dashboard-report-widgets.ts b/src/lib/services/dashboard-report-widgets.ts new file mode 100644 index 00000000..2fe0fcfd --- /dev/null +++ b/src/lib/services/dashboard-report-widgets.ts @@ -0,0 +1,61 @@ +/** + * 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'". + */ + +export const PDF_DASHBOARD_WIDGET_IDS = [ + 'kpi_overview', + 'pipeline_funnel', + 'berth_status', + 'source_conversion', + 'hot_deals', +] as const; + +export type PdfDashboardWidgetId = (typeof PDF_DASHBOARD_WIDGET_IDS)[number]; + +export interface PdfDashboardWidgetOption { + id: PdfDashboardWidgetId; + label: string; + description: string; +} + +/** + * Public widget list (label + description) for the export dialog. + * Mirrored from the on-screen widget-registry but with PDF-friendly + * copy: a "Berth heat" chart is "Berth demand ranking" in print. + */ +export const PDF_DASHBOARD_WIDGETS: readonly PdfDashboardWidgetOption[] = [ + { + id: 'kpi_overview', + label: 'Key metrics', + description: 'Total clients, active interests, pipeline value, occupancy %.', + }, + { + id: 'pipeline_funnel', + label: 'Pipeline funnel', + description: 'Active interests grouped by pipeline stage.', + }, + { + id: 'berth_status', + label: 'Berth status distribution', + description: 'Available / under offer / reserved / sold counts.', + }, + { + id: 'source_conversion', + label: 'Source conversion', + description: 'Inquiries → Clients → Interests → Won, by lead source.', + }, + { + id: 'hot_deals', + label: 'Hot deals', + description: 'Top 5 active interests by deal-health score.', + }, +];