Support deep links into IDM assignments

This commit is contained in:
2026-07-11 00:55:23 +02:00
parent b09c1449b9
commit 05a10846a8

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState, type FormEvent } from "react"; import { useCallback, useEffect, useMemo, useRef, useState, type FormEvent } from "react";
import { Edit3, RefreshCw, Save, XCircle } from "lucide-react"; import { Edit3, RefreshCw, Save, XCircle } from "lucide-react";
import { import {
ApiError, ApiError,
@@ -219,6 +219,15 @@ function sourceLabel(value: string): string {
return SOURCE_OPTIONS.find((item) => item.value === value)?.label ?? value; return SOURCE_OPTIONS.find((item) => item.value === value)?.label ?? value;
} }
function idmInitialQuery(): { assignmentId: string; functionId: string } {
if (typeof window === "undefined") return { assignmentId: "", functionId: "" };
const params = new URLSearchParams(window.location.search);
return {
assignmentId: params.get("assignment_id") || "",
functionId: params.get("function_id") || ""
};
}
export default function IdmPage({ settings, auth }: IdmPageProps) { export default function IdmPage({ settings, auth }: IdmPageProps) {
const [model, setModel] = useState<OrganizationModel>(EMPTY_MODEL); const [model, setModel] = useState<OrganizationModel>(EMPTY_MODEL);
const [assignments, setAssignments] = useState<OrganizationFunctionAssignmentItem[]>([]); const [assignments, setAssignments] = useState<OrganizationFunctionAssignmentItem[]>([]);
@@ -237,6 +246,8 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
const [error, setError] = useState(""); const [error, setError] = useState("");
const [success, setSuccess] = useState(""); const [success, setSuccess] = useState("");
const initialQuery = useMemo(() => idmInitialQuery(), []);
const appliedInitialQueryRef = useRef(false);
const canManage = hasScope(auth, "idm:organization_assignment:write") || hasScope(auth, "organizations:function:assign"); const canManage = hasScope(auth, "idm:organization_assignment:write") || hasScope(auth, "organizations:function:assign");
const canSearchIdentities = canManage || hasScope(auth, "idm:organization_identity:read") || hasScope(auth, "admin:users:read"); const canSearchIdentities = canManage || hasScope(auth, "idm:organization_identity:read") || hasScope(auth, "admin:users:read");
@@ -286,10 +297,7 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
if (assignmentDraft.acting_for_account_id) values.add(assignmentDraft.acting_for_account_id); if (assignmentDraft.acting_for_account_id) values.add(assignmentDraft.acting_for_account_id);
return Array.from(values).sort((left, right) => left.localeCompare(right)); return Array.from(values).sort((left, right) => left.localeCompare(right));
}, [actingForOptionById, actingForOptions, assignmentDraft.acting_for_account_id, identityOptionById, sourceAssignment]); }, [actingForOptionById, actingForOptions, assignmentDraft.acting_for_account_id, identityOptionById, sourceAssignment]);
const initialFunctionFilter = useMemo(() => { const initialFunctionFilter = initialQuery.functionId;
if (typeof window === "undefined") return "";
return new URLSearchParams(window.location.search).get("function_id") || "";
}, []);
const hasDirtyAssignmentDraft = isAssignmentDirty(assignmentDraft); const hasDirtyAssignmentDraft = isAssignmentDirty(assignmentDraft);
const hasDirtySettingsDraft = canReadSettings && isSettingsDirty(settingsDraft, idmSettings); const hasDirtySettingsDraft = canReadSettings && isSettingsDirty(settingsDraft, idmSettings);
const hasDirtyDraft = hasDirtyAssignmentDraft || hasDirtySettingsDraft; const hasDirtyDraft = hasDirtyAssignmentDraft || hasDirtySettingsDraft;
@@ -307,7 +315,16 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
setAssignments(nextAssignments.assignments); setAssignments(nextAssignments.assignments);
setIdmSettings(nextSettings); setIdmSettings(nextSettings);
if (nextSettings) setSettingsDraft(settingsDraftFrom(nextSettings)); if (nextSettings) setSettingsDraft(settingsDraftFrom(nextSettings));
if (initialFunctionFilter && nextModel.functions.some((item) => item.id === initialFunctionFilter)) { if (!appliedInitialQueryRef.current && initialQuery.assignmentId) {
const initialAssignment = nextAssignments.assignments.find((item) => item.id === initialQuery.assignmentId);
if (initialAssignment) {
appliedInitialQueryRef.current = true;
setEditingAssignmentId(initialAssignment.id);
setAssignmentDraft(assignmentDraftFrom(initialAssignment));
}
}
if (!appliedInitialQueryRef.current && initialFunctionFilter && nextModel.functions.some((item) => item.id === initialFunctionFilter)) {
appliedInitialQueryRef.current = true;
setAssignmentDraft((draft) => draft.function_id ? draft : { ...draft, function_id: initialFunctionFilter }); setAssignmentDraft((draft) => draft.function_id ? draft : { ...draft, function_id: initialFunctionFilter });
} }
if (canSearchIdentities) { if (canSearchIdentities) {
@@ -326,7 +343,7 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [canReadSettings, canSearchIdentities, initialFunctionFilter, settings]); }, [canReadSettings, canSearchIdentities, initialFunctionFilter, initialQuery.assignmentId, settings]);
useEffect(() => { useEffect(() => {
void loadData(); void loadData();