From 2efe574f187146ab476a73c872f1202299b67d04 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Tue, 18 Aug 2026 21:32:25 +0200 Subject: [PATCH] feat: publish case launch context and product area --- src/govoplan_cases/backend/manifest.py | 50 +++++++++++++++++++++ webui/src/features/cases/CaseDetailPage.tsx | 11 +++++ webui/src/styles/cases.css | 8 ++-- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/govoplan_cases/backend/manifest.py b/src/govoplan_cases/backend/manifest.py index c048c1b..e891681 100644 --- a/src/govoplan_cases/backend/manifest.py +++ b/src/govoplan_cases/backend/manifest.py @@ -26,6 +26,7 @@ from govoplan_core.core.modules import ( ModuleManifest, NavItem, PermissionDefinition, + ProductAreaContribution, RoleTemplate, ) from govoplan_core.core.views import ViewSurface @@ -216,6 +217,17 @@ manifest = ModuleManifest( order=35, ), ), + product_areas=( + ProductAreaContribution( + id="services-cases", + module_id=MODULE_ID, + label="i18n:govoplan-core.product_area.services_cases", + icon="landmark", + description="i18n:govoplan-core.product_area.services_cases_description", + surface_ids=("cases.nav.cases", "cases.route.cases", "cases.route.cases.caseid"), + order=20, + ), + ), view_surfaces=( ViewSurface( id="cases.navigation", @@ -388,6 +400,44 @@ manifest = ModuleManifest( ), tenant_summary_providers=(_tenant_summary,), documentation=( + DocumentationTopic( + id="cases.workflow.quick-access-context", + title="Use task-local tools from an active Case", + summary="Launch an authorized full-page tool and return to the exact Case route and context.", + body=( + "After an exact Case revision loads, the Case detail page publishes a bounded active-object reference to Core. " + "Quick Access may carry that reference together with the current tenant, acting assignment, temporal selection, " + "View revision, and route when opening an owning module's full page. It never carries Case content or an access " + "decision. The destination reauthorizes its data and effects. Unsaved Case edits use the shared save/discard guard " + "before navigation, and the shell provides an explicit return action." + ), + layer="available", + documentation_types=("admin", "user"), + audience=("case_manager", "operator", "module_admin"), + related_modules=("quick_access", "views", "access"), + order=13, + links=( + DocumentationLink(label="Cases", href="/cases", kind="runtime"), + DocumentationLink( + label="Quick Access architecture", + href="govoplan/docs/architecture/QUICK_ACCESS_AND_PRODUCT_AREAS.md", + kind="repository", + ), + ), + translations={ + "de": { + "title": "Aufgabenbezogene Werkzeuge aus einem aktiven Vorgang verwenden", + "summary": "Ein berechtigtes Werkzeug oeffnen und zum genauen Vorgangskontext zurueckkehren.", + "body": ( + "Nach dem Laden einer genauen Vorgangsrevision veroeffentlicht die Detailseite einen begrenzten Objektverweis an Core. " + "Der Schnellzugriff darf diesen Verweis zusammen mit Mandant, handelnder Zuordnung, Zeitbezug, Ansichtsversion und Ruecksprungort uebergeben. " + "Vorgangsinhalte oder Zugriffsentscheidungen werden nicht uebertragen. Das Ziel prueft Daten und Wirkungen erneut; ungespeicherte Aenderungen " + "werden vor dem Wechsel durch den gemeinsamen Speichern-Verwerfen-Dialog geschuetzt." + ), + } + }, + metadata={"help_contexts": ["cases.detail.quick-access"]}, + ), DocumentationTopic( id="cases.workflow.record-formal-decision", title="Record a formal Decision from a Case", diff --git a/webui/src/features/cases/CaseDetailPage.tsx b/webui/src/features/cases/CaseDetailPage.tsx index 13ab089..8c42b27 100644 --- a/webui/src/features/cases/CaseDetailPage.tsx +++ b/webui/src/features/cases/CaseDetailPage.tsx @@ -14,6 +14,7 @@ import { ActionToolbar, hasScope, usePlatformModuleInstalled, useGuardedNavigate, + useRegisterActiveObject, useUnsavedDraftGuard, type PlatformRouteContext } from "@govoplan/core-webui"; @@ -74,6 +75,16 @@ export default function CaseDetailPage({ settings, auth }: PlatformRouteContext) ? "Enable Mandates to verify formal authority." : undefined; const canFileRecords = recordsAvailable && hasScope(auth, "records:workspace:write"); + const activeCase = useMemo(() => record ? { + ownerModule: record.reference.owner_module, + kind: record.reference.kind, + objectId: record.reference.object_id, + tenantId: record.reference.tenant_id, + label: `${record.case_number} ยท ${record.title}`, + version: record.reference.version ?? String(record.revision), + path: `/cases/${encodeURIComponent(record.reference.object_id)}` + } : null, [record]); + useRegisterActiveObject(activeCase); const load = useCallback((signal?: AbortSignal) => { setLoading(true); diff --git a/webui/src/styles/cases.css b/webui/src/styles/cases.css index 876dd1d..9c252ba 100644 --- a/webui/src/styles/cases.css +++ b/webui/src/styles/cases.css @@ -97,7 +97,7 @@ margin-top: 16px; padding: 14px; border: 1px solid var(--border); - border-radius: 6px; + border-radius: var(--radius-compact); background: var(--surface-raised); } @@ -128,7 +128,7 @@ margin-top: 18px; background: var(--border); border: 1px solid var(--border); - border-radius: 6px; + border-radius: var(--radius-compact); overflow: hidden; } @@ -233,7 +233,7 @@ .case-decision-list > article { padding: 12px; border: 1px solid var(--border); - border-radius: 6px; + border-radius: var(--radius-compact); background: var(--surface-raised); } @@ -326,7 +326,7 @@ font-size: 0.76rem; } -@media (max-width: 860px) { +@media (max-width: 900px) { .cases-count { margin-left: 0; }