Files
govoplan-core/webui/conformance/ConformanceApp.tsx
T
zemion 6d37aa527f fix(ui): unify heading help, table sizing and navigation contracts
Verified with the coordinated workspace changes by devkit full run
2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed).
This shared UI pass does not mark the individual module reviews complete.
2026-09-09 02:03:16 +02:00

651 lines
30 KiB
TypeScript

import { useMemo, useState } from "react";
import { CalendarDays, FileText, Folder, GitBranch, Inbox, ListChecks, Mail, Search, ShieldCheck } from "lucide-react";
import { useLocation } from "react-router";
import DialogLayoutScenario from "./DialogLayoutScenario";
import HeadingHelpScenario from "./HeadingHelpScenario";
import DataGridLayoutScenario from "./DataGridLayoutScenario";
import NavigationLayoutScenario from "./NavigationLayoutScenario";
import ManagedArchiveScenario from "./ManagedArchiveScenario";
import FilesToolbarScenario from "./FilesToolbarScenario";
import CredentialReferencesScenario from "./CredentialReferencesScenario";
import FormControlLayoutScenario from "./FormControlLayoutScenario";
import CampaignWorkspaceScenario from "./CampaignWorkspaceScenario";
import CampaignReportScenario from "./CampaignReportScenario";
import ModuleLayoutScenario from "./ModuleLayoutScenario";
import DashboardConfigurationScenario from "./DashboardConfigurationScenario";
import HelpCenterScenario from "./HelpCenterScenario";
import NotificationFilterScenario from "./NotificationFilterScenario";
import MultiSelectFilterScenario from "./MultiSelectFilterScenario";
import SearchFiltersScenario from "./SearchFiltersScenario";
import AddressExplorerScenario from "./AddressExplorerScenario";
import MailFolderExplorerScenario from "./MailFolderExplorerScenario";
import MailToolbarScenario from "./MailToolbarScenario";
import CampaignDeliveryProgressScenario from "./CampaignDeliveryProgressScenario";
import CampaignSavingScenario from "./CampaignSavingScenario";
import CampaignMailSettingsScenario from "./CampaignMailSettingsScenario";
import CampaignAttachmentsScenario from "./CampaignAttachmentsScenario";
import CampaignRecipientOrderScenario from "./CampaignRecipientOrderScenario";
import CampaignReviewScenario from "./CampaignReviewScenario";
import CampaignBulkReviewScenario from "./CampaignBulkReviewScenario";
import CampaignReviewDetailsScenario from "./CampaignReviewDetailsScenario";
import CampaignDeliveryPolicyScenario from "./CampaignDeliveryPolicyScenario";
import MailCredentialPolicyScenario from "./MailCredentialPolicyScenario";
import PasswordLifecycleScenario from "./PasswordLifecycleScenario";
import type { NavigationPreferenceScope } from "../src/components/navigationPreferenceLayout";
import FormInstancePage from "../../../govoplan-forms-runtime/webui/src/features/forms/FormInstancePage";
import FormsRuntimePage from "../../../govoplan-forms-runtime/webui/src/features/forms/FormsRuntimePage";
import PublicFormPage from "../../../govoplan-forms-runtime/webui/src/features/forms/PublicFormPage";
import QuickAccessRail from "../../../govoplan-quick-access/webui/src/components/QuickAccessRail";
import ActionToolbar from "../src/components/ActionToolbar";
import Button from "../src/components/Button";
import Card from "../src/components/Card";
import ContentGrid, { FormGrid } from "../src/components/ContentGrid";
import ContentSection from "../src/components/ContentSection";
import CountBadge from "../src/components/CountBadge";
import DefinitionNodeIcon from "../src/components/DefinitionNodeIcon";
import DefinitionPalette, { DefinitionPaletteGroup, DefinitionPaletteItem } from "../src/components/DefinitionPalette";
import DescriptionList, { DescriptionItem } from "../src/components/DescriptionList";
import Dialog from "../src/components/Dialog";
import { DialogForm, DialogSection } from "../src/components/DialogAnatomy";
import FilterBar from "../src/components/FilterBar";
import FloatingStatus from "../src/components/FloatingStatus";
import FormSection from "../src/components/FormSection";
import MetricCard from "../src/components/MetricCard";
import MetricGrid from "../src/components/MetricGrid";
import PageLayout from "../src/components/PageLayout";
import PageActionBar from "../src/components/PageActionBar";
import SelectionList, { SelectionListItem, SelectionListItemContent } from "../src/components/SelectionList";
import StatePanel from "../src/components/StatePanel";
import WorkspaceFrame from "../src/components/WorkspaceFrame";
import WorkspaceLayout from "../src/components/WorkspaceLayout";
import WorkspaceActionBar from "../src/components/WorkspaceActionBar";
import WysiwygEditor from "../src/components/WysiwygEditor";
import BreadcrumbBar from "../src/layout/BreadcrumbBar";
import HelpMenu from "../src/layout/HelpMenu";
import IconRail from "../src/layout/IconRail";
import { useGuardedNavigate } from "../src/components/UnsavedChangesGuard";
import {
createQuickAccessLaunchContext,
quickAccessLaunchState
} from "../src/platform/launchContext";
import { projectProductNavigation } from "../src/platform/productSurfaces";
import type {
ApiSettings,
AuthInfo,
EffectiveViewProjection,
PlatformNavItem,
PlatformWebModule,
ProductAreaContribution,
ProductSurfaceContribution,
QuickAccessToolMetadata
} from "../src/types";
export default function ConformanceApp() {
const location = useLocation();
const [dialogOpen, setDialogOpen] = useState(false);
const [editorDirty, setEditorDirty] = useState(true);
const [metricDrilldown, setMetricDrilldown] = useState("");
if (new URLSearchParams(location.search).has("heading-help")) return <HeadingHelpScenario />;
if (new URLSearchParams(location.search).has("password-lifecycle")) return <PasswordLifecycleScenario />;
if (new URLSearchParams(location.search).has("credential-references")) return <CredentialReferencesScenario />;
if (new URLSearchParams(location.search).has("files-toolbar")) return <FilesToolbarScenario />;
if (new URLSearchParams(location.search).has("form-control-layout")) return <FormControlLayoutScenario />;
if (new URLSearchParams(location.search).has("campaign-workspace")) return <CampaignWorkspaceScenario />;
if (new URLSearchParams(location.search).has("campaign-report")) return <CampaignReportScenario />;
if (new URLSearchParams(location.search).has("module-layouts")) return <ModuleLayoutScenario />;
if (new URLSearchParams(location.search).has("dashboard-configuration")) return <DashboardConfigurationScenario />;
if (new URLSearchParams(location.search).has("help-center")) return <HelpCenterScenario />;
if (new URLSearchParams(location.search).has("notification-filter")) return <NotificationFilterScenario />;
if (new URLSearchParams(location.search).has("multi-select-filter")) return <MultiSelectFilterScenario />;
if (new URLSearchParams(location.search).has("search-filters")) return <SearchFiltersScenario />;
if (new URLSearchParams(location.search).has("address-explorer")) return <AddressExplorerScenario />;
if (new URLSearchParams(location.search).has("mail-folder-explorer")) return <MailFolderExplorerScenario />;
if (new URLSearchParams(location.search).has("mail-toolbar")) return <MailToolbarScenario />;
if (new URLSearchParams(location.search).has("campaign-delivery-progress")) return <CampaignDeliveryProgressScenario />;
if (new URLSearchParams(location.search).has("campaign-saving")) return <CampaignSavingScenario />;
if (new URLSearchParams(location.search).has("campaign-mail-settings")) return <CampaignMailSettingsScenario />;
if (new URLSearchParams(location.search).has("campaign-attachments")) return <CampaignAttachmentsScenario />;
if (new URLSearchParams(location.search).has("campaign-recipient-order")) return <CampaignRecipientOrderScenario />;
if (new URLSearchParams(location.search).has("campaign-review")) return <CampaignReviewScenario />;
if (new URLSearchParams(location.search).has("campaign-bulk-review")) return <CampaignBulkReviewScenario />;
if (new URLSearchParams(location.search).has("campaign-review-details")) return <CampaignReviewDetailsScenario />;
if (new URLSearchParams(location.search).has("campaign-delivery-policy")) return <CampaignDeliveryPolicyScenario />;
if (new URLSearchParams(location.search).has("mail-credential-policy")) return <MailCredentialPolicyScenario />;
if (new URLSearchParams(location.search).has("data-grid-layout")) return <DataGridLayoutScenario />;
if (new URLSearchParams(location.search).has("managed-archive")) {
const params = new URLSearchParams(location.search);
return <ManagedArchiveScenario language={params.get("language") ?? "en"} downloadAllowed={!params.has("no-download")} />;
}
if (new URLSearchParams(location.search).has("navigation-layout")) {
const params = new URLSearchParams(location.search);
return <NavigationLayoutScenario scope={(params.get("scope") ?? "user") as NavigationPreferenceScope} language={params.get("language") ?? "en"} disabled={params.has("disabled")} />;
}
if (new URLSearchParams(location.search).has("dialog-layout")) {
const params = new URLSearchParams(location.search);
return <DialogLayoutScenario templates={params.get("fixture") === "templates"} language={params.get("language") ?? "de"} />;
}
if (new URLSearchParams(location.search).has("wysiwyg-lifecycle")) {
return <WysiwygLifecycleScenario source={new URLSearchParams(location.search).get("mode") === "source"} />;
}
if (new URLSearchParams(location.search).has("product-navigation")) {
return <ProductNavigationScenario />;
}
if (location.pathname.startsWith("/forms/public/")) {
return <PublicFormPage settings={CONFORMANCE_SETTINGS} auth={FORMS_RUNTIME_AUTH} />;
}
if (location.pathname === "/forms-runtime") {
return <FormsRuntimePage settings={CONFORMANCE_SETTINGS} auth={FORMS_RUNTIME_AUTH} />;
}
if (location.pathname.startsWith("/forms-runtime/")) {
return <FormInstancePage settings={CONFORMANCE_SETTINGS} auth={FORMS_RUNTIME_AUTH} />;
}
return (
<main className="conformance-root" data-conformance-id="shared-ui-lab">
<PageLayout
archetype="overview"
mode="embedded"
title="Zentrale GovOPlaN-Oberflächen"
description="Diese Prüfseite rendert reale gemeinsame Komponenten mit langen deutschen Beschriftungen, Zuständen und responsiven Zusammensetzungen."
actions={<PageActionBar variant="overview" primaryActions={<Button variant="primary" data-testid="open-dialog" onClick={() => setDialogOpen(true)}>Prüfdialog öffnen</Button>} />}
>
<section className="conformance-section" aria-labelledby="actions-heading">
<h2 id="actions-heading">Aktionen, Filter und Status</h2>
<PageActionBar
variant="editor"
refreshable
state={editorDirty ? "dirty" : "clean"}
label="Bearbeitungsaktionen"
reloadAction={{ onReload: () => undefined, label: "Neu laden" }}
contextActions={<Button>Vorschau öffnen</Button>}
destructiveActions={<Button variant="danger" disabledReason="Nur die federführende Stelle darf diesen Vorgang endgültig löschen.">Löschen</Button>}
discardAction={{ label: "Verwerfen", onClick: () => setEditorDirty(false) }}
saveAction={{ label: "Änderungen speichern", onClick: () => setEditorDirty(false) }}
/>
<FilterBar surface="control" aria-label="Vorgangsliste filtern">
<label>Suche<input type="search" placeholder="Aktenzeichen oder verantwortliche Stelle" /></label>
<label>Status<select defaultValue="open"><option value="open">Offen</option><option value="done">Abgeschlossen</option></select></label>
<Button><Search size={16} aria-hidden="true" /> Anwenden</Button>
<CountBadge aria-label="27 Treffer">27</CountBadge>
</FilterBar>
<ContentGrid columns={3} collapseAt="standard" spacing="block">
<StatePanel title="Keine Einträge" description="Für den gewählten Zeitraum liegen noch keine Einträge vor." surface="dashed" size="compact" icon={<Inbox size={22} />} />
<StatePanel title="Berechtigung erforderlich" description="Die zuständige Administration kann den fehlenden Zugriff prüfen." tone="warning" surface="subtle" size="compact" icon={<ShieldCheck size={22} />} />
<StatePanel title="Verbindung unterbrochen" description="Gespeicherte Daten bleiben erhalten. Erneut versuchen, sobald die Verbindung verfügbar ist." tone="danger" surface="subtle" size="compact" actions={<Button>Erneut versuchen</Button>} />
</ContentGrid>
</section>
<section className="conformance-section" aria-labelledby="metrics-heading">
<h2 id="metrics-heading">Kennzahlen und Eigenschaften</h2>
<MetricGrid columns={4} collapseAt="standard">
<MetricCard label="Offene Aufgaben" value="18" detail="3 heute fällig" drilldown={{ label: "Offene Aufgaben prüfen", onActivate: () => setMetricDrilldown("18 offene Aufgaben") }} />
<MetricCard label="Fristgerecht" value="94 %" tone="good" detail="Letzte 30 Tage" />
<MetricCard label="Klärung erforderlich" value="4" tone="warning" density="compact" />
<MetricCard label="Fehlgeschlagen" value="1" tone="danger" surface="subtle" />
</MetricGrid>
<p data-testid="metric-drilldown-result" role="status">{metricDrilldown}</p>
<ContentSection density="default" surface="subtle">
<h3>Nachvollziehbare Entscheidung</h3>
<DescriptionList columns={3} collapseAt="standard">
<DescriptionItem term="Aktenzeichen">A-2026-004218</DescriptionItem>
<DescriptionItem term="Verantwortliche Stelle">Fachbereich Öffentlicher Raum und nachhaltige Mobilität</DescriptionItem>
<DescriptionItem term="Letzte Änderung">18. August 2026, 14:42 Uhr</DescriptionItem>
</DescriptionList>
</ContentSection>
</section>
<section className="conformance-section" aria-labelledby="workspace-heading">
<h2 id="workspace-heading">Listen- und Arbeitsbereich</h2>
<WorkspaceFrame className="conformance-workspace" label="Vorgangsauswahl">
<WorkspaceLayout
variant="split"
primarySize="compact"
surface="contained"
primaryLabel="Vorgänge"
contentLabel="Ausgewählter Vorgang"
primary={
<>
<WorkspaceActionBar scope="collection-pane" variant="collection" refreshable reloadAction={{ onReload: () => undefined, label: "Vorgänge neu laden" }} contextActions={<strong>Vorgänge</strong>} createAction={<Button variant="primary">Neu</Button>} />
<SelectionList label="Vorgänge" variant="navigation">
<SelectionListItem selected>
<SelectionListItemContent leading={<FileText size={18} />} title="Anwohnerparkausweis" description="A-2026-004218 · Prüfung läuft" />
</SelectionListItem>
<SelectionListItem selected={false}>
<SelectionListItemContent leading={<FileText size={18} />} title="Sondernutzung öffentlicher Fläche" description="A-2026-004219 · Rückfrage offen" />
</SelectionListItem>
</SelectionList>
</>
}
>
<>
<WorkspaceActionBar scope="detail-pane" variant="detail" contextActions={<strong>Anwohnerparkausweis</strong>} primaryActions={<Button variant="primary">Bearbeiten</Button>} destructiveActions={<Button variant="danger">Schließen</Button>} />
<Card title="Anwohnerparkausweis">
<p>Die Identität wurde geprüft. Ein aktueller Wohnsitznachweis muss noch bestätigt werden.</p>
<FormSection title="Nächster Arbeitsschritt" description="Die Entscheidung bleibt nachvollziehbar und kann vor Abschluss korrigiert werden." variant="panel">
<FormGrid columns={2}>
<label>Zuständigkeit<input defaultValue="Bürgerdienste Mitte" /></label>
<label>Bearbeitungsfrist<input type="date" defaultValue="2026-08-28" /></label>
</FormGrid>
</FormSection>
</Card>
</>
</WorkspaceLayout>
</WorkspaceFrame>
</section>
<section className="conformance-section" aria-labelledby="definition-heading">
<h2 id="definition-heading">Definitionsarbeitsbereich</h2>
<div className="conformance-definition">
<DefinitionPalette label="Bausteine" description="Mit Tastatur oder Schaltfläche einfügen">
<DefinitionPaletteGroup label="Verarbeitung">
<DefinitionPaletteItem icon={<DefinitionNodeIcon><GitBranch size={16} /></DefinitionNodeIcon>} label="Bedingung prüfen" />
<DefinitionPaletteItem icon={<DefinitionNodeIcon><ShieldCheck size={16} /></DefinitionNodeIcon>} label="Freigabe anfordern" />
</DefinitionPaletteGroup>
</DefinitionPalette>
<ContentSection className="conformance-canvas" density="default" spacing="none">
<StatePanel title="Definition auswählen" description="Wählen Sie links einen Baustein oder öffnen Sie eine vorhandene Definition." size="fill" />
<FloatingStatus>Automatische Prüfung läuft </FloatingStatus>
</ContentSection>
</div>
</section>
<LaunchContextScenario />
{new URLSearchParams(location.search).has("help") ? <HelpConformanceScenario /> : null}
</PageLayout>
<Dialog
open={dialogOpen}
title="Konsequenz vor Ausführung prüfen"
description="Die Änderung betrifft nachgelagerte Aufgaben und wird mit Verantwortlichkeit und Zeitpunkt protokolliert."
size="default"
onClose={() => setDialogOpen(false)}
footer={<><Button onClick={() => setDialogOpen(false)}>Abbrechen</Button><Button variant="primary" onClick={() => setDialogOpen(false)}>Änderung bestätigen</Button></>}
>
<DialogForm onSubmit={(event) => event.preventDefault()}>
<DialogSection title="Begründung">
<label>Begründung<textarea defaultValue="Die vorliegenden Nachweise wurden vollständig geprüft." /></label>
</DialogSection>
</DialogForm>
</Dialog>
{new URLSearchParams(location.search).has("quick-access") ? <QuickAccessScenario /> : null}
</main>
);
}
function WysiwygLifecycleScenario({ source }: { source: boolean }) {
const initialHtml = source
? '<table style="width: 100%"><tbody><tr><td>Legacy template</td></tr></tbody></table>'
: "<p>Legacy <i>template</i></p>";
const [value, setValue] = useState(initialHtml);
const [changeCount, setChangeCount] = useState(0);
const [disabled, setDisabled] = useState(false);
const [mounted, setMounted] = useState(true);
return <main className="conformance-root">
<h1>Rich-text lifecycle fixture</h1>
<Button onClick={() => setDisabled((current) => !current)}>Toggle read-only</Button>
<Button onClick={() => setMounted((current) => !current)}>Toggle editor mount</Button>
<Button onClick={() => setValue(initialHtml.replace("Legacy", "Reloaded"))}>Load another value</Button>
<output data-testid="wysiwyg-change-count">{changeCount}</output>
<pre data-testid="wysiwyg-controlled-value">{value}</pre>
{mounted && <WysiwygEditor
value={value}
disabled={disabled}
ariaLabel="Rich-text fixture content"
labels={{ visual: "Visual fixture mode", source: "Source fixture mode" }}
onChange={(nextValue) => {
setValue(nextValue);
setChangeCount((current) => current + 1);
}}
/>}
</main>;
}
function ProductNavigationScenario() {
const projection = useMemo(
() => projectProductNavigation(
PRODUCT_NAV_ITEMS,
PRODUCT_NAV_MODULES,
PRODUCT_NAV_AUTH
),
[]
);
return (
<div className="app-shell" data-conformance-id="product-navigation">
<IconRail
navItems={projection.primaryItems}
allToolItems={projection.allToolItems}
productAreas={PRODUCT_NAV_AREAS}
/>
<main className="main-area">
<PageLayout
archetype="overview"
mode="embedded"
title="Anwohnerparkausweis bearbeiten"
description="Die Navigation beschreibt Arbeit und Ergebnisse; technische Eigentümer bleiben nachvollziehbar erreichbar."
>
<StatePanel
title="Vorgang ist bereit"
description="Nutzen Sie Arbeit, Kalender, Nachrichten oder Dateien für den nächsten Schritt."
/>
</PageLayout>
</main>
</div>
);
}
function HelpConformanceScenario() {
return (
<section className="conformance-section" aria-labelledby="help-heading">
<h2 id="help-heading">Kontextsensitive Hilfe</h2>
<p>F1 löst den Hilfekontext des fokussierten Bedienelements auf und kehrt nach dem Schließen dorthin zurück.</p>
<ActionToolbar surface="subtle">
<Button
variant="danger"
data-testid="f1-retention-action"
helpContextId="policy.retention.action.apply"
helpModuleId="policy"
>
Aufbewahrungsregeln anwenden
</Button>
<Button
data-testid="f1-fallback-action"
interfaceId="core.conformance.action.review"
>
Allgemeine Aktion prüfen
</Button>
<HelpMenu auth={null} />
</ActionToolbar>
</section>
);
}
function QuickAccessScenario() {
const location = useLocation();
const mode = new URLSearchParams(location.search).get("quick-access");
const viewContext = useMemo(
() => mode === "focused" || mode === "stale-focus"
? {
...CONFORMANCE_VIEW_PROJECTION,
presentation: {
quickAccessFocusedToolIds: mode === "focused"
? ["files.recent"]
: ["mail.compose"]
}
}
: null,
[mode]
);
const launchContext = useMemo(() => createQuickAccessLaunchContext({
pathname: location.pathname,
search: location.search,
hash: location.hash,
historyIndex: browserHistoryIndex(),
auth: CONFORMANCE_AUTH,
activeObject: {
ownerModule: "cases",
kind: "case",
objectId: "case-1",
tenantId: "tenant-1",
label: "RPP-2026-0001 · Anwohnerparkausweis"
},
temporalContext: { validityMode: "current", validAt: null, recordedAt: null },
viewContext
}), [location.hash, location.pathname, location.search, viewContext]);
return <QuickAccessRail
settings={CONFORMANCE_SETTINGS}
auth={CONFORMANCE_AUTH}
tools={CONFORMANCE_QUICK_ACCESS_TOOLS}
launchContext={launchContext}
/>;
}
function LaunchContextScenario() {
const location = useLocation();
const navigate = useGuardedNavigate();
const launchContext = useMemo(() => createQuickAccessLaunchContext({
pathname: location.pathname,
search: location.search,
hash: location.hash,
historyIndex: browserHistoryIndex(),
auth: CONFORMANCE_AUTH,
activeObject: {
ownerModule: "cases",
kind: "case",
objectId: "case-1",
tenantId: "tenant-1",
label: "RPP-2026-0001 · Anwohnerparkausweis",
version: "4",
path: "/cases/case-1"
},
temporalContext: { validityMode: "current", validAt: null, recordedAt: null }
}), [location.hash, location.pathname, location.search]);
return (
<section className="conformance-section" aria-labelledby="launch-heading">
<h2 id="launch-heading">Start- und Rücksprungkontext</h2>
<p>Ein begrenzter Objektverweis öffnet das vollständige Werkzeug und erhält einen eindeutigen Rücksprung zum Vorgang.</p>
<BreadcrumbBar
pathname={location.pathname === "/" ? "/cases/case-1" : location.pathname}
locationState={location.state}
/>
<ActionToolbar surface="subtle">
<Button
data-testid="launch-full-page"
onClick={() => navigate("/files", { state: quickAccessLaunchState(launchContext) })}
>
Vollständige Dateiansicht öffnen
</Button>
</ActionToolbar>
</section>
);
}
const CONFORMANCE_AUTH = {
user: { id: "user-1", account_id: "account-1", email: "case@example.test" },
tenant: { id: "tenant-1", slug: "reference", name: "Referenzkommune" },
scopes: [],
roles: [],
groups: [],
profile_loaded: true,
roles_loaded: true,
groups_loaded: true
} satisfies AuthInfo;
const PRODUCT_NAV_AUTH = {
...CONFORMANCE_AUTH,
scopes: [
"tasks:item:read",
"calendar:event:read",
"mail:mailbox:read",
"postbox:message:read",
"files:file:read"
]
} satisfies AuthInfo;
const PRODUCT_NAV_ITEMS: PlatformNavItem[] = [
{ to: "/tasks", label: "Tasks", icon: ListChecks, surfaceId: "tasks.nav.tasks", anyOf: ["tasks:item:read"], order: 30 },
{ to: "/files", label: "Files", icon: Folder, surfaceId: "files.nav.files", anyOf: ["files:file:read"], order: 40 },
{ to: "/mail", label: "Mail", icon: Mail, surfaceId: "mail.nav.mail", anyOf: ["mail:mailbox:read"], order: 50 },
{ to: "/postbox", label: "Postbox", icon: Inbox, surfaceId: "postbox.nav.postbox", anyOf: ["postbox:message:read"], order: 51 },
{ to: "/calendar", label: "Calendar", icon: CalendarDays, surfaceId: "calendar.nav.calendar", anyOf: ["calendar:event:read"], order: 55 }
];
const PRODUCT_NAV_AREAS: ProductAreaContribution[] = [
{ id: "work", moduleId: "tasks", label: "i18n:govoplan-core.product_area.work", iconName: "list-checks", surfaceIds: ["tasks.nav.tasks"], order: 10 },
{ id: "records-documents", moduleId: "files", label: "i18n:govoplan-core.product_area.records_documents", iconName: "folder", surfaceIds: ["files.nav.files"], order: 30 },
{ id: "communication", moduleId: "mail", label: "i18n:govoplan-core.product_area.communication", iconName: "mail", surfaceIds: ["mail.nav.mail", "postbox.nav.postbox"], order: 40 },
{ id: "meetings-decisions", moduleId: "calendar", label: "i18n:govoplan-core.product_area.meetings_decisions", iconName: "calendar", surfaceIds: ["calendar.nav.calendar"], order: 50 }
];
const PRODUCT_NAV_MODULES: PlatformWebModule[] = [
productModule("tasks", productSurface({
id: "work.items",
moduleId: "tasks",
label: "i18n:govoplan-core.product_surface.work",
description: "i18n:govoplan-core.product_surface.work_description",
iconName: "list-checks",
entryPath: "/work",
routePath: "/tasks",
surfaceIds: ["tasks.nav.tasks"],
anyOf: ["tasks:item:read"]
})),
productModule("files", productSurface({
id: "records.files",
moduleId: "files",
label: "i18n:govoplan-core.product_surface.files",
description: "i18n:govoplan-core.product_surface.files_description",
iconName: "folder",
entryPath: "/documents",
routePath: "/files",
surfaceIds: ["files.nav.files"],
anyOf: ["files:file:read"]
})),
productModule("mail", productSurface({
id: "communication.messages",
moduleId: "mail",
label: "i18n:govoplan-core.product_surface.messages",
description: "i18n:govoplan-core.product_surface.messages_description",
iconName: "mail",
entryPath: "/messages",
routePath: "/mail",
surfaceIds: ["mail.nav.mail"],
anyOf: ["mail:mailbox:read"],
aliases: ["/inbox"]
})),
productModule("postbox", productSurface({
id: "communication.messages",
moduleId: "postbox",
label: "i18n:govoplan-core.product_surface.messages",
description: "i18n:govoplan-core.product_surface.messages_description",
iconName: "mail",
entryPath: "/messages",
routePath: "/postbox",
surfaceIds: ["postbox.nav.postbox"],
anyOf: ["postbox:message:read"],
aliases: ["/inbox"],
order: 20
})),
productModule("calendar", productSurface({
id: "meetings.calendar",
moduleId: "calendar",
label: "i18n:govoplan-core.product_surface.calendar",
description: "i18n:govoplan-core.product_surface.calendar_description",
iconName: "calendar",
entryPath: "/agenda",
routePath: "/calendar",
surfaceIds: ["calendar.nav.calendar"],
anyOf: ["calendar:event:read"]
}))
];
function productModule(id: string, surface: ProductSurfaceContribution): PlatformWebModule {
return { id, label: id, version: "test", productSurfaces: [surface] };
}
function productSurface(
partial: Pick<ProductSurfaceContribution,
"id" | "moduleId" | "label" | "description" | "iconName" | "entryPath" |
"routePath" | "surfaceIds" | "anyOf"> & Partial<ProductSurfaceContribution>
): ProductSurfaceContribution {
return {
contractVersion: "1",
presentations: ["task", "reader"],
capabilityIds: [],
searchSourceIds: [],
helpContextIds: [],
documentationTopicIds: [],
allOf: [],
aliases: [],
order: 10,
unavailable: {
reason: "authorization",
title: "Not available",
description: "The destination is not available for this responsibility.",
resolution: "Ask the access administrator to review the assignment."
},
...partial
};
}
const FORMS_RUNTIME_AUTH = {
...CONFORMANCE_AUTH,
scopes: [
"forms_runtime:submission:assist",
"forms_runtime:submission:participate",
"forms_runtime:workspace:read",
"forms_runtime:workspace:write"
]
} satisfies AuthInfo;
const CONFORMANCE_SETTINGS: ApiSettings = {
apiBaseUrl: "",
apiKey: "",
accessToken: ""
};
const CONFORMANCE_QUICK_ACCESS_TOOLS: QuickAccessToolMetadata[] = [{
contractVersion: "1",
id: "files.recent",
moduleId: "files",
categoryId: "files",
label: "Recent files",
description: "Select an authorized file without leaving this case.",
iconName: "files",
surfaceId: "files.route.files",
fullPagePath: "/files",
allOf: [],
anyOf: [],
order: 10,
defaultEnabled: true,
modes: ["select"],
availability: "global",
acceptedReferenceKinds: [],
returnedReferenceKinds: ["files.file-version"],
helpContextId: "files.quick_access.files"
}, {
contractVersion: "1",
id: "postbox.unread",
moduleId: "postbox",
categoryId: "messages",
label: "Unread messages",
description: "Open an authorized unread message.",
iconName: "messages",
surfaceId: "postbox.route.postbox",
fullPagePath: "/postbox",
allOf: [],
anyOf: [],
order: 10,
defaultEnabled: true,
modes: ["view"],
availability: "global",
acceptedReferenceKinds: [],
returnedReferenceKinds: ["postbox.message"],
helpContextId: "postbox.quick_access.messages"
}];
const CONFORMANCE_VIEW_PROJECTION: EffectiveViewProjection = {
activeViewId: "case-processing",
activeRevisionId: "case-processing-r4",
activeViewName: "Case processing",
visibleSurfaceIds: [],
projectionActive: true,
locked: false,
availableViews: [],
provenance: [{ source: "tenant-default", scopeType: "tenant", scopeId: "tenant-1" }],
diagnostics: []
};
function browserHistoryIndex(): number | null {
const value = (window.history.state as { idx?: unknown } | null)?.idx;
return typeof value === "number" && Number.isInteger(value) ? value : null;
}