Sync GovOPlaN module state
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user