Migrate Tenancy interface patterns

This commit is contained in:
2026-08-03 11:42:34 +02:00
parent 2ab89eb809
commit e76fe16870
11 changed files with 453 additions and 92 deletions
+2 -1
View File
@@ -7,7 +7,8 @@
"module": "src/index.ts",
"types": "src/index.ts",
"scripts": {
"test:tenancy-admin": "node scripts/test-tenancy-admin-structure.mjs"
"test:tenancy-admin": "node scripts/test-tenancy-admin-structure.mjs",
"test:interface-patterns": "node scripts/test-interface-pattern-language.mjs"
},
"exports": {
".": {
@@ -0,0 +1,30 @@
import { readFileSync } from "node:fs";
function source(path) {
return readFileSync(new URL(path, import.meta.url), "utf8");
}
function assert(condition, message) {
if (!condition) throw new Error(message);
}
const tenants = source("../src/features/admin/TenantsPanel.tsx");
const settings = source("../src/features/admin/TenantSettingsPanel.tsx");
const patterns = source("../src/features/admin/interfacePatterns.ts");
const moduleSource = source("../src/module.ts");
const translations = source("../src/i18n/generatedTranslations.ts");
assert(tenants.includes("DocumentationHelpLink") && settings.includes("DocumentationHelpLink"), "Both Tenancy admin surfaces expose contextual documentation");
assert(tenants.includes("ActionBlockerHint") && settings.includes("ActionBlockerHint"), "Read-only Tenancy states identify the actor, action, and destination");
assert(tenants.includes("disabledReason") && settings.includes("disabledReason"), "Disabled Tenancy actions explain their state");
assert(tenants.includes("requestDiscard(closeEditor)"), "The tenant editor uses the shared unsaved-change guard when closing");
assert(settings.includes("requestDiscard(() => void load())"), "Tenant settings protect dirty state when reloading");
assert(tenants.includes("ConfirmDialog") && tenants.includes("confirmSuspend"), "Tenant suspension remains explicitly confirmed");
assert(tenants.includes("minimumSlots={3}"), "Tenant row actions reserve stable keyboard and visual positions");
assert(!tenants.includes("applicable:"), "Row-specific unavailable actions stay visible with an explanation");
assert(patterns.includes('topicId: "tenancy.lifecycle-and-settings"') && patterns.includes('topicId: "tenancy.reference.admin-fields"'), "Tenancy uses stable manifest-backed help references");
assert(moduleSource.includes('version: "0.1.8"'), "The WebUI contribution reports the module release version");
assert(moduleSource.includes('label: "i18n:govoplan-tenancy.tenants.1f7ae776"') && moduleSource.includes('label: "i18n:govoplan-tenancy.tenant_general_settings.db1c3ba8"'), "View-surface labels are localized");
assert(translations.includes('"i18n:govoplan-tenancy.tenant_administration_is_read_only.7c9a1011"'), "Availability explanations are present in the translation catalog");
console.log("Tenancy surfaces satisfy the recorded interface pattern-language contract.");
@@ -1,10 +1,25 @@
import { useEffect, useState } from "react";
import type { ApiSettings } from "@govoplan/core-webui";
import { Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
import {
ActionBlockerHint,
AdminPageLayout,
AdminSelectionList,
Button,
Card,
DocumentationHelpLink,
FormField,
adminErrorMessage,
useDeltaWatermarks,
useUnsavedChanges,
useUnsavedDraftGuard,
type ApiSettings
} from "@govoplan/core-webui";
import { fetchTenantSettingsDelta, updateTenantSettings, type TenantSettingsDeltaSections, type TenantSettingsItem } from "../../api/tenancy";
import { AdminPageLayout, AdminSelectionList, adminErrorMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
import {
TENANCY_ADMIN_DOCUMENTATION,
TENANCY_FIELD_DOCUMENTATION,
TENANCY_INTERFACE_I18N,
tenantMutationDisabledReason
} from "./interfacePatterns";
const DELTA_KEY = "tenancy:tenant-settings";
@@ -31,6 +46,7 @@ export default function TenantSettingsPanel({
}: {settings: ApiSettings;canWrite: boolean;onAuthRefresh: () => Promise<void>;}) {
const { requestDiscard } = useUnsavedChanges();
const [draft, setDraft] = useState<TenantSettingsItem>(fallback);
const [savedDraft, setSavedDraft] = useState<TenantSettingsItem>(fallback);
const [loading, setLoading] = useState(true);
@@ -40,6 +56,13 @@ export default function TenantSettingsPanel({
const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks();
const defaultLocaleOptions = localeOptions(draft.default_locale, draft.enabled_language_codes);
const dirty = tenantSettingsDraftKey(draft) !== tenantSettingsDraftKey(savedDraft);
const saveDisabledReason = tenantMutationDisabledReason({
busy,
permitted: canWrite,
complete: Boolean(draft.default_locale.trim() && draft.enabled_language_codes.length),
changed: dirty,
permissionReason: TENANCY_INTERFACE_I18N.settingsWriteRequired
});
useUnsavedDraftGuard({
dirty,
@@ -108,11 +131,26 @@ export default function TenantSettingsPanel({
loading={loading}
error={error}
success={success}
actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-tenancy.reload.cce71553</Button><Button variant="primary" onClick={() => void save()} disabled={!canWrite || busy || !draft.default_locale.trim()}>{busy ? "i18n:govoplan-tenancy.saving.ae7e8875" : "i18n:govoplan-tenancy.save_general_settings.5c90f8c4"}</Button></>}>
actions={<><DocumentationHelpLink reference={TENANCY_ADMIN_DOCUMENTATION} /><Button onClick={() => requestDiscard(() => void load())} disabled={loading || busy} disabledReason={loading ? TENANCY_INTERFACE_I18N.loading : busy ? TENANCY_INTERFACE_I18N.busy : undefined}>i18n:govoplan-tenancy.reload.cce71553</Button><Button variant="primary" onClick={() => void save()} disabled={Boolean(saveDisabledReason)} disabledReason={saveDisabledReason}>{busy ? "i18n:govoplan-tenancy.saving.ae7e8875" : "i18n:govoplan-tenancy.save_general_settings.5c90f8c4"}</Button></>}>
{!canWrite && <ActionBlockerHint
reason={{
summary: TENANCY_INTERFACE_I18N.readOnlySummary,
requiredAction: TENANCY_INTERFACE_I18N.settingsPermissionGuidance,
actor: TENANCY_INTERFACE_I18N.administratorActor,
target: TENANCY_INTERFACE_I18N.tenantSettingsTarget
}}
labels={{
requiredAction: TENANCY_INTERFACE_I18N.requiredActionLabel,
actor: TENANCY_INTERFACE_I18N.actorLabel,
target: TENANCY_INTERFACE_I18N.targetLabel
}}
documentation={TENANCY_ADMIN_DOCUMENTATION}
/>}
<div className="admin-settings-form">
<Card title="i18n:govoplan-tenancy.locale.8970f0e6">
<FormField label="i18n:govoplan-tenancy.tenant_locale.8fc19914" help="i18n:govoplan-tenancy.used_as_this_tenant_s_locale_default_for_tenant_.cf298b8b">
<FormField label="i18n:govoplan-tenancy.tenant_locale.8fc19914" help={!canWrite ? TENANCY_INTERFACE_I18N.settingsWriteRequired : "i18n:govoplan-tenancy.used_as_this_tenant_s_locale_default_for_tenant_.cf298b8b"} documentation={TENANCY_FIELD_DOCUMENTATION}>
<select value={draft.default_locale} disabled={!canWrite || busy || defaultLocaleOptions.length === 0} onChange={(event) => setDraft({ ...draft, default_locale: event.target.value })}>
{defaultLocaleOptions.map((code) => {
const language = draft.available_languages.find((item) => item.code === code);
@@ -128,7 +166,7 @@ export default function TenantSettingsPanel({
selected={draft.enabled_language_codes}
onChange={setEnabledLanguages}
/>
<p className="muted small-note">i18n:govoplan-tenancy.tenant_languages_help</p>
<p className="muted small-note"><span>i18n:govoplan-tenancy.tenant_languages_help</span>{" "}<span>{TENANCY_INTERFACE_I18N.defaultLanguageRequired}</span></p>
<dl className="detail-list">
<div><dt>i18n:govoplan-tenancy.tenant.3ca93c78</dt><dd>{draft.name || "-"}</dd></div>
<div><dt>i18n:govoplan-tenancy.slug.094da9b9</dt><dd>{draft.slug || "-"}</dd></div>
+90 -36
View File
@@ -1,16 +1,35 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { Pencil, Plus, Search, Trash2 } from "lucide-react";
import type { ApiSettings, AuthInfo } from "@govoplan/core-webui";
import type { ApiSettings, AuthInfo, TenantAdminItem } from "@govoplan/core-webui";
import {
ActionBlockerHint,
AdminIconButton,
AdminPageLayout,
Button,
ConfirmDialog,
DataGrid,
Dialog,
DocumentationHelpLink,
FormField,
StatusBadge,
TableActionGroup,
adminErrorMessage,
formatAdminDateTime as formatDateTime,
i18nMessage,
useDeltaWatermarks,
usePlatformLanguage,
useUnsavedChanges,
useUnsavedDraftGuard,
type DataGridColumn
} from "@govoplan/core-webui";
import { createTenant, fetchSystemSettings, fetchTenantOwnerCandidates, fetchTenantsDelta, updateTenant, type SystemSettingsItem, type TenantOwnerCandidate } from "../../api/tenancy";
import type { TenantAdminItem } from "@govoplan/core-webui";
import { Button } from "@govoplan/core-webui";
import { DataGrid, type DataGridColumn } from "@govoplan/core-webui";
import { Dialog } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
import { StatusBadge } from "@govoplan/core-webui";
import { ConfirmDialog } from "@govoplan/core-webui";
import { AdminIconButton, AdminPageLayout, TableActionGroup, adminErrorMessage, formatAdminDateTime as formatDateTime, i18nMessage, useDeltaWatermarks, useUnsavedDraftGuard } from "@govoplan/core-webui";
import { loadDeltaRows } from "./utils/deltaRows";
import {
TENANCY_ADMIN_DOCUMENTATION,
TENANCY_FIELD_DOCUMENTATION,
TENANCY_INTERFACE_I18N,
tenantMutationDisabledReason
} from "./interfacePatterns";
type OverrideValue = "inherit" | "allow" | "deny";
type TenantDraft = {
@@ -64,6 +83,8 @@ export default function TenantsPanel({
}: {settings: ApiSettings;auth: AuthInfo;canCreate: boolean;canUpdate: boolean;canSuspend: boolean;onAuthRefresh: () => Promise<void>;}) {
const { translateText } = usePlatformLanguage();
const { requestDiscard } = useUnsavedChanges();
const [tenants, setTenants] = useState<TenantAdminItem[]>([]);
const [systemSettings, setSystemSettings] = useState<SystemSettingsItem | null>(null);
const [ownerCandidates, setOwnerCandidates] = useState<TenantOwnerCandidate[]>([]);
@@ -144,6 +165,11 @@ export default function TenantsPanel({
setSavedDraftKey(draftKey(emptyDraft));
}
function requestCloseEditor() {
if (busy) return;
requestDiscard(closeEditor);
}
async function save(): Promise<boolean> {
setBusy(true);
setError("");
@@ -164,7 +190,7 @@ export default function TenantsPanel({
...governance
});
const selectedOwner = ownerCandidates.find((candidate) => candidate.account_id === draft.ownerAccountId);
setSuccess(i18nMessage("i18n:govoplan-tenancy.tenant_value_created_with_value_as_owner.1c18b6fb", { value0: created.name, value1: selectedOwner?.display_name || selectedOwner?.email || "i18n:govoplan-tenancy.the_selected_account.1211bfb9" }));
setSuccess(i18nMessage("i18n:govoplan-tenancy.tenant_value_created_with_value_as_owner.1c18b6fb", { value0: created.name, value1: selectedOwner?.display_name || selectedOwner?.email || translateText("i18n:govoplan-tenancy.the_selected_account.1211bfb9") }));
await onAuthRefresh();
} else if (editing) {
const payload: Parameters<typeof updateTenant>[2] = {};
@@ -214,10 +240,17 @@ export default function TenantsPanel({
const systemAllowsCustomRoles = systemSettings?.allow_tenant_custom_roles !== false;
const systemAllowsApiKeys = systemSettings?.allow_tenant_api_keys !== false;
const systemDeniedGovernance = [
systemAllowsCustomGroups ? "" : "i18n:govoplan-tenancy.custom_groups.453a605c",
systemAllowsCustomRoles ? "" : "i18n:govoplan-tenancy.custom_roles.d48dc976",
systemAllowsApiKeys ? "" : "i18n:govoplan-tenancy.api_keys.94fcf3c2"].
filter(Boolean).join(", ");
systemAllowsCustomGroups ? "" : translateText("i18n:govoplan-tenancy.custom_groups.453a605c"),
systemAllowsCustomRoles ? "" : translateText("i18n:govoplan-tenancy.custom_roles.d48dc976"),
systemAllowsApiKeys ? "" : translateText("i18n:govoplan-tenancy.api_keys.94fcf3c2")
].filter(Boolean).join(", ");
const saveDisabledReason = tenantMutationDisabledReason({
busy,
permitted: editing === "new" ? canCreate : canUpdate,
complete: Boolean(draft.name.trim() && draft.slug.trim() && (editing !== "new" || draft.ownerAccountId)),
changed: editing === "new" || dirty,
permissionReason: editing === "new" ? TENANCY_INTERFACE_I18N.createRequired : TENANCY_INTERFACE_I18N.updateRequired
});
const columns = useMemo<DataGridColumn<TenantAdminItem>[]>(() => [
{ id: "name", header: "i18n:govoplan-tenancy.tenant.3ca93c78", width: "minmax(210px, 1fr)", minWidth: 190, resizable: true, sticky: "start", sortable: true, filterable: true, value: (row) => `${row.name} ${row.slug}`, render: (row) => <div><strong>{row.name}</strong><div className="muted small-note">{row.slug}</div></div> },
{ id: "users", header: "i18n:govoplan-tenancy.users.57f2b181", width: 100, resizable: false, sortable: true, filterable: true, filterType: "integer", value: (row) => row.counts.users ?? 0, render: (row) => `${row.counts.active_users ?? 0}/${row.counts.users ?? 0}` },
@@ -228,10 +261,10 @@ export default function TenantsPanel({
{ id: "status", header: "i18n:govoplan-tenancy.status.bae7d5be", width: 120, resizable: false, sortable: true, filterable: true, value: (row) => row.is_active ? "active" : "inactive", render: (row) => <StatusBadge status={row.is_active ? "active" : "inactive"} /> },
{ id: "actions", header: "i18n:govoplan-tenancy.actions.c3cd636a", width: 150, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[
{ id: "inspect", label: i18nMessage("i18n:govoplan-tenancy.inspect_value.9d5d1071", { value0: row.name }), icon: <Search />, onClick: () => setViewing(row) },
{ id: "edit", label: i18nMessage("i18n:govoplan-tenancy.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, disabled: !canUpdate, onClick: () => openEdit(row) },
{ id: "suspend", label: i18nMessage("i18n:govoplan-tenancy.suspend_value.03a74b32", { value0: row.name }), icon: <Trash2 />, variant: "danger", applicable: row.is_active, disabled: !canSuspend || row.id === activeTenantId, onClick: () => setConfirmSuspend(row) }
]} /> }],
[activeTenantId, canSuspend, canUpdate]);
{ id: "edit", label: i18nMessage("i18n:govoplan-tenancy.edit_value.fad75899", { value0: row.name }), icon: <Pencil />, disabled: !canUpdate || busy, disabledReason: busy ? TENANCY_INTERFACE_I18N.busy : !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined, onClick: () => openEdit(row) },
{ id: "suspend", label: i18nMessage("i18n:govoplan-tenancy.suspend_value.03a74b32", { value0: row.name }), icon: <Trash2 />, variant: "danger", disabled: !canSuspend || row.id === activeTenantId || !row.is_active || busy, disabledReason: busy ? TENANCY_INTERFACE_I18N.busy : !canSuspend ? TENANCY_INTERFACE_I18N.suspendRequired : row.id === activeTenantId ? TENANCY_INTERFACE_I18N.activeTenant : !row.is_active ? TENANCY_INTERFACE_I18N.alreadySuspended : undefined, onClick: () => setConfirmSuspend(row) }
]} minimumSlots={3} /> }],
[activeTenantId, busy, canSuspend, canUpdate]);
return (
<>
@@ -241,28 +274,43 @@ export default function TenantsPanel({
loading={loading}
error={error}
success={success}
actions={<><Button onClick={() => void load()} disabled={loading}>i18n:govoplan-tenancy.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-tenancy.add_tenant.b8e32af0" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate} /></>}>
actions={<><DocumentationHelpLink reference={TENANCY_ADMIN_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading || busy} disabledReason={loading ? TENANCY_INTERFACE_I18N.loading : busy ? TENANCY_INTERFACE_I18N.busy : undefined}>i18n:govoplan-tenancy.reload.cce71553</Button><AdminIconButton label="i18n:govoplan-tenancy.add_tenant.b8e32af0" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canCreate || busy} disabledReason={busy ? TENANCY_INTERFACE_I18N.busy : !canCreate ? TENANCY_INTERFACE_I18N.createRequired : undefined} /></>}>
{!canCreate && !canUpdate && !canSuspend && <ActionBlockerHint
reason={{
summary: TENANCY_INTERFACE_I18N.readOnlySummary,
requiredAction: TENANCY_INTERFACE_I18N.registryPermissionGuidance,
actor: TENANCY_INTERFACE_I18N.administratorActor,
target: TENANCY_INTERFACE_I18N.tenantRegistryTarget
}}
labels={{
requiredAction: TENANCY_INTERFACE_I18N.requiredActionLabel,
actor: TENANCY_INTERFACE_I18N.actorLabel,
target: TENANCY_INTERFACE_I18N.targetLabel
}}
documentation={TENANCY_ADMIN_DOCUMENTATION}
/>}
<div className="admin-table-surface"><DataGrid id="admin-tenants-v3" rows={tenants} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="i18n:govoplan-tenancy.no_tenants_found.72d04cf4" /></div>
</AdminPageLayout>
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-tenancy.create_tenant.4dbd55d9" : "i18n:govoplan-tenancy.edit_tenant.e2ba43f9"} onClose={() => !busy && setEditing(null)} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={() => setEditing(null)} disabled={busy}>i18n:govoplan-tenancy.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={!(editing === "new" ? canCreate : canUpdate) || busy || !draft.name.trim() || !draft.slug.trim() || editing === "new" && !draft.ownerAccountId}>{busy ? "i18n:govoplan-tenancy.saving.56a2285c" : "i18n:govoplan-tenancy.save_tenant.9eb2ac74"}</Button></>}>
<Dialog open={editing !== null} title={editing === "new" ? "i18n:govoplan-tenancy.create_tenant.4dbd55d9" : "i18n:govoplan-tenancy.edit_tenant.e2ba43f9"} onClose={requestCloseEditor} className="admin-dialog admin-dialog-wide" footer={<><Button onClick={requestCloseEditor} disabled={busy} disabledReason={busy ? TENANCY_INTERFACE_I18N.busy : undefined}>i18n:govoplan-tenancy.cancel.77dfd213</Button><Button variant="primary" onClick={() => void save()} disabled={Boolean(saveDisabledReason)} disabledReason={saveDisabledReason}>{busy ? "i18n:govoplan-tenancy.saving.56a2285c" : "i18n:govoplan-tenancy.save_tenant.9eb2ac74"}</Button></>}>
<div className="admin-form-grid two-columns">
<FormField label="i18n:govoplan-tenancy.name.709a2322"><input value={draft.name} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
<FormField label="i18n:govoplan-tenancy.slug.094da9b9"><input value={draft.slug} disabled={editing !== "new" || !canCreate} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
{editing === "new" && <FormField label="i18n:govoplan-tenancy.initial_tenant_owner.682291a9"><select value={draft.ownerAccountId} onChange={(event) => setDraft({ ...draft, ownerAccountId: event.target.value })}>{ownerCandidates.map((candidate) => <option key={candidate.account_id} value={candidate.account_id}>{candidate.display_name ? i18nMessage("i18n:govoplan-tenancy.value_value.c189e8bc", { value0: candidate.display_name, value1: candidate.email }) : candidate.email}</option>)}</select></FormField>}
<FormField label="i18n:govoplan-tenancy.default_locale.b99d021f"><input value={draft.defaultLocale} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, defaultLocale: event.target.value })} /></FormField>
{editing !== "new" && <FormField label="i18n:govoplan-tenancy.status.bae7d5be"><select value={draft.isActive ? "active" : "inactive"} disabled={!canSuspend} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-tenancy.active.a733b809</option><option value="inactive">i18n:govoplan-tenancy.suspended.794696a7</option></select></FormField>}
<FormField label="i18n:govoplan-tenancy.description.55f8ebc8"><textarea rows={4} value={draft.description} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
<FormField label="i18n:govoplan-tenancy.name.709a2322" help={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} documentation={TENANCY_FIELD_DOCUMENTATION}><input value={draft.name} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></FormField>
<FormField label="i18n:govoplan-tenancy.slug.094da9b9" help={editing !== "new" ? "i18n:govoplan-tenancy.the_tenant_slug_is_immutable_after_creation.7c9a1025" : !canCreate ? TENANCY_INTERFACE_I18N.createRequired : undefined} documentation={TENANCY_FIELD_DOCUMENTATION}><input value={draft.slug} disabled={editing !== "new" || !canCreate} onChange={(event) => setDraft({ ...draft, slug: event.target.value })} /></FormField>
{editing === "new" && <FormField label="i18n:govoplan-tenancy.initial_tenant_owner.682291a9" documentation={TENANCY_FIELD_DOCUMENTATION}><select value={draft.ownerAccountId} onChange={(event) => setDraft({ ...draft, ownerAccountId: event.target.value })}>{ownerCandidates.map((candidate) => <option key={candidate.account_id} value={candidate.account_id}>{candidate.display_name ? i18nMessage("i18n:govoplan-tenancy.value_value.c189e8bc", { value0: candidate.display_name, value1: candidate.email }) : candidate.email}</option>)}</select></FormField>}
<FormField label="i18n:govoplan-tenancy.default_locale.b99d021f" help={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} documentation={TENANCY_FIELD_DOCUMENTATION}><input value={draft.defaultLocale} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, defaultLocale: event.target.value })} /></FormField>
{editing !== "new" && <FormField label="i18n:govoplan-tenancy.status.bae7d5be" help={!canSuspend ? TENANCY_INTERFACE_I18N.suspendRequired : undefined} documentation={TENANCY_ADMIN_DOCUMENTATION}><select value={draft.isActive ? "active" : "inactive"} disabled={!canSuspend} onChange={(event) => setDraft({ ...draft, isActive: event.target.value === "active" })}><option value="active">i18n:govoplan-tenancy.active.a733b809</option><option value="inactive">i18n:govoplan-tenancy.suspended.794696a7</option></select></FormField>}
<FormField label="i18n:govoplan-tenancy.description.55f8ebc8" help={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} documentation={TENANCY_FIELD_DOCUMENTATION}><textarea rows={4} value={draft.description} disabled={editing !== "new" && !canUpdate} onChange={(event) => setDraft({ ...draft, description: event.target.value })} /></FormField>
</div>
<h3>i18n:govoplan-tenancy.system_governance_overrides.97cdf3ce</h3>
<div className="admin-form-grid two-columns">
<GovernanceSelect disabled={editing !== "new" && !canUpdate} allowDisabled={!systemAllowsCustomGroups} label="i18n:govoplan-tenancy.custom_tenant_groups.570ee603" value={draft.customGroups} onChange={(customGroups) => setDraft({ ...draft, customGroups })} />
<GovernanceSelect disabled={editing !== "new" && !canUpdate} allowDisabled={!systemAllowsCustomRoles} label="i18n:govoplan-tenancy.custom_tenant_roles.a738c37c" value={draft.customRoles} onChange={(customRoles) => setDraft({ ...draft, customRoles })} />
<GovernanceSelect disabled={editing !== "new" && !canUpdate} allowDisabled={!systemAllowsApiKeys} label="i18n:govoplan-tenancy.tenant_api_keys.4b1d81f8" value={draft.apiKeys} onChange={(apiKeys) => setDraft({ ...draft, apiKeys })} />
<GovernanceSelect disabled={editing !== "new" && !canUpdate} disabledReason={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} allowDisabled={!systemAllowsCustomGroups} label="i18n:govoplan-tenancy.custom_tenant_groups.570ee603" value={draft.customGroups} onChange={(customGroups) => setDraft({ ...draft, customGroups })} />
<GovernanceSelect disabled={editing !== "new" && !canUpdate} disabledReason={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} allowDisabled={!systemAllowsCustomRoles} label="i18n:govoplan-tenancy.custom_tenant_roles.a738c37c" value={draft.customRoles} onChange={(customRoles) => setDraft({ ...draft, customRoles })} />
<GovernanceSelect disabled={editing !== "new" && !canUpdate} disabledReason={editing !== "new" && !canUpdate ? TENANCY_INTERFACE_I18N.updateRequired : undefined} allowDisabled={!systemAllowsApiKeys} label="i18n:govoplan-tenancy.tenant_api_keys.4b1d81f8" value={draft.apiKeys} onChange={(apiKeys) => setDraft({ ...draft, apiKeys })} />
</div>
<p className="muted small-note">i18n:govoplan-tenancy.inherit_follows_the_current_system_setting_expli.60d4d868</p>
{systemDeniedGovernance && <p className="muted small-note">i18n:govoplan-tenancy.explicit_allow_is_unavailable_for.8d05fd4a {systemDeniedGovernance} i18n:govoplan-tenancy.because_the_current_system_setting_denies_it.3f59c244</p>}
{systemDeniedGovernance && <p className="muted small-note"><span>i18n:govoplan-tenancy.explicit_allow_is_unavailable_for.8d05fd4a</span>{" "}{systemDeniedGovernance}{" "}<span>i18n:govoplan-tenancy.because_the_current_system_setting_denies_it.3f59c244</span></p>}
</Dialog>
<Dialog open={Boolean(viewing)} title="i18n:govoplan-tenancy.tenant_details.5976ba72" onClose={() => setViewing(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setViewing(null)}>i18n:govoplan-tenancy.close.bbfa773e</Button>}>
@@ -270,10 +318,10 @@ export default function TenantsPanel({
<div><dt>i18n:govoplan-tenancy.tenant.3ca93c78</dt><dd>{viewing.name}</dd></div><div><dt>i18n:govoplan-tenancy.slug.094da9b9</dt><dd>{viewing.slug}</dd></div>
<div><dt>i18n:govoplan-tenancy.status.bae7d5be</dt><dd>{viewing.is_active ? "i18n:govoplan-tenancy.active.a733b809" : "i18n:govoplan-tenancy.suspended.794696a7"}</dd></div><div><dt>i18n:govoplan-tenancy.default_locale.b99d021f</dt><dd>{viewing.default_locale}</dd></div>
<div><dt>i18n:govoplan-tenancy.created.accf40c8</dt><dd>{formatDateTime(viewing.created_at)}</dd></div><div><dt>i18n:govoplan-tenancy.updated.f2f8570d</dt><dd>{formatDateTime(viewing.updated_at)}</dd></div>
<div><dt>i18n:govoplan-tenancy.custom_groups.1f7b7c8f</dt><dd>{viewing.effective_governance.allow_custom_groups ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({fromOverride(viewing.allow_custom_groups)})</dd></div>
<div><dt>i18n:govoplan-tenancy.custom_roles.e78ef63d</dt><dd>{viewing.effective_governance.allow_custom_roles ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({fromOverride(viewing.allow_custom_roles)})</dd></div>
<div><dt>i18n:govoplan-tenancy.api_keys.94fcf3c2</dt><dd>{viewing.effective_governance.allow_api_keys ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({fromOverride(viewing.allow_api_keys)})</dd></div>
<div><dt>i18n:govoplan-tenancy.objects.72a83add</dt><dd>{viewing.counts.users ?? 0} i18n:govoplan-tenancy.users.81651889 {viewing.counts.groups ?? 0} i18n:govoplan-tenancy.groups.07551586 {viewing.counts.campaigns ?? 0} i18n:govoplan-tenancy.campaigns.2282ffeb {viewing.counts.files ?? 0} files</dd></div>
<div><dt>i18n:govoplan-tenancy.custom_groups.1f7b7c8f</dt><dd>{viewing.effective_governance.allow_custom_groups ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({overrideLabel(fromOverride(viewing.allow_custom_groups))})</dd></div>
<div><dt>i18n:govoplan-tenancy.custom_roles.e78ef63d</dt><dd>{viewing.effective_governance.allow_custom_roles ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({overrideLabel(fromOverride(viewing.allow_custom_roles))})</dd></div>
<div><dt>i18n:govoplan-tenancy.api_keys.94fcf3c2</dt><dd>{viewing.effective_governance.allow_api_keys ? "i18n:govoplan-tenancy.allowed.77c7b490" : "i18n:govoplan-tenancy.denied.63b16bd4"} ({overrideLabel(fromOverride(viewing.allow_api_keys))})</dd></div>
<div><dt>i18n:govoplan-tenancy.objects.72a83add</dt><dd>{viewing.counts.users ?? 0}{" "}<span>i18n:govoplan-tenancy.users.81651889</span>{" "}{viewing.counts.groups ?? 0}{" "}<span>i18n:govoplan-tenancy.groups.07551586</span>{" "}{viewing.counts.campaigns ?? 0}{" "}<span>i18n:govoplan-tenancy.campaigns.2282ffeb</span>{" "}{viewing.counts.files ?? 0}{" "}<span>i18n:govoplan-tenancy.files_lowercase.7c9a1026</span></dd></div>
</dl>{viewing.description && <p>{viewing.description}</p>}</>}
</Dialog>
@@ -282,8 +330,14 @@ export default function TenantsPanel({
}
function GovernanceSelect({ label, value, onChange, disabled = false, allowDisabled = false }: {label: string;value: OverrideValue;onChange: (value: OverrideValue) => void;disabled?: boolean;allowDisabled?: boolean;}) {
return <FormField label={label}><select value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as OverrideValue)}><option value="inherit">i18n:govoplan-tenancy.inherit_system_setting.7f125156</option><option value="allow" disabled={allowDisabled}>i18n:govoplan-tenancy.allow_when_system_allows.4c5178cb</option><option value="deny">i18n:govoplan-tenancy.explicitly_deny.17ad945a</option></select></FormField>;
function GovernanceSelect({ label, value, onChange, disabled = false, disabledReason, allowDisabled = false }: {label: string;value: OverrideValue;onChange: (value: OverrideValue) => void;disabled?: boolean;disabledReason?: string;allowDisabled?: boolean;}) {
return <FormField label={label} help={disabledReason ?? (allowDisabled ? TENANCY_INTERFACE_I18N.governanceSystemLimit : undefined)} documentation={TENANCY_FIELD_DOCUMENTATION}><select value={value} disabled={disabled} onChange={(event) => onChange(event.target.value as OverrideValue)}><option value="inherit">i18n:govoplan-tenancy.inherit_system_setting.7f125156</option><option value="allow" disabled={allowDisabled}>i18n:govoplan-tenancy.allow_when_system_allows.4c5178cb</option><option value="deny">i18n:govoplan-tenancy.explicitly_deny.17ad945a</option></select></FormField>;
}
function overrideLabel(value: OverrideValue): string {
if (value === "allow") return TENANCY_INTERFACE_I18N.allowLabel;
if (value === "deny") return TENANCY_INTERFACE_I18N.denyLabel;
return TENANCY_INTERFACE_I18N.inheritLabel;
}
function draftKey(draft: TenantDraft): string {
@@ -0,0 +1,58 @@
import type { DocumentationHelpReference } from "@govoplan/core-webui";
export const TENANCY_ADMIN_DOCUMENTATION = {
topicId: "tenancy.lifecycle-and-settings",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const TENANCY_FIELD_DOCUMENTATION = {
topicId: "tenancy.reference.admin-fields",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const TENANCY_INTERFACE_I18N = {
loading: "i18n:govoplan-tenancy.tenant_information_is_loading.7c9a1001",
busy: "i18n:govoplan-tenancy.a_tenant_change_is_in_progress.7c9a1002",
createRequired: "i18n:govoplan-tenancy.tenant_creation_permission_is_required.7c9a1003",
updateRequired: "i18n:govoplan-tenancy.tenant_update_permission_is_required.7c9a1004",
suspendRequired: "i18n:govoplan-tenancy.tenant_suspension_permission_is_required.7c9a1005",
settingsWriteRequired: "i18n:govoplan-tenancy.tenant_settings_write_permission_is_required.7c9a1006",
completeRequiredFields: "i18n:govoplan-tenancy.complete_all_required_tenant_fields_before_saving.7c9a1007",
noPendingChanges: "i18n:govoplan-tenancy.make_a_change_before_saving.7c9a1008",
activeTenant: "i18n:govoplan-tenancy.switch_to_another_tenant_before_suspending_the_active_tenant.7c9a1009",
alreadySuspended: "i18n:govoplan-tenancy.this_tenant_is_already_suspended.7c9a1010",
readOnlySummary: "i18n:govoplan-tenancy.tenant_administration_is_read_only.7c9a1011",
requiredActionLabel: "i18n:govoplan-tenancy.required_action.7c9a1012",
actorLabel: "i18n:govoplan-tenancy.responsible_actor.7c9a1013",
targetLabel: "i18n:govoplan-tenancy.destination.7c9a1014",
administratorActor: "i18n:govoplan-tenancy.a_system_or_tenant_owner_with_the_required_permission.7c9a1015",
tenantRegistryTarget: "i18n:govoplan-tenancy.administration_tenants.7c9a1016",
tenantSettingsTarget: "i18n:govoplan-tenancy.administration_tenant_settings.7c9a1017",
registryPermissionGuidance: "i18n:govoplan-tenancy.request_a_tenant_management_permission_or_contact_a_system_owner.7c9a1018",
settingsPermissionGuidance: "i18n:govoplan-tenancy.request_tenant_settings_write_permission_or_contact_a_tenant_owner.7c9a1019",
defaultLanguageRequired: "i18n:govoplan-tenancy.the_default_language_must_remain_enabled.7c9a1020",
governanceSystemLimit: "i18n:govoplan-tenancy.system_policy_prevents_this_tenant_from_enabling_the_capability.7c9a1021",
inheritLabel: "i18n:govoplan-tenancy.inherit.7c9a1022",
allowLabel: "i18n:govoplan-tenancy.allow.7c9a1023",
denyLabel: "i18n:govoplan-tenancy.deny.7c9a1024"
} as const;
export function tenantMutationDisabledReason({
busy,
permitted,
complete = true,
changed = true,
permissionReason
}: {
busy: boolean;
permitted: boolean;
complete?: boolean;
changed?: boolean;
permissionReason: string;
}): string | undefined {
if (busy) return TENANCY_INTERFACE_I18N.busy;
if (!permitted) return permissionReason;
if (!complete) return TENANCY_INTERFACE_I18N.completeRequiredFields;
if (!changed) return TENANCY_INTERFACE_I18N.noPendingChanges;
return undefined;
}
+93 -41
View File
@@ -2,79 +2,131 @@ import type { PlatformTranslations } from "@govoplan/core-webui";
export const generatedTranslations: PlatformTranslations = {
"en": {
"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.",
"i18n:govoplan-tenancy.tenant_update_permission_is_required.7c9a1004": "Tenant update permission is required.",
"i18n:govoplan-tenancy.tenant_suspension_permission_is_required.7c9a1005": "Tenant suspension permission is required.",
"i18n:govoplan-tenancy.tenant_settings_write_permission_is_required.7c9a1006": "Tenant settings write permission is required.",
"i18n:govoplan-tenancy.complete_all_required_tenant_fields_before_saving.7c9a1007": "Complete all required tenant fields before saving.",
"i18n:govoplan-tenancy.make_a_change_before_saving.7c9a1008": "Make a change before saving.",
"i18n:govoplan-tenancy.switch_to_another_tenant_before_suspending_the_active_tenant.7c9a1009": "Switch to another tenant before suspending the active tenant.",
"i18n:govoplan-tenancy.this_tenant_is_already_suspended.7c9a1010": "This tenant is already suspended.",
"i18n:govoplan-tenancy.tenant_administration_is_read_only.7c9a1011": "Tenant administration is read-only.",
"i18n:govoplan-tenancy.required_action.7c9a1012": "Required action",
"i18n:govoplan-tenancy.responsible_actor.7c9a1013": "Responsible actor",
"i18n:govoplan-tenancy.destination.7c9a1014": "Destination",
"i18n:govoplan-tenancy.a_system_or_tenant_owner_with_the_required_permission.7c9a1015": "A system or tenant owner with the required permission",
"i18n:govoplan-tenancy.administration_tenants.7c9a1016": "Administration > Tenants",
"i18n:govoplan-tenancy.administration_tenant_settings.7c9a1017": "Administration > Tenant settings",
"i18n:govoplan-tenancy.request_a_tenant_management_permission_or_contact_a_system_owner.7c9a1018": "Request a tenant-management permission or contact a system owner.",
"i18n:govoplan-tenancy.request_tenant_settings_write_permission_or_contact_a_tenant_owner.7c9a1019": "Request tenant-settings write permission or contact a tenant owner.",
"i18n:govoplan-tenancy.the_default_language_must_remain_enabled.7c9a1020": "The default language must remain enabled.",
"i18n:govoplan-tenancy.system_policy_prevents_this_tenant_from_enabling_the_capability.7c9a1021": "System policy prevents this tenant from enabling the capability.",
"i18n:govoplan-tenancy.inherit.7c9a1022": "inherit",
"i18n:govoplan-tenancy.allow.7c9a1023": "allow",
"i18n:govoplan-tenancy.deny.7c9a1024": "deny",
"i18n:govoplan-tenancy.the_tenant_slug_is_immutable_after_creation.7c9a1025": "The tenant slug is immutable after creation.",
"i18n:govoplan-tenancy.files_lowercase.7c9a1026": "files",
"i18n:govoplan-tenancy.actions.c3cd636a": "Actions",
"i18n:govoplan-tenancy.active.a733b809": "Active",
"i18n:govoplan-tenancy.add_tenant.b8e32af0": "Add tenant",
"i18n:govoplan-tenancy.allow_when_system_allows.4c5178cb": "Allow when system allows",
"i18n:govoplan-tenancy.allowed.77c7b490": "Allowed",
"i18n:govoplan-tenancy.allow_when_system_allows.4c5178cb": "Erlauben, wenn systemweit zulässig",
"i18n:govoplan-tenancy.allowed.77c7b490": "Erlaubt",
"i18n:govoplan-tenancy.api_keys.94fcf3c2": "API keys",
"i18n:govoplan-tenancy.available.7c62a142": "Available",
"i18n:govoplan-tenancy.because_the_current_system_setting_denies_it.3f59c244": "because the current system setting denies it.",
"i18n:govoplan-tenancy.because_the_current_system_setting_denies_it.3f59c244": "da die aktuelle Systemeinstellung dies verweigert.",
"i18n:govoplan-tenancy.campaigns.01a23a28": "Campaigns",
"i18n:govoplan-tenancy.campaigns.2282ffeb": "campaigns,",
"i18n:govoplan-tenancy.campaigns.2282ffeb": "Kampagnen,",
"i18n:govoplan-tenancy.cancel.77dfd213": "Cancel",
"i18n:govoplan-tenancy.close.bbfa773e": "Close",
"i18n:govoplan-tenancy.create_and_govern_tenant_spaces_suspension_retai.1b76d377": "Create and govern tenant spaces. Suspension retains campaigns, files and audit evidence; the tenant backing the current session cannot be suspended.",
"i18n:govoplan-tenancy.create_and_govern_tenant_spaces_suspension_retai.1b76d377": "Mandantenbereiche erstellen und verwalten. Eine Sperrung bewahrt Kampagnen, Dateien und Nachweise; der Mandant der aktuellen Sitzung kann nicht gesperrt werden.",
"i18n:govoplan-tenancy.create_tenant.4dbd55d9": "Create tenant",
"i18n:govoplan-tenancy.created.accf40c8": "Created",
"i18n:govoplan-tenancy.custom_groups.1f7b7c8f": "Custom groups",
"i18n:govoplan-tenancy.custom_groups.453a605c": "custom groups",
"i18n:govoplan-tenancy.custom_roles.d48dc976": "custom roles",
"i18n:govoplan-tenancy.custom_roles.e78ef63d": "Custom roles",
"i18n:govoplan-tenancy.custom_tenant_groups.570ee603": "Custom tenant groups",
"i18n:govoplan-tenancy.custom_tenant_roles.a738c37c": "Custom tenant roles",
"i18n:govoplan-tenancy.custom_groups.1f7b7c8f": "Eigene Gruppen",
"i18n:govoplan-tenancy.custom_groups.453a605c": "eigene Gruppen",
"i18n:govoplan-tenancy.custom_roles.d48dc976": "eigene Rollen",
"i18n:govoplan-tenancy.custom_roles.e78ef63d": "Eigene Rollen",
"i18n:govoplan-tenancy.custom_tenant_groups.570ee603": "Eigene Mandantengruppen",
"i18n:govoplan-tenancy.custom_tenant_roles.a738c37c": "Eigene Mandantenrollen",
"i18n:govoplan-tenancy.default_locale.b99d021f": "Default locale",
"i18n:govoplan-tenancy.denied.63b16bd4": "Denied",
"i18n:govoplan-tenancy.denied.63b16bd4": "Verweigert",
"i18n:govoplan-tenancy.description.55f8ebc8": "Description",
"i18n:govoplan-tenancy.edit_tenant.e2ba43f9": "Edit tenant",
"i18n:govoplan-tenancy.edit_value.fad75899": "Edit {value0}",
"i18n:govoplan-tenancy.explicit_allow_is_unavailable_for.8d05fd4a": "Explicit allow is unavailable for",
"i18n:govoplan-tenancy.explicitly_deny.17ad945a": "Explicitly deny",
"i18n:govoplan-tenancy.edit_value.fad75899": "{value0} bearbeiten",
"i18n:govoplan-tenancy.explicit_allow_is_unavailable_for.8d05fd4a": "Eine ausdrückliche Erlaubnis ist nicht verfügbar für",
"i18n:govoplan-tenancy.explicitly_deny.17ad945a": "Ausdrücklich verweigern",
"i18n:govoplan-tenancy.files.6ce6c512": "Files",
"i18n:govoplan-tenancy.general.9239ee2c": "General",
"i18n:govoplan-tenancy.groups.07551586": "groups,",
"i18n:govoplan-tenancy.groups.07551586": "Gruppen,",
"i18n:govoplan-tenancy.groups.ae9629f4": "Groups",
"i18n:govoplan-tenancy.inherit_follows_the_current_system_setting_expli.60d4d868": "Inherit follows the current system setting. Explicit deny narrows access; explicit allow is valid only while the system setting allows it.",
"i18n:govoplan-tenancy.inherit_system_setting.7f125156": "Inherit system setting",
"i18n:govoplan-tenancy.initial_tenant_owner.682291a9": "Initial tenant owner",
"i18n:govoplan-tenancy.inspect_value.9d5d1071": "Inspect {value0}",
"i18n:govoplan-tenancy.inherit_follows_the_current_system_setting_expli.60d4d868": "Vererben folgt der aktuellen Systemeinstellung. Eine ausdrückliche Verweigerung schränkt ein; eine ausdrückliche Erlaubnis gilt nur, solange sie systemweit zulässig ist.",
"i18n:govoplan-tenancy.inherit_system_setting.7f125156": "Systemeinstellung vererben",
"i18n:govoplan-tenancy.initial_tenant_owner.682291a9": "Anfängliche Mandanteneigentümerschaft",
"i18n:govoplan-tenancy.inspect_value.9d5d1071": "{value0} anzeigen",
"i18n:govoplan-tenancy.locale.8970f0e6": "Locale",
"i18n:govoplan-tenancy.name.709a2322": "Name",
"i18n:govoplan-tenancy.no_tenants_found.72d04cf4": "No tenants found.",
"i18n:govoplan-tenancy.objects.72a83add": "Objects",
"i18n:govoplan-tenancy.no_tenants_found.72d04cf4": "Keine Mandanten gefunden.",
"i18n:govoplan-tenancy.objects.72a83add": "Objekte",
"i18n:govoplan-tenancy.reload.cce71553": "Reload",
"i18n:govoplan-tenancy.save_general_settings.5c90f8c4": "Save general settings",
"i18n:govoplan-tenancy.save_general_settings.5c90f8c4": "Allgemeine Einstellungen speichern",
"i18n:govoplan-tenancy.save_tenant.9eb2ac74": "Save tenant",
"i18n:govoplan-tenancy.saving.56a2285c": "Saving…",
"i18n:govoplan-tenancy.saving.ae7e8875": "Saving...",
"i18n:govoplan-tenancy.settings_for_the_active_tenant_context.ad267b86": "Settings for the active tenant context.",
"i18n:govoplan-tenancy.saving.56a2285c": "Speichern…",
"i18n:govoplan-tenancy.saving.ae7e8875": "Speichern...",
"i18n:govoplan-tenancy.settings_for_the_active_tenant_context.ad267b86": "Einstellungen für den aktiven Mandantenkontext.",
"i18n:govoplan-tenancy.slug.094da9b9": "Slug",
"i18n:govoplan-tenancy.status.bae7d5be": "Status",
"i18n:govoplan-tenancy.suspend_tenant.151d283a": "Suspend tenant",
"i18n:govoplan-tenancy.suspend_value.03a74b32": "Suspend {value0}",
"i18n:govoplan-tenancy.suspend_value_existing_data_remains_retained_but.19bccd78": "Suspend {value0}? Existing data remains retained, but its members cannot use the tenant.",
"i18n:govoplan-tenancy.suspended.794696a7": "Suspended",
"i18n:govoplan-tenancy.system_governance_overrides.97cdf3ce": "System governance overrides",
"i18n:govoplan-tenancy.suspend_tenant.151d283a": "Mandant sperren",
"i18n:govoplan-tenancy.suspend_value.03a74b32": "{value0} sperren",
"i18n:govoplan-tenancy.suspend_value_existing_data_remains_retained_but.19bccd78": "{value0} sperren? Bestehende Daten bleiben erhalten, aber Mitglieder können den Mandanten nicht mehr verwenden.",
"i18n:govoplan-tenancy.suspended.794696a7": "Gesperrt",
"i18n:govoplan-tenancy.system_governance_overrides.97cdf3ce": "Systemvorgaben",
"i18n:govoplan-tenancy.tenancy": "Tenancy",
"i18n:govoplan-tenancy.tenant.3ca93c78": "Tenant",
"i18n:govoplan-tenancy.tenant_api_keys.4b1d81f8": "Tenant API keys",
"i18n:govoplan-tenancy.tenant_details.5976ba72": "Tenant details",
"i18n:govoplan-tenancy.tenant_general_settings.db1c3ba8": "Tenant general settings",
"i18n:govoplan-tenancy.tenant_general_settings_saved.485e7681": "Tenant general settings saved.",
"i18n:govoplan-tenancy.tenant_details.5976ba72": "Mandantendetails",
"i18n:govoplan-tenancy.tenant_general_settings.db1c3ba8": "Allgemeine Mandanteneinstellungen",
"i18n:govoplan-tenancy.tenant_general_settings_saved.485e7681": "Allgemeine Mandanteneinstellungen gespeichert.",
"i18n:govoplan-tenancy.tenant_languages_help": "Tenant languages can only be selected from languages enabled by the system. Users can choose from the tenant-enabled set.",
"i18n:govoplan-tenancy.tenant_locale.8fc19914": "Tenant locale",
"i18n:govoplan-tenancy.tenant_value_created_with_value_as_owner.1c18b6fb": "Tenant {value0} created with {value1} as Owner.",
"i18n:govoplan-tenancy.tenant_value_updated.25b2c855": "Tenant {value0} updated.",
"i18n:govoplan-tenancy.tenant_locale.8fc19914": "Mandantensprache",
"i18n:govoplan-tenancy.tenant_value_created_with_value_as_owner.1c18b6fb": "Mandant {value0} mit {value1} als Eigentümerschaft erstellt.",
"i18n:govoplan-tenancy.tenant_value_updated.25b2c855": "Mandant {value0} aktualisiert.",
"i18n:govoplan-tenancy.tenants.1f7ae776": "Tenants",
"i18n:govoplan-tenancy.the_selected_account.1211bfb9": "the selected account",
"i18n:govoplan-tenancy.the_selected_account.1211bfb9": "das ausgewählte Konto",
"i18n:govoplan-tenancy.updated.f2f8570d": "Updated",
"i18n:govoplan-tenancy.used_as_this_tenant_s_locale_default_for_tenant_.cf298b8b": "Used as this tenant's locale default for tenant-aware views and future formatting defaults.",
"i18n:govoplan-tenancy.used_as_this_tenant_s_locale_default_for_tenant_.cf298b8b": "Wird als Standardsprache dieses Mandanten für mandantenbezogene Ansichten und Formatierungen verwendet.",
"i18n:govoplan-tenancy.users.57f2b181": "Users",
"i18n:govoplan-tenancy.users.81651889": "users,",
"i18n:govoplan-tenancy.value_suspended.31731a28": "{value0} suspended.",
"i18n:govoplan-tenancy.users.81651889": "Benutzer,",
"i18n:govoplan-tenancy.value_suspended.31731a28": "{value0} gesperrt.",
"i18n:govoplan-tenancy.value_value.c189e8bc": "{value0} ({value1})"
},
"de": {
"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.",
"i18n:govoplan-tenancy.tenant_update_permission_is_required.7c9a1004": "Die Berechtigung zum Bearbeiten von Mandanten ist erforderlich.",
"i18n:govoplan-tenancy.tenant_suspension_permission_is_required.7c9a1005": "Die Berechtigung zum Sperren von Mandanten ist erforderlich.",
"i18n:govoplan-tenancy.tenant_settings_write_permission_is_required.7c9a1006": "Die Schreibberechtigung für Mandanteneinstellungen ist erforderlich.",
"i18n:govoplan-tenancy.complete_all_required_tenant_fields_before_saving.7c9a1007": "Füllen Sie vor dem Speichern alle erforderlichen Mandantenfelder aus.",
"i18n:govoplan-tenancy.make_a_change_before_saving.7c9a1008": "Nehmen Sie vor dem Speichern eine Änderung vor.",
"i18n:govoplan-tenancy.switch_to_another_tenant_before_suspending_the_active_tenant.7c9a1009": "Wechseln Sie zu einem anderen Mandanten, bevor Sie den aktiven Mandanten sperren.",
"i18n:govoplan-tenancy.this_tenant_is_already_suspended.7c9a1010": "Dieser Mandant ist bereits gesperrt.",
"i18n:govoplan-tenancy.tenant_administration_is_read_only.7c9a1011": "Die Mandantenverwaltung ist schreibgeschützt.",
"i18n:govoplan-tenancy.required_action.7c9a1012": "Erforderliche Aktion",
"i18n:govoplan-tenancy.responsible_actor.7c9a1013": "Zuständige Stelle",
"i18n:govoplan-tenancy.destination.7c9a1014": "Ziel",
"i18n:govoplan-tenancy.a_system_or_tenant_owner_with_the_required_permission.7c9a1015": "Eine System- oder Mandanteneigentümerschaft mit der erforderlichen Berechtigung",
"i18n:govoplan-tenancy.administration_tenants.7c9a1016": "Administration > Mandanten",
"i18n:govoplan-tenancy.administration_tenant_settings.7c9a1017": "Administration > Mandanteneinstellungen",
"i18n:govoplan-tenancy.request_a_tenant_management_permission_or_contact_a_system_owner.7c9a1018": "Fordern Sie eine Mandantenverwaltungsberechtigung an oder wenden Sie sich an eine Systemeigentümerschaft.",
"i18n:govoplan-tenancy.request_tenant_settings_write_permission_or_contact_a_tenant_owner.7c9a1019": "Fordern Sie die Schreibberechtigung für Mandanteneinstellungen an oder wenden Sie sich an eine Mandanteneigentümerschaft.",
"i18n:govoplan-tenancy.the_default_language_must_remain_enabled.7c9a1020": "Die Standardsprache muss aktiviert bleiben.",
"i18n:govoplan-tenancy.system_policy_prevents_this_tenant_from_enabling_the_capability.7c9a1021": "Die Systemrichtlinie verhindert, dass dieser Mandant die Funktion aktiviert.",
"i18n:govoplan-tenancy.inherit.7c9a1022": "vererbt",
"i18n:govoplan-tenancy.allow.7c9a1023": "erlauben",
"i18n:govoplan-tenancy.deny.7c9a1024": "verweigern",
"i18n:govoplan-tenancy.the_tenant_slug_is_immutable_after_creation.7c9a1025": "Der Mandanten-Slug kann nach der Erstellung nicht geändert werden.",
"i18n:govoplan-tenancy.files_lowercase.7c9a1026": "Dateien",
"i18n:govoplan-tenancy.actions.c3cd636a": "Aktionen",
"i18n:govoplan-tenancy.active.a733b809": "Aktiv",
"i18n:govoplan-tenancy.add_tenant.b8e32af0": "Mandant hinzufügen",
+3 -3
View File
@@ -53,7 +53,7 @@ const adminSections: AdminSectionsUiCapability = {
export const tenancyModule: PlatformWebModule = {
id: "tenancy",
label: "i18n:govoplan-tenancy.tenancy",
version: "1.0.0",
version: "0.1.8",
optionalDependencies: ["access"],
translations: {
en: generatedTranslations.en,
@@ -64,14 +64,14 @@ export const tenancyModule: PlatformWebModule = {
id: "tenancy.admin.system-tenants",
moduleId: "tenancy",
kind: "section",
label: "System tenants",
label: "i18n:govoplan-tenancy.tenants.1f7ae776",
order: 10
},
{
id: "tenancy.admin.tenant-settings",
moduleId: "tenancy",
kind: "section",
label: "Tenant settings",
label: "i18n:govoplan-tenancy.tenant_general_settings.db1c3ba8",
order: 90
}
],