import { useCallback, useEffect, useMemo, useRef, useState, type FormEvent } from "react"; import { Edit3, Plus, RefreshCw, Save, XCircle } from "lucide-react"; import { ApiError, Button, Card, DataGrid, DismissibleAlert, FormField, LoadingFrame, ModuleSubnav, PageTitle, StatusBadge, hasScope, useUnsavedDraftGuard, usePlatformUiCapabilities, type ApiSettings, type AuthInfo, type DataGridColumn, type ModuleSubnavGroup, type OrganizationFunctionActionContribution, type OrganizationFunctionActionsUiCapability } from "@govoplan/core-webui"; import { createFunction, createFunctionType, createRelation, createRelationType, createStructure, createUnit, createUnitType, getOrganizationModel, patchFunction, patchFunctionType, patchRelation, patchRelationType, patchStructure, patchUnit, patchUnitType, type FunctionCreatePayload, type FunctionTypeCreatePayload, type OrganizationFunctionItem, type OrganizationFunctionTypeItem, type OrganizationModel, type OrganizationRelationItem, type OrganizationRelationTypeItem, type OrganizationStructureItem, type OrganizationStructureKind, type OrganizationUnitItem, type OrganizationUnitTypeItem, type RelationCreatePayload, type RelationTypeCreatePayload, type SluggedCreatePayload, type StructureCreatePayload, type UnitCreatePayload } from "../../api/organizations"; export type OrganizationSection = "model" | "units" | "relations" | "functions"; type OrganizationsPageMode = "workspace" | "admin"; type OrganizationsPageProps = { settings: ApiSettings; auth: AuthInfo; mode?: OrganizationsPageMode; initialSection?: OrganizationSection; availableSections?: OrganizationSection[]; title?: string; description?: string; }; type SluggedDraft = { name: string; slug: string; description: string; is_active: boolean; }; type StructureDraft = SluggedDraft & { structure_kind: OrganizationStructureKind; }; type RelationTypeDraft = SluggedDraft & { structure_id: string; source_unit_type_id: string; target_unit_type_id: string; is_hierarchical: boolean; allow_cycles: boolean; }; type UnitDraft = SluggedDraft & { unit_type_id: string; parent_id: string; }; type RelationDraft = { structure_id: string; relation_type_id: string; source_unit_id: string; target_unit_id: string; is_active: boolean; }; type FunctionTypeDraft = SluggedDraft & { organization_unit_type_id: string; delegable: boolean; act_in_place_allowed: boolean; }; type FunctionDraft = SluggedDraft & { organization_unit_id: string; function_type_id: string; delegable: boolean; act_in_place_allowed: boolean; }; type ActiveItem = { id: string; is_active: boolean; }; type OrganizationsInitialQuery = { section: OrganizationSection | ""; unitId: string; functionId: string; }; function organizationSectionFromQuery(value: string | null): OrganizationSection | "" { return value === "model" || value === "units" || value === "relations" || value === "functions" ? value : ""; } function organizationsInitialQuery(): OrganizationsInitialQuery { if (typeof window === "undefined") return { section: "", unitId: "", functionId: "" }; const params = new URLSearchParams(window.location.search); return { section: organizationSectionFromQuery(params.get("section")), unitId: params.get("unit_id") || "", functionId: params.get("function_id") || "" }; } const EMPTY_MODEL: OrganizationModel = { unit_types: [], structures: [], relation_types: [], units: [], relations: [], function_types: [], functions: [] }; const SECTION_GROUPS: ModuleSubnavGroup[] = [ { title: "i18n:govoplan-organizations.organizations.220edf64", items: [ { id: "model", label: "i18n:govoplan-organizations.meta_model.7398487c", primary: true }, { id: "units", label: "i18n:govoplan-organizations.units.e14d0d92" }, { id: "relations", label: "i18n:govoplan-organizations.relations.1c796711" }, { id: "functions", label: "i18n:govoplan-organizations.functions.805dc49b" } ] } ]; const STRUCTURE_KINDS: Array<{ value: OrganizationStructureKind; label: string }> = [ { value: "hierarchy", label: "i18n:govoplan-organizations.hierarchy.74797f37" }, { value: "network", label: "i18n:govoplan-organizations.network.a24d31c3" }, { value: "membership", label: "i18n:govoplan-organizations.membership.4531d86d" }, { value: "classification", label: "i18n:govoplan-organizations.classification.3e9f6c3a" } ]; const ALL_SECTIONS: OrganizationSection[] = ["model", "units", "relations", "functions"]; function emptySluggedDraft(): SluggedDraft { return { name: "", slug: "", description: "", is_active: true }; } function emptyStructureDraft(): StructureDraft { return { ...emptySluggedDraft(), structure_kind: "hierarchy" }; } function emptyRelationTypeDraft(): RelationTypeDraft { return { ...emptySluggedDraft(), structure_id: "", source_unit_type_id: "", target_unit_type_id: "", is_hierarchical: true, allow_cycles: false }; } function emptyUnitDraft(): UnitDraft { return { ...emptySluggedDraft(), unit_type_id: "", parent_id: "" }; } function emptyRelationDraft(): RelationDraft { return { structure_id: "", relation_type_id: "", source_unit_id: "", target_unit_id: "", is_active: true }; } function emptyFunctionTypeDraft(): FunctionTypeDraft { return { ...emptySluggedDraft(), organization_unit_type_id: "", delegable: false, act_in_place_allowed: false }; } function emptyFunctionDraft(): FunctionDraft { return { ...emptySluggedDraft(), organization_unit_id: "", function_type_id: "", delegable: false, act_in_place_allowed: false }; } function textOrNull(value: string): string | null { const trimmed = value.trim(); return trimmed ? trimmed : null; } function sluggedPayload(draft: SluggedDraft): SluggedCreatePayload { return { name: draft.name.trim(), slug: textOrNull(draft.slug), description: textOrNull(draft.description), is_active: draft.is_active, settings: {} }; } function sluggedPatchPayload(draft: SluggedDraft): Partial { return { name: draft.name.trim(), slug: textOrNull(draft.slug), description: textOrNull(draft.description), is_active: draft.is_active }; } function sluggedDraftFrom(item: { name: string; slug: string; description?: string | null; is_active: boolean }): SluggedDraft { return { name: item.name, slug: item.slug, description: item.description ?? "", is_active: item.is_active }; } function isSluggedDirty(draft: SluggedDraft): boolean { return draft.name.trim() !== "" || draft.slug.trim() !== "" || draft.description.trim() !== "" || !draft.is_active; } function isRelationTypeDirty(draft: RelationTypeDraft): boolean { return isSluggedDirty(draft) || Boolean(draft.structure_id || draft.source_unit_type_id || draft.target_unit_type_id) || !draft.is_hierarchical || draft.allow_cycles; } function isUnitDirty(draft: UnitDraft): boolean { return isSluggedDirty(draft) || Boolean(draft.unit_type_id || draft.parent_id); } function isRelationDirty(draft: RelationDraft): boolean { return Boolean(draft.structure_id || draft.relation_type_id || draft.source_unit_id || draft.target_unit_id) || !draft.is_active; } function isFunctionTypeDirty(draft: FunctionTypeDraft): boolean { return isSluggedDirty(draft) || Boolean(draft.organization_unit_type_id) || draft.delegable || draft.act_in_place_allowed; } function isFunctionDirty(draft: FunctionDraft): boolean { return isSluggedDirty(draft) || Boolean(draft.organization_unit_id || draft.function_type_id) || draft.delegable || draft.act_in_place_allowed; } function mapById(items: T[]): Map { return new Map(items.map((item) => [item.id, item])); } function sectionGroupsFor(sections: ReadonlySet): ModuleSubnavGroup[] { return SECTION_GROUPS.map((group) => ({ ...group, items: group.items.filter((item) => sections.has(item.id)) })).filter((group) => group.items.length > 0); } function apiErrorMessage(error: unknown): string { if (error instanceof ApiError) { try { const parsed = JSON.parse(error.body) as { detail?: string | { message?: string } }; if (typeof parsed.detail === "string") return parsed.detail; if (parsed.detail && typeof parsed.detail.message === "string") return parsed.detail.message; } catch { // Fall back to the transport message below. } return error.message; } if (error instanceof Error) return error.message; return String(error); } function optionalLabel(label: string | null | undefined): JSX.Element { return label ? {label} : i18n:govoplan-organizations.none.334c4a4c; } 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[] }) { return (
{extra}
); } function SluggedFields({ draft, onChange, disabled }: { draft: SluggedDraft; onChange: (next: SluggedDraft) => void; disabled: boolean; }) { return ( <> onChange({ ...draft, name: event.target.value })} /> onChange({ ...draft, slug: event.target.value })} />