Files
pn-new-crm/src/lib/services/dashboard-report-data.service.ts
Matt adf4e2ba78 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) <noreply@anthropic.com>
2026-05-22 13:03:44 +02:00

72 lines
2.3 KiB
TypeScript

/**
* Server-side data resolver for the dashboard PDF report.
*
* Each section is gated on its widget id being present in
* `config.widgetIds`, so a report that only includes the pipeline
* funnel runs ONE query instead of the full dashboard panel. Keeps
* cold-call latency low even when the actual port has hundreds of
* berths.
*
* Lives in its own file (not inside dashboard.service.ts) so the
* report-builder concerns — what widget ids map to what fetcher,
* which fields the PDF shape requires — stay scoped to the
* report-side surface, not the dashboard UI.
*/
import {
getKpis,
getPipelineCounts,
getBerthStatusDistribution,
getHotDeals,
getSourceConversion,
} from './dashboard.service';
import type { DashboardReportData } from '@/lib/pdf/reports/dashboard-report';
// 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,
widgetIds: string[],
): Promise<DashboardReportData> {
const want = new Set(widgetIds);
// Each fetcher returns its own shape; default to undefined to
// signal "don't render this section" downstream.
const data: DashboardReportData = {};
if (want.has('kpi_overview')) {
data.kpis = await getKpis(portId);
}
if (want.has('pipeline_funnel')) {
data.pipelineCounts = await getPipelineCounts(portId);
}
if (want.has('berth_status')) {
const dist = await getBerthStatusDistribution(portId);
// `dist` shape from the service is already the totals dict; pass
// straight through. If the service changes shape, the type-check
// here will trip.
data.berthStatus = dist;
}
if (want.has('source_conversion')) {
data.sourceConversion = await getSourceConversion(portId);
}
if (want.has('hot_deals')) {
const deals = await getHotDeals(portId, 5);
data.hotDeals = deals.map((d) => ({
id: d.id,
clientName: d.clientName,
mooringNumber: d.mooringNumber,
stage: d.stage,
lastContact: d.lastContact,
}));
}
return data;
}