import { useEffect, useMemo, useState } from "react"; import { RefreshCw, RotateCcw, SlidersHorizontal } from "lucide-react"; import { Button, Card, MetricCard, PageTitle, dashboardWidgetsForModules, hasAnyScope, hasScope, usePlatformModules, type ApiSettings, type AuthInfo, type DashboardWidgetContribution, type DashboardWidgetSize } from "@govoplan/core-webui"; type DashboardLayout = { visible: string[]; known: string[]; }; export default function DashboardPage({ settings, auth }: { settings: ApiSettings; auth: AuthInfo }) { const modules = usePlatformModules(); const widgets = useMemo(() => dashboardWidgetsForModules(modules).filter((widget) => canUseWidget(auth, widget)), [auth, modules]); const widgetSignature = widgets.map((widget) => widget.id).join("|"); const storageKey = `govoplan.dashboard.layout:${auth.active_tenant?.id ?? auth.tenant.id}:${auth.user.id}`; const [layout, setLayout] = useState(() => defaultLayout(widgets)); const [configuring, setConfiguring] = useState(false); const [refreshKey, setRefreshKey] = useState(0); useEffect(() => { setLayout(readLayout(storageKey) ?? defaultLayout(widgets)); }, [storageKey, widgetSignature]); const visibleWidgetIds = useMemo(() => { const known = new Set(layout.known); const visible = new Set(layout.visible); for (const widget of widgets) { if (!known.has(widget.id) && widget.defaultVisible !== false) visible.add(widget.id); } const availableIds = new Set(widgets.map((widget) => widget.id)); return widgets.map((widget) => widget.id).filter((id) => availableIds.has(id) && visible.has(id)); }, [layout, widgets]); const visibleWidgets = widgets.filter((widget) => visibleWidgetIds.includes(widget.id)); const hiddenCount = Math.max(widgets.length - visibleWidgets.length, 0); function saveLayout(next: DashboardLayout) { setLayout(next); writeLayout(storageKey, next); } function setWidgetVisible(widgetId: string, visible: boolean) { const allIds = widgets.map((widget) => widget.id); const nextVisible = new Set(visibleWidgetIds); if (visible) { nextVisible.add(widgetId); } else { nextVisible.delete(widgetId); } saveLayout({ known: allIds, visible: allIds.filter((id) => nextVisible.has(id)) }); } function resetLayout() { saveLayout(defaultLayout(widgets)); } return (
i18n:govoplan-dashboard.dashboard.3f8b4df2

Personal workspace assembled from installed module widgets.

{configuring && Reset}> {widgets.length === 0 ?

No installed module exposes dashboard widgets yet.

:
{widgets.map((widget) => )}
}
} {visibleWidgets.length === 0 ?

Open Configure and select at least one widget. Modules can add more widgets by exposing the dashboard.widgets capability.

:
{visibleWidgets.map((widget) =>
{widget.render({ settings, auth, modules, widgetId: widget.id, refreshKey, size: widgetSize(widget) })}
)}
}
); } function canUseWidget(auth: AuthInfo, widget: DashboardWidgetContribution): boolean { if (widget.allOf?.length && !widget.allOf.every((scope) => hasScope(auth, scope))) return false; if (widget.anyOf?.length && !hasAnyScope(auth, widget.anyOf)) return false; return true; } function widgetSize(widget: DashboardWidgetContribution): DashboardWidgetSize { return widget.defaultSize ?? "medium"; } function defaultLayout(widgets: DashboardWidgetContribution[]): DashboardLayout { const ids = widgets.map((widget) => widget.id); return { known: ids, visible: widgets.filter((widget) => widget.defaultVisible !== false).map((widget) => widget.id) }; } function readLayout(storageKey: string): DashboardLayout | null { if (typeof window === "undefined") return null; try { const raw = window.localStorage.getItem(storageKey); if (!raw) return null; const parsed = JSON.parse(raw) as Partial; if (!Array.isArray(parsed.visible) || !Array.isArray(parsed.known)) return null; return { visible: parsed.visible.filter((id): id is string => typeof id === "string"), known: parsed.known.filter((id): id is string => typeof id === "string") }; } catch { return null; } } function writeLayout(storageKey: string, layout: DashboardLayout): void { if (typeof window === "undefined") return; try { window.localStorage.setItem(storageKey, JSON.stringify(layout)); } catch { // localStorage may be unavailable in restricted browsing contexts. } } function moduleLabels(modules: Array<{ id: string }>): string { if (!modules.length) return "Core shell only"; return modules.slice(0, 4).map((module) => module.id).join(", "); }