Sync GovOPlaN module state
This commit is contained in:
@@ -125,25 +125,6 @@ export type OrganizationFunctionItem = {
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type OrganizationFunctionAssignmentItem = {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
identity_id: string;
|
||||
account_id?: string | null;
|
||||
function_id: string;
|
||||
organization_unit_id: string;
|
||||
applies_to_subunits: boolean;
|
||||
source: string;
|
||||
delegated_from_assignment_id?: string | null;
|
||||
acting_for_account_id?: string | null;
|
||||
valid_from?: string | null;
|
||||
valid_until?: string | null;
|
||||
is_active: boolean;
|
||||
settings: Record<string, unknown>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type OrganizationModel = {
|
||||
unit_types: OrganizationUnitTypeItem[];
|
||||
structures: OrganizationStructureItem[];
|
||||
@@ -152,21 +133,6 @@ export type OrganizationModel = {
|
||||
relations: OrganizationRelationItem[];
|
||||
function_types: OrganizationFunctionTypeItem[];
|
||||
functions: OrganizationFunctionItem[];
|
||||
function_assignments: OrganizationFunctionAssignmentItem[];
|
||||
};
|
||||
|
||||
export type IdentityOption = {
|
||||
id: string;
|
||||
display_name?: string | null;
|
||||
external_subject?: string | null;
|
||||
source: string;
|
||||
primary_account_id?: string | null;
|
||||
account_ids: string[];
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type IdentityListResponse = {
|
||||
identities: IdentityOption[];
|
||||
};
|
||||
|
||||
export type OrganizationChangeRequestPayload = {
|
||||
@@ -220,18 +186,6 @@ export type FunctionCreatePayload = SluggedCreatePayload & {
|
||||
act_in_place_allowed?: boolean | null;
|
||||
};
|
||||
|
||||
export type FunctionAssignmentCreatePayload = {
|
||||
identity_id: string;
|
||||
account_id?: string | null;
|
||||
function_id: string;
|
||||
applies_to_subunits: boolean;
|
||||
source: string;
|
||||
delegated_from_assignment_id?: string | null;
|
||||
acting_for_account_id?: string | null;
|
||||
is_active?: boolean;
|
||||
settings?: Record<string, unknown>;
|
||||
} & OrganizationChangeRequestPayload;
|
||||
|
||||
function post<T, P extends Record<string, unknown>>(settings: ApiSettings, path: string, payload: P): Promise<T> {
|
||||
return apiFetch<T>(settings, path, { method: "POST", body: JSON.stringify(payload) });
|
||||
}
|
||||
@@ -244,15 +198,6 @@ export function getOrganizationModel(settings: ApiSettings): Promise<Organizatio
|
||||
return apiFetch<OrganizationModel>(settings, "/api/v1/organizations/model");
|
||||
}
|
||||
|
||||
export async function searchIdentityOptions(settings: ApiSettings, query = "", limit = 25): Promise<IdentityOption[]> {
|
||||
const params = new URLSearchParams();
|
||||
const trimmed = query.trim();
|
||||
if (trimmed) params.set("query", trimmed);
|
||||
params.set("limit", String(limit));
|
||||
const response = await apiFetch<IdentityListResponse>(settings, `/api/v1/identity/identities?${params.toString()}`);
|
||||
return response.identities;
|
||||
}
|
||||
|
||||
export function getOrganizationSettings(settings: ApiSettings): Promise<OrganizationSettingsItem> {
|
||||
return apiFetch<OrganizationSettingsItem>(settings, "/api/v1/organizations/settings");
|
||||
}
|
||||
@@ -316,11 +261,3 @@ export function createFunction(settings: ApiSettings, payload: FunctionCreatePay
|
||||
export function patchFunction(settings: ApiSettings, id: string, payload: Partial<FunctionCreatePayload>): Promise<OrganizationFunctionItem> {
|
||||
return patch(settings, `/api/v1/organizations/functions/${encodeURIComponent(id)}`, payload);
|
||||
}
|
||||
|
||||
export function createFunctionAssignment(settings: ApiSettings, payload: FunctionAssignmentCreatePayload): Promise<OrganizationFunctionAssignmentItem> {
|
||||
return post(settings, "/api/v1/organizations/function-assignments", payload);
|
||||
}
|
||||
|
||||
export function patchFunctionAssignment(settings: ApiSettings, id: string, payload: Partial<FunctionAssignmentCreatePayload>): Promise<OrganizationFunctionAssignmentItem> {
|
||||
return patch(settings, `/api/v1/organizations/function-assignments/${encodeURIComponent(id)}`, payload);
|
||||
}
|
||||
|
||||
114
webui/src/features/organizations/OrganizationFunctionPicker.tsx
Normal file
114
webui/src/features/organizations/OrganizationFunctionPicker.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import type {
|
||||
OrganizationFunctionLabelContext,
|
||||
OrganizationFunctionPickerContext,
|
||||
OrganizationFunctionSelection
|
||||
} from "@govoplan/core-webui";
|
||||
import { getOrganizationModel, type OrganizationFunctionItem, type OrganizationModel, type OrganizationUnitItem } from "../../api/organizations";
|
||||
|
||||
type FunctionOption = OrganizationFunctionSelection & {
|
||||
slug: string;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
export function OrganizationFunctionPicker({ settings, auth, value, disabled, onChange }: OrganizationFunctionPickerContext) {
|
||||
const [model, setModel] = useState<OrganizationModel | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const tenantId = (auth.active_tenant ?? auth.tenant).id;
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
setError("");
|
||||
getOrganizationModel(settings)
|
||||
.then((nextModel) => {
|
||||
if (!cancelled) setModel(nextModel);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!cancelled) setError(err instanceof Error ? err.message : String(err));
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [settings.accessToken, settings.apiBaseUrl, tenantId]);
|
||||
|
||||
const options = useMemo(() => functionOptions(model), [model]);
|
||||
const selectedId = value?.functionId ?? "";
|
||||
|
||||
return (
|
||||
<>
|
||||
<select
|
||||
value={selectedId}
|
||||
disabled={disabled || loading || Boolean(error)}
|
||||
onChange={(event) => {
|
||||
const selected = options.find((option) => option.functionId === event.target.value);
|
||||
onChange(selected ?? null);
|
||||
}}
|
||||
>
|
||||
<option value="">{loading ? "i18n:govoplan-organizations.loading_functions.77e8e684" : "i18n:govoplan-organizations.select_function.4895a67d"}</option>
|
||||
{options.map((option) => (
|
||||
<option key={option.functionId} value={option.functionId}>
|
||||
{option.label}{option.organizationUnitLabel ? ` (${option.organizationUnitLabel})` : ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{error && <p className="muted small-note">i18n:govoplan-organizations.functions_unavailable.4fd6765d</p>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function OrganizationFunctionLabel({ settings, auth, sourceModule, functionId, fallback }: OrganizationFunctionLabelContext) {
|
||||
const [model, setModel] = useState<OrganizationModel | null>(null);
|
||||
const tenantId = (auth.active_tenant ?? auth.tenant).id;
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
getOrganizationModel(settings)
|
||||
.then((nextModel) => {
|
||||
if (!cancelled) setModel(nextModel);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setModel(null);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [settings.accessToken, settings.apiBaseUrl, tenantId]);
|
||||
|
||||
const option = useMemo(() => functionOptions(model).find((item) => item.functionId === functionId), [functionId, model]);
|
||||
if (sourceModule !== "organizations" || !option) return <>{fallback ?? functionId}</>;
|
||||
return (
|
||||
<div>
|
||||
<strong>{option.label}</strong>
|
||||
<div className="muted small-note">{option.organizationUnitLabel ?? option.slug}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function functionOptions(model: OrganizationModel | null): FunctionOption[] {
|
||||
if (!model) return [];
|
||||
const unitById = new Map<string, OrganizationUnitItem>(model.units.map((unit) => [unit.id, unit]));
|
||||
return model.functions
|
||||
.filter((item) => item.is_active)
|
||||
.map((item) => toOption(item, unitById.get(item.organization_unit_id)))
|
||||
.sort((left, right) => {
|
||||
const unitDelta = (left.organizationUnitLabel ?? "").localeCompare(right.organizationUnitLabel ?? "");
|
||||
return unitDelta !== 0 ? unitDelta : (left.label ?? left.functionId).localeCompare(right.label ?? right.functionId);
|
||||
});
|
||||
}
|
||||
|
||||
function toOption(item: OrganizationFunctionItem, unit: OrganizationUnitItem | undefined): FunctionOption {
|
||||
return {
|
||||
sourceModule: "organizations",
|
||||
functionId: item.id,
|
||||
label: item.name,
|
||||
organizationUnitId: item.organization_unit_id,
|
||||
organizationUnitLabel: unit?.name ?? null,
|
||||
slug: item.slug,
|
||||
active: item.is_active
|
||||
};
|
||||
}
|
||||
@@ -13,14 +13,16 @@ import {
|
||||
StatusBadge,
|
||||
hasScope,
|
||||
useUnsavedDraftGuard,
|
||||
usePlatformUiCapabilities,
|
||||
type ApiSettings,
|
||||
type AuthInfo,
|
||||
type DataGridColumn,
|
||||
type ModuleSubnavGroup
|
||||
type ModuleSubnavGroup,
|
||||
type OrganizationFunctionActionContribution,
|
||||
type OrganizationFunctionActionsUiCapability
|
||||
} from "@govoplan/core-webui";
|
||||
import {
|
||||
createFunction,
|
||||
createFunctionAssignment,
|
||||
createFunctionType,
|
||||
createRelation,
|
||||
createRelationType,
|
||||
@@ -29,19 +31,14 @@ import {
|
||||
createUnitType,
|
||||
getOrganizationModel,
|
||||
patchFunction,
|
||||
patchFunctionAssignment,
|
||||
patchFunctionType,
|
||||
patchRelation,
|
||||
patchRelationType,
|
||||
patchStructure,
|
||||
patchUnit,
|
||||
patchUnitType,
|
||||
searchIdentityOptions,
|
||||
type FunctionAssignmentCreatePayload,
|
||||
type FunctionCreatePayload,
|
||||
type FunctionTypeCreatePayload,
|
||||
type IdentityOption,
|
||||
type OrganizationFunctionAssignmentItem,
|
||||
type OrganizationFunctionItem,
|
||||
type OrganizationFunctionTypeItem,
|
||||
type OrganizationModel,
|
||||
@@ -58,7 +55,7 @@ import {
|
||||
type UnitCreatePayload
|
||||
} from "../../api/organizations";
|
||||
|
||||
export type OrganizationSection = "model" | "units" | "relations" | "functions" | "assignments";
|
||||
export type OrganizationSection = "model" | "units" | "relations" | "functions";
|
||||
type OrganizationsPageMode = "workspace" | "admin";
|
||||
|
||||
type OrganizationsPageProps = {
|
||||
@@ -116,17 +113,6 @@ type FunctionDraft = SluggedDraft & {
|
||||
act_in_place_allowed: boolean;
|
||||
};
|
||||
|
||||
type AssignmentDraft = {
|
||||
identity_id: string;
|
||||
account_id: string;
|
||||
function_id: string;
|
||||
applies_to_subunits: boolean;
|
||||
source: string;
|
||||
delegated_from_assignment_id: string;
|
||||
acting_for_account_id: string;
|
||||
is_active: boolean;
|
||||
};
|
||||
|
||||
type ActiveItem = {
|
||||
id: string;
|
||||
is_active: boolean;
|
||||
@@ -139,8 +125,7 @@ const EMPTY_MODEL: OrganizationModel = {
|
||||
units: [],
|
||||
relations: [],
|
||||
function_types: [],
|
||||
functions: [],
|
||||
function_assignments: []
|
||||
functions: []
|
||||
};
|
||||
|
||||
const SECTION_GROUPS: ModuleSubnavGroup<OrganizationSection>[] = [
|
||||
@@ -150,8 +135,7 @@ const SECTION_GROUPS: ModuleSubnavGroup<OrganizationSection>[] = [
|
||||
{ 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" },
|
||||
{ id: "assignments", label: "i18n:govoplan-organizations.assignments.278f513e" }
|
||||
{ id: "functions", label: "i18n:govoplan-organizations.functions.805dc49b" }
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -163,7 +147,7 @@ const STRUCTURE_KINDS: Array<{ value: OrganizationStructureKind; label: string }
|
||||
{ value: "classification", label: "i18n:govoplan-organizations.classification.3e9f6c3a" }
|
||||
];
|
||||
|
||||
const ALL_SECTIONS: OrganizationSection[] = ["model", "units", "relations", "functions", "assignments"];
|
||||
const ALL_SECTIONS: OrganizationSection[] = ["model", "units", "relations", "functions"];
|
||||
|
||||
function emptySluggedDraft(): SluggedDraft {
|
||||
return { name: "", slug: "", description: "", is_active: true };
|
||||
@@ -200,19 +184,6 @@ function emptyFunctionDraft(): FunctionDraft {
|
||||
return { ...emptySluggedDraft(), organization_unit_id: "", function_type_id: "", delegable: false, act_in_place_allowed: false };
|
||||
}
|
||||
|
||||
function emptyAssignmentDraft(): AssignmentDraft {
|
||||
return {
|
||||
identity_id: "",
|
||||
account_id: "",
|
||||
function_id: "",
|
||||
applies_to_subunits: false,
|
||||
source: "direct",
|
||||
delegated_from_assignment_id: "",
|
||||
acting_for_account_id: "",
|
||||
is_active: true
|
||||
};
|
||||
}
|
||||
|
||||
function textOrNull(value: string): string | null {
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed : null;
|
||||
@@ -270,19 +241,6 @@ 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 isAssignmentDirty(draft: AssignmentDraft): boolean {
|
||||
return Boolean(
|
||||
draft.identity_id.trim() ||
|
||||
draft.account_id.trim() ||
|
||||
draft.function_id ||
|
||||
draft.applies_to_subunits ||
|
||||
draft.source.trim() !== "direct" ||
|
||||
draft.delegated_from_assignment_id ||
|
||||
draft.acting_for_account_id ||
|
||||
!draft.is_active
|
||||
);
|
||||
}
|
||||
|
||||
function mapById<T extends { id: string }>(items: T[]): Map<string, T> {
|
||||
return new Map(items.map((item) => [item.id, item]));
|
||||
}
|
||||
@@ -317,21 +275,6 @@ 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 identityOptionLabel(identity: IdentityOption): string {
|
||||
return identity.display_name || identity.external_subject || identity.primary_account_id || identity.id;
|
||||
}
|
||||
|
||||
function identityDisplay(identityId: string, identities: Map<string, IdentityOption>): JSX.Element {
|
||||
const identity = identities.get(identityId);
|
||||
if (!identity) return <span className="organizations-id">{identityId}</span>;
|
||||
return (
|
||||
<span className="organizations-identity">
|
||||
<span>{identityOptionLabel(identity)}</span>
|
||||
<span className="organizations-id">{identity.id}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
@@ -341,13 +284,14 @@ function ActiveToggleButton({ item, disabled, onToggle }: { item: ActiveItem; di
|
||||
);
|
||||
}
|
||||
|
||||
function RowActions({ item, disabled, onEdit, onToggle }: { item: ActiveItem; disabled: boolean; onEdit: () => void; onToggle: () => void }) {
|
||||
function RowActions({ item, disabled, onEdit, onToggle, extra }: { item: ActiveItem; disabled: boolean; onEdit: () => void; onToggle: () => void; extra?: JSX.Element[] }) {
|
||||
return (
|
||||
<div className="organizations-row-actions">
|
||||
<Button type="button" variant="ghost" disabled={disabled} onClick={onEdit} title="i18n:govoplan-organizations.edit.7dce1220">
|
||||
<Edit3 size={16} aria-hidden="true" /> i18n:govoplan-organizations.edit.7dce1220
|
||||
</Button>
|
||||
<ActiveToggleButton item={item} disabled={disabled} onToggle={onToggle} />
|
||||
{extra}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -382,6 +326,14 @@ function SluggedFields({
|
||||
);
|
||||
}
|
||||
|
||||
function contributionVisible(auth: AuthInfo, contribution: { anyOf?: string[]; allOf?: string[] }): boolean {
|
||||
const anyOf = contribution.anyOf ?? [];
|
||||
const allOf = contribution.allOf ?? [];
|
||||
if (anyOf.length && !anyOf.some((scope) => hasScope(auth, scope))) return false;
|
||||
if (allOf.length && !allOf.every((scope) => hasScope(auth, scope))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export default function OrganizationsPage({
|
||||
settings,
|
||||
auth,
|
||||
@@ -408,7 +360,6 @@ export default function OrganizationsPage({
|
||||
const [relationDraft, setRelationDraft] = useState<RelationDraft>(() => emptyRelationDraft());
|
||||
const [functionTypeDraft, setFunctionTypeDraft] = useState<FunctionTypeDraft>(() => emptyFunctionTypeDraft());
|
||||
const [functionDraft, setFunctionDraft] = useState<FunctionDraft>(() => emptyFunctionDraft());
|
||||
const [assignmentDraft, setAssignmentDraft] = useState<AssignmentDraft>(() => emptyAssignmentDraft());
|
||||
const [editingUnitTypeId, setEditingUnitTypeId] = useState<string | null>(null);
|
||||
const [editingStructureId, setEditingStructureId] = useState<string | null>(null);
|
||||
const [editingRelationTypeId, setEditingRelationTypeId] = useState<string | null>(null);
|
||||
@@ -416,18 +367,13 @@ export default function OrganizationsPage({
|
||||
const [editingRelationId, setEditingRelationId] = useState<string | null>(null);
|
||||
const [editingFunctionTypeId, setEditingFunctionTypeId] = useState<string | null>(null);
|
||||
const [editingFunctionId, setEditingFunctionId] = useState<string | null>(null);
|
||||
const [editingAssignmentId, setEditingAssignmentId] = useState<string | null>(null);
|
||||
const [selectedUnitId, setSelectedUnitId] = useState("");
|
||||
const [changeRequestId, setChangeRequestId] = useState("");
|
||||
const [identitySearch, setIdentitySearch] = useState("");
|
||||
const [identityOptions, setIdentityOptions] = useState<IdentityOption[]>([]);
|
||||
const [identityLoading, setIdentityLoading] = useState(false);
|
||||
const [identityLookupAvailable, setIdentityLookupAvailable] = useState(true);
|
||||
const functionActionCapabilities = usePlatformUiCapabilities<OrganizationFunctionActionsUiCapability>("organizations.functionActions");
|
||||
|
||||
const canWriteModel = hasScope(auth, "organizations:model:write");
|
||||
const canWriteUnits = hasScope(auth, "organizations:unit:write");
|
||||
const canWriteFunctions = hasScope(auth, "organizations:function:write");
|
||||
const canAssignFunctions = hasScope(auth, "organizations:function:assign");
|
||||
|
||||
const unitTypeById = useMemo(() => mapById(model.unit_types), [model.unit_types]);
|
||||
const structureById = useMemo(() => mapById(model.structures), [model.structures]);
|
||||
@@ -435,7 +381,13 @@ export default function OrganizationsPage({
|
||||
const unitById = useMemo(() => mapById(model.units), [model.units]);
|
||||
const functionTypeById = useMemo(() => mapById(model.function_types), [model.function_types]);
|
||||
const functionById = useMemo(() => mapById(model.functions), [model.functions]);
|
||||
const identityOptionById = useMemo(() => mapById(identityOptions), [identityOptions]);
|
||||
const functionActionContributions = useMemo(
|
||||
() => functionActionCapabilities
|
||||
.flatMap((capability) => capability.actions)
|
||||
.filter((contribution) => contributionVisible(auth, contribution))
|
||||
.sort((left, right) => (left.order ?? 100) - (right.order ?? 100)),
|
||||
[auth, functionActionCapabilities]
|
||||
);
|
||||
const unitsByParentId = useMemo(() => {
|
||||
const mapped = new Map<string, OrganizationUnitItem[]>();
|
||||
for (const unit of model.units) {
|
||||
@@ -452,8 +404,7 @@ export default function OrganizationsPage({
|
||||
isUnitDirty(unitDraft) ||
|
||||
isRelationDirty(relationDraft) ||
|
||||
isFunctionTypeDirty(functionTypeDraft) ||
|
||||
isFunctionDirty(functionDraft) ||
|
||||
isAssignmentDirty(assignmentDraft);
|
||||
isFunctionDirty(functionDraft);
|
||||
|
||||
const loadModel = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -484,36 +435,6 @@ export default function OrganizationsPage({
|
||||
if (selectedUnitId && !unitById.has(selectedUnitId)) setSelectedUnitId("");
|
||||
}, [selectedUnitId, unitById]);
|
||||
|
||||
useEffect(() => {
|
||||
if (active !== "assignments" || !identityLookupAvailable) return undefined;
|
||||
let cancelled = false;
|
||||
const timeout = window.setTimeout(() => {
|
||||
setIdentityLoading(true);
|
||||
searchIdentityOptions(settings, identitySearch, 50).
|
||||
then((items) => {
|
||||
if (cancelled) return;
|
||||
setIdentityOptions(items);
|
||||
setIdentityLookupAvailable(true);
|
||||
}).
|
||||
catch((caught) => {
|
||||
if (cancelled) return;
|
||||
if (caught instanceof ApiError && (caught.status === 403 || caught.status === 404)) {
|
||||
setIdentityLookupAvailable(false);
|
||||
setIdentityOptions([]);
|
||||
return;
|
||||
}
|
||||
setError(apiErrorMessage(caught));
|
||||
}).
|
||||
finally(() => {
|
||||
if (!cancelled) setIdentityLoading(false);
|
||||
});
|
||||
}, 250);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.clearTimeout(timeout);
|
||||
};
|
||||
}, [active, identityLookupAvailable, identitySearch, settings]);
|
||||
|
||||
const runAction = useCallback(async (action: () => Promise<unknown>, successMessage = ""): Promise<boolean> => {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
@@ -539,7 +460,6 @@ export default function OrganizationsPage({
|
||||
setRelationDraft(emptyRelationDraft());
|
||||
setFunctionTypeDraft(emptyFunctionTypeDraft());
|
||||
setFunctionDraft(emptyFunctionDraft());
|
||||
setAssignmentDraft(emptyAssignmentDraft());
|
||||
setEditingUnitTypeId(null);
|
||||
setEditingStructureId(null);
|
||||
setEditingRelationTypeId(null);
|
||||
@@ -547,7 +467,6 @@ export default function OrganizationsPage({
|
||||
setEditingRelationId(null);
|
||||
setEditingFunctionTypeId(null);
|
||||
setEditingFunctionId(null);
|
||||
setEditingAssignmentId(null);
|
||||
}, []);
|
||||
|
||||
function rejectMissingName(): Promise<boolean> {
|
||||
@@ -728,35 +647,6 @@ export default function OrganizationsPage({
|
||||
return ok;
|
||||
}, [canWriteFunctions, editingFunctionId, functionDraft, runAction, settings, withChangeRequest]);
|
||||
|
||||
const submitAssignment = useCallback(async (event?: FormEvent): Promise<boolean> => {
|
||||
event?.preventDefault();
|
||||
if (!canAssignFunctions) return rejectMissingWritePermission();
|
||||
if (!assignmentDraft.identity_id.trim() || !assignmentDraft.function_id) {
|
||||
setError("i18n:govoplan-organizations.select_function.4895a67d");
|
||||
return false;
|
||||
}
|
||||
const payload: FunctionAssignmentCreatePayload = {
|
||||
identity_id: assignmentDraft.identity_id.trim(),
|
||||
account_id: textOrNull(assignmentDraft.account_id),
|
||||
function_id: assignmentDraft.function_id,
|
||||
applies_to_subunits: assignmentDraft.applies_to_subunits,
|
||||
source: assignmentDraft.source.trim() || "direct",
|
||||
delegated_from_assignment_id: textOrNull(assignmentDraft.delegated_from_assignment_id),
|
||||
acting_for_account_id: textOrNull(assignmentDraft.acting_for_account_id),
|
||||
is_active: assignmentDraft.is_active,
|
||||
settings: {}
|
||||
};
|
||||
const ok = await runAction(
|
||||
() => editingAssignmentId ? patchFunctionAssignment(settings, editingAssignmentId, withChangeRequest(payload)) : createFunctionAssignment(settings, withChangeRequest(payload)),
|
||||
editingAssignmentId ? "i18n:govoplan-organizations.assignment_updated.680b6e33" : "i18n:govoplan-organizations.assignment_added.94263d1b"
|
||||
);
|
||||
if (ok) {
|
||||
setAssignmentDraft(emptyAssignmentDraft());
|
||||
setEditingAssignmentId(null);
|
||||
}
|
||||
return ok;
|
||||
}, [assignmentDraft, canAssignFunctions, editingAssignmentId, runAction, settings, withChangeRequest]);
|
||||
|
||||
const saveDrafts = useCallback(async (): Promise<boolean> => {
|
||||
if (isSluggedDirty(unitTypeDraft) && !(await submitUnitType())) return false;
|
||||
if (isSluggedDirty(structureDraft) && !(await submitStructure())) return false;
|
||||
@@ -765,16 +655,13 @@ export default function OrganizationsPage({
|
||||
if (isRelationDirty(relationDraft) && !(await submitRelation())) return false;
|
||||
if (isFunctionTypeDirty(functionTypeDraft) && !(await submitFunctionType())) return false;
|
||||
if (isFunctionDirty(functionDraft) && !(await submitFunction())) return false;
|
||||
if (isAssignmentDirty(assignmentDraft) && !(await submitAssignment())) return false;
|
||||
return true;
|
||||
}, [
|
||||
assignmentDraft,
|
||||
functionDraft,
|
||||
functionTypeDraft,
|
||||
relationDraft,
|
||||
relationTypeDraft,
|
||||
structureDraft,
|
||||
submitAssignment,
|
||||
submitFunction,
|
||||
submitFunctionType,
|
||||
submitRelation,
|
||||
@@ -822,10 +709,6 @@ export default function OrganizationsPage({
|
||||
void runAction(() => patchFunction(settings, item.id, withChangeRequest({ is_active: !item.is_active })));
|
||||
}, [runAction, settings, withChangeRequest]);
|
||||
|
||||
const toggleAssignment = useCallback((item: OrganizationFunctionAssignmentItem) => {
|
||||
void runAction(() => patchFunctionAssignment(settings, item.id, withChangeRequest({ is_active: !item.is_active })));
|
||||
}, [runAction, settings, withChangeRequest]);
|
||||
|
||||
const editUnitType = useCallback((item: OrganizationUnitTypeItem) => {
|
||||
setEditingUnitTypeId(item.id);
|
||||
setUnitTypeDraft(sluggedDraftFrom(item));
|
||||
@@ -901,22 +784,6 @@ export default function OrganizationsPage({
|
||||
setActive("functions");
|
||||
}, []);
|
||||
|
||||
const editAssignment = useCallback((item: OrganizationFunctionAssignmentItem) => {
|
||||
setEditingAssignmentId(item.id);
|
||||
setSelectedUnitId(item.organization_unit_id);
|
||||
setAssignmentDraft({
|
||||
identity_id: item.identity_id,
|
||||
account_id: item.account_id ?? "",
|
||||
function_id: item.function_id,
|
||||
applies_to_subunits: item.applies_to_subunits,
|
||||
source: item.source,
|
||||
delegated_from_assignment_id: item.delegated_from_assignment_id ?? "",
|
||||
acting_for_account_id: item.acting_for_account_id ?? "",
|
||||
is_active: item.is_active
|
||||
});
|
||||
setActive("assignments");
|
||||
}, []);
|
||||
|
||||
const unitTypeColumns: DataGridColumn<OrganizationUnitTypeItem>[] = [
|
||||
{ id: "name", header: "i18n:govoplan-organizations.name.709a2322", minWidth: 180, sortable: true, filterable: true, value: (row) => row.name },
|
||||
{ id: "slug", header: "i18n:govoplan-organizations.slug.094da9b9", width: 160, sortable: true, filterable: true, value: (row) => row.slug },
|
||||
@@ -976,20 +843,28 @@ export default function OrganizationsPage({
|
||||
{ id: "type", header: "i18n:govoplan-organizations.function_type.501fe7c0", minWidth: 180, render: (row) => optionalLabel(functionTypeById.get(row.function_type_id || "")?.name) },
|
||||
{ id: "delegable", header: "i18n:govoplan-organizations.delegable.b4f0137d", width: 120, render: (row) => activeStatus(row.delegable) },
|
||||
{ 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) => <RowActions item={row} disabled={!canWriteFunctions || busy} onEdit={() => editFunction(row)} onToggle={() => toggleFunction(row)} /> }
|
||||
{
|
||||
id: "actions",
|
||||
header: "",
|
||||
width: functionActionContributions.length ? 340 : 220,
|
||||
sticky: "end",
|
||||
render: (row) => (
|
||||
<RowActions
|
||||
item={row}
|
||||
disabled={!canWriteFunctions || busy}
|
||||
onEdit={() => editFunction(row)}
|
||||
onToggle={() => toggleFunction(row)}
|
||||
extra={functionActionContributions.map((contribution) => (
|
||||
<span className="organizations-contributed-action" key={contribution.id}>
|
||||
{contribution.render({ settings, auth, function: row })}
|
||||
</span>
|
||||
))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const assignmentColumns: DataGridColumn<OrganizationFunctionAssignmentItem>[] = [
|
||||
{ id: "identity", header: "i18n:govoplan-organizations.identity.3252f35f", minWidth: 220, render: (row) => identityDisplay(row.identity_id, identityOptionById) },
|
||||
{ id: "function", header: "i18n:govoplan-organizations.function.28822f3a", minWidth: 190, render: (row) => optionalLabel(functionById.get(row.function_id)?.name) },
|
||||
{ id: "unit", header: "i18n:govoplan-organizations.unit.8fe4d595", minWidth: 180, render: (row) => optionalLabel(unitById.get(row.organization_unit_id)?.name) },
|
||||
{ id: "subunits", header: "i18n:govoplan-organizations.applies_to_subunits.7a15f741", width: 150, render: (row) => activeStatus(row.applies_to_subunits) },
|
||||
{ id: "source", header: "i18n:govoplan-organizations.source.6da13add", width: 130, value: (row) => row.source },
|
||||
{ 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) => <RowActions item={row} disabled={!canAssignFunctions || busy} onEdit={() => editAssignment(row)} onToggle={() => toggleAssignment(row)} /> }
|
||||
];
|
||||
|
||||
const canWriteActive = active === "model" ? canWriteModel : active === "units" || active === "relations" ? canWriteUnits : active === "functions" ? canWriteFunctions : canAssignFunctions;
|
||||
const canWriteActive = active === "model" ? canWriteModel : active === "units" || active === "relations" ? canWriteUnits : canWriteFunctions;
|
||||
|
||||
function cancelEditButton(onCancel: () => void) {
|
||||
return <Button type="button" variant="ghost" onClick={onCancel} disabled={busy}>i18n:govoplan-organizations.cancel_edit.309c2a6f</Button>;
|
||||
@@ -1032,7 +907,6 @@ export default function OrganizationsPage({
|
||||
{active === "units" && renderUnitsSection()}
|
||||
{active === "relations" && renderRelationsSection()}
|
||||
{active === "functions" && renderFunctionsSection()}
|
||||
{active === "assignments" && renderAssignmentsSection()}
|
||||
</LoadingFrame>
|
||||
</div>
|
||||
);
|
||||
@@ -1302,77 +1176,4 @@ export default function OrganizationsPage({
|
||||
);
|
||||
}
|
||||
|
||||
function renderAssignmentsSection() {
|
||||
const selectedIdentity = identityOptionById.get(assignmentDraft.identity_id);
|
||||
return (
|
||||
<div className="organizations-section-grid">
|
||||
<Card title="i18n:govoplan-organizations.add_assignment.6682f5f5">
|
||||
<form className="organizations-form-grid" onSubmit={(event) => void submitAssignment(event)}>
|
||||
{identityLookupAvailable ? (
|
||||
<div className="organizations-identity-picker wide">
|
||||
<FormField label="i18n:govoplan-organizations.identity_search.eff6eb16">
|
||||
<input value={identitySearch} placeholder="i18n:govoplan-organizations.search_identities.004afdfd" disabled={!canAssignFunctions || busy} onChange={(event) => setIdentitySearch(event.target.value)} />
|
||||
</FormField>
|
||||
<FormField label="i18n:govoplan-organizations.identity.3252f35f">
|
||||
<select
|
||||
value={assignmentDraft.identity_id}
|
||||
disabled={!canAssignFunctions || busy || identityLoading}
|
||||
onChange={(event) => {
|
||||
const identity = identityOptionById.get(event.target.value);
|
||||
setAssignmentDraft({
|
||||
...assignmentDraft,
|
||||
identity_id: event.target.value,
|
||||
account_id: identity?.primary_account_id ?? identity?.account_ids[0] ?? ""
|
||||
});
|
||||
}}
|
||||
>
|
||||
<option value="">{identityLoading ? "i18n:govoplan-organizations.loading_identities.9c31d2b5" : "i18n:govoplan-organizations.select_identity.510f1134"}</option>
|
||||
{assignmentDraft.identity_id && !identityOptionById.has(assignmentDraft.identity_id) && <option value={assignmentDraft.identity_id}>{assignmentDraft.identity_id}</option>}
|
||||
{identityOptions.map((identity) => <option key={identity.id} value={identity.id}>{identityOptionLabel(identity)}</option>)}
|
||||
</select>
|
||||
</FormField>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="organizations-muted wide">i18n:govoplan-organizations.identity_lookup_unavailable.5a3e77b2</p>
|
||||
<FormField label="i18n:govoplan-organizations.identity_id.b6ef5010">
|
||||
<input value={assignmentDraft.identity_id} placeholder="i18n:govoplan-organizations.identity_id_placeholder.298cbd85" disabled={!canAssignFunctions || busy} onChange={(event) => setAssignmentDraft({ ...assignmentDraft, identity_id: event.target.value })} />
|
||||
</FormField>
|
||||
</>
|
||||
)}
|
||||
<FormField label="i18n:govoplan-organizations.optional_account_id.5fcf495c">
|
||||
{selectedIdentity && selectedIdentity.account_ids.length > 0 ? (
|
||||
<select value={assignmentDraft.account_id} disabled={!canAssignFunctions || busy} onChange={(event) => setAssignmentDraft({ ...assignmentDraft, account_id: event.target.value })}>
|
||||
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
||||
{selectedIdentity.account_ids.map((accountId) => <option key={accountId} value={accountId}>{accountId}</option>)}
|
||||
</select>
|
||||
) : (
|
||||
<input value={assignmentDraft.account_id} disabled={!canAssignFunctions || busy} onChange={(event) => setAssignmentDraft({ ...assignmentDraft, account_id: event.target.value })} />
|
||||
)}
|
||||
</FormField>
|
||||
<FormField label="i18n:govoplan-organizations.function.28822f3a">
|
||||
<select value={assignmentDraft.function_id} disabled={!canAssignFunctions || busy} onChange={(event) => setAssignmentDraft({ ...assignmentDraft, function_id: event.target.value })}>
|
||||
<option value="">i18n:govoplan-organizations.select_function.4895a67d</option>
|
||||
{model.functions.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label="i18n:govoplan-organizations.source.6da13add">
|
||||
<input value={assignmentDraft.source} disabled={!canAssignFunctions || busy} onChange={(event) => setAssignmentDraft({ ...assignmentDraft, source: event.target.value })} />
|
||||
</FormField>
|
||||
<div className="organizations-check-list">
|
||||
<label><input type="checkbox" checked={assignmentDraft.applies_to_subunits} disabled={!canAssignFunctions || busy} onChange={(event) => setAssignmentDraft({ ...assignmentDraft, applies_to_subunits: event.target.checked })} /> i18n:govoplan-organizations.applies_to_subunits.7a15f741</label>
|
||||
<label><input type="checkbox" checked={assignmentDraft.is_active} disabled={!canAssignFunctions || busy} onChange={(event) => setAssignmentDraft({ ...assignmentDraft, is_active: event.target.checked })} /> i18n:govoplan-organizations.active.a733b809</label>
|
||||
</div>
|
||||
<div className="organizations-form-actions wide">
|
||||
<Button type="submit" variant="primary" disabled={!canAssignFunctions || busy}>{editingAssignmentId ? "i18n:govoplan-organizations.update_assignment.bf130723" : "i18n:govoplan-organizations.add_assignment.6682f5f5"}</Button>
|
||||
{editingAssignmentId && cancelEditButton(() => { setEditingAssignmentId(null); setAssignmentDraft(emptyAssignmentDraft()); })}
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
<Card title="i18n:govoplan-organizations.function_assignments.4c5c6dae">
|
||||
<DataGrid id="organizations-assignments" rows={model.function_assignments} columns={assignmentColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_assignments_found.7ecc5bb7" initialFit="container" />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
en: {
|
||||
"i18n:govoplan-organizations.act_in_place.49b942bd": "Act in place",
|
||||
"i18n:govoplan-organizations.active.a733b809": "Active",
|
||||
"i18n:govoplan-organizations.add_assignment.6682f5f5": "Add assignment",
|
||||
"i18n:govoplan-organizations.add_function.6abafee0": "Add function",
|
||||
"i18n:govoplan-organizations.add_function_type.90d793f1": "Add function type",
|
||||
"i18n:govoplan-organizations.add_relation.6b6e67ea": "Add relation",
|
||||
@@ -15,10 +14,6 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.add_unit.8fa12fb1": "Add unit",
|
||||
"i18n:govoplan-organizations.add_unit_type.58f2c05a": "Add unit type",
|
||||
"i18n:govoplan-organizations.allow_cycles.31327578": "Allow cycles",
|
||||
"i18n:govoplan-organizations.applies_to_subunits.7a15f741": "Applies to subunits",
|
||||
"i18n:govoplan-organizations.assignment_added.94263d1b": "Function assignment added.",
|
||||
"i18n:govoplan-organizations.assignment_updated.680b6e33": "Function assignment updated.",
|
||||
"i18n:govoplan-organizations.assignments.278f513e": "Assignments",
|
||||
"i18n:govoplan-organizations.audit_and_retention.3ba1d2fc": "Audit and retention",
|
||||
"i18n:govoplan-organizations.audit_detail_level.7397355d": "Audit detail level",
|
||||
"i18n:govoplan-organizations.audit_full.0c83669c": "Full",
|
||||
@@ -34,28 +29,22 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.deactivate.46ab070d": "Deactivate",
|
||||
"i18n:govoplan-organizations.delegable.b4f0137d": "Delegable",
|
||||
"i18n:govoplan-organizations.description.55f8ebc8": "Description",
|
||||
"i18n:govoplan-organizations.direct_assignment.2a068aac": "direct",
|
||||
"i18n:govoplan-organizations.discard_organization_drafts.766d5f46": "Discard organization drafts",
|
||||
"i18n:govoplan-organizations.edit.7dce1220": "Edit",
|
||||
"i18n:govoplan-organizations.function.28822f3a": "Function",
|
||||
"i18n:govoplan-organizations.function_added.e2266702": "Function added.",
|
||||
"i18n:govoplan-organizations.function_assignments.4c5c6dae": "Function assignments",
|
||||
"i18n:govoplan-organizations.function_type.501fe7c0": "Function type",
|
||||
"i18n:govoplan-organizations.function_type_added.2cd9e899": "Function type added.",
|
||||
"i18n:govoplan-organizations.function_type_updated.1a4f29f6": "Function type updated.",
|
||||
"i18n:govoplan-organizations.function_types.172c01fe": "Function types",
|
||||
"i18n:govoplan-organizations.function_updated.65016009": "Function updated.",
|
||||
"i18n:govoplan-organizations.functions_unavailable.4fd6765d": "Functions are unavailable.",
|
||||
"i18n:govoplan-organizations.functions.805dc49b": "Functions",
|
||||
"i18n:govoplan-organizations.hierarchical.8964f313": "Hierarchical",
|
||||
"i18n:govoplan-organizations.hierarchy.74797f37": "Hierarchy",
|
||||
"i18n:govoplan-organizations.identity.3252f35f": "Identity",
|
||||
"i18n:govoplan-organizations.identity_id.b6ef5010": "Identity ID",
|
||||
"i18n:govoplan-organizations.identity_id_placeholder.298cbd85": "identity UUID",
|
||||
"i18n:govoplan-organizations.identity_lookup_unavailable.5a3e77b2": "Identity lookup is unavailable. Enable the identity module or enter an identity ID manually.",
|
||||
"i18n:govoplan-organizations.identity_search.eff6eb16": "Identity search",
|
||||
"i18n:govoplan-organizations.inactive.09af574c": "Inactive",
|
||||
"i18n:govoplan-organizations.kind.794c9d9c": "Kind",
|
||||
"i18n:govoplan-organizations.loading_identities.9c31d2b5": "Loading identities...",
|
||||
"i18n:govoplan-organizations.loading_functions.77e8e684": "Loading functions...",
|
||||
"i18n:govoplan-organizations.loading_organization_model.846aa317": "Loading organization model...",
|
||||
"i18n:govoplan-organizations.loading_organization_settings.c6008db8": "Loading organization settings...",
|
||||
"i18n:govoplan-organizations.membership.4531d86d": "Membership",
|
||||
@@ -66,7 +55,6 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.name.709a2322": "Name",
|
||||
"i18n:govoplan-organizations.name_is_required.27cd3782": "Name is required.",
|
||||
"i18n:govoplan-organizations.network.a24d31c3": "Network",
|
||||
"i18n:govoplan-organizations.no_assignments_found.7ecc5bb7": "No function assignments found.",
|
||||
"i18n:govoplan-organizations.no_function_types_found.5eef31b1": "No function types found.",
|
||||
"i18n:govoplan-organizations.no_functions_found.54e759a0": "No functions found.",
|
||||
"i18n:govoplan-organizations.no_relation_types_found.6f90bb81": "No relation types found.",
|
||||
@@ -76,7 +64,6 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.no_unit_types_found.c81fb2a7": "No unit types found.",
|
||||
"i18n:govoplan-organizations.no_units_found.eea2dd0c": "No units found.",
|
||||
"i18n:govoplan-organizations.none.334c4a4c": "None",
|
||||
"i18n:govoplan-organizations.optional_account_id.5fcf495c": "Optional account ID",
|
||||
"i18n:govoplan-organizations.organization_model.4f924c0e": "Organization model",
|
||||
"i18n:govoplan-organizations.organization_model_admin_description.35dc9f10": "Configure tenant-local unit types, parallel structures, relation types, and function types used by the organization module.",
|
||||
"i18n:govoplan-organizations.organization_settings.c9ab9829": "Organization settings",
|
||||
@@ -84,7 +71,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.organization_settings_saved.bfbcfdfa": "Organization settings saved.",
|
||||
"i18n:govoplan-organizations.organization_tree.e5bfb195": "Organization tree",
|
||||
"i18n:govoplan-organizations.organizations.220edf64": "Organizations",
|
||||
"i18n:govoplan-organizations.organizations_intro.4e67c4bb": "Model organization types, concrete units, structures, functions, and identity-held function assignments.",
|
||||
"i18n:govoplan-organizations.organizations_intro.4e67c4bb": "Model organization types, concrete units, structures, and functions.",
|
||||
"i18n:govoplan-organizations.parent_unit.1986c35e": "Parent unit",
|
||||
"i18n:govoplan-organizations.reactivate.58f2855d": "Reactivate",
|
||||
"i18n:govoplan-organizations.relation_added.ca90461a": "Relation added.",
|
||||
@@ -99,10 +86,8 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.save_settings.913aba9f": "Save settings",
|
||||
"i18n:govoplan-organizations.save_drafts.606f9401": "Save drafts",
|
||||
"i18n:govoplan-organizations.saving.56a2285c": "Saving...",
|
||||
"i18n:govoplan-organizations.search_identities.004afdfd": "Search by name, subject, account, or ID",
|
||||
"i18n:govoplan-organizations.select_function.4895a67d": "Select function",
|
||||
"i18n:govoplan-organizations.select_function_type.2f426c43": "Select function type",
|
||||
"i18n:govoplan-organizations.select_identity.510f1134": "Select identity",
|
||||
"i18n:govoplan-organizations.select_relation_type.73f92478": "Select relation type",
|
||||
"i18n:govoplan-organizations.select_source_unit.9b5a29c8": "Select source unit",
|
||||
"i18n:govoplan-organizations.select_structure.c10f551c": "Select structure",
|
||||
@@ -129,7 +114,6 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.unit_types.c7afc174": "Unit types",
|
||||
"i18n:govoplan-organizations.units.e14d0d92": "Units",
|
||||
"i18n:govoplan-organizations.unlimited.35569464": "Unlimited",
|
||||
"i18n:govoplan-organizations.update_assignment.bf130723": "Update assignment",
|
||||
"i18n:govoplan-organizations.update_function.504f03e5": "Update function",
|
||||
"i18n:govoplan-organizations.update_function_type.0b7e07d3": "Update function type",
|
||||
"i18n:govoplan-organizations.update_relation.4c44ce83": "Update relation",
|
||||
@@ -142,7 +126,6 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
de: {
|
||||
"i18n:govoplan-organizations.act_in_place.49b942bd": "In Vertretung handeln",
|
||||
"i18n:govoplan-organizations.active.a733b809": "Aktiv",
|
||||
"i18n:govoplan-organizations.add_assignment.6682f5f5": "Zuordnung hinzufügen",
|
||||
"i18n:govoplan-organizations.add_function.6abafee0": "Funktion hinzufügen",
|
||||
"i18n:govoplan-organizations.add_function_type.90d793f1": "Funktionstyp hinzufügen",
|
||||
"i18n:govoplan-organizations.add_relation.6b6e67ea": "Beziehung hinzufügen",
|
||||
@@ -153,10 +136,6 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.add_unit.8fa12fb1": "Einheit hinzufügen",
|
||||
"i18n:govoplan-organizations.add_unit_type.58f2c05a": "Einheitstyp hinzufügen",
|
||||
"i18n:govoplan-organizations.allow_cycles.31327578": "Zyklen erlauben",
|
||||
"i18n:govoplan-organizations.applies_to_subunits.7a15f741": "Gilt für Untereinheiten",
|
||||
"i18n:govoplan-organizations.assignment_added.94263d1b": "Funktionszuordnung hinzugefügt.",
|
||||
"i18n:govoplan-organizations.assignment_updated.680b6e33": "Funktionszuordnung aktualisiert.",
|
||||
"i18n:govoplan-organizations.assignments.278f513e": "Zuordnungen",
|
||||
"i18n:govoplan-organizations.audit_and_retention.3ba1d2fc": "Audit und Aufbewahrung",
|
||||
"i18n:govoplan-organizations.audit_detail_level.7397355d": "Audit-Detailgrad",
|
||||
"i18n:govoplan-organizations.audit_full.0c83669c": "Vollständig",
|
||||
@@ -172,28 +151,22 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.deactivate.46ab070d": "Deaktivieren",
|
||||
"i18n:govoplan-organizations.delegable.b4f0137d": "Delegierbar",
|
||||
"i18n:govoplan-organizations.description.55f8ebc8": "Beschreibung",
|
||||
"i18n:govoplan-organizations.direct_assignment.2a068aac": "direkt",
|
||||
"i18n:govoplan-organizations.discard_organization_drafts.766d5f46": "Organisationsentwürfe verwerfen",
|
||||
"i18n:govoplan-organizations.edit.7dce1220": "Bearbeiten",
|
||||
"i18n:govoplan-organizations.function.28822f3a": "Funktion",
|
||||
"i18n:govoplan-organizations.function_added.e2266702": "Funktion hinzugefügt.",
|
||||
"i18n:govoplan-organizations.function_assignments.4c5c6dae": "Funktionszuordnungen",
|
||||
"i18n:govoplan-organizations.function_type.501fe7c0": "Funktionstyp",
|
||||
"i18n:govoplan-organizations.function_type_added.2cd9e899": "Funktionstyp hinzugefügt.",
|
||||
"i18n:govoplan-organizations.function_type_updated.1a4f29f6": "Funktionstyp aktualisiert.",
|
||||
"i18n:govoplan-organizations.function_types.172c01fe": "Funktionstypen",
|
||||
"i18n:govoplan-organizations.function_updated.65016009": "Funktion aktualisiert.",
|
||||
"i18n:govoplan-organizations.functions_unavailable.4fd6765d": "Funktionen sind nicht verfügbar.",
|
||||
"i18n:govoplan-organizations.functions.805dc49b": "Funktionen",
|
||||
"i18n:govoplan-organizations.hierarchical.8964f313": "Hierarchisch",
|
||||
"i18n:govoplan-organizations.hierarchy.74797f37": "Hierarchie",
|
||||
"i18n:govoplan-organizations.identity.3252f35f": "Identität",
|
||||
"i18n:govoplan-organizations.identity_id.b6ef5010": "Identitäts-ID",
|
||||
"i18n:govoplan-organizations.identity_id_placeholder.298cbd85": "Identitäts-UUID",
|
||||
"i18n:govoplan-organizations.identity_lookup_unavailable.5a3e77b2": "Die Identitätssuche ist nicht verfügbar. Aktiviere das Identitätsmodul oder trage eine Identitäts-ID manuell ein.",
|
||||
"i18n:govoplan-organizations.identity_search.eff6eb16": "Identitätssuche",
|
||||
"i18n:govoplan-organizations.inactive.09af574c": "Inaktiv",
|
||||
"i18n:govoplan-organizations.kind.794c9d9c": "Art",
|
||||
"i18n:govoplan-organizations.loading_identities.9c31d2b5": "Identitäten werden geladen...",
|
||||
"i18n:govoplan-organizations.loading_functions.77e8e684": "Funktionen werden geladen...",
|
||||
"i18n:govoplan-organizations.loading_organization_model.846aa317": "Organisationsmodell wird geladen...",
|
||||
"i18n:govoplan-organizations.loading_organization_settings.c6008db8": "Organisationseinstellungen werden geladen...",
|
||||
"i18n:govoplan-organizations.membership.4531d86d": "Mitgliedschaft",
|
||||
@@ -204,7 +177,6 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.name.709a2322": "Name",
|
||||
"i18n:govoplan-organizations.name_is_required.27cd3782": "Name ist erforderlich.",
|
||||
"i18n:govoplan-organizations.network.a24d31c3": "Netzwerk",
|
||||
"i18n:govoplan-organizations.no_assignments_found.7ecc5bb7": "Keine Funktionszuordnungen gefunden.",
|
||||
"i18n:govoplan-organizations.no_function_types_found.5eef31b1": "Keine Funktionstypen gefunden.",
|
||||
"i18n:govoplan-organizations.no_functions_found.54e759a0": "Keine Funktionen gefunden.",
|
||||
"i18n:govoplan-organizations.no_relation_types_found.6f90bb81": "Keine Beziehungstypen gefunden.",
|
||||
@@ -214,7 +186,6 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.no_unit_types_found.c81fb2a7": "Keine Einheitstypen gefunden.",
|
||||
"i18n:govoplan-organizations.no_units_found.eea2dd0c": "Keine Einheiten gefunden.",
|
||||
"i18n:govoplan-organizations.none.334c4a4c": "Keine",
|
||||
"i18n:govoplan-organizations.optional_account_id.5fcf495c": "Optionale Konto-ID",
|
||||
"i18n:govoplan-organizations.organization_model.4f924c0e": "Organisationsmodell",
|
||||
"i18n:govoplan-organizations.organization_model_admin_description.35dc9f10": "Konfiguriere mandantenbezogene Einheitstypen, parallele Strukturen, Beziehungstypen und Funktionstypen des Organisationsmoduls.",
|
||||
"i18n:govoplan-organizations.organization_settings.c9ab9829": "Organisationseinstellungen",
|
||||
@@ -222,7 +193,7 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.organization_settings_saved.bfbcfdfa": "Organisationseinstellungen gespeichert.",
|
||||
"i18n:govoplan-organizations.organization_tree.e5bfb195": "Organisationsbaum",
|
||||
"i18n:govoplan-organizations.organizations.220edf64": "Organisationen",
|
||||
"i18n:govoplan-organizations.organizations_intro.4e67c4bb": "Modelliere Organisationstypen, konkrete Einheiten, Strukturen, Funktionen und identitätsbezogene Funktionszuordnungen.",
|
||||
"i18n:govoplan-organizations.organizations_intro.4e67c4bb": "Modelliere Organisationstypen, konkrete Einheiten, Strukturen und Funktionen.",
|
||||
"i18n:govoplan-organizations.parent_unit.1986c35e": "Übergeordnete Einheit",
|
||||
"i18n:govoplan-organizations.reactivate.58f2855d": "Reaktivieren",
|
||||
"i18n:govoplan-organizations.relation_added.ca90461a": "Beziehung hinzugefügt.",
|
||||
@@ -237,10 +208,8 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.save_settings.913aba9f": "Einstellungen speichern",
|
||||
"i18n:govoplan-organizations.save_drafts.606f9401": "Entwürfe speichern",
|
||||
"i18n:govoplan-organizations.saving.56a2285c": "Speichern...",
|
||||
"i18n:govoplan-organizations.search_identities.004afdfd": "Nach Name, Subject, Konto oder ID suchen",
|
||||
"i18n:govoplan-organizations.select_function.4895a67d": "Funktion auswählen",
|
||||
"i18n:govoplan-organizations.select_function_type.2f426c43": "Funktionstyp auswählen",
|
||||
"i18n:govoplan-organizations.select_identity.510f1134": "Identität auswählen",
|
||||
"i18n:govoplan-organizations.select_relation_type.73f92478": "Beziehungstyp auswählen",
|
||||
"i18n:govoplan-organizations.select_source_unit.9b5a29c8": "Quell-Einheit auswählen",
|
||||
"i18n:govoplan-organizations.select_structure.c10f551c": "Struktur auswählen",
|
||||
@@ -267,7 +236,6 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-organizations.unit_types.c7afc174": "Einheitstypen",
|
||||
"i18n:govoplan-organizations.units.e14d0d92": "Einheiten",
|
||||
"i18n:govoplan-organizations.unlimited.35569464": "Unbegrenzt",
|
||||
"i18n:govoplan-organizations.update_assignment.bf130723": "Zuordnung aktualisieren",
|
||||
"i18n:govoplan-organizations.update_function.504f03e5": "Funktion aktualisieren",
|
||||
"i18n:govoplan-organizations.update_function_type.0b7e07d3": "Funktionstyp aktualisieren",
|
||||
"i18n:govoplan-organizations.update_relation.4c44ce83": "Beziehung aktualisieren",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createElement, lazy } from "react";
|
||||
import type { AdminSectionsUiCapability, PlatformWebModule } from "@govoplan/core-webui";
|
||||
import type { AdminSectionsUiCapability, OrganizationFunctionPickerUiCapability, PlatformWebModule } from "@govoplan/core-webui";
|
||||
import { generatedTranslations } from "./i18n/generatedTranslations";
|
||||
import { OrganizationFunctionLabel, OrganizationFunctionPicker } from "./features/organizations/OrganizationFunctionPicker";
|
||||
import "./styles/organizations.css";
|
||||
|
||||
const OrganizationsPage = lazy(() => import("./features/organizations/OrganizationsPage"));
|
||||
@@ -31,12 +32,18 @@ const organizationAdminSections: AdminSectionsUiCapability = {
|
||||
]
|
||||
};
|
||||
|
||||
const organizationFunctionPicker: OrganizationFunctionPickerUiCapability = {
|
||||
sourceModule: "organizations",
|
||||
renderPicker: (context) => createElement(OrganizationFunctionPicker, context),
|
||||
renderLabel: (context) => createElement(OrganizationFunctionLabel, context)
|
||||
};
|
||||
|
||||
export const organizationsModule: PlatformWebModule = {
|
||||
id: "organizations",
|
||||
label: "i18n:govoplan-organizations.organizations.220edf64",
|
||||
version: "1.0.0",
|
||||
dependencies: ["access"],
|
||||
optionalDependencies: ["identity", "admin"],
|
||||
optionalDependencies: ["admin"],
|
||||
translations,
|
||||
navItems: [
|
||||
{
|
||||
@@ -56,7 +63,8 @@ export const organizationsModule: PlatformWebModule = {
|
||||
}
|
||||
],
|
||||
uiCapabilities: {
|
||||
"admin.sections": organizationAdminSections
|
||||
"admin.sections": organizationAdminSections,
|
||||
"organizations.functionPicker": organizationFunctionPicker
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -90,6 +90,10 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.organizations-contributed-action {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.organizations-check-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
@@ -167,18 +171,6 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.organizations-identity-picker {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.organizations-identity {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.organizations-id {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 12px;
|
||||
|
||||
Reference in New Issue
Block a user