643 lines
30 KiB
TypeScript
643 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 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 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 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("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("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;
|
|
}
|