feat(webui): expose stable product destinations
Module Package Release / publish-packages (push) Successful in 13s

This commit is contained in:
2026-08-24 18:06:52 +02:00
parent 9a3008002d
commit ac40774785
27 changed files with 532 additions and 68 deletions
+166 -2
View File
@@ -1,5 +1,5 @@
import { useMemo, useState } from "react";
import { FileText, GitBranch, Inbox, Search, ShieldCheck } from "lucide-react";
import { CalendarDays, FileText, Folder, GitBranch, Inbox, ListChecks, Mail, Search, ShieldCheck } from "lucide-react";
import { useLocation } from "react-router";
import FormInstancePage from "../../../govoplan-forms-runtime/webui/src/features/forms/FormInstancePage";
import FormsRuntimePage from "../../../govoplan-forms-runtime/webui/src/features/forms/FormsRuntimePage";
@@ -30,12 +30,23 @@ import WorkspaceLayout from "../src/components/WorkspaceLayout";
import WorkspaceActionBar from "../src/components/WorkspaceActionBar";
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 type { ApiSettings, AuthInfo, EffectiveViewProjection, QuickAccessToolMetadata } from "../src/types";
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();
@@ -43,6 +54,10 @@ export default function ConformanceApp() {
const [editorDirty, setEditorDirty] = useState(true);
const [metricDrilldown, setMetricDrilldown] = useState("");
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} />;
}
@@ -185,6 +200,39 @@ export default function ConformanceApp() {
);
}
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">
@@ -303,6 +351,122 @@ const CONFORMANCE_AUTH = {
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: [