feat(reports-p7): subtitle override field in dashboard builder

- DashboardReportBuilder gains an optional Subtitle input alongside
  Title. Persisted in the config payload sent to /api/v1/reports/runs
  + /api/v1/reports/generate + threaded through the preview payload's
  useMemo dep list so live preview reflects the override.
- Cover-page brand picker (admin-only) — deferred. Today the renderer
  uses the active port's brand kit; cross-port branding swap needs a
  permission gate, port-pick UI, and a renderer override and is queued
  for a follow-up. Subtitle alone covers the most common ad-hoc need
  (custom cover-page subtext like "Board pack — March 2026").

Verified: tsc clean, 1493/1493 vitest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-25 16:40:28 +02:00
parent 3f9c4589e0
commit 866b910ae9

View File

@@ -50,6 +50,7 @@ interface Props {
export function DashboardReportBuilder({ portSlug, initialFrom, initialTo }: Props) {
const router = useRouter();
const [title, setTitle] = useState(`Report - ${new Date().toLocaleDateString(undefined)}`);
const [subtitle, setSubtitle] = useState('');
const [selected, setSelected] = useState<PdfDashboardWidgetId[]>(
PDF_DASHBOARD_WIDGETS.map((w) => w.id),
);
@@ -69,11 +70,12 @@ export function DashboardReportBuilder({ portSlug, initialFrom, initialTo }: Pro
config: {
kind: 'dashboard' as const,
widgetIds: selected,
...(subtitle.trim() ? { subtitle: subtitle.trim() } : {}),
...(dateFrom ? { dateFrom } : {}),
...(dateTo ? { dateTo } : {}),
},
}),
[title, selected, dateFrom, dateTo],
[title, subtitle, selected, dateFrom, dateTo],
);
function toggle(id: PdfDashboardWidgetId) {
@@ -102,6 +104,7 @@ export function DashboardReportBuilder({ portSlug, initialFrom, initialTo }: Pro
config: {
kind: 'dashboard',
widgetIds: selected,
...(subtitle.trim() ? { subtitle: subtitle.trim() } : {}),
...(dateFrom ? { dateFrom } : {}),
...(dateTo ? { dateTo } : {}),
},
@@ -142,6 +145,8 @@ export function DashboardReportBuilder({ portSlug, initialFrom, initialTo }: Pro
config: {
kind: 'dashboard',
widgetIds: selected,
title: title.trim() || 'Report',
...(subtitle.trim() ? { subtitle: subtitle.trim() } : {}),
...(dateFrom ? { dateFrom } : {}),
...(dateTo ? { dateTo } : {}),
},
@@ -193,6 +198,18 @@ export function DashboardReportBuilder({ portSlug, initialFrom, initialTo }: Pro
className="max-w-md"
/>
</div>
<div className="space-y-1">
<Label htmlFor="export-subtitle">
Subtitle <span className="text-xs font-normal text-muted-foreground">(optional)</span>
</Label>
<Input
id="export-subtitle"
value={subtitle}
onChange={(e) => setSubtitle(e.target.value)}
className="max-w-md"
placeholder="e.g. Board pack — March 2026"
/>
</div>
<div className="space-y-1">
<Label>Report window</Label>
<div className="flex flex-wrap items-center gap-2">