Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c067be36c1 | ||
|
|
58ebf2c7e6 | ||
|
|
b23e6e4eee |
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/organizations-webui",
|
||||
"version": "0.1.19",
|
||||
"version": "0.1.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "webui/src/index.ts",
|
||||
@@ -19,7 +19,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@govoplan/core-webui": "^0.1.18",
|
||||
"@govoplan/core-webui": "^0.1.45",
|
||||
"@vitejs/plugin-react": "^5.2.0",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": ">=19.2.7 <20",
|
||||
|
||||
+2
-2
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "govoplan-organizations"
|
||||
version = "0.1.19"
|
||||
version = "0.1.21"
|
||||
description = "GovOPlaN organizational model module."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
authors = [{ name = "GovOPlaN" }]
|
||||
dependencies = [
|
||||
"govoplan-core>=0.1.18",
|
||||
"govoplan-core>=0.1.45",
|
||||
"govoplan-tenancy>=0.1.18",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""GovOPlaN organizations module."""
|
||||
|
||||
__version__ = "0.1.19"
|
||||
__version__ = "0.1.21"
|
||||
|
||||
@@ -144,7 +144,7 @@ def _organizations_dsar_provider(context: ModuleContext) -> object:
|
||||
manifest = ModuleManifest(
|
||||
id="organizations",
|
||||
name="Organizations",
|
||||
version="0.1.19",
|
||||
version="0.1.21",
|
||||
required_capabilities=(
|
||||
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
|
||||
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
||||
@@ -265,6 +265,27 @@ manifest = ModuleManifest(
|
||||
),
|
||||
},
|
||||
documentation=(
|
||||
DocumentationTopic(
|
||||
id="organizations.workspace-layout",
|
||||
title="Organizations workspace layout",
|
||||
summary="Find workspace actions and read consistently arranged content.",
|
||||
body="Documentation books sit immediately beside the visible heading or contextual label for "
|
||||
"Organizations, organization settings and record dialogs, not among operational action "
|
||||
"buttons. Field help remains beside its label. "
|
||||
"Organization model tables use the complete card width; headings and creation actions stay in the card header, while the shared DataGrid owns filtering, sizing, pagination, and row actions. Unit types, structures, relation types, function types, units, relations, and functions follow the same layout. Model upgrades keep their meaningful version and decision context separate from the result table. Administrators and configurators must use Core's table-card layout instead of local padding or width overrides. The change does not modify model permissions, effective dates, or template-copy semantics.",
|
||||
layer="static",
|
||||
documentation_types=("user", "admin"),
|
||||
audience=("user", "module_admin", "operator"),
|
||||
order=5,
|
||||
translations={"de": {
|
||||
"title": "Organisationen: Aufbau des Arbeitsbereichs",
|
||||
"summary": "Arbeitsbereichsaktionen finden und einheitlich angeordnete Inhalte lesen.",
|
||||
"body": "Dokumentationsbücher stehen unmittelbar neben der sichtbaren Überschrift oder "
|
||||
"Kontextbezeichnung für Organisationen, Organisationseinstellungen und Eintragsdialoge, nicht "
|
||||
"zwischen ausführbaren Aktionsschaltflächen. Feldhilfe bleibt neben der Feldbezeichnung. "
|
||||
"Tabellen des Organisationsmodells nutzen die gesamte Kartenbreite. Überschrift und Anlegen-Aktionen bleiben im Kartenkopf; das gemeinsame DataGrid übernimmt Filter, Größe, Seitennavigation und Zeilenaktionen. Einheitentypen, Strukturen, Beziehungstypen, Funktionstypen, Einheiten, Beziehungen und Funktionen folgen demselben Layout. Modell-Upgrades behalten ihren relevanten Versions- und Entscheidungskontext getrennt von der Ergebnistabelle. Administratoren und Konfiguratoren verwenden Cores Tabellenkartenlayout statt lokaler Innenabstands- oder Breitenregeln. Rechte, Gültigkeitsdaten und das Kopierprinzip der Vorlagen ändern sich dadurch nicht.",
|
||||
}},
|
||||
),
|
||||
DocumentationTopic(
|
||||
id="organizations.privacy.data-subject-requests",
|
||||
title="Review Organizations data in a data-subject request",
|
||||
|
||||
@@ -45,7 +45,13 @@ class OrganizationsInterfaceDocumentationContractTests(unittest.TestCase):
|
||||
|
||||
def test_german_reference_documentation_is_complete(self) -> None:
|
||||
topics = manifest.documentation
|
||||
self.assertEqual(4, len(topics))
|
||||
self.assertEqual({
|
||||
"organizations.workspace-layout",
|
||||
"organizations.privacy.data-subject-requests",
|
||||
"organizations.template-upgrades",
|
||||
"organizations.model",
|
||||
"organizations.reference.fields-and-consequences",
|
||||
}, {topic.id for topic in topics})
|
||||
for topic in topics:
|
||||
translation = topic.translations.get("de", {})
|
||||
self.assertTrue(translation.get("title"), topic.id)
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/organizations-webui",
|
||||
"version": "0.1.19",
|
||||
"version": "0.1.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
@@ -19,7 +19,7 @@
|
||||
"./styles/organizations.css": "./src/styles/organizations.css"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@govoplan/core-webui": "^0.1.18",
|
||||
"@govoplan/core-webui": "^0.1.45",
|
||||
"@vitejs/plugin-react": "^5.2.0",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": ">=19.2.7 <20",
|
||||
|
||||
@@ -17,7 +17,8 @@ const translations = source("../src/i18n/generatedTranslations.ts");
|
||||
assert(page.includes("DocumentationHelpLink") && settings.includes("DocumentationHelpLink"), "Workspace and tenant settings expose contextual documentation");
|
||||
assert(page.includes("ActionBlockerHint") && settings.includes("ActionBlockerHint"), "Read-only states identify action, actor, and destination");
|
||||
assert(page.includes("disabledReason") && settings.includes("disabledReason"), "Unavailable organization actions explain their state");
|
||||
assert(page.includes("requestDiscard(() => void loadModel())") && settings.includes("requestDiscard(() => void load())"), "Reload preserves dirty organization drafts and settings");
|
||||
assert(page.includes("<PageActionBar") && page.includes("reloadAction={{") && page.includes("onReload: () => void loadModel()") && settings.includes("requestDiscard(() => void load())"), "Reload uses the guarded semantic page action or the settings draft guard");
|
||||
assert((page.match(/<Card bodyLayout="table"/g) ?? []).length === 7, "All seven organization collection tables use the shared edge-to-edge card body");
|
||||
assert(page.includes("hasDirtyDraft ? requestDiscard(discardDrafts)"), "Editor close uses the shared unsaved-change guard");
|
||||
assert(page.includes("ORGANIZATIONS_FIELD_DOCUMENTATION"), "Model fields link to stable consequence documentation");
|
||||
assert(patterns.includes('topicId: "organizations.model"') && patterns.includes('topicId: "organizations.reference.fields-and-consequences"'), "Organizations uses manifest-backed help references");
|
||||
|
||||
@@ -178,8 +178,9 @@ export default function OrganizationTemplateUpgradePanel({ settings, auth }: { s
|
||||
return <>
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
{success && <DismissibleAlert tone="success" resetKey={success}>{success}</DismissibleAlert>}
|
||||
<Card title="Organization template upgrades" actions={<Button onClick={() => void load()} disabled={loading || busy}><RefreshCw aria-hidden="true" /> Reload</Button>}>
|
||||
<Card bodyLayout="table" title="Organization template upgrades" actions={<Button onClick={() => void load()} disabled={loading || busy}><RefreshCw aria-hidden="true" /> Reload</Button>}>
|
||||
<LoadingFrame loading={loading} label="Loading organization template upgrade state">
|
||||
<div className="content-pad">
|
||||
<MetricGrid density="compact">
|
||||
<MetricCard label="Applied version" value={currentVersion?.version ?? "Custom model"} tone="info" />
|
||||
<MetricCard label="Available upgrades" value={targets.length} tone={targets.length ? "info" : "good"} />
|
||||
@@ -191,6 +192,7 @@ export default function OrganizationTemplateUpgradePanel({ settings, auth }: { s
|
||||
<FormField label="Published target version"><select value={targetVersionId} disabled={!canWrite || busy || !targets.length} onChange={(event) => setTargetVersionId(event.target.value)}>{targets.length ? targets.map((version) => <option key={version.id} value={version.id}>{templateVersionLabel(templates, version)}</option>) : <option value="">No newer published version</option>}</select></FormField>
|
||||
<Button variant="primary" onClick={() => void createPreview()} disabled={!canWrite || busy || !targetVersionId}><GitCompareArrows aria-hidden="true" /> Create preview</Button>
|
||||
</ActionToolbar>
|
||||
</div>
|
||||
<div className="organization-upgrade-table"><DataGrid id="organization-model-upgrades" rows={upgrades} columns={upgradeColumns} initialFit="container" getRowKey={(row) => row.id} emptyText={current ? "No organization template upgrades have been previewed." : "This tenant model was not instantiated from a system template."} /></div>
|
||||
</LoadingFrame>
|
||||
</Card>
|
||||
|
||||
@@ -111,7 +111,7 @@ export default function OrganizationsAdminPanel({ settings, auth }: { settings:
|
||||
|
||||
return (
|
||||
<AdminPageLayout
|
||||
title="i18n:govoplan-organizations.organization_settings.c9ab9829"
|
||||
title="i18n:govoplan-organizations.organization_settings.c9ab9829" titleHelp={<DocumentationHelpLink reference={ORGANIZATIONS_DOCUMENTATION} />}
|
||||
description="i18n:govoplan-organizations.organization_settings_description.35dc9f10"
|
||||
loading={loading}
|
||||
loadingLabel="i18n:govoplan-organizations.loading_organization_settings.c6008db8"
|
||||
@@ -119,7 +119,7 @@ export default function OrganizationsAdminPanel({ settings, auth }: { settings:
|
||||
success={success}
|
||||
actions={(
|
||||
<>
|
||||
<DocumentationHelpLink reference={ORGANIZATIONS_DOCUMENTATION} />
|
||||
|
||||
<Button
|
||||
onClick={() => requestDiscard(() => void load())}
|
||||
disabled={Boolean(reloadDisabledReason)}
|
||||
@@ -190,6 +190,8 @@ export default function OrganizationsAdminPanel({ settings, auth }: { settings:
|
||||
label="i18n:govoplan-organizations.change_retention_days.71bbd140"
|
||||
help={organizationWriteReason(canWrite, busy, "settings")}
|
||||
documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}
|
||||
helpContextId="organizations.field.audit-retention"
|
||||
helpModuleId="organizations"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type FormEvent } from "react";
|
||||
import { Edit3, Plus, RefreshCw } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type FormEvent, type JSX } from "react";
|
||||
import { Edit3, Plus } from "lucide-react";
|
||||
import { FormLayout, ActionToolbar,
|
||||
ActionBlockerHint,
|
||||
AdminIconButton,
|
||||
@@ -282,7 +282,7 @@ function mapById<T extends { id: string }>(items: T[]): Map<string, T> {
|
||||
function sectionGroupsFor(sections: ReadonlySet<OrganizationSection>): ModuleSubnavGroup<OrganizationSection>[] {
|
||||
return SECTION_GROUPS.map((group) => ({
|
||||
...group,
|
||||
items: group.items.filter((item) => sections.has(item.id))
|
||||
items: group.items.filter((item) => "id" in item && sections.has(item.id))
|
||||
})).filter((group) => group.items.length > 0);
|
||||
}
|
||||
|
||||
@@ -309,14 +309,14 @@ function activeStatus(active: boolean): JSX.Element {
|
||||
return <StatusBadge status={active ? "success" : "inactive"} label={active ? "i18n:govoplan-organizations.active.a733b809" : "i18n:govoplan-organizations.inactive.09af574c"} />;
|
||||
}
|
||||
|
||||
function SluggedFields({
|
||||
function SluggedFields<T extends SluggedDraft>({
|
||||
draft,
|
||||
onChange,
|
||||
disabled,
|
||||
disabledReason
|
||||
}: {
|
||||
draft: SluggedDraft;
|
||||
onChange: (next: SluggedDraft) => void;
|
||||
draft: T;
|
||||
onChange: (next: T) => void;
|
||||
disabled: boolean;
|
||||
disabledReason?: string;
|
||||
}) {
|
||||
@@ -1002,7 +1002,7 @@ export default function OrganizationsPage({
|
||||
<>
|
||||
<PageLayout
|
||||
archetype="workspace"
|
||||
title={title}
|
||||
title={title} titleHelp={<DocumentationHelpLink reference={ORGANIZATIONS_DOCUMENTATION} />}
|
||||
description={description}
|
||||
actions={<PageActionBar
|
||||
variant="workspace"
|
||||
@@ -1013,7 +1013,7 @@ export default function OrganizationsPage({
|
||||
disabledReason: reloadDisabledReason,
|
||||
title: "i18n:govoplan-organizations.reload.cce71553"
|
||||
}}
|
||||
helpAction={<DocumentationHelpLink reference={ORGANIZATIONS_DOCUMENTATION} />}
|
||||
|
||||
/>}
|
||||
mode={mode === "workspace" ? "workspace" : "embedded"}
|
||||
headerLoading={loading}
|
||||
@@ -1066,7 +1066,7 @@ export default function OrganizationsPage({
|
||||
)}
|
||||
primaryLabel="i18n:govoplan-organizations.organization_model.5945c48a"
|
||||
contentLabel={title}
|
||||
documentationType={mode === "admin" ? "admin" : "user"}
|
||||
documentationType="user"
|
||||
>
|
||||
{content}
|
||||
</WorkspaceLayout>
|
||||
@@ -1075,16 +1075,16 @@ export default function OrganizationsPage({
|
||||
function renderModelSection() {
|
||||
return (
|
||||
<div className="organizations-table-stack">
|
||||
<Card title="i18n:govoplan-organizations.unit_types.c7afc174" actions={<AdminIconButton label="i18n:govoplan-organizations.add_unit_type.58f2c05a" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openUnitTypeCreate} />}>
|
||||
<Card bodyLayout="table" title="i18n:govoplan-organizations.unit_types.c7afc174" actions={<AdminIconButton label="i18n:govoplan-organizations.add_unit_type.58f2c05a" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openUnitTypeCreate} />}>
|
||||
<DataGrid id="organizations-unit-types" rows={model.unit_types} columns={unitTypeColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_unit_types_found.c81fb2a7" initialFit="container" />
|
||||
</Card>
|
||||
<Card title="i18n:govoplan-organizations.structures.f9b7f3b4" actions={<AdminIconButton label="i18n:govoplan-organizations.add_structure.b722042a" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openStructureCreate} />}>
|
||||
<Card bodyLayout="table" title="i18n:govoplan-organizations.structures.f9b7f3b4" actions={<AdminIconButton label="i18n:govoplan-organizations.add_structure.b722042a" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openStructureCreate} />}>
|
||||
<DataGrid id="organizations-structures" rows={model.structures} columns={structureColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_structures_found.20b382d0" initialFit="container" />
|
||||
</Card>
|
||||
<Card title="i18n:govoplan-organizations.relation_types.e5890528" actions={<AdminIconButton label="i18n:govoplan-organizations.add_relation_type.2ad19d03" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openRelationTypeCreate} />}>
|
||||
<Card bodyLayout="table" title="i18n:govoplan-organizations.relation_types.e5890528" actions={<AdminIconButton label="i18n:govoplan-organizations.add_relation_type.2ad19d03" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openRelationTypeCreate} />}>
|
||||
<DataGrid id="organizations-relation-types" rows={model.relation_types} columns={relationTypeColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_relation_types_found.6f90bb81" initialFit="container" />
|
||||
</Card>
|
||||
<Card title="i18n:govoplan-organizations.function_types.172c01fe" actions={<AdminIconButton label="i18n:govoplan-organizations.add_function_type.90d793f1" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openFunctionTypeCreate} />}>
|
||||
<Card bodyLayout="table" title="i18n:govoplan-organizations.function_types.172c01fe" actions={<AdminIconButton label="i18n:govoplan-organizations.add_function_type.90d793f1" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openFunctionTypeCreate} />}>
|
||||
<DataGrid id="organizations-function-types" rows={model.function_types} columns={functionTypeColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_function_types_found.5eef31b1" initialFit="container" />
|
||||
</Card>
|
||||
</div>
|
||||
@@ -1129,7 +1129,7 @@ export default function OrganizationsPage({
|
||||
/>
|
||||
) : <p className="muted small-note">i18n:govoplan-organizations.no_units_found.eea2dd0c</p>}
|
||||
</Card>
|
||||
<Card title="i18n:govoplan-organizations.units.e14d0d92" actions={<AdminIconButton label="i18n:govoplan-organizations.add_unit.8fa12fb1" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteUnits || busy} disabledReason={organizationWriteReason(canWriteUnits, busy, "unit")} onClick={() => openUnitCreate()} />}>
|
||||
<Card bodyLayout="table" title="i18n:govoplan-organizations.units.e14d0d92" actions={<AdminIconButton label="i18n:govoplan-organizations.add_unit.8fa12fb1" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteUnits || busy} disabledReason={organizationWriteReason(canWriteUnits, busy, "unit")} onClick={() => openUnitCreate()} />}>
|
||||
<DataGrid id="organizations-units" rows={model.units} columns={unitColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_units_found.eea2dd0c" initialFit="container" />
|
||||
</Card>
|
||||
</div>
|
||||
@@ -1139,7 +1139,7 @@ export default function OrganizationsPage({
|
||||
function renderRelationsSection() {
|
||||
return (
|
||||
<div className="organizations-table-stack">
|
||||
<Card title="i18n:govoplan-organizations.relations.1c796711" actions={<AdminIconButton label="i18n:govoplan-organizations.add_relation.6b6e67ea" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteUnits || busy} disabledReason={organizationWriteReason(canWriteUnits, busy, "unit")} onClick={openRelationCreate} />}>
|
||||
<Card bodyLayout="table" title="i18n:govoplan-organizations.relations.1c796711" actions={<AdminIconButton label="i18n:govoplan-organizations.add_relation.6b6e67ea" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteUnits || busy} disabledReason={organizationWriteReason(canWriteUnits, busy, "unit")} onClick={openRelationCreate} />}>
|
||||
<DataGrid id="organizations-relations" rows={model.relations} columns={relationColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_relations_found.4e75b11d" initialFit="container" />
|
||||
</Card>
|
||||
</div>
|
||||
@@ -1149,7 +1149,7 @@ export default function OrganizationsPage({
|
||||
function renderFunctionsSection() {
|
||||
return (
|
||||
<div className="organizations-table-stack">
|
||||
<Card title="i18n:govoplan-organizations.functions.805dc49b" actions={<AdminIconButton label="i18n:govoplan-organizations.add_function.6abafee0" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteFunctions || busy} disabledReason={organizationWriteReason(canWriteFunctions, busy, "function")} onClick={() => openFunctionCreate()} />}>
|
||||
<Card bodyLayout="table" title="i18n:govoplan-organizations.functions.805dc49b" actions={<AdminIconButton label="i18n:govoplan-organizations.add_function.6abafee0" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteFunctions || busy} disabledReason={organizationWriteReason(canWriteFunctions, busy, "function")} onClick={() => openFunctionCreate()} />}>
|
||||
<DataGrid id="organizations-functions" rows={model.functions} columns={functionColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_functions_found.54e759a0" initialFit="container" />
|
||||
</Card>
|
||||
</div>
|
||||
@@ -1226,7 +1226,7 @@ export default function OrganizationsPage({
|
||||
return (
|
||||
<Dialog variant="administration" size="wide"
|
||||
open={Boolean(activeEditor)}
|
||||
title={editorTitle()}
|
||||
title={editorTitle()} titleHelp={<DocumentationHelpLink reference={ORGANIZATIONS_FIELD_DOCUMENTATION} />}
|
||||
onClose={() => {
|
||||
if (busy) return;
|
||||
if (hasDirtyDraft) requestDiscard(discardDrafts);
|
||||
@@ -1256,9 +1256,7 @@ export default function OrganizationsPage({
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<div className="button-row organizations-dialog-help">
|
||||
<DocumentationHelpLink reference={ORGANIZATIONS_FIELD_DOCUMENTATION} />
|
||||
</div>
|
||||
|
||||
<FormLayout columns={2} gap="small" collapseAt="workspace" id={formId} className="" onSubmit={(event) => void submit(event)}>
|
||||
{renderEditorFields()}
|
||||
</FormLayout>
|
||||
|
||||
Reference in New Issue
Block a user