Files
govoplan-core/webui/conformance/ConformanceApp.tsx
T

217 lines
12 KiB
TypeScript

import { useMemo, useState } from "react";
import { FileText, GitBranch, Inbox, Search, ShieldCheck } from "lucide-react";
import { useLocation } from "react-router";
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 BreadcrumbBar from "../src/layout/BreadcrumbBar";
import { useGuardedNavigate } from "../src/components/UnsavedChangesGuard";
import {
createQuickAccessLaunchContext,
quickAccessLaunchState
} from "../src/platform/launchContext";
import type { AuthInfo } from "../src/types";
export default function ConformanceApp() {
const [dialogOpen, setDialogOpen] = useState(false);
const [editorDirty, setEditorDirty] = useState(true);
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
dirty={editorDirty}
label="Bearbeitungsaktionen"
reloadAction={<Button variant="ghost">Neu laden</Button>}
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" />
<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>
<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={
<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>
}
>
<Card title="Anwohnerparkausweis" actions={<Button variant="primary">Bearbeiten</Button>}>
<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 />
</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>
</main>
);
}
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;
function browserHistoryIndex(): number | null {
const value = (window.history.state as { idx?: unknown } | null)?.idx;
return typeof value === "number" && Number.isInteger(value) ? value : null;
}