feat: manage tenant appearance defaults

This commit is contained in:
2026-08-20 07:03:28 +02:00
parent 56f0661a10
commit c5119ab868
8 changed files with 162 additions and 18 deletions
+11 -1
View File
@@ -3,7 +3,8 @@ import type {
DeltaDeletedItem,
NavigationPreferences,
PrivacyRetentionPolicy,
TenantAdminItem
TenantAdminItem,
UserUiPalette
} from "@govoplan/core-webui";
import {
apiFetch,
@@ -43,6 +44,12 @@ export type TenantSettingsItem = {
system_enabled_language_codes: string[];
enabled_language_codes: string[];
navigation?: NavigationPreferences | null;
appearance_palette: UserUiPalette | null;
appearance_palette_locked: boolean;
system_appearance_palette: UserUiPalette;
system_appearance_palette_locked: boolean;
effective_appearance_palette: UserUiPalette;
effective_appearance_source: "tenant" | "system" | "tenant_lock" | "system_lock";
settings: Record<string, unknown>;
};
@@ -54,6 +61,7 @@ export type TenantSettingsDeltaSections = Partial<{
"available_languages" | "system_enabled_language_codes" | "enabled_language_codes"
>;
navigation: TenantSettingsItem["navigation"];
appearance: Pick<TenantSettingsItem, "appearance_palette" | "appearance_palette_locked" | "system_appearance_palette" | "system_appearance_palette_locked" | "effective_appearance_palette" | "effective_appearance_source">;
settings: TenantSettingsItem["settings"];
}>;
@@ -151,6 +159,8 @@ export function updateTenantSettings(
default_locale: string;
enabled_language_codes?: string[] | null;
navigation?: NavigationPreferences | null;
appearance_palette?: UserUiPalette | null;
appearance_palette_locked?: boolean;
}
): Promise<TenantSettingsItem> {
return apiFetch(settings, "/api/v1/admin/tenant/settings", {
@@ -1,5 +1,7 @@
import {
DescriptionList,
AppearancePalettePreview,
AppearancePaletteSelect,
NavigationPreferenceEditor,
configurableNavigationItemsForModules,
dispatchPlatformModulesChanged,
@@ -14,6 +16,7 @@ import {
Card,
DocumentationHelpLink,
FormField,
ToggleSwitch,
adminErrorMessage,
useDeltaWatermarks,
useUnsavedChanges,
@@ -42,18 +45,25 @@ const fallback: TenantSettingsItem = {
system_enabled_language_codes: ["de", "en"],
enabled_language_codes: ["de", "en"],
navigation: null,
settings: {}
settings: {},
appearance_palette: null,
appearance_palette_locked: false,
system_appearance_palette: "default",
system_appearance_palette_locked: false,
effective_appearance_palette: "default",
effective_appearance_source: "system"
};
export default function TenantSettingsPanel({
settings,
canWrite,
canWritePolicy,
onAuthRefresh
}: {settings: ApiSettings;canWrite: boolean;onAuthRefresh: () => Promise<void>;}) {
}: {settings: ApiSettings;canWrite: boolean;canWritePolicy: boolean;onAuthRefresh: () => Promise<void>;}) {
const { requestDiscard } = useUnsavedChanges();
const { modules } = usePlatformModules();
const navigationItems = configurableNavigationItemsForModules(modules);
@@ -115,7 +125,9 @@ export default function TenantSettingsPanel({
const saved = await updateTenantSettings(settings, {
default_locale: draft.default_locale,
enabled_language_codes: draft.enabled_language_codes,
navigation: draft.navigation
navigation: draft.navigation,
appearance_palette: draft.appearance_palette,
appearance_palette_locked: draft.appearance_palette_locked
});
setDraft(saved);
setSavedDraft(saved);
@@ -197,6 +209,33 @@ export default function TenantSettingsPanel({
onChange={(navigation) => setDraft({ ...draft, navigation })}
/>
</Card>
<Card title="i18n:govoplan-tenancy.appearance_defaults">
<FormField label="i18n:govoplan-tenancy.tenant_palette_default" help="i18n:govoplan-tenancy.tenant_palette_default_help">
<AppearancePaletteSelect
value={draft.appearance_palette}
onChange={(appearance_palette) => setDraft({
...draft,
appearance_palette,
effective_appearance_palette: appearance_palette ?? draft.system_appearance_palette,
effective_appearance_source: appearance_palette ? "tenant" : "system"
})}
allowInherit
disabled={!canWrite || busy || draft.system_appearance_palette_locked || (draft.appearance_palette_locked && !canWritePolicy)}
/>
</FormField>
<ToggleSwitch
checked={draft.appearance_palette_locked}
onChange={(appearance_palette_locked) => setDraft({ ...draft, appearance_palette_locked })}
disabled={!canWrite || !canWritePolicy || busy || draft.system_appearance_palette_locked}
help={!canWritePolicy ? "i18n:govoplan-tenancy.appearance_lock_policy_permission" : draft.system_appearance_palette_locked ? "i18n:govoplan-tenancy.system_palette_is_locked" : undefined}
label="i18n:govoplan-tenancy.lock_tenant_palette"
/>
<AppearancePalettePreview palette={draft.system_appearance_palette_locked ? draft.system_appearance_palette : draft.appearance_palette ?? draft.system_appearance_palette} />
<DescriptionList variant="inline">
<div><dt>i18n:govoplan-tenancy.effective_source</dt><dd>{draft.system_appearance_palette_locked ? "i18n:govoplan-tenancy.system_lock" : draft.appearance_palette ? "i18n:govoplan-tenancy.tenant_default" : "i18n:govoplan-tenancy.system_default"}</dd></div>
<div><dt>i18n:govoplan-tenancy.user_override</dt><dd>{draft.system_appearance_palette_locked || draft.appearance_palette_locked ? "i18n:govoplan-tenancy.blocked_by_policy" : "i18n:govoplan-tenancy.allowed"}</dd></div>
</DescriptionList>
</Card>
</div>
</AdminPageLayout>);
@@ -214,7 +253,9 @@ function tenantSettingsDraftKey(item: TenantSettingsItem): string {
return JSON.stringify({
default_locale: item.default_locale,
enabled_language_codes: item.enabled_language_codes,
navigation: item.navigation
navigation: item.navigation,
appearance_palette: item.appearance_palette,
appearance_palette_locked: item.appearance_palette_locked
});
}
@@ -225,6 +266,7 @@ function applyTenantSettingsSections(item: TenantSettingsItem, sections: TenantS
...(sections.locale ?? {}),
...(sections.languages ?? {}),
...(sections.navigation !== undefined ? { navigation: sections.navigation } : {}),
...(sections.appearance ?? {}),
...(sections.settings ? { settings: sections.settings } : {})
};
}
+26
View File
@@ -2,6 +2,19 @@ import type { PlatformTranslations } from "@govoplan/core-webui";
export const generatedTranslations: PlatformTranslations = {
"en": {
"i18n:govoplan-tenancy.appearance_defaults": "Appearance defaults",
"i18n:govoplan-tenancy.tenant_palette_default": "Tenant palette default",
"i18n:govoplan-tenancy.tenant_palette_default_help": "Inherit the system palette or select the default for this tenant.",
"i18n:govoplan-tenancy.appearance_lock_policy_permission": "Policy-write permission is required to change this lock.",
"i18n:govoplan-tenancy.system_palette_is_locked": "The system palette is locked and takes precedence.",
"i18n:govoplan-tenancy.lock_tenant_palette": "Lock the tenant palette",
"i18n:govoplan-tenancy.effective_source": "Effective source",
"i18n:govoplan-tenancy.system_lock": "System policy lock",
"i18n:govoplan-tenancy.tenant_default": "Tenant default",
"i18n:govoplan-tenancy.system_default": "System default",
"i18n:govoplan-tenancy.user_override": "Personal choice",
"i18n:govoplan-tenancy.blocked_by_policy": "Blocked by policy",
"i18n:govoplan-tenancy.allowed": "Allowed",
"i18n:govoplan-tenancy.tenant_information_is_loading.7c9a1001": "Tenant information is loading.",
"i18n:govoplan-tenancy.a_tenant_change_is_in_progress.7c9a1002": "A tenant change is in progress.",
"i18n:govoplan-tenancy.tenant_creation_permission_is_required.7c9a1003": "Tenant creation permission is required.",
@@ -101,6 +114,19 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-tenancy.value_value.c189e8bc": "{value0} ({value1})"
},
"de": {
"i18n:govoplan-tenancy.appearance_defaults": "Darstellungsstandards",
"i18n:govoplan-tenancy.tenant_palette_default": "Mandantenstandard für die Farbpalette",
"i18n:govoplan-tenancy.tenant_palette_default_help": "Systempalette übernehmen oder einen Standard für diesen Mandanten auswählen.",
"i18n:govoplan-tenancy.appearance_lock_policy_permission": "Zum Ändern dieser Sperre ist das Recht zum Bearbeiten von Richtlinien erforderlich.",
"i18n:govoplan-tenancy.system_palette_is_locked": "Die Systempalette ist verbindlich und hat Vorrang.",
"i18n:govoplan-tenancy.lock_tenant_palette": "Mandantenpalette verbindlich festlegen",
"i18n:govoplan-tenancy.effective_source": "Wirksame Quelle",
"i18n:govoplan-tenancy.system_lock": "Systemrichtlinie",
"i18n:govoplan-tenancy.tenant_default": "Mandantenstandard",
"i18n:govoplan-tenancy.system_default": "Systemstandard",
"i18n:govoplan-tenancy.user_override": "Persönliche Auswahl",
"i18n:govoplan-tenancy.blocked_by_policy": "Durch Richtlinie gesperrt",
"i18n:govoplan-tenancy.allowed": "Zulässig",
"i18n:govoplan-tenancy.tenant_information_is_loading.7c9a1001": "Mandanteninformationen werden geladen.",
"i18n:govoplan-tenancy.a_tenant_change_is_in_progress.7c9a1002": "Eine Mandantenänderung wird gerade ausgeführt.",
"i18n:govoplan-tenancy.tenant_creation_permission_is_required.7c9a1003": "Die Berechtigung zum Erstellen von Mandanten ist erforderlich.",
+1
View File
@@ -44,6 +44,7 @@ const adminSections: AdminSectionsUiCapability = {
createElement(TenantSettingsPanel, {
settings,
canWrite: auth.scopes.includes("admin:settings:write"),
canWritePolicy: auth.scopes.includes("admin:policies:write"),
onAuthRefresh: refreshAuth
})
}