Migrate Organizations interface patterns

This commit is contained in:
2026-08-03 12:09:33 +02:00
parent 3f0f38d226
commit 97acfcba7d
11 changed files with 533 additions and 74 deletions
@@ -1,12 +1,15 @@
import { useEffect, useState } from "react";
import {
ActionBlockerHint,
AdminPageLayout,
Button,
Card,
DocumentationHelpLink,
FormField,
ToggleSwitch,
adminErrorMessage,
hasAnyScope,
useUnsavedChanges,
useUnsavedDraftGuard,
type ApiSettings,
type AuthInfo
@@ -17,6 +20,12 @@ import {
type OrganizationAuditDetailLevel,
type OrganizationSettingsItem
} from "../../api/organizations";
import {
ORGANIZATIONS_DOCUMENTATION,
ORGANIZATIONS_FIELD_DOCUMENTATION,
ORGANIZATIONS_INTERFACE_I18N,
organizationWriteReason
} from "./interfacePatterns";
const FALLBACK_SETTINGS: OrganizationSettingsItem = {
tenant_id: "",
@@ -40,8 +49,16 @@ export default function OrganizationsAdminPanel({ settings, auth }: { settings:
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
const { requestDiscard } = useUnsavedChanges();
const canWrite = hasAnyScope(auth, ["organizations:settings:write", "admin:settings:write"]);
const dirty = settingsKey(draft) !== settingsKey(savedDraft);
const reloadDisabledReason = loading
? ORGANIZATIONS_INTERFACE_I18N.loading
: busy
? ORGANIZATIONS_INTERFACE_I18N.busy
: undefined;
const saveDisabledReason = organizationWriteReason(canWrite, busy, "settings")
?? (!dirty ? ORGANIZATIONS_INTERFACE_I18N.noChanges : undefined);
useUnsavedDraftGuard({
dirty,
@@ -99,20 +116,57 @@ export default function OrganizationsAdminPanel({ settings, auth }: { settings:
loadingLabel="i18n:govoplan-organizations.loading_organization_settings.c6008db8"
error={error}
success={success}
actions={<><Button onClick={() => void load()} disabled={loading || busy}>i18n:govoplan-organizations.reload.cce71553</Button><Button variant="primary" onClick={() => void save()} disabled={!canWrite || busy || !dirty}>{busy ? "i18n:govoplan-organizations.saving.56a2285c" : "i18n:govoplan-organizations.save_settings.913aba9f"}</Button></>}
actions={(
<>
<DocumentationHelpLink reference={ORGANIZATIONS_DOCUMENTATION} />
<Button
onClick={() => requestDiscard(() => void load())}
disabled={Boolean(reloadDisabledReason)}
disabledReason={reloadDisabledReason}
>
i18n:govoplan-organizations.reload.cce71553
</Button>
<Button
variant="primary"
onClick={() => void save()}
disabled={Boolean(saveDisabledReason)}
disabledReason={saveDisabledReason}
>
{busy ? "i18n:govoplan-organizations.saving.56a2285c" : "i18n:govoplan-organizations.save_settings.913aba9f"}
</Button>
</>
)}
>
{!canWrite && (
<ActionBlockerHint
reason={{
summary: ORGANIZATIONS_INTERFACE_I18N.readOnlySummary,
requiredAction: ORGANIZATIONS_INTERFACE_I18N.permissionGuidance,
actor: ORGANIZATIONS_INTERFACE_I18N.administrator,
target: ORGANIZATIONS_INTERFACE_I18N.administrationTarget
}}
labels={{
requiredAction: ORGANIZATIONS_INTERFACE_I18N.requiredAction,
actor: ORGANIZATIONS_INTERFACE_I18N.actor,
target: ORGANIZATIONS_INTERFACE_I18N.destination
}}
documentation={ORGANIZATIONS_DOCUMENTATION}
/>
)}
<div className="organizations-settings-grid">
<Card title="i18n:govoplan-organizations.model_governance.6aa18fd0">
<div className="settings-list">
<ToggleSwitch
checked={draft.allow_tenant_model_customization}
disabled={!canWrite || busy}
help={organizationWriteReason(canWrite, busy, "settings")}
onChange={(checked) => setDraft({ ...draft, allow_tenant_model_customization: checked })}
label="i18n:govoplan-organizations.allow_tenant_model_customization.2425d751"
/>
<ToggleSwitch
checked={draft.require_model_change_requests}
disabled={!canWrite || busy}
help={organizationWriteReason(canWrite, busy, "settings")}
onChange={(checked) => setDraft({ ...draft, require_model_change_requests: checked })}
label="i18n:govoplan-organizations.require_model_change_requests.83454cad"
/>
@@ -122,12 +176,20 @@ export default function OrganizationsAdminPanel({ settings, auth }: { settings:
<Card title="i18n:govoplan-organizations.audit_and_retention.3ba1d2fc">
<div className="admin-form-grid two-columns">
<FormField label="i18n:govoplan-organizations.audit_detail_level.7397355d">
<FormField
label="i18n:govoplan-organizations.audit_detail_level.7397355d"
help={organizationWriteReason(canWrite, busy, "settings")}
documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}
>
<select value={draft.audit_detail_level} disabled={!canWrite || busy} onChange={(event) => setDraft({ ...draft, audit_detail_level: event.target.value as OrganizationAuditDetailLevel })}>
{AUDIT_DETAIL_LEVELS.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
</select>
</FormField>
<FormField label="i18n:govoplan-organizations.change_retention_days.71bbd140">
<FormField
label="i18n:govoplan-organizations.change_retention_days.71bbd140"
help={organizationWriteReason(canWrite, busy, "settings")}
documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}
>
<input
type="number"
min={0}