diff --git a/package.json b/package.json
index f9e66b4..ea870b2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@govoplan/organizations-webui",
- "version": "0.1.6",
+ "version": "0.1.7",
"private": true,
"type": "module",
"main": "webui/src/index.ts",
@@ -19,7 +19,7 @@
"LICENSE"
],
"peerDependencies": {
- "@govoplan/core-webui": "^0.1.6",
+ "@govoplan/core-webui": "^0.1.7",
"@vitejs/plugin-react": "^4.3.4",
"lucide-react": "^1.23.0",
"react": "^19.0.0",
diff --git a/pyproject.toml b/pyproject.toml
index e31ec11..d8dca2b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
[project]
name = "govoplan-organizations"
-version = "0.1.6"
+version = "0.1.7"
description = "GovOPlaN organizational model module."
readme = "README.md"
requires-python = ">=3.12"
authors = [{ name = "GovOPlaN" }]
dependencies = [
- "govoplan-core>=0.1.6",
- "govoplan-tenancy>=0.1.6",
+ "govoplan-core>=0.1.7",
+ "govoplan-tenancy>=0.1.7",
]
[tool.setuptools.packages.find]
diff --git a/src/govoplan_organizations/backend/manifest.py b/src/govoplan_organizations/backend/manifest.py
index 63e15f2..1a488a4 100644
--- a/src/govoplan_organizations/backend/manifest.py
+++ b/src/govoplan_organizations/backend/manifest.py
@@ -86,7 +86,7 @@ def _organization_directory(context: ModuleContext) -> object:
manifest = ModuleManifest(
id="organizations",
name="Organizations",
- version="0.1.6",
+ version="0.1.7",
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
optional_dependencies=("tenancy", "access", "audit", "policy"),
permissions=PERMISSIONS,
diff --git a/webui/package.json b/webui/package.json
index 5e4e70d..8c3fafc 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -1,6 +1,6 @@
{
"name": "@govoplan/organizations-webui",
- "version": "0.1.6",
+ "version": "0.1.7",
"private": true,
"type": "module",
"main": "src/index.ts",
@@ -14,7 +14,7 @@
"./styles/organizations.css": "./src/styles/organizations.css"
},
"peerDependencies": {
- "@govoplan/core-webui": "^0.1.6",
+ "@govoplan/core-webui": "^0.1.7",
"@vitejs/plugin-react": "^4.3.4",
"lucide-react": "^1.23.0",
"react": "^19.0.0",
diff --git a/webui/src/features/organizations/OrganizationsPage.tsx b/webui/src/features/organizations/OrganizationsPage.tsx
index 310217c..bbda632 100644
--- a/webui/src/features/organizations/OrganizationsPage.tsx
+++ b/webui/src/features/organizations/OrganizationsPage.tsx
@@ -1,10 +1,12 @@
import { useCallback, useEffect, useMemo, useRef, useState, type FormEvent } from "react";
-import { Edit3, Plus, RefreshCw, Save, XCircle } from "lucide-react";
+import { Edit3, Plus, RefreshCw } from "lucide-react";
import {
+ AdminIconButton,
ApiError,
Button,
Card,
DataGrid,
+ Dialog,
DismissibleAlert,
FormField,
LoadingFrame,
@@ -57,6 +59,7 @@ import {
export type OrganizationSection = "model" | "units" | "relations" | "functions";
type OrganizationsPageMode = "workspace" | "admin";
+type OrganizationEditor = "unitType" | "structure" | "relationType" | "unit" | "relation" | "functionType" | "function";
type OrganizationsPageProps = {
settings: ApiSettings;
@@ -113,11 +116,6 @@ type FunctionDraft = SluggedDraft & {
act_in_place_allowed: boolean;
};
-type ActiveItem = {
- id: string;
- is_active: boolean;
-};
-
type OrganizationsInitialQuery = {
section: OrganizationSection | "";
unitId: string;
@@ -295,22 +293,10 @@ function activeStatus(active: boolean): JSX.Element {
return ;
}
-function ActiveToggleButton({ item, disabled, onToggle }: { item: ActiveItem; disabled: boolean; onToggle: () => void }) {
- const label = item.is_active ? "i18n:govoplan-organizations.deactivate.46ab070d" : "i18n:govoplan-organizations.reactivate.58f2855d";
- return (
-
- );
-}
-
-function RowActions({ item, disabled, onEdit, onToggle, extra }: { item: ActiveItem; disabled: boolean; onEdit: () => void; onToggle: () => void; extra?: JSX.Element[] }) {
+function RowActions({ disabled, onEdit, extra }: { disabled: boolean; onEdit: () => void; extra?: JSX.Element[] }) {
return (
-
-
+
} disabled={disabled} onClick={onEdit} />
{extra}
);
@@ -390,6 +376,7 @@ export default function OrganizationsPage({
const [editingRelationId, setEditingRelationId] = useState(null);
const [editingFunctionTypeId, setEditingFunctionTypeId] = useState(null);
const [editingFunctionId, setEditingFunctionId] = useState(null);
+ const [activeEditor, setActiveEditor] = useState(null);
const [selectedUnitId, setSelectedUnitId] = useState("");
const [changeRequestId, setChangeRequestId] = useState("");
const functionActionCapabilities = usePlatformUiCapabilities("organizations.functionActions");
@@ -490,6 +477,8 @@ export default function OrganizationsPage({
setEditingRelationId(null);
setEditingFunctionTypeId(null);
setEditingFunctionId(null);
+ setActiveEditor(null);
+ setChangeRequestId("");
}, []);
function rejectMissingName(): Promise {
@@ -513,6 +502,8 @@ export default function OrganizationsPage({
if (ok) {
setUnitTypeDraft(emptySluggedDraft());
setEditingUnitTypeId(null);
+ setActiveEditor(null);
+ setChangeRequestId("");
}
return ok;
}, [canWriteModel, editingUnitTypeId, runAction, settings, unitTypeDraft, withChangeRequest]);
@@ -530,6 +521,8 @@ export default function OrganizationsPage({
if (ok) {
setStructureDraft(emptyStructureDraft());
setEditingStructureId(null);
+ setActiveEditor(null);
+ setChangeRequestId("");
}
return ok;
}, [canWriteModel, editingStructureId, runAction, settings, structureDraft, withChangeRequest]);
@@ -561,6 +554,8 @@ export default function OrganizationsPage({
if (ok) {
setRelationTypeDraft(emptyRelationTypeDraft());
setEditingRelationTypeId(null);
+ setActiveEditor(null);
+ setChangeRequestId("");
}
return ok;
}, [canWriteModel, editingRelationTypeId, relationTypeDraft, runAction, settings, withChangeRequest]);
@@ -586,6 +581,8 @@ export default function OrganizationsPage({
if (ok) {
setUnitDraft(emptyUnitDraft());
setEditingUnitId(null);
+ setActiveEditor(null);
+ setChangeRequestId("");
}
return ok;
}, [canWriteUnits, editingUnitId, runAction, settings, unitDraft, withChangeRequest]);
@@ -606,6 +603,8 @@ export default function OrganizationsPage({
if (ok) {
setRelationDraft(emptyRelationDraft());
setEditingRelationId(null);
+ setActiveEditor(null);
+ setChangeRequestId("");
}
return ok;
}, [canWriteUnits, editingRelationId, relationDraft, runAction, settings, withChangeRequest]);
@@ -633,6 +632,8 @@ export default function OrganizationsPage({
if (ok) {
setFunctionTypeDraft(emptyFunctionTypeDraft());
setEditingFunctionTypeId(null);
+ setActiveEditor(null);
+ setChangeRequestId("");
}
return ok;
}, [canWriteModel, editingFunctionTypeId, functionTypeDraft, runAction, settings, withChangeRequest]);
@@ -666,6 +667,8 @@ export default function OrganizationsPage({
if (ok) {
setFunctionDraft(emptyFunctionDraft());
setEditingFunctionId(null);
+ setActiveEditor(null);
+ setChangeRequestId("");
}
return ok;
}, [canWriteFunctions, editingFunctionId, functionDraft, runAction, settings, withChangeRequest]);
@@ -704,44 +707,68 @@ export default function OrganizationsPage({
message: "i18n:govoplan-core.this_page_has_unsaved_changes_save_them_before_l.419a9d8b"
});
- const toggleUnitType = useCallback((item: OrganizationUnitTypeItem) => {
- void runAction(() => patchUnitType(settings, item.id, withChangeRequest({ is_active: !item.is_active })));
- }, [runAction, settings, withChangeRequest]);
+ const openUnitTypeCreate = useCallback(() => {
+ setEditingUnitTypeId(null);
+ setUnitTypeDraft(emptySluggedDraft());
+ setActive("model");
+ setActiveEditor("unitType");
+ }, []);
- const toggleStructure = useCallback((item: OrganizationStructureItem) => {
- void runAction(() => patchStructure(settings, item.id, withChangeRequest({ is_active: !item.is_active })));
- }, [runAction, settings, withChangeRequest]);
+ const openStructureCreate = useCallback(() => {
+ setEditingStructureId(null);
+ setStructureDraft(emptyStructureDraft());
+ setActive("model");
+ setActiveEditor("structure");
+ }, []);
- const toggleRelationType = useCallback((item: OrganizationRelationTypeItem) => {
- void runAction(() => patchRelationType(settings, item.id, withChangeRequest({ is_active: !item.is_active })));
- }, [runAction, settings, withChangeRequest]);
+ const openRelationTypeCreate = useCallback(() => {
+ setEditingRelationTypeId(null);
+ setRelationTypeDraft(emptyRelationTypeDraft());
+ setActive("model");
+ setActiveEditor("relationType");
+ }, []);
- const toggleUnit = useCallback((item: OrganizationUnitItem) => {
- void runAction(() => patchUnit(settings, item.id, withChangeRequest({ is_active: !item.is_active })));
- }, [runAction, settings, withChangeRequest]);
+ const openUnitCreate = useCallback((parentId = "") => {
+ setEditingUnitId(null);
+ setUnitDraft({ ...emptyUnitDraft(), parent_id: parentId });
+ if (parentId) setSelectedUnitId(parentId);
+ setActive("units");
+ setActiveEditor("unit");
+ }, []);
- const toggleRelation = useCallback((item: OrganizationRelationItem) => {
- void runAction(() => patchRelation(settings, item.id, withChangeRequest({ is_active: !item.is_active })));
- }, [runAction, settings, withChangeRequest]);
+ const openRelationCreate = useCallback(() => {
+ setEditingRelationId(null);
+ setRelationDraft(emptyRelationDraft());
+ setActive("relations");
+ setActiveEditor("relation");
+ }, []);
- const toggleFunctionType = useCallback((item: OrganizationFunctionTypeItem) => {
- void runAction(() => patchFunctionType(settings, item.id, withChangeRequest({ is_active: !item.is_active })));
- }, [runAction, settings, withChangeRequest]);
+ const openFunctionTypeCreate = useCallback(() => {
+ setEditingFunctionTypeId(null);
+ setFunctionTypeDraft(emptyFunctionTypeDraft());
+ setActive("model");
+ setActiveEditor("functionType");
+ }, []);
- const toggleFunction = useCallback((item: OrganizationFunctionItem) => {
- void runAction(() => patchFunction(settings, item.id, withChangeRequest({ is_active: !item.is_active })));
- }, [runAction, settings, withChangeRequest]);
+ const openFunctionCreate = useCallback((organizationUnitId = "") => {
+ setEditingFunctionId(null);
+ setFunctionDraft({ ...emptyFunctionDraft(), organization_unit_id: organizationUnitId || selectedUnitId });
+ setActive("functions");
+ setActiveEditor("function");
+ }, [selectedUnitId]);
const editUnitType = useCallback((item: OrganizationUnitTypeItem) => {
setEditingUnitTypeId(item.id);
setUnitTypeDraft(sluggedDraftFrom(item));
setActive("model");
+ setActiveEditor("unitType");
}, []);
const editStructure = useCallback((item: OrganizationStructureItem) => {
setEditingStructureId(item.id);
setStructureDraft({ ...sluggedDraftFrom(item), structure_kind: item.structure_kind as OrganizationStructureKind });
setActive("model");
+ setActiveEditor("structure");
}, []);
const editRelationType = useCallback((item: OrganizationRelationTypeItem) => {
@@ -755,6 +782,7 @@ export default function OrganizationsPage({
allow_cycles: item.allow_cycles
});
setActive("model");
+ setActiveEditor("relationType");
}, []);
const editUnit = useCallback((item: OrganizationUnitItem) => {
@@ -762,14 +790,12 @@ export default function OrganizationsPage({
setSelectedUnitId(item.id);
setUnitDraft({ ...sluggedDraftFrom(item), unit_type_id: item.unit_type_id ?? "", parent_id: item.parent_id ?? "" });
setActive("units");
+ setActiveEditor("unit");
}, []);
const addSubunit = useCallback((item: OrganizationUnitItem) => {
- setSelectedUnitId(item.id);
- setEditingUnitId(null);
- setUnitDraft({ ...emptyUnitDraft(), parent_id: item.id });
- setActive("units");
- }, []);
+ openUnitCreate(item.id);
+ }, [openUnitCreate]);
const editRelation = useCallback((item: OrganizationRelationItem) => {
setEditingRelationId(item.id);
@@ -781,6 +807,7 @@ export default function OrganizationsPage({
is_active: item.is_active
});
setActive("relations");
+ setActiveEditor("relation");
}, []);
const editFunctionType = useCallback((item: OrganizationFunctionTypeItem) => {
@@ -792,6 +819,7 @@ export default function OrganizationsPage({
act_in_place_allowed: item.act_in_place_allowed
});
setActive("model");
+ setActiveEditor("functionType");
}, []);
const editFunction = useCallback((item: OrganizationFunctionItem) => {
@@ -805,6 +833,7 @@ export default function OrganizationsPage({
act_in_place_allowed: item.act_in_place_allowed
});
setActive("functions");
+ setActiveEditor("function");
}, []);
useEffect(() => {
@@ -836,7 +865,7 @@ export default function OrganizationsPage({
{ id: "slug", header: "i18n:govoplan-organizations.slug.094da9b9", width: 160, sortable: true, filterable: true, value: (row) => row.slug },
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
{ id: "description", header: "i18n:govoplan-organizations.description.55f8ebc8", minWidth: 220, value: (row) => row.description ?? "" },
- { id: "actions", header: "", width: 220, sticky: "end", render: (row) => editUnitType(row)} onToggle={() => toggleUnitType(row)} /> }
+ { id: "actions", header: "", width: 88, sticky: "end", render: (row) => editUnitType(row)} /> }
];
const structureColumns: DataGridColumn[] = [
@@ -844,7 +873,7 @@ export default function OrganizationsPage({
{ id: "kind", header: "i18n:govoplan-organizations.kind.794c9d9c", width: 140, sortable: true, filterable: true, value: (row) => row.structure_kind },
{ id: "slug", header: "i18n:govoplan-organizations.slug.094da9b9", width: 160, value: (row) => row.slug },
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
- { id: "actions", header: "", width: 220, sticky: "end", render: (row) => editStructure(row)} onToggle={() => toggleStructure(row)} /> }
+ { id: "actions", header: "", width: 88, sticky: "end", render: (row) => editStructure(row)} /> }
];
const relationTypeColumns: DataGridColumn[] = [
@@ -854,7 +883,7 @@ export default function OrganizationsPage({
{ id: "target", header: "i18n:govoplan-organizations.target_unit.a1507d86", minWidth: 170, render: (row) => optionalLabel(unitTypeById.get(row.target_unit_type_id || "")?.name) },
{ id: "hierarchical", header: "i18n:govoplan-organizations.hierarchical.8964f313", width: 130, render: (row) => activeStatus(row.is_hierarchical) },
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
- { id: "actions", header: "", width: 220, sticky: "end", render: (row) => editRelationType(row)} onToggle={() => toggleRelationType(row)} /> }
+ { id: "actions", header: "", width: 88, sticky: "end", render: (row) => editRelationType(row)} /> }
];
const unitColumns: DataGridColumn[] = [
@@ -863,7 +892,7 @@ export default function OrganizationsPage({
{ id: "parent", header: "i18n:govoplan-organizations.parent_unit.1986c35e", minWidth: 180, render: (row) => optionalLabel(unitById.get(row.parent_id || "")?.name) },
{ id: "slug", header: "i18n:govoplan-organizations.slug.094da9b9", width: 160, value: (row) => row.slug },
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
- { id: "actions", header: "", width: 220, sticky: "end", render: (row) => editUnit(row)} onToggle={() => toggleUnit(row)} /> }
+ { id: "actions", header: "", width: 88, sticky: "end", render: (row) => editUnit(row)} /> }
];
const relationColumns: DataGridColumn[] = [
@@ -872,7 +901,7 @@ export default function OrganizationsPage({
{ id: "source", header: "i18n:govoplan-organizations.source_unit.2fbd8baa", minWidth: 190, render: (row) => optionalLabel(unitById.get(row.source_unit_id)?.name) },
{ id: "target", header: "i18n:govoplan-organizations.target_unit.a1507d86", minWidth: 190, render: (row) => optionalLabel(unitById.get(row.target_unit_id)?.name) },
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
- { id: "actions", header: "", width: 220, sticky: "end", render: (row) => editRelation(row)} onToggle={() => toggleRelation(row)} /> }
+ { id: "actions", header: "", width: 88, sticky: "end", render: (row) => editRelation(row)} /> }
];
const functionTypeColumns: DataGridColumn[] = [
@@ -881,7 +910,7 @@ export default function OrganizationsPage({
{ id: "delegable", header: "i18n:govoplan-organizations.delegable.b4f0137d", width: 120, render: (row) => activeStatus(row.delegable) },
{ id: "actInPlace", header: "i18n:govoplan-organizations.act_in_place.49b942bd", width: 140, render: (row) => activeStatus(row.act_in_place_allowed) },
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
- { id: "actions", header: "", width: 220, sticky: "end", render: (row) => editFunctionType(row)} onToggle={() => toggleFunctionType(row)} /> }
+ { id: "actions", header: "", width: 88, sticky: "end", render: (row) => editFunctionType(row)} /> }
];
const functionColumns: DataGridColumn[] = [
@@ -893,14 +922,12 @@ export default function OrganizationsPage({
{
id: "actions",
header: "",
- width: functionActionContributions.length ? 340 : 220,
+ width: functionActionContributions.length ? 220 : 88,
sticky: "end",
render: (row) => (
editFunction(row)}
- onToggle={() => toggleFunction(row)}
extra={functionActionContributions.map((contribution) => (
{contribution.render({ settings, auth, function: row })}
@@ -913,10 +940,6 @@ export default function OrganizationsPage({
const canWriteActive = active === "model" ? canWriteModel : active === "units" || active === "relations" ? canWriteUnits : canWriteFunctions;
- function cancelEditButton(onCancel: () => void) {
- return ;
- }
-
const content = (
@@ -925,23 +948,9 @@ export default function OrganizationsPage({
{description}
-
- {hasDirtyDraft && (
- <>
-
-
- >
- )}
@@ -955,6 +964,7 @@ export default function OrganizationsPage({
{active === "relations" && renderRelationsSection()}
{active === "functions" && renderFunctionsSection()}
+ {renderEditorDialog()}
);
@@ -972,99 +982,18 @@ export default function OrganizationsPage({
function renderModelSection() {
return (
-
-
-
-
-
- row.id} emptyText="i18n:govoplan-organizations.no_unit_types_found.c81fb2a7" initialFit="container" />
-
-
-
-
-
-
-
- row.id} emptyText="i18n:govoplan-organizations.no_structures_found.20b382d0" initialFit="container" />
-
-
-
-
-
-
-
- row.id} emptyText="i18n:govoplan-organizations.no_relation_types_found.6f90bb81" initialFit="container" />
-
-
-
-
-
-
-
- row.id} emptyText="i18n:govoplan-organizations.no_function_types_found.5eef31b1" initialFit="container" />
-
-
+
} variant="primary" disabled={!canWriteModel || busy} onClick={openUnitTypeCreate} />}>
+
row.id} emptyText="i18n:govoplan-organizations.no_unit_types_found.c81fb2a7" initialFit="container" />
+
+ } variant="primary" disabled={!canWriteModel || busy} onClick={openStructureCreate} />}>
+ row.id} emptyText="i18n:govoplan-organizations.no_structures_found.20b382d0" initialFit="container" />
+
+ } variant="primary" disabled={!canWriteModel || busy} onClick={openRelationTypeCreate} />}>
+ row.id} emptyText="i18n:govoplan-organizations.no_relation_types_found.6f90bb81" initialFit="container" />
+
+ } variant="primary" disabled={!canWriteModel || busy} onClick={openFunctionTypeCreate} />}>
+ row.id} emptyText="i18n:govoplan-organizations.no_function_types_found.5eef31b1" initialFit="container" />
+
);
}
@@ -1096,92 +1025,26 @@ export default function OrganizationsPage({
function renderUnitsSection() {
return (
-
-
+
+
} variant="primary" disabled={!canWriteUnits || busy} onClick={() => openUnitCreate()} />}>
-
+ {selectedUnitId &&
}
{model.units.length ? renderUnitTreeNodes() :
i18n:govoplan-organizations.no_units_found.eea2dd0c
}
-
-
-
-
-
- row.id} emptyText="i18n:govoplan-organizations.no_units_found.eea2dd0c" initialFit="container" />
-
-
+
} variant="primary" disabled={!canWriteUnits || busy} onClick={() => openUnitCreate()} />}>
+
row.id} emptyText="i18n:govoplan-organizations.no_units_found.eea2dd0c" initialFit="container" />
+
);
}
function renderRelationsSection() {
return (
-
-
-
-
-
+
+ } variant="primary" disabled={!canWriteUnits || busy} onClick={openRelationCreate} />}>
row.id} emptyText="i18n:govoplan-organizations.no_relations_found.4e75b11d" initialFit="container" />
@@ -1190,37 +1053,236 @@ export default function OrganizationsPage({
function renderFunctionsSection() {
return (
-
-
-
-
-
+
+ } variant="primary" disabled={!canWriteFunctions || busy} onClick={() => openFunctionCreate()} />}>
row.id} emptyText="i18n:govoplan-organizations.no_functions_found.54e759a0" initialFit="container" />
);
}
+ function renderChangeRequestField() {
+ return (
+
+
+ setChangeRequestId(event.target.value)} placeholder="cfgreq-..." disabled={busy} />
+
+
i18n:govoplan-organizations.change_request_id_help.0c119a5d
+
+ );
+ }
+
+ function editorTitle(): string {
+ switch (activeEditor) {
+ case "unitType": return editingUnitTypeId ? "i18n:govoplan-organizations.update_unit_type.b96fc0ad" : "i18n:govoplan-organizations.add_unit_type.58f2c05a";
+ case "structure": return editingStructureId ? "i18n:govoplan-organizations.update_structure.b2e25446" : "i18n:govoplan-organizations.add_structure.b722042a";
+ case "relationType": return editingRelationTypeId ? "i18n:govoplan-organizations.update_relation_type.e6e7f30c" : "i18n:govoplan-organizations.add_relation_type.2ad19d03";
+ case "unit": return editingUnitId ? "i18n:govoplan-organizations.update_unit.67e2500f" : "i18n:govoplan-organizations.add_unit.8fa12fb1";
+ case "relation": return editingRelationId ? "i18n:govoplan-organizations.update_relation.4c44ce83" : "i18n:govoplan-organizations.add_relation.6b6e67ea";
+ case "functionType": return editingFunctionTypeId ? "i18n:govoplan-organizations.update_function_type.0b7e07d3" : "i18n:govoplan-organizations.add_function_type.90d793f1";
+ case "function": return editingFunctionId ? "i18n:govoplan-organizations.update_function.504f03e5" : "i18n:govoplan-organizations.add_function.6abafee0";
+ default: return "i18n:govoplan-organizations.organizations.220edf64";
+ }
+ }
+
+ function editorSubmitDisabled(): boolean {
+ if (busy || !activeEditor) return true;
+ if ((activeEditor === "unitType" || activeEditor === "structure" || activeEditor === "relationType" || activeEditor === "functionType") && !canWriteModel) return true;
+ if ((activeEditor === "unit" || activeEditor === "relation") && !canWriteUnits) return true;
+ if (activeEditor === "function" && !canWriteFunctions) return true;
+ if (activeEditor === "unitType") return !unitTypeDraft.name.trim();
+ if (activeEditor === "structure") return !structureDraft.name.trim();
+ if (activeEditor === "relationType") return !relationTypeDraft.name.trim();
+ if (activeEditor === "unit") return !unitDraft.name.trim();
+ if (activeEditor === "relation") return !relationDraft.structure_id || !relationDraft.relation_type_id || !relationDraft.source_unit_id || !relationDraft.target_unit_id;
+ if (activeEditor === "functionType") return !functionTypeDraft.name.trim();
+ if (activeEditor === "function") return !functionDraft.name.trim() || !functionDraft.organization_unit_id;
+ return true;
+ }
+
+ function renderEditorDialog() {
+ if (!activeEditor) return null;
+ const formId = `organizations-${activeEditor}-editor`;
+ const submit = activeEditor === "unitType" ? submitUnitType :
+ activeEditor === "structure" ? submitStructure :
+ activeEditor === "relationType" ? submitRelationType :
+ activeEditor === "unit" ? submitUnit :
+ activeEditor === "relation" ? submitRelation :
+ activeEditor === "functionType" ? submitFunctionType :
+ submitFunction;
+ return (
+
+ );
+ }
+
+ function renderEditorFields() {
+ if (activeEditor === "unitType") {
+ return (
+ <>
+
+ {renderChangeRequestField()}
+ >
+ );
+ }
+ if (activeEditor === "structure") {
+ return (
+ <>
+
+
+
+
+ {renderChangeRequestField()}
+ >
+ );
+ }
+ if (activeEditor === "relationType") {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {renderChangeRequestField()}
+ >
+ );
+ }
+ if (activeEditor === "unit") {
+ return (
+ <>
+
+
+
+
+
+
+
+ {unitDraft.parent_id && (
+
+ i18n:govoplan-organizations.selected_parent.0e013d44 {unitById.get(unitDraft.parent_id)?.name ?? unitDraft.parent_id}
+
+ )}
+ {renderChangeRequestField()}
+ >
+ );
+ }
+ if (activeEditor === "relation") {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {renderChangeRequestField()}
+ >
+ );
+ }
+ if (activeEditor === "functionType") {
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {renderChangeRequestField()}
+ >
+ );
+ }
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ {renderChangeRequestField()}
+ >
+ );
+ }
+
}
diff --git a/webui/src/i18n/generatedTranslations.ts b/webui/src/i18n/generatedTranslations.ts
index ad1a27d..b38fc7b 100644
--- a/webui/src/i18n/generatedTranslations.ts
+++ b/webui/src/i18n/generatedTranslations.ts
@@ -24,6 +24,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-organizations.allow_tenant_model_customization.2425d751": "Tenant admins may customize the organization meta-model",
"i18n:govoplan-organizations.change_retention_days.71bbd140": "Retain organization change detail for days",
"i18n:govoplan-organizations.change_request_id.a6e7fd6b": "Change request ID",
+ "i18n:govoplan-organizations.change_request_id_help.0c119a5d": "Only required when organization model governance requires an approved recorded change request.",
"i18n:govoplan-organizations.cancel_edit.309c2a6f": "Cancel edit",
"i18n:govoplan-organizations.concrete_model.4e9c531a": "Concrete model",
"i18n:govoplan-organizations.deactivate.46ab070d": "Deactivate",
@@ -146,6 +147,7 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-organizations.allow_tenant_model_customization.2425d751": "Mandantenadmins dürfen das Organisations-Metamodell anpassen",
"i18n:govoplan-organizations.change_retention_days.71bbd140": "Änderungsdetails zur Organisation in Tagen aufbewahren",
"i18n:govoplan-organizations.change_request_id.a6e7fd6b": "Änderungsantrags-ID",
+ "i18n:govoplan-organizations.change_request_id_help.0c119a5d": "Nur erforderlich, wenn die Organisationsmodell-Governance einen genehmigten erfassten Änderungsantrag verlangt.",
"i18n:govoplan-organizations.cancel_edit.309c2a6f": "Bearbeitung abbrechen",
"i18n:govoplan-organizations.concrete_model.4e9c531a": "Konkretes Modell",
"i18n:govoplan-organizations.deactivate.46ab070d": "Deaktivieren",
diff --git a/webui/src/styles/organizations.css b/webui/src/styles/organizations.css
index 4c51e28..e89e175 100644
--- a/webui/src/styles/organizations.css
+++ b/webui/src/styles/organizations.css
@@ -26,48 +26,11 @@
.organizations-toolbar {
display: flex;
align-items: center;
- justify-content: space-between;
+ justify-content: flex-end;
gap: 12px;
flex-wrap: wrap;
}
-.organizations-change-request {
- display: inline-grid;
- gap: 4px;
- min-width: 220px;
- color: var(--muted);
- font-size: 12px;
- font-weight: 600;
-}
-
-.organizations-change-request input {
- min-height: 36px;
-}
-
-.organizations-section-grid {
- display: grid;
- grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
- gap: 18px;
- align-items: start;
-}
-
-.organizations-editor-grid {
- display: grid;
- grid-template-columns: minmax(260px, 340px) minmax(0, 1fr);
- gap: 18px;
- align-items: start;
-}
-
-.organizations-form-panel,
-.organizations-table-panel {
- min-width: 0;
-}
-
-.organizations-form-panel .card-body,
-.organizations-table-panel .card-body {
- min-width: 0;
-}
-
.organizations-form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -82,8 +45,8 @@
.organizations-row-actions {
display: flex;
align-items: center;
- gap: 8px;
- flex-wrap: wrap;
+ gap: 6px;
+ flex-wrap: nowrap;
}
.organizations-row-actions {
@@ -112,6 +75,15 @@
.organizations-table-stack {
display: grid;
gap: 18px;
+ width: 100%;
+}
+
+.organizations-table-stack > .card {
+ width: 100%;
+}
+
+.organizations-dialog-change-request {
+ padding-top: 2px;
}
.organizations-tree-toolbar {
@@ -181,13 +153,6 @@
color: var(--muted);
}
-@media (max-width: 1100px) {
- .organizations-section-grid,
- .organizations-editor-grid {
- grid-template-columns: 1fr;
- }
-}
-
@media (max-width: 900px) {
.organizations-workspace {
grid-template-columns: 1fr;