diff --git a/src/components/reports/builders/dashboard-report-builder.tsx b/src/components/reports/builders/dashboard-report-builder.tsx index e344b5b0..751b1265 100644 --- a/src/components/reports/builders/dashboard-report-builder.tsx +++ b/src/components/reports/builders/dashboard-report-builder.tsx @@ -11,6 +11,15 @@ import { DatePicker } from '@/components/ui/date-picker'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { usePermissions } from '@/hooks/use-permissions'; +import { usePortContext } from '@/providers/port-provider'; import { PDF_DASHBOARD_WIDGETS, PDF_DASHBOARD_CATEGORY_LABELS, @@ -60,10 +69,17 @@ export function DashboardReportBuilder({ portSlug, initialFrom, initialTo }: Pro const [dateFrom, setDateFrom] = useState(initialFrom ?? toIsoLocal(last30)); const [dateTo, setDateTo] = useState(initialTo ?? toIsoLocal(today)); const [outputFormat, setOutputFormat] = useState<'pdf' | 'csv'>('pdf'); + const [coverBrandPortId, setCoverBrandPortId] = useState(''); const [loading, setLoading] = useState(false); const [enqueuing, setEnqueuing] = useState(false); const [previewOpen, setPreviewOpen] = useState(false); + // P7: cover-brand swap — admin-only. The renderer falls back to the + // active port's brand kit when this is empty or invalid. + const { can } = usePermissions(); + const { ports, currentPortId } = usePortContext(); + const canPickBrand = can('admin', 'manage_settings') && ports.length > 1; + const previewPayload = useMemo( () => ({ title: title.trim() || 'Report', @@ -71,11 +87,12 @@ export function DashboardReportBuilder({ portSlug, initialFrom, initialTo }: Pro kind: 'dashboard' as const, widgetIds: selected, ...(subtitle.trim() ? { subtitle: subtitle.trim() } : {}), + ...(coverBrandPortId ? { coverBrandPortId } : {}), ...(dateFrom ? { dateFrom } : {}), ...(dateTo ? { dateTo } : {}), }, }), - [title, subtitle, selected, dateFrom, dateTo], + [title, subtitle, selected, coverBrandPortId, dateFrom, dateTo], ); function toggle(id: PdfDashboardWidgetId) { @@ -105,6 +122,7 @@ export function DashboardReportBuilder({ portSlug, initialFrom, initialTo }: Pro kind: 'dashboard', widgetIds: selected, ...(subtitle.trim() ? { subtitle: subtitle.trim() } : {}), + ...(coverBrandPortId ? { coverBrandPortId } : {}), ...(dateFrom ? { dateFrom } : {}), ...(dateTo ? { dateTo } : {}), }, @@ -147,6 +165,7 @@ export function DashboardReportBuilder({ portSlug, initialFrom, initialTo }: Pro widgetIds: selected, title: title.trim() || 'Report', ...(subtitle.trim() ? { subtitle: subtitle.trim() } : {}), + ...(coverBrandPortId ? { coverBrandPortId } : {}), ...(dateFrom ? { dateFrom } : {}), ...(dateTo ? { dateTo } : {}), }, @@ -266,6 +285,38 @@ export function DashboardReportBuilder({ portSlug, initialFrom, initialTo }: Pro Sections marked “needs date range” only render when both dates are set.

+ {canPickBrand ? ( +
+ + +

+ Swaps the cover-page logo and port name to the picked brand. The data inside stays + from the active port. +

+
+ ) : null}
diff --git a/src/lib/services/report-render.service.ts b/src/lib/services/report-render.service.ts index ac204126..f7d458c6 100644 --- a/src/lib/services/report-render.service.ts +++ b/src/lib/services/report-render.service.ts @@ -202,11 +202,26 @@ export async function renderReportRun(reportRunId: string): Promise { throw new Error(`Cannot render report ${run.id}: port ${run.portId} not found`); } - const logo = await resolvePortLogo(run.portId).catch(() => ({ + // P7: optional cover-brand swap. When config.coverBrandPortId points + // at a port the rep has access to, the cover-page logo + port name + // come from THAT port's brand kit instead of the report's source + // port. Useful for cross-port leadership decks; falls back to the + // source port when the override port is missing / inaccessible. + const params = (run.config as Record) ?? {}; + const overrideBrandPortId = + typeof params.coverBrandPortId === 'string' && params.coverBrandPortId.length > 0 + ? params.coverBrandPortId + : null; + const brandPortId = overrideBrandPortId ?? run.portId; + const brandPort = + overrideBrandPortId === null + ? port + : ((await db.query.ports.findFirst({ where: eq(ports.id, brandPortId) })) ?? port); + + const logo = await resolvePortLogo(brandPort.id).catch(() => ({ buffer: null as Buffer | null, })); - const ctx: RenderCtx = { portName: port.name, logoBuffer: logo.buffer ?? null }; - const params = (run.config as Record) ?? {}; + const ctx: RenderCtx = { portName: brandPort.name, logoBuffer: logo.buffer ?? null }; const data = await renderer.fetchData(run.portId, params);