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);