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 (
- - +
); @@ -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 (
-
- -
void submitUnitType(event)}> - -
- - {editingUnitTypeId && cancelEditButton(() => { setEditingUnitTypeId(null); setUnitTypeDraft(emptySluggedDraft()); })} -
- -
- - row.id} emptyText="i18n:govoplan-organizations.no_unit_types_found.c81fb2a7" initialFit="container" /> - -
-
- -
void submitStructure(event)}> - - - - -
- - {editingStructureId && cancelEditButton(() => { setEditingStructureId(null); setStructureDraft(emptyStructureDraft()); })} -
- -
- - row.id} emptyText="i18n:govoplan-organizations.no_structures_found.20b382d0" initialFit="container" /> - -
-
- -
void submitRelationType(event)}> - - - - - - - - - - -
- - -
-
- - {editingRelationTypeId && cancelEditButton(() => { setEditingRelationTypeId(null); setRelationTypeDraft(emptyRelationTypeDraft()); })} -
- -
- - row.id} emptyText="i18n:govoplan-organizations.no_relation_types_found.6f90bb81" initialFit="container" /> - -
-
- -
void submitFunctionType(event)}> - - - - -
- - -
-
- - {editingFunctionTypeId && cancelEditButton(() => { setEditingFunctionTypeId(null); setFunctionTypeDraft(emptyFunctionTypeDraft()); })} -
- -
- - row.id} emptyText="i18n:govoplan-organizations.no_function_types_found.5eef31b1" initialFit="container" /> - -
+ + + +
); } @@ -1096,92 +1025,26 @@ export default function OrganizationsPage({ function renderUnitsSection() { return ( -
- +
+ -
- -
void submitUnit(event)}> - - - - - - - - {unitDraft.parent_id && ( -

- i18n:govoplan-organizations.selected_parent.0e013d44 {unitById.get(unitDraft.parent_id)?.name ?? unitDraft.parent_id} -

- )} -
- - {editingUnitId && cancelEditButton(() => { setEditingUnitId(null); setUnitDraft(emptyUnitDraft()); })} -
- -
- - row.id} emptyText="i18n:govoplan-organizations.no_units_found.eea2dd0c" initialFit="container" /> - -
+
); } function renderRelationsSection() { return ( -
- -
void submitRelation(event)}> - - - - - - - - - - - - - -
- - {editingRelationId && cancelEditButton(() => { setEditingRelationId(null); setRelationDraft(emptyRelationDraft()); })} -
-
-
- +
+
@@ -1190,37 +1053,236 @@ export default function OrganizationsPage({ function renderFunctionsSection() { return ( -
- -
void submitFunction(event)}> - - - - - - - -
- - -
-
- - {editingFunctionId && cancelEditButton(() => { setEditingFunctionId(null); setFunctionDraft(emptyFunctionDraft()); })} -
- -
- +
+
); } + 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 ( + !busy && discardDrafts()} + closeDisabled={busy} + className="admin-dialog admin-dialog-wide organizations-editor-dialog" + footer={( + <> + + + + )} + > +
void submit(event)}> + {renderEditorFields()} +
+
+ ); + } + + 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;