Add organization tenant settings
This commit is contained in:
@@ -1,18 +1,156 @@
|
||||
import type { ApiSettings, AuthInfo } from "@govoplan/core-webui";
|
||||
import OrganizationsPage, { type OrganizationSection } from "./OrganizationsPage";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
AdminPageLayout,
|
||||
Button,
|
||||
Card,
|
||||
FormField,
|
||||
ToggleSwitch,
|
||||
adminErrorMessage,
|
||||
hasAnyScope,
|
||||
useUnsavedDraftGuard,
|
||||
type ApiSettings,
|
||||
type AuthInfo
|
||||
} from "@govoplan/core-webui";
|
||||
import {
|
||||
getOrganizationSettings,
|
||||
patchOrganizationSettings,
|
||||
type OrganizationAuditDetailLevel,
|
||||
type OrganizationSettingsItem
|
||||
} from "../../api/organizations";
|
||||
|
||||
const MODEL_SECTIONS: OrganizationSection[] = ["model"];
|
||||
const FALLBACK_SETTINGS: OrganizationSettingsItem = {
|
||||
tenant_id: "",
|
||||
allow_tenant_model_customization: true,
|
||||
require_model_change_requests: false,
|
||||
audit_detail_level: "standard",
|
||||
change_retention_days: null,
|
||||
settings: {}
|
||||
};
|
||||
|
||||
const AUDIT_DETAIL_LEVELS: Array<{ value: OrganizationAuditDetailLevel; label: string }> = [
|
||||
{ value: "summary", label: "i18n:govoplan-organizations.audit_summary.6a7d68a5" },
|
||||
{ value: "standard", label: "i18n:govoplan-organizations.audit_standard.785d981f" },
|
||||
{ value: "full", label: "i18n:govoplan-organizations.audit_full.0c83669c" }
|
||||
];
|
||||
|
||||
export default function OrganizationsAdminPanel({ settings, auth }: { settings: ApiSettings; auth: AuthInfo }) {
|
||||
const [draft, setDraft] = useState<OrganizationSettingsItem>(FALLBACK_SETTINGS);
|
||||
const [savedDraft, setSavedDraft] = useState<OrganizationSettingsItem>(FALLBACK_SETTINGS);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [success, setSuccess] = useState("");
|
||||
const canWrite = hasAnyScope(auth, ["organizations:settings:write", "admin:settings:write"]);
|
||||
const dirty = settingsKey(draft) !== settingsKey(savedDraft);
|
||||
|
||||
useUnsavedDraftGuard({
|
||||
dirty,
|
||||
onSave: save,
|
||||
onDiscard: () => setDraft(savedDraft)
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
}, [settings.accessToken, settings.apiBaseUrl]);
|
||||
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
const next = await getOrganizationSettings(settings);
|
||||
setDraft(next);
|
||||
setSavedDraft(next);
|
||||
} catch (caught) {
|
||||
setError(adminErrorMessage(caught));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function save(): Promise<boolean> {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
setSuccess("");
|
||||
try {
|
||||
const saved = await patchOrganizationSettings(settings, {
|
||||
allow_tenant_model_customization: draft.allow_tenant_model_customization,
|
||||
require_model_change_requests: draft.require_model_change_requests,
|
||||
audit_detail_level: draft.audit_detail_level,
|
||||
change_retention_days: draft.change_retention_days ?? null,
|
||||
settings: draft.settings
|
||||
});
|
||||
setDraft(saved);
|
||||
setSavedDraft(saved);
|
||||
setSuccess("i18n:govoplan-organizations.organization_settings_saved.bfbcfdfa");
|
||||
return true;
|
||||
} catch (caught) {
|
||||
setError(adminErrorMessage(caught));
|
||||
return false;
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<OrganizationsPage
|
||||
settings={settings}
|
||||
auth={auth}
|
||||
mode="admin"
|
||||
initialSection="model"
|
||||
availableSections={MODEL_SECTIONS}
|
||||
title="i18n:govoplan-organizations.organization_model.4f924c0e"
|
||||
description="i18n:govoplan-organizations.organization_model_admin_description.35dc9f10"
|
||||
/>
|
||||
<AdminPageLayout
|
||||
title="i18n:govoplan-organizations.organization_settings.c9ab9829"
|
||||
description="i18n:govoplan-organizations.organization_settings_description.35dc9f10"
|
||||
loading={loading}
|
||||
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></>}
|
||||
>
|
||||
<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}
|
||||
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}
|
||||
onChange={(checked) => setDraft({ ...draft, require_model_change_requests: checked })}
|
||||
label="i18n:govoplan-organizations.require_model_change_requests.83454cad"
|
||||
/>
|
||||
</div>
|
||||
<p className="muted small-note">i18n:govoplan-organizations.model_governance_help.0fa69021</p>
|
||||
</Card>
|
||||
|
||||
<Card title="i18n:govoplan-organizations.audit_and_retention.3ba1d2fc">
|
||||
<div className="organizations-form-grid">
|
||||
<FormField label="i18n:govoplan-organizations.audit_detail_level.7397355d">
|
||||
<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">
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
value={draft.change_retention_days ?? ""}
|
||||
disabled={!canWrite || busy}
|
||||
placeholder="i18n:govoplan-organizations.unlimited.35569464"
|
||||
onChange={(event) => setDraft({ ...draft, change_retention_days: event.target.value === "" ? null : Math.max(0, Number(event.target.value)) })}
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
<p className="muted small-note">i18n:govoplan-organizations.audit_retention_help.42dec57d</p>
|
||||
</Card>
|
||||
</div>
|
||||
</AdminPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
function settingsKey(settings: OrganizationSettingsItem): string {
|
||||
return JSON.stringify({
|
||||
allow_tenant_model_customization: settings.allow_tenant_model_customization,
|
||||
require_model_change_requests: settings.require_model_change_requests,
|
||||
audit_detail_level: settings.audit_detail_level,
|
||||
change_retention_days: settings.change_retention_days ?? null,
|
||||
settings: settings.settings
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user