chore: consolidate platform split checks
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Module Matrix / module-matrix (push) Has been cancelled

This commit is contained in:
2026-07-10 12:51:19 +02:00
parent 150b720f12
commit 635d25c74c
216 changed files with 23336 additions and 4077 deletions

View File

@@ -1,149 +1,46 @@
import { useEffect, useMemo, useState } from "react";
import Card from "../../components/Card";
import DismissibleAlert from "../../components/DismissibleAlert";
import LoadingFrame from "../../components/LoadingFrame";
import MetricCard from "../../components/MetricCard";
import StatusBadge from "../../components/StatusBadge";
import { apiFetch, isApiError } from "../../api/client";
import PageTitle from "../../components/PageTitle";
import { usePlatformModules } from "../../platform/ModuleContext";
import type { ApiSettings } from "../../types";
type DashboardCampaign = {
id: string;
name?: string | null;
external_id?: string | null;
status?: string | null;
updated_at?: string | null;
updatedAt?: string | null;
};
type CampaignListResponse = DashboardCampaign[] | { campaigns?: DashboardCampaign[]; items?: DashboardCampaign[]; results?: DashboardCampaign[] };
export default function DashboardPage({ settings }: { settings: ApiSettings }) {
export default function DashboardPage() {
const modules = usePlatformModules();
const campaignsInstalled = modules.some((module) => module.id === "campaigns");
const [campaigns, setCampaigns] = useState<DashboardCampaign[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
if (!campaignsInstalled) {
setCampaigns([]);
setError("");
return;
}
let cancelled = false;
setLoading(true);
setError("");
apiFetch<CampaignListResponse>(settings, "/api/v1/campaigns")
.then((response) => {
if (!cancelled) setCampaigns(campaignsFromResponse(response));
})
.catch((reason: unknown) => {
if (cancelled) return;
if (isApiError(reason, 403, 404)) {
setCampaigns([]);
setError("");
return;
}
setError(reason instanceof Error ? reason.message : String(reason));
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => { cancelled = true; };
}, [campaignsInstalled, settings]);
const statusCounts = useMemo(() => countByStatus(campaigns), [campaigns]);
const recentCampaigns = useMemo(
() => [...campaigns].sort((left, right) => timestamp(right) - timestamp(left)).slice(0, 5),
[campaigns],
);
const activeCampaigns = (statusCounts.active ?? 0) + (statusCounts.draft ?? 0);
const completedCampaigns = (statusCounts.completed ?? 0) + (statusCounts.sent ?? 0);
return (
<div className="content-pad workspace-data-page">
<div className="page-heading split workspace-heading">
<div>
<h1>Dashboard</h1>
<p>Tenant overview across installed modules and accessible campaigns.</p>
<PageTitle>i18n:govoplan-core.dashboard.d87f47b4</PageTitle>
<p>Install and enable the dashboard module to make this page configurable.</p>
</div>
</div>
{error && <DismissibleAlert tone="warning" resetKey={error} floating>{error}</DismissibleAlert>}
<div className="metric-grid">
<MetricCard label="Installed modules" value={modules.length} tone="info" detail={modules.length ? moduleLabels(modules).join(", ") : "Core only"} />
<MetricCard label="Campaigns" value={campaignsInstalled ? campaigns.length : "—"} tone="neutral" detail={campaignsInstalled ? "Accessible to you" : "Campaign module not installed"} />
<MetricCard label="Active drafts" value={campaignsInstalled ? activeCampaigns : "—"} tone="warning" detail="Draft or active" />
<MetricCard label="Completed" value={campaignsInstalled ? completedCampaigns : "—"} tone="good" detail="Completed or sent" />
<MetricCard label="i18n:govoplan-core.installed_modules.32b3e799" value={modules.length} tone="info" detail={modules.length ? moduleLabels(modules).join(", ") : "i18n:govoplan-core.core_only.d46ce7d9"} />
<MetricCard label="Dashboard module" value="not installed" tone="neutral" detail="Core fallback is active." />
</div>
<div className="dashboard-grid">
<Card title="Recent campaigns" collapsible>
<LoadingFrame loading={loading} label="Loading campaigns...">
{!campaignsInstalled && <p className="muted">Install the Campaign module to show campaign activity here.</p>}
{campaignsInstalled && recentCampaigns.length === 0 && <p className="muted">No accessible campaigns found.</p>}
{recentCampaigns.length > 0 && (
<dl className="detail-list">
{recentCampaigns.map((campaign) => (
<div key={campaign.id}>
<dt><StatusBadge status={campaign.status || "draft"} /></dt>
<dd>
<strong>{campaign.name || campaign.external_id || campaign.id}</strong>
<span className="muted"> · {formatDashboardDate(campaign.updated_at ?? campaign.updatedAt)}</span>
</dd>
</div>
))}
</dl>
)}
</LoadingFrame>
</Card>
<Card title="Installed modules" collapsible>
{modules.length === 0 ? <p className="muted">No feature modules are active.</p> : (
<dl className="detail-list">
{modules.map((module) => (
<div key={module.id}>
<Card title="i18n:govoplan-core.installed_modules.32b3e799">
{modules.length === 0 ? <p className="muted">i18n:govoplan-core.no_feature_modules_are_active.e9c2d936</p> :
<dl className="detail-list">
{modules.map((module) =>
<div key={module.id}>
<dt>{module.id}</dt>
<dd><strong>{module.label}</strong><span className="muted"> · v{module.version}</span></dd>
<dd><strong>{module.label}</strong><span className="muted"> i18n:govoplan-core.v.26c12f1e{module.version}</span></dd>
</div>
))}
)}
</dl>
)}
}
</Card>
<Card title="Home">
<p className="muted">This minimal core home is only shown while no dashboard WebUI module is available. Feature modules own their pages and can expose dashboard widgets once the dashboard module is installed.</p>
</Card>
</div>
</div>
);
</div>);
}
function campaignsFromResponse(response: CampaignListResponse): DashboardCampaign[] {
if (Array.isArray(response)) return response;
return response.campaigns ?? response.items ?? response.results ?? [];
}
function countByStatus(campaigns: DashboardCampaign[]): Record<string, number> {
return campaigns.reduce<Record<string, number>>((counts, campaign) => {
const status = String(campaign.status || "draft").toLowerCase();
counts[status] = (counts[status] ?? 0) + 1;
return counts;
}, {});
}
function timestamp(campaign: DashboardCampaign): number {
const value = campaign.updated_at ?? campaign.updatedAt ?? "";
const parsed = Date.parse(value);
return Number.isFinite(parsed) ? parsed : 0;
}
function formatDashboardDate(value?: string | null): string {
if (!value) return "not updated";
const parsed = new Date(value);
if (Number.isNaN(parsed.getTime())) return value;
return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(parsed);
}
function moduleLabels(modules: Array<{ label: string }>): string[] {
function moduleLabels(modules: Array<{label: string;}>): string[] {
return modules.map((module) => module.label).slice(0, 4);
}