- report-render.service.ts: KindRenderer now carries a per-kind toCsv
serializer alongside the PDF renderer. renderReportRun branches on
run.outputFormat — 'pdf' (existing path), 'csv' (new), 'png' (throws
with a clear "deferred" message so the run lands as 'failed' without
a partial blob). Storage path, mime type, filename + extension all
pick up the output-format suffix; the file row mirror records the
matching mime so the standard download surface serves it correctly.
- csvCell / rowsToCsv helpers: RFC-4180 escaping (always double-quoted,
doubles internal quotes, CRLF newlines).
- 4 per-kind serializers:
- dashboard: stage-count + top-interests + meta as 3-col CSV
- clients: activity log rows (id/createdAt/action/entityType/entityId/userId)
- berths: occupancy metrics (totalBerths + occupancyRate + status counts)
- interests: revenue metrics (completed + forecast + per-stage breakdown)
- DashboardReportBuilder + SimpleReportBuilder gain an Output-format
toggle (PDF | CSV). DashboardReportBuilder threads it into the queued-
run POST; SimpleReportBuilder threads it directly. Synchronous PDF
download path (Dashboard "Download PDF" button) stays PDF-only since
/api/v1/reports/generate returns a blob, not a run row.
PNG remains deferred — flagged with a follow-up TODO inside the render
branch + the builder selector deliberately omits PNG so reps don't pick
it and watch a run fail.
Verified: tsc clean, 1493/1493 vitest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P4 — landing + builder:
- /[portSlug]/reports — new landing page with 4 build-kind cards
(dashboard / clients / berths / interests), 3 library cards
(Templates / Runs / Schedules), and the pre-P4 reports list
preserved under "Legacy library" so historical PDFs stay accessible.
- /[portSlug]/reports/[kind] — kind-aware builder route.
- dashboard: refactored the existing export dialog body into
DashboardReportBuilder (page-mounted; same widget grouping +
date-range + SavedTemplatesPicker + preview). New "Queue + go to
Runs" CTA enqueues a report_runs row via /api/v1/reports/runs
(Reports P3 path); "Download PDF" keeps the synchronous /generate
fallback for ad-hoc one-shots.
- clients / berths / interests: SimpleReportBuilder — date-range +
enqueue to /api/v1/reports/runs. Kind-specific filters land
alongside dedicated renderers in P6+.
- Dashboard "Export as PDF" button rewired: no longer opens an
in-dashboard Dialog. Becomes a Link → /reports/dashboard?from=...&to=...
carrying the currently-active range through search params so the
builder pre-fills it. Removes the dialog body (~290 lines) from the
button file; the same UI lives in DashboardReportBuilder.
- ?from=YYYY-MM-DD&to=YYYY-MM-DD search params pass the range into the
builder page.
P5 — sub-pages (functional, backed by P2 CRUD endpoints):
- /reports/runs — paginated table of report_runs with status badges,
auto-polls every 5s while any row is pending/rendering, per-row
Download (file by storageKey) + Re-run actions.
- /reports/templates — saved template grid. Clicking the name links to
the builder with ?templateId=… so it pre-applies.
- /reports/schedules — schedule table with cadence labels (weekly /
monthly / quarterly), next-run timestamps, recipient counts, and a
per-row enable Switch (PATCH /api/v1/reports/schedules/[id]).
Verified: tsc clean, 1493/1493 vitest, dev-server compile clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>