3 Commits

Author SHA1 Message Date
b9b371a704 Polish IDM assignment table layout 2026-07-11 03:02:46 +02:00
3b0e8bc600 Release v0.1.7 2026-07-11 03:00:44 +02:00
05a10846a8 Support deep links into IDM assignments 2026-07-11 00:55:23 +02:00
7 changed files with 175 additions and 148 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/idm-webui", "name": "@govoplan/idm-webui",
"version": "0.1.6", "version": "0.1.7",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "webui/src/index.ts", "main": "webui/src/index.ts",
@@ -19,7 +19,7 @@
"LICENSE" "LICENSE"
], ],
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.6", "@govoplan/core-webui": "^0.1.7",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
"react": "^19.0.0", "react": "^19.0.0",

View File

@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "govoplan-idm" name = "govoplan-idm"
version = "0.1.6" version = "0.1.7"
description = "GovOPlaN identity management bridge module." description = "GovOPlaN identity management bridge module."
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
authors = [{ name = "GovOPlaN" }] authors = [{ name = "GovOPlaN" }]
dependencies = [ dependencies = [
"govoplan-core>=0.1.6", "govoplan-core>=0.1.7",
"govoplan-identity>=0.1.6", "govoplan-identity>=0.1.7",
"govoplan-organizations>=0.1.6", "govoplan-organizations>=0.1.7",
] ]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]

View File

@@ -102,7 +102,7 @@ def _idm_directory(context: ModuleContext) -> object:
manifest = ModuleManifest( manifest = ModuleManifest(
id="idm", id="idm",
name="IDM", name="IDM",
version="0.1.6", version="0.1.7",
dependencies=("identity", "organizations"), dependencies=("identity", "organizations"),
optional_dependencies=("access", "audit"), optional_dependencies=("access", "audit"),
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR), required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),

View File

@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/idm-webui", "name": "@govoplan/idm-webui",
"version": "0.1.6", "version": "0.1.7",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
@@ -14,7 +14,7 @@
"./styles/idm.css": "./src/styles/idm.css" "./styles/idm.css": "./src/styles/idm.css"
}, },
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.6", "@govoplan/core-webui": "^0.1.7",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
"react": "^19.0.0", "react": "^19.0.0",

View File

@@ -1,10 +1,12 @@
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, Plus, RefreshCw } from "lucide-react";
import { import {
AdminIconButton,
ApiError, ApiError,
Button, Button,
Card, Card,
DataGrid, DataGrid,
Dialog,
DismissibleAlert, DismissibleAlert,
FormField, FormField,
LoadingFrame, LoadingFrame,
@@ -219,6 +221,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[]>([]);
@@ -233,10 +244,14 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
const [assignmentDraft, setAssignmentDraft] = useState<AssignmentDraft>(() => emptyAssignmentDraft()); const [assignmentDraft, setAssignmentDraft] = useState<AssignmentDraft>(() => emptyAssignmentDraft());
const [settingsDraft, setSettingsDraft] = useState<SettingsDraft>(() => settingsDraftFrom(null)); const [settingsDraft, setSettingsDraft] = useState<SettingsDraft>(() => settingsDraftFrom(null));
const [editingAssignmentId, setEditingAssignmentId] = useState<string | null>(null); const [editingAssignmentId, setEditingAssignmentId] = useState<string | null>(null);
const [assignmentEditorOpen, setAssignmentEditorOpen] = useState(false);
const [assignmentChangeRequestId, setAssignmentChangeRequestId] = useState("");
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
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,11 +301,8 @@ 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 ""; const hasDirtyAssignmentDraft = assignmentEditorOpen && isAssignmentDirty(assignmentDraft);
return new URLSearchParams(window.location.search).get("function_id") || "";
}, []);
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,8 +319,14 @@ 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) {
setAssignmentDraft((draft) => draft.function_id ? draft : { ...draft, function_id: initialFunctionFilter }); const initialAssignment = nextAssignments.assignments.find((item) => item.id === initialQuery.assignmentId);
if (initialAssignment) {
appliedInitialQueryRef.current = true;
setEditingAssignmentId(initialAssignment.id);
setAssignmentDraft(assignmentDraftFrom(initialAssignment));
setAssignmentEditorOpen(true);
}
} }
if (canSearchIdentities) { if (canSearchIdentities) {
try { try {
@@ -326,7 +344,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();
@@ -393,6 +411,8 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
const discardAssignmentDraft = useCallback(() => { const discardAssignmentDraft = useCallback(() => {
setAssignmentDraft(emptyAssignmentDraft()); setAssignmentDraft(emptyAssignmentDraft());
setEditingAssignmentId(null); setEditingAssignmentId(null);
setAssignmentEditorOpen(false);
setAssignmentChangeRequestId("");
}, []); }, []);
const discardDrafts = useCallback(() => { const discardDrafts = useCallback(() => {
@@ -427,14 +447,15 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
setError("i18n:govoplan-idm.function_is_required.5cce5b41"); setError("i18n:govoplan-idm.function_is_required.5cce5b41");
return false; return false;
} }
const payload = assignmentPayload(assignmentDraft); const requestId = textOrNull(assignmentChangeRequestId);
const payload = requestId ? { ...assignmentPayload(assignmentDraft), change_request_id: requestId } : assignmentPayload(assignmentDraft);
const ok = await runAction( const ok = await runAction(
() => editingAssignmentId ? patchOrganizationFunctionAssignment(settings, editingAssignmentId, payload) : createOrganizationFunctionAssignment(settings, payload), () => editingAssignmentId ? patchOrganizationFunctionAssignment(settings, editingAssignmentId, payload) : createOrganizationFunctionAssignment(settings, payload),
editingAssignmentId ? "i18n:govoplan-idm.assignment_updated.fbbf9bd6" : "i18n:govoplan-idm.assignment_added.91f1ee42" editingAssignmentId ? "i18n:govoplan-idm.assignment_updated.fbbf9bd6" : "i18n:govoplan-idm.assignment_added.91f1ee42"
); );
if (ok) discardAssignmentDraft(); if (ok) discardAssignmentDraft();
return ok; return ok;
}, [assignmentDraft, canManage, discardAssignmentDraft, editingAssignmentId, runAction, settings]); }, [assignmentChangeRequestId, assignmentDraft, canManage, discardAssignmentDraft, editingAssignmentId, runAction, settings]);
const saveDrafts = useCallback(async (): Promise<boolean> => { const saveDrafts = useCallback(async (): Promise<boolean> => {
if (hasDirtyAssignmentDraft && !(await submitAssignment())) return false; if (hasDirtyAssignmentDraft && !(await submitAssignment())) return false;
@@ -450,15 +471,20 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
message: "i18n:govoplan-core.this_page_has_unsaved_changes_save_them_before_l.419a9d8b" message: "i18n:govoplan-core.this_page_has_unsaved_changes_save_them_before_l.419a9d8b"
}); });
const openCreateAssignment = useCallback(() => {
setEditingAssignmentId(null);
setAssignmentDraft({ ...emptyAssignmentDraft(), function_id: initialFunctionFilter && functionById.has(initialFunctionFilter) ? initialFunctionFilter : "" });
setAssignmentChangeRequestId("");
setAssignmentEditorOpen(true);
}, [functionById, initialFunctionFilter]);
const editAssignment = useCallback((item: OrganizationFunctionAssignmentItem) => { const editAssignment = useCallback((item: OrganizationFunctionAssignmentItem) => {
setEditingAssignmentId(item.id); setEditingAssignmentId(item.id);
setAssignmentDraft(assignmentDraftFrom(item)); setAssignmentDraft(assignmentDraftFrom(item));
setAssignmentChangeRequestId("");
setAssignmentEditorOpen(true);
}, []); }, []);
const toggleAssignment = useCallback((item: OrganizationFunctionAssignmentItem) => {
void runAction(() => patchOrganizationFunctionAssignment(settings, item.id, { is_active: !item.is_active }));
}, [runAction, settings]);
function onIdentityChange(identityId: string) { function onIdentityChange(identityId: string) {
const identity = identityOptionById.get(identityId); const identity = identityOptionById.get(identityId);
setAssignmentDraft({ setAssignmentDraft({
@@ -534,16 +560,11 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
{ {
id: "actions", id: "actions",
header: "", header: "",
width: 220, width: 88,
sticky: "end", sticky: "end",
render: (row) => ( render: (row) => (
<div className="idm-row-actions"> <div className="idm-row-actions">
<Button type="button" variant="ghost" disabled={!canManage || busy} onClick={() => editAssignment(row)} title="i18n:govoplan-idm.edit.a5a0f3cc"> <AdminIconButton label="i18n:govoplan-idm.edit.a5a0f3cc" icon={<Edit3 size={16} aria-hidden="true" />} disabled={!canManage || busy} onClick={() => editAssignment(row)} />
<Edit3 size={16} aria-hidden="true" /> i18n:govoplan-idm.edit.a5a0f3cc
</Button>
<Button type="button" variant={row.is_active ? "secondary" : "primary"} disabled={!canManage || busy} onClick={() => toggleAssignment(row)}>
{row.is_active ? "i18n:govoplan-idm.deactivate.585777c8" : "i18n:govoplan-idm.reactivate.e4871a43"}
</Button>
</div> </div>
) )
} }
@@ -560,16 +581,6 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
<Button type="button" onClick={() => void loadData()} disabled={loading || busy} title="i18n:govoplan-idm.reload.870ca3ec"> <Button type="button" onClick={() => void loadData()} disabled={loading || busy} title="i18n:govoplan-idm.reload.870ca3ec">
<RefreshCw size={16} aria-hidden="true" /> i18n:govoplan-idm.reload.870ca3ec <RefreshCw size={16} aria-hidden="true" /> i18n:govoplan-idm.reload.870ca3ec
</Button> </Button>
{hasDirtyDraft && (
<>
<Button type="button" onClick={() => void saveDrafts()} disabled={busy} title="i18n:govoplan-idm.save_drafts.32a0d60a">
<Save size={16} aria-hidden="true" /> i18n:govoplan-idm.save_drafts.32a0d60a
</Button>
<Button type="button" variant="ghost" onClick={discardDrafts} disabled={busy} title="i18n:govoplan-idm.discard_drafts.c0a86816">
<XCircle size={16} aria-hidden="true" /> i18n:govoplan-idm.discard_drafts.c0a86816
</Button>
</>
)}
</div> </div>
</div> </div>
@@ -578,10 +589,86 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
{!canManage && <DismissibleAlert tone="warning" dismissible={false}>i18n:govoplan-idm.write_permission_required.c7dde7c6</DismissibleAlert>} {!canManage && <DismissibleAlert tone="warning" dismissible={false}>i18n:govoplan-idm.write_permission_required.c7dde7c6</DismissibleAlert>}
<LoadingFrame loading={loading || busy} label="i18n:govoplan-idm.loading_idm_assignments.0b1501bd"> <LoadingFrame loading={loading || busy} label="i18n:govoplan-idm.loading_idm_assignments.0b1501bd">
<div className="idm-layout"> <div className="idm-table-stack">
<div className="idm-editor-stack"> {canReadSettings && (
<Card title="i18n:govoplan-idm.assignment_editor.e20598e7"> <Card title="i18n:govoplan-idm.idm_governance.6e4f3251" collapsible collapseKey="idm.governance">
<form className="idm-form-grid" onSubmit={(event) => void submitAssignment(event)}> <form className="idm-form-grid" onSubmit={(event) => { event.preventDefault(); void submitSettings(); }}>
<div className="idm-check-list wide">
<label>
<input
type="checkbox"
checked={settingsDraft.require_assignment_change_requests}
disabled={!canManageSettings || busy}
onChange={(event) => setSettingsDraft({ ...settingsDraft, require_assignment_change_requests: event.target.checked })}
/>
<span>i18n:govoplan-idm.require_assignment_change_requests.697718a1</span>
</label>
</div>
<FormField label="i18n:govoplan-idm.audit_detail_level.eb2e6fd2">
<select
value={settingsDraft.audit_detail_level}
disabled={!canManageSettings || busy}
onChange={(event) => setSettingsDraft({ ...settingsDraft, audit_detail_level: event.target.value as SettingsDraft["audit_detail_level"] })}
>
<option value="summary">i18n:govoplan-idm.summary.f1c5b7ab</option>
<option value="standard">i18n:govoplan-idm.standard.6edc51aa</option>
<option value="full">i18n:govoplan-idm.full.7f021a14</option>
</select>
</FormField>
<FormField label="i18n:govoplan-idm.change_retention_days.4a91f7d3">
<input
type="number"
min="0"
value={settingsDraft.change_retention_days}
disabled={!canManageSettings || busy}
onChange={(event) => setSettingsDraft({ ...settingsDraft, change_retention_days: event.target.value })}
/>
</FormField>
<div className="idm-form-actions wide">
<Button type="submit" variant="primary" disabled={!canManageSettings || busy || !hasDirtySettingsDraft}>
i18n:govoplan-idm.save_settings.4602c430
</Button>
</div>
</form>
</Card>
)}
<Card title="i18n:govoplan-idm.assignments.a0d19ec5" collapsible collapseKey="idm.assignments" actions={<AdminIconButton label="i18n:govoplan-idm.add_assignment.08f2a0d5" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canManage || busy} onClick={openCreateAssignment} />}>
<DataGrid
id="idm-organization-function-assignments"
rows={assignments}
columns={assignmentColumns}
getRowKey={(row) => row.id}
emptyText="i18n:govoplan-idm.no_assignments.41193ce8"
initialFilters={initialFunctionFilter ? { function: initialFunctionFilter } : undefined}
initialFit="container"
/>
</Card>
</div>
</LoadingFrame>
{renderAssignmentDialog()}
</div>
);
function renderAssignmentDialog() {
const formId = "idm-assignment-editor";
return (
<Dialog
open={assignmentEditorOpen}
title={editingAssignmentId ? "i18n:govoplan-idm.update_assignment.e20f52aa" : "i18n:govoplan-idm.add_assignment.08f2a0d5"}
onClose={() => !busy && discardAssignmentDraft()}
closeDisabled={busy}
className="admin-dialog admin-dialog-wide idm-editor-dialog"
footer={(
<>
<Button type="button" onClick={discardAssignmentDraft} disabled={busy}>i18n:govoplan-idm.cancel_edit.ea4781e0</Button>
<Button type="submit" form={formId} variant="primary" disabled={!canManage || busy || !assignmentDraft.identity_id || !assignmentDraft.function_id}>
{editingAssignmentId ? "i18n:govoplan-idm.update_assignment.e20f52aa" : "i18n:govoplan-idm.add_assignment.08f2a0d5"}
</Button>
</>
)}
>
<form id={formId} className="idm-form-grid" onSubmit={(event) => void submitAssignment(event)}>
<FormField label="i18n:govoplan-idm.identity_search.d3460fcf"> <FormField label="i18n:govoplan-idm.identity_search.d3460fcf">
<input <input
value={identitySearch} value={identitySearch}
@@ -653,78 +740,18 @@ export default function IdmPage({ settings, auth }: IdmPageProps) {
<span>i18n:govoplan-idm.active.7bd0e9f8</span> <span>i18n:govoplan-idm.active.7bd0e9f8</span>
</label> </label>
</div> </div>
<div className="wide idm-dialog-change-request">
<FormField label="i18n:govoplan-idm.change_request_id.b7d816db">
<input value={assignmentChangeRequestId} onChange={(event) => setAssignmentChangeRequestId(event.target.value)} placeholder="cfgreq-..." disabled={busy} />
</FormField>
<p className="idm-muted">i18n:govoplan-idm.change_request_id_help.cc7de508</p>
</div>
{!identityLookupAvailable && <p className="idm-muted wide">i18n:govoplan-idm.identity_lookup_unavailable.b76f7714</p>} {!identityLookupAvailable && <p className="idm-muted wide">i18n:govoplan-idm.identity_lookup_unavailable.b76f7714</p>}
{identityLoading && <p className="idm-muted wide">i18n:govoplan-idm.loading_identities.f3b84693</p>} {identityLoading && <p className="idm-muted wide">i18n:govoplan-idm.loading_identities.f3b84693</p>}
{actingForLoading && <p className="idm-muted wide">i18n:govoplan-idm.loading_acting_for_accounts.c9894b1e</p>} {actingForLoading && <p className="idm-muted wide">i18n:govoplan-idm.loading_acting_for_accounts.c9894b1e</p>}
{!model.functions.length && <p className="idm-muted wide">i18n:govoplan-idm.no_functions_available.51ba08eb</p>} {!model.functions.length && <p className="idm-muted wide">i18n:govoplan-idm.no_functions_available.51ba08eb</p>}
<div className="idm-form-actions wide">
<Button type="submit" variant="primary" disabled={!canManage || busy}>
{editingAssignmentId ? "i18n:govoplan-idm.update_assignment.e20f52aa" : "i18n:govoplan-idm.add_assignment.08f2a0d5"}
</Button>
{editingAssignmentId && (
<Button type="button" variant="ghost" onClick={discardAssignmentDraft} disabled={busy}>i18n:govoplan-idm.cancel_edit.ea4781e0</Button>
)}
</div>
</form> </form>
</Card> </Dialog>
{canReadSettings && (
<Card title="i18n:govoplan-idm.idm_governance.6e4f3251">
<form className="idm-form-grid" onSubmit={(event) => { event.preventDefault(); void submitSettings(); }}>
<div className="idm-check-list wide">
<label>
<input
type="checkbox"
checked={settingsDraft.require_assignment_change_requests}
disabled={!canManageSettings || busy}
onChange={(event) => setSettingsDraft({ ...settingsDraft, require_assignment_change_requests: event.target.checked })}
/>
<span>i18n:govoplan-idm.require_assignment_change_requests.697718a1</span>
</label>
</div>
<FormField label="i18n:govoplan-idm.audit_detail_level.eb2e6fd2">
<select
value={settingsDraft.audit_detail_level}
disabled={!canManageSettings || busy}
onChange={(event) => setSettingsDraft({ ...settingsDraft, audit_detail_level: event.target.value as SettingsDraft["audit_detail_level"] })}
>
<option value="summary">i18n:govoplan-idm.summary.f1c5b7ab</option>
<option value="standard">i18n:govoplan-idm.standard.6edc51aa</option>
<option value="full">i18n:govoplan-idm.full.7f021a14</option>
</select>
</FormField>
<FormField label="i18n:govoplan-idm.change_retention_days.4a91f7d3">
<input
type="number"
min="0"
value={settingsDraft.change_retention_days}
disabled={!canManageSettings || busy}
onChange={(event) => setSettingsDraft({ ...settingsDraft, change_retention_days: event.target.value })}
/>
</FormField>
<div className="idm-form-actions wide">
<Button type="submit" variant="primary" disabled={!canManageSettings || busy || !hasDirtySettingsDraft}>
i18n:govoplan-idm.save_settings.4602c430
</Button>
</div>
</form>
</Card>
)}
</div>
<Card title="i18n:govoplan-idm.assignments.a0d19ec5">
<DataGrid
id="idm-organization-function-assignments"
rows={assignments}
columns={assignmentColumns}
getRowKey={(row) => row.id}
emptyText="i18n:govoplan-idm.no_assignments.41193ce8"
initialFilters={initialFunctionFilter ? { function: initialFunctionFilter } : undefined}
initialFit="container"
/>
</Card>
</div>
</LoadingFrame>
</div>
); );
} }
}

View File

@@ -15,6 +15,8 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-idm.assignments.a0d19ec5": "Assignments", "i18n:govoplan-idm.assignments.a0d19ec5": "Assignments",
"i18n:govoplan-idm.cancel_edit.ea4781e0": "Cancel edit", "i18n:govoplan-idm.cancel_edit.ea4781e0": "Cancel edit",
"i18n:govoplan-idm.audit_detail_level.eb2e6fd2": "Audit detail level", "i18n:govoplan-idm.audit_detail_level.eb2e6fd2": "Audit detail level",
"i18n:govoplan-idm.change_request_id.b7d816db": "Change request ID",
"i18n:govoplan-idm.change_request_id_help.cc7de508": "Only required when IDM assignment governance requires an approved recorded change request.",
"i18n:govoplan-idm.change_retention_days.4a91f7d3": "Change retention days", "i18n:govoplan-idm.change_retention_days.4a91f7d3": "Change retention days",
"i18n:govoplan-idm.deactivate.585777c8": "Deactivate", "i18n:govoplan-idm.deactivate.585777c8": "Deactivate",
"i18n:govoplan-idm.delegated.b189f4b6": "delegated", "i18n:govoplan-idm.delegated.b189f4b6": "delegated",
@@ -76,6 +78,8 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-idm.assignments.a0d19ec5": "Zuordnungen", "i18n:govoplan-idm.assignments.a0d19ec5": "Zuordnungen",
"i18n:govoplan-idm.cancel_edit.ea4781e0": "Bearbeitung abbrechen", "i18n:govoplan-idm.cancel_edit.ea4781e0": "Bearbeitung abbrechen",
"i18n:govoplan-idm.audit_detail_level.eb2e6fd2": "Audit-Detailgrad", "i18n:govoplan-idm.audit_detail_level.eb2e6fd2": "Audit-Detailgrad",
"i18n:govoplan-idm.change_request_id.b7d816db": "Änderungsantrags-ID",
"i18n:govoplan-idm.change_request_id_help.cc7de508": "Nur erforderlich, wenn die IDM-Zuordnungs-Governance einen genehmigten erfassten Änderungsantrag verlangt.",
"i18n:govoplan-idm.change_retention_days.4a91f7d3": "Änderungsaufbewahrung in Tagen", "i18n:govoplan-idm.change_retention_days.4a91f7d3": "Änderungsaufbewahrung in Tagen",
"i18n:govoplan-idm.deactivate.585777c8": "Deaktivieren", "i18n:govoplan-idm.deactivate.585777c8": "Deaktivieren",
"i18n:govoplan-idm.delegated.b189f4b6": "delegiert", "i18n:govoplan-idm.delegated.b189f4b6": "delegiert",

View File

@@ -17,16 +17,14 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.idm-layout { .idm-table-stack {
display: grid; display: grid;
grid-template-columns: minmax(300px, 420px) minmax(0, 1fr);
gap: 18px; gap: 18px;
align-items: start; width: 100%;
} }
.idm-editor-stack { .idm-table-stack > .card {
display: grid; width: 100%;
gap: 18px;
} }
.idm-form-grid { .idm-form-grid {
@@ -57,8 +55,8 @@
.idm-row-actions { .idm-row-actions {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 6px;
flex-wrap: wrap; flex-wrap: nowrap;
} }
.idm-row-actions { .idm-row-actions {
@@ -87,10 +85,8 @@
color: var(--muted); color: var(--muted);
} }
@media (max-width: 1100px) { .idm-dialog-change-request {
.idm-layout { padding-top: 2px;
grid-template-columns: 1fr;
}
} }
@media (max-width: 900px) { @media (max-width: 900px) {