Migrate Organizations interface patterns
This commit is contained in:
@@ -40,6 +40,10 @@ organizations module is active. Those controls are limited to governance and
|
|||||||
policy settings such as tenant model customization, change-request
|
policy settings such as tenant model customization, change-request
|
||||||
requirements, audit detail, and retention behavior.
|
requirements, audit detail, and retention behavior.
|
||||||
|
|
||||||
|
The reviewed surface inventory, consequence classes, availability rules, and
|
||||||
|
accessibility evidence are recorded in
|
||||||
|
[`docs/INTERFACE_PATTERN_MIGRATION.md`](docs/INTERFACE_PATTERN_MIGRATION.md).
|
||||||
|
|
||||||
## Module Contract
|
## Module Contract
|
||||||
|
|
||||||
The module registers two capabilities from
|
The module registers two capabilities from
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Organizations Interface Pattern Migration
|
||||||
|
|
||||||
|
This document records the bounded migration of Organizations-owned WebUI
|
||||||
|
surfaces to the GovOPlaN interface pattern language. Core owns shared controls
|
||||||
|
and host shells. Organizations owns tenant-local model definitions, concrete
|
||||||
|
units, relations, functions, settings, and their mutation consequences.
|
||||||
|
|
||||||
|
## Surface Inventory
|
||||||
|
|
||||||
|
| Surface | Archetype | Consequence class | Contract |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `/organizations` model section | Repeated administration and definition library | Change model vocabulary | Shared DataGrid/cards/dialogs, stable row actions, governed change reference, contextual help |
|
||||||
|
| `/organizations` units section | Hierarchy explorer and directory | Change hierarchy and routing context | Shared ExplorerTree/DataGrid, parent selection, explicit write blockers, guarded drafts |
|
||||||
|
| `/organizations` relations section | Repeated administration | Change structure traversal | Typed source/target editor, lifecycle state, contextual field help |
|
||||||
|
| `/organizations` functions section | Repeated administration | Change institutional responsibility vocabulary | Stable function rows, optional capability actions, explicit Access boundary |
|
||||||
|
| `organizations.admin.tenant` | Effective tenant configuration | Change governance, audit, and retention behavior | Shared admin layout, permission blocker, dirty-state guard, contextual help |
|
||||||
|
| `organizations.functionPicker` | Governed reference selector | Select an active function | Tenant-safe labels, bounded loading/error state, no sibling-private import |
|
||||||
|
|
||||||
|
## Consequence And Availability Rules
|
||||||
|
|
||||||
|
- Organization hierarchies and settings are tenant-owned. A tenant may start
|
||||||
|
from a versioned system template, but it does not inherit one mutable global
|
||||||
|
hierarchy.
|
||||||
|
- Unit types, structures, relation types, and function types define the model;
|
||||||
|
units, relations, and functions are concrete facts within it.
|
||||||
|
- Parent and relation changes can affect downstream routing, Postbox
|
||||||
|
resolution, reporting, and policy context. Slugs are stable integration
|
||||||
|
references.
|
||||||
|
- Deactivation preserves the record and audit evidence while removing it from
|
||||||
|
active selection. The UI does not represent deactivation as deletion.
|
||||||
|
- Delegation and act-in-place flags describe organizational semantics only.
|
||||||
|
Access and governed workflows still decide effective authority.
|
||||||
|
- When tenant governance requires it, model mutations need an approved
|
||||||
|
change-request reference. Dirty dialogs, section changes, and reloads use the
|
||||||
|
shared unsaved-change guard.
|
||||||
|
- Unavailable actions remain visible and identify the missing permission,
|
||||||
|
responsible administrator, and administration destination.
|
||||||
|
|
||||||
|
## State And Accessibility Evidence
|
||||||
|
|
||||||
|
The module uses Core page, subnavigation, grid, tree, card, dialog, loading,
|
||||||
|
alert, status, blocker, field-help, and disabled-action controls. Stable table
|
||||||
|
action slots remain keyboard reachable; dialogs retain shared focus containment
|
||||||
|
and return. Responsive behavior stays owned by the existing workspace and admin
|
||||||
|
shell CSS. Labels and accessible properties use the English/German module
|
||||||
|
catalogue, and API errors are bounded to the current tenant operation.
|
||||||
|
|
||||||
|
The manifest contributes stable help contexts for the workspace, sections,
|
||||||
|
settings, fields, lifecycle changes, and governed change references. Backend
|
||||||
|
and WebUI contract tests prevent those boundaries and explanations from
|
||||||
|
silently regressing.
|
||||||
@@ -8,6 +8,7 @@ from govoplan_core.core.access import (
|
|||||||
)
|
)
|
||||||
from govoplan_core.core.module_guards import persistent_table_uninstall_guard
|
from govoplan_core.core.module_guards import persistent_table_uninstall_guard
|
||||||
from govoplan_core.core.modules import (
|
from govoplan_core.core.modules import (
|
||||||
|
DocumentationLink,
|
||||||
DocumentationTopic,
|
DocumentationTopic,
|
||||||
FrontendModule,
|
FrontendModule,
|
||||||
FrontendRoute,
|
FrontendRoute,
|
||||||
@@ -155,7 +156,7 @@ manifest = ModuleManifest(
|
|||||||
NavItem(
|
NavItem(
|
||||||
path="/organizations",
|
path="/organizations",
|
||||||
label="Organizations",
|
label="Organizations",
|
||||||
icon="users",
|
icon="building-2",
|
||||||
required_any=ORGANIZATIONS_READ_SCOPES,
|
required_any=ORGANIZATIONS_READ_SCOPES,
|
||||||
order=70,
|
order=70,
|
||||||
),
|
),
|
||||||
@@ -175,7 +176,7 @@ manifest = ModuleManifest(
|
|||||||
NavItem(
|
NavItem(
|
||||||
path="/organizations",
|
path="/organizations",
|
||||||
label="Organizations",
|
label="Organizations",
|
||||||
icon="users",
|
icon="building-2",
|
||||||
required_any=ORGANIZATIONS_READ_SCOPES,
|
required_any=ORGANIZATIONS_READ_SCOPES,
|
||||||
order=70,
|
order=70,
|
||||||
),
|
),
|
||||||
@@ -229,8 +230,89 @@ manifest = ModuleManifest(
|
|||||||
layer="configured",
|
layer="configured",
|
||||||
documentation_types=("admin", "user"),
|
documentation_types=("admin", "user"),
|
||||||
audience=("tenant_admin", "access_admin", "operator"),
|
audience=("tenant_admin", "access_admin", "operator"),
|
||||||
|
related_modules=("tenancy", "access", "idm", "policy", "audit"),
|
||||||
|
links=(
|
||||||
|
DocumentationLink(
|
||||||
|
label="Organizations workspace",
|
||||||
|
href="/organizations",
|
||||||
|
kind="runtime",
|
||||||
|
),
|
||||||
|
DocumentationLink(
|
||||||
|
label="Organization model API",
|
||||||
|
href="/api/v1/organizations/model",
|
||||||
|
kind="api",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
metadata={
|
||||||
|
"kind": "guide",
|
||||||
|
"help_contexts": [
|
||||||
|
"organizations.workspace",
|
||||||
|
"organizations.model",
|
||||||
|
"organizations.units",
|
||||||
|
"organizations.relations",
|
||||||
|
"organizations.functions",
|
||||||
|
"organizations.admin.tenant",
|
||||||
|
"organizations.blocked",
|
||||||
|
],
|
||||||
|
},
|
||||||
order=25,
|
order=25,
|
||||||
),
|
),
|
||||||
|
DocumentationTopic(
|
||||||
|
id="organizations.reference.fields-and-consequences",
|
||||||
|
title="Organization model fields and consequences",
|
||||||
|
summary=(
|
||||||
|
"Distinguish tenant-owned model definitions, concrete units, "
|
||||||
|
"relations, functions, governance references, and lifecycle state."
|
||||||
|
),
|
||||||
|
body=(
|
||||||
|
"Unit types, structures, relation types, and function types define "
|
||||||
|
"the tenant-owned model. Units, relations, and functions are concrete "
|
||||||
|
"institutional facts within that model. Slugs are stable references for "
|
||||||
|
"integrations; parent and relation changes affect hierarchy traversal and "
|
||||||
|
"downstream routing. Deactivation retains the record and evidence but "
|
||||||
|
"removes it from active selection. Delegation and act-in-place flags only "
|
||||||
|
"describe permitted organizational semantics; Access and governed workflows "
|
||||||
|
"still decide effective authority. When configured, a recorded change-request "
|
||||||
|
"ID is required before model mutations. Organization settings are tenant-owned "
|
||||||
|
"and do not inherit a live global hierarchy."
|
||||||
|
),
|
||||||
|
layer="configured",
|
||||||
|
documentation_types=("admin",),
|
||||||
|
audience=("tenant_admin", "access_admin", "operator"),
|
||||||
|
related_modules=("tenancy", "access", "idm", "policy", "audit"),
|
||||||
|
links=(
|
||||||
|
DocumentationLink(
|
||||||
|
label="Organizations workspace",
|
||||||
|
href="/organizations",
|
||||||
|
kind="runtime",
|
||||||
|
),
|
||||||
|
DocumentationLink(
|
||||||
|
label="Organization settings API",
|
||||||
|
href="/api/v1/organizations/settings",
|
||||||
|
kind="api",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
metadata={
|
||||||
|
"kind": "reference",
|
||||||
|
"help_contexts": [
|
||||||
|
"organizations.field.name",
|
||||||
|
"organizations.field.slug",
|
||||||
|
"organizations.field.parent",
|
||||||
|
"organizations.field.relation",
|
||||||
|
"organizations.field.function",
|
||||||
|
"organizations.field.change-request",
|
||||||
|
"organizations.field.audit-retention",
|
||||||
|
"organizations.action.deactivate",
|
||||||
|
],
|
||||||
|
"consequence_classes": {
|
||||||
|
"model_change": "Changes the valid vocabulary and constraints for tenant-owned organization facts.",
|
||||||
|
"hierarchy_change": "Changes traversal, routing, and inherited institutional context for downstream modules.",
|
||||||
|
"deactivate": "Retains the fact and evidence while removing it from active selection.",
|
||||||
|
"settings": "Changes tenant-owned governance, audit detail, and retention behavior.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order=26,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
architecture=declared_module_architecture(
|
architecture=declared_module_architecture(
|
||||||
layer="institutional_foundation",
|
layer="institutional_foundation",
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from govoplan_organizations.backend.manifest import manifest
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationsInterfaceDocumentationContractTests(unittest.TestCase):
|
||||||
|
def test_route_and_admin_surface_remain_declared(self) -> None:
|
||||||
|
frontend = manifest.frontend
|
||||||
|
self.assertIsNotNone(frontend)
|
||||||
|
self.assertEqual(
|
||||||
|
{"/organizations"},
|
||||||
|
{route.path for route in frontend.routes}, # type: ignore[union-attr]
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
{"organizations.admin.tenant"},
|
||||||
|
{surface.id for surface in frontend.view_surfaces}, # type: ignore[union-attr]
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
all(item.icon == "building-2" for item in manifest.nav_items)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_topics_publish_stable_help_and_consequence_metadata(self) -> None:
|
||||||
|
topics = {topic.id: topic for topic in manifest.documentation}
|
||||||
|
self.assertIn("organizations.model", topics)
|
||||||
|
self.assertIn("organizations.reference.fields-and-consequences", topics)
|
||||||
|
self.assertIn(
|
||||||
|
"organizations.admin.tenant",
|
||||||
|
topics["organizations.model"].metadata["help_contexts"],
|
||||||
|
)
|
||||||
|
reference = topics["organizations.reference.fields-and-consequences"]
|
||||||
|
self.assertIn(
|
||||||
|
"organizations.field.change-request",
|
||||||
|
reference.metadata["help_contexts"],
|
||||||
|
)
|
||||||
|
self.assertIn("hierarchy_change", reference.metadata["consequence_classes"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+2
-1
@@ -7,7 +7,8 @@
|
|||||||
"module": "src/index.ts",
|
"module": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test:organizations-tree": "node scripts/test-organizations-tree-structure.mjs"
|
"test:organizations-tree": "node scripts/test-organizations-tree-structure.mjs",
|
||||||
|
"test:interface-patterns": "node scripts/test-interface-pattern-language.mjs"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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 page = source("../src/features/organizations/OrganizationsPage.tsx");
|
||||||
|
const settings = source("../src/features/organizations/OrganizationsAdminPanel.tsx");
|
||||||
|
const patterns = source("../src/features/organizations/interfacePatterns.ts");
|
||||||
|
const moduleSource = source("../src/module.ts");
|
||||||
|
const translations = source("../src/i18n/generatedTranslations.ts");
|
||||||
|
|
||||||
|
assert(page.includes("DocumentationHelpLink") && settings.includes("DocumentationHelpLink"), "Workspace and tenant settings expose contextual documentation");
|
||||||
|
assert(page.includes("ActionBlockerHint") && settings.includes("ActionBlockerHint"), "Read-only states identify action, actor, and destination");
|
||||||
|
assert(page.includes("disabledReason") && settings.includes("disabledReason"), "Unavailable organization actions explain their state");
|
||||||
|
assert(page.includes("requestDiscard(() => void loadModel())") && settings.includes("requestDiscard(() => void load())"), "Reload preserves dirty organization drafts and settings");
|
||||||
|
assert(page.includes("hasDirtyDraft ? requestDiscard(discardDrafts)"), "Editor close uses the shared unsaved-change guard");
|
||||||
|
assert(page.includes("ORGANIZATIONS_FIELD_DOCUMENTATION"), "Model fields link to stable consequence documentation");
|
||||||
|
assert(patterns.includes('topicId: "organizations.model"') && patterns.includes('topicId: "organizations.reference.fields-and-consequences"'), "Organizations uses manifest-backed help references");
|
||||||
|
assert(moduleSource.includes('version: "0.1.8"') && moduleSource.includes('label: "i18n:govoplan-organizations.organizations_administration"'), "WebUI metadata matches the module release and localizes its composed surface");
|
||||||
|
assert(translations.includes('"i18n:govoplan-organizations.read_only_summary"'), "Blocker explanations are present in the translation catalogue");
|
||||||
|
assert(!page.includes("window.confirm"), "Organizations does not use browser-native consequential confirmation");
|
||||||
|
|
||||||
|
console.log("Organizations surfaces satisfy the recorded interface pattern-language contract.");
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
|
ActionBlockerHint,
|
||||||
AdminPageLayout,
|
AdminPageLayout,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
|
DocumentationHelpLink,
|
||||||
FormField,
|
FormField,
|
||||||
ToggleSwitch,
|
ToggleSwitch,
|
||||||
adminErrorMessage,
|
adminErrorMessage,
|
||||||
hasAnyScope,
|
hasAnyScope,
|
||||||
|
useUnsavedChanges,
|
||||||
useUnsavedDraftGuard,
|
useUnsavedDraftGuard,
|
||||||
type ApiSettings,
|
type ApiSettings,
|
||||||
type AuthInfo
|
type AuthInfo
|
||||||
@@ -17,6 +20,12 @@ import {
|
|||||||
type OrganizationAuditDetailLevel,
|
type OrganizationAuditDetailLevel,
|
||||||
type OrganizationSettingsItem
|
type OrganizationSettingsItem
|
||||||
} from "../../api/organizations";
|
} from "../../api/organizations";
|
||||||
|
import {
|
||||||
|
ORGANIZATIONS_DOCUMENTATION,
|
||||||
|
ORGANIZATIONS_FIELD_DOCUMENTATION,
|
||||||
|
ORGANIZATIONS_INTERFACE_I18N,
|
||||||
|
organizationWriteReason
|
||||||
|
} from "./interfacePatterns";
|
||||||
|
|
||||||
const FALLBACK_SETTINGS: OrganizationSettingsItem = {
|
const FALLBACK_SETTINGS: OrganizationSettingsItem = {
|
||||||
tenant_id: "",
|
tenant_id: "",
|
||||||
@@ -40,8 +49,16 @@ export default function OrganizationsAdminPanel({ settings, auth }: { settings:
|
|||||||
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 { requestDiscard } = useUnsavedChanges();
|
||||||
const canWrite = hasAnyScope(auth, ["organizations:settings:write", "admin:settings:write"]);
|
const canWrite = hasAnyScope(auth, ["organizations:settings:write", "admin:settings:write"]);
|
||||||
const dirty = settingsKey(draft) !== settingsKey(savedDraft);
|
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({
|
useUnsavedDraftGuard({
|
||||||
dirty,
|
dirty,
|
||||||
@@ -99,20 +116,57 @@ export default function OrganizationsAdminPanel({ settings, auth }: { settings:
|
|||||||
loadingLabel="i18n:govoplan-organizations.loading_organization_settings.c6008db8"
|
loadingLabel="i18n:govoplan-organizations.loading_organization_settings.c6008db8"
|
||||||
error={error}
|
error={error}
|
||||||
success={success}
|
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">
|
<div className="organizations-settings-grid">
|
||||||
<Card title="i18n:govoplan-organizations.model_governance.6aa18fd0">
|
<Card title="i18n:govoplan-organizations.model_governance.6aa18fd0">
|
||||||
<div className="settings-list">
|
<div className="settings-list">
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
checked={draft.allow_tenant_model_customization}
|
checked={draft.allow_tenant_model_customization}
|
||||||
disabled={!canWrite || busy}
|
disabled={!canWrite || busy}
|
||||||
|
help={organizationWriteReason(canWrite, busy, "settings")}
|
||||||
onChange={(checked) => setDraft({ ...draft, allow_tenant_model_customization: checked })}
|
onChange={(checked) => setDraft({ ...draft, allow_tenant_model_customization: checked })}
|
||||||
label="i18n:govoplan-organizations.allow_tenant_model_customization.2425d751"
|
label="i18n:govoplan-organizations.allow_tenant_model_customization.2425d751"
|
||||||
/>
|
/>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
checked={draft.require_model_change_requests}
|
checked={draft.require_model_change_requests}
|
||||||
disabled={!canWrite || busy}
|
disabled={!canWrite || busy}
|
||||||
|
help={organizationWriteReason(canWrite, busy, "settings")}
|
||||||
onChange={(checked) => setDraft({ ...draft, require_model_change_requests: checked })}
|
onChange={(checked) => setDraft({ ...draft, require_model_change_requests: checked })}
|
||||||
label="i18n:govoplan-organizations.require_model_change_requests.83454cad"
|
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">
|
<Card title="i18n:govoplan-organizations.audit_and_retention.3ba1d2fc">
|
||||||
<div className="admin-form-grid two-columns">
|
<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 })}>
|
<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>)}
|
{AUDIT_DETAIL_LEVELS.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</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
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState, type FormEvent } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState, type FormEvent } from "react";
|
||||||
import { Edit3, Plus, RefreshCw } from "lucide-react";
|
import { Edit3, Plus, RefreshCw } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
|
ActionBlockerHint,
|
||||||
AdminIconButton,
|
AdminIconButton,
|
||||||
ApiError,
|
ApiError,
|
||||||
Button,
|
Button,
|
||||||
@@ -8,6 +9,7 @@ import {
|
|||||||
DataGrid,
|
DataGrid,
|
||||||
Dialog,
|
Dialog,
|
||||||
DismissibleAlert,
|
DismissibleAlert,
|
||||||
|
DocumentationHelpLink,
|
||||||
ExplorerTree,
|
ExplorerTree,
|
||||||
FormField,
|
FormField,
|
||||||
IconButton,
|
IconButton,
|
||||||
@@ -20,6 +22,7 @@ import {
|
|||||||
hasScope,
|
hasScope,
|
||||||
isViewSurfaceVisible,
|
isViewSurfaceVisible,
|
||||||
useEffectiveView,
|
useEffectiveView,
|
||||||
|
useUnsavedChanges,
|
||||||
useUnsavedDraftGuard,
|
useUnsavedDraftGuard,
|
||||||
usePlatformUiCapabilities,
|
usePlatformUiCapabilities,
|
||||||
useViewSurfaces,
|
useViewSurfaces,
|
||||||
@@ -62,6 +65,12 @@ import {
|
|||||||
type StructureCreatePayload,
|
type StructureCreatePayload,
|
||||||
type UnitCreatePayload
|
type UnitCreatePayload
|
||||||
} from "../../api/organizations";
|
} from "../../api/organizations";
|
||||||
|
import {
|
||||||
|
ORGANIZATIONS_DOCUMENTATION,
|
||||||
|
ORGANIZATIONS_FIELD_DOCUMENTATION,
|
||||||
|
ORGANIZATIONS_INTERFACE_I18N,
|
||||||
|
organizationWriteReason
|
||||||
|
} from "./interfacePatterns";
|
||||||
|
|
||||||
export type OrganizationSection = "model" | "units" | "relations" | "functions";
|
export type OrganizationSection = "model" | "units" | "relations" | "functions";
|
||||||
type OrganizationsPageMode = "workspace" | "admin";
|
type OrganizationsPageMode = "workspace" | "admin";
|
||||||
@@ -302,22 +311,36 @@ function activeStatus(active: boolean): JSX.Element {
|
|||||||
function SluggedFields({
|
function SluggedFields({
|
||||||
draft,
|
draft,
|
||||||
onChange,
|
onChange,
|
||||||
disabled
|
disabled,
|
||||||
|
disabledReason
|
||||||
}: {
|
}: {
|
||||||
draft: SluggedDraft;
|
draft: SluggedDraft;
|
||||||
onChange: (next: SluggedDraft) => void;
|
onChange: (next: SluggedDraft) => void;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
disabledReason?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormField label="i18n:govoplan-organizations.name.709a2322">
|
<FormField
|
||||||
|
label="i18n:govoplan-organizations.name.709a2322"
|
||||||
|
help={disabledReason}
|
||||||
|
documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}
|
||||||
|
>
|
||||||
<input value={draft.name} disabled={disabled} onChange={(event) => onChange({ ...draft, name: event.target.value })} />
|
<input value={draft.name} disabled={disabled} onChange={(event) => onChange({ ...draft, name: event.target.value })} />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="i18n:govoplan-organizations.slug.094da9b9">
|
<FormField
|
||||||
|
label="i18n:govoplan-organizations.slug.094da9b9"
|
||||||
|
help={disabledReason}
|
||||||
|
documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}
|
||||||
|
>
|
||||||
<input value={draft.slug} disabled={disabled} onChange={(event) => onChange({ ...draft, slug: event.target.value })} />
|
<input value={draft.slug} disabled={disabled} onChange={(event) => onChange({ ...draft, slug: event.target.value })} />
|
||||||
</FormField>
|
</FormField>
|
||||||
<div className="wide">
|
<div className="wide">
|
||||||
<FormField label="i18n:govoplan-organizations.description.55f8ebc8">
|
<FormField
|
||||||
|
label="i18n:govoplan-organizations.description.55f8ebc8"
|
||||||
|
help={disabledReason}
|
||||||
|
documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}
|
||||||
|
>
|
||||||
<textarea rows={3} value={draft.description} disabled={disabled} onChange={(event) => onChange({ ...draft, description: event.target.value })} />
|
<textarea rows={3} value={draft.description} disabled={disabled} onChange={(event) => onChange({ ...draft, description: event.target.value })} />
|
||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</div>
|
||||||
@@ -325,6 +348,7 @@ function SluggedFields({
|
|||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
checked={draft.is_active}
|
checked={draft.is_active}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
help={disabledReason}
|
||||||
onChange={(checked) => onChange({ ...draft, is_active: checked })}
|
onChange={(checked) => onChange({ ...draft, is_active: checked })}
|
||||||
label="i18n:govoplan-organizations.active.a733b809"
|
label="i18n:govoplan-organizations.active.a733b809"
|
||||||
/>
|
/>
|
||||||
@@ -362,6 +386,7 @@ export default function OrganizationsPage({
|
|||||||
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 { requestDiscard } = useUnsavedChanges();
|
||||||
|
|
||||||
const [unitTypeDraft, setUnitTypeDraft] = useState<SluggedDraft>(() => emptySluggedDraft());
|
const [unitTypeDraft, setUnitTypeDraft] = useState<SluggedDraft>(() => emptySluggedDraft());
|
||||||
const [structureDraft, setStructureDraft] = useState<StructureDraft>(() => emptyStructureDraft());
|
const [structureDraft, setStructureDraft] = useState<StructureDraft>(() => emptyStructureDraft());
|
||||||
@@ -871,7 +896,7 @@ export default function OrganizationsPage({
|
|||||||
{ id: "slug", header: "i18n:govoplan-organizations.slug.094da9b9", width: 160, sortable: true, filterable: true, value: (row) => row.slug },
|
{ id: "slug", header: "i18n:govoplan-organizations.slug.094da9b9", width: 160, sortable: true, filterable: true, value: (row) => row.slug },
|
||||||
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
||||||
{ id: "description", header: "i18n:govoplan-organizations.description.55f8ebc8", minWidth: 220, value: (row) => row.description ?? "" },
|
{ id: "description", header: "i18n:govoplan-organizations.description.55f8ebc8", minWidth: 220, value: (row) => row.description ?? "" },
|
||||||
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteModel || busy, onClick: () => editUnitType(row) }]} /> }
|
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteModel || busy, disabledReason: organizationWriteReason(canWriteModel, busy, "model"), onClick: () => editUnitType(row) }]} /> }
|
||||||
];
|
];
|
||||||
|
|
||||||
const structureColumns: DataGridColumn<OrganizationStructureItem>[] = [
|
const structureColumns: DataGridColumn<OrganizationStructureItem>[] = [
|
||||||
@@ -879,7 +904,7 @@ export default function OrganizationsPage({
|
|||||||
{ id: "kind", header: "i18n:govoplan-organizations.kind.794c9d9c", width: 140, sortable: true, filterable: true, value: (row) => row.structure_kind },
|
{ id: "kind", header: "i18n:govoplan-organizations.kind.794c9d9c", width: 140, sortable: true, filterable: true, value: (row) => row.structure_kind },
|
||||||
{ id: "slug", header: "i18n:govoplan-organizations.slug.094da9b9", width: 160, value: (row) => row.slug },
|
{ id: "slug", header: "i18n:govoplan-organizations.slug.094da9b9", width: 160, value: (row) => row.slug },
|
||||||
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
||||||
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteModel || busy, onClick: () => editStructure(row) }]} /> }
|
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteModel || busy, disabledReason: organizationWriteReason(canWriteModel, busy, "model"), onClick: () => editStructure(row) }]} /> }
|
||||||
];
|
];
|
||||||
|
|
||||||
const relationTypeColumns: DataGridColumn<OrganizationRelationTypeItem>[] = [
|
const relationTypeColumns: DataGridColumn<OrganizationRelationTypeItem>[] = [
|
||||||
@@ -889,7 +914,7 @@ export default function OrganizationsPage({
|
|||||||
{ id: "target", header: "i18n:govoplan-organizations.target_unit.a1507d86", minWidth: 170, render: (row) => optionalLabel(unitTypeById.get(row.target_unit_type_id || "")?.name) },
|
{ id: "target", header: "i18n:govoplan-organizations.target_unit.a1507d86", minWidth: 170, render: (row) => optionalLabel(unitTypeById.get(row.target_unit_type_id || "")?.name) },
|
||||||
{ id: "hierarchical", header: "i18n:govoplan-organizations.hierarchical.8964f313", width: 130, render: (row) => activeStatus(row.is_hierarchical) },
|
{ id: "hierarchical", header: "i18n:govoplan-organizations.hierarchical.8964f313", width: 130, render: (row) => activeStatus(row.is_hierarchical) },
|
||||||
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
||||||
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteModel || busy, onClick: () => editRelationType(row) }]} /> }
|
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteModel || busy, disabledReason: organizationWriteReason(canWriteModel, busy, "model"), onClick: () => editRelationType(row) }]} /> }
|
||||||
];
|
];
|
||||||
|
|
||||||
const unitColumns: DataGridColumn<OrganizationUnitItem>[] = [
|
const unitColumns: DataGridColumn<OrganizationUnitItem>[] = [
|
||||||
@@ -898,7 +923,7 @@ export default function OrganizationsPage({
|
|||||||
{ id: "parent", header: "i18n:govoplan-organizations.parent_unit.1986c35e", minWidth: 180, render: (row) => optionalLabel(unitById.get(row.parent_id || "")?.name) },
|
{ id: "parent", header: "i18n:govoplan-organizations.parent_unit.1986c35e", minWidth: 180, render: (row) => optionalLabel(unitById.get(row.parent_id || "")?.name) },
|
||||||
{ id: "slug", header: "i18n:govoplan-organizations.slug.094da9b9", width: 160, value: (row) => row.slug },
|
{ id: "slug", header: "i18n:govoplan-organizations.slug.094da9b9", width: 160, value: (row) => row.slug },
|
||||||
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
||||||
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteUnits || busy, onClick: () => editUnit(row) }]} /> }
|
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteUnits || busy, disabledReason: organizationWriteReason(canWriteUnits, busy, "unit"), onClick: () => editUnit(row) }]} /> }
|
||||||
];
|
];
|
||||||
|
|
||||||
const relationColumns: DataGridColumn<OrganizationRelationItem>[] = [
|
const relationColumns: DataGridColumn<OrganizationRelationItem>[] = [
|
||||||
@@ -907,7 +932,7 @@ export default function OrganizationsPage({
|
|||||||
{ id: "source", header: "i18n:govoplan-organizations.source_unit.2fbd8baa", minWidth: 190, render: (row) => optionalLabel(unitById.get(row.source_unit_id)?.name) },
|
{ id: "source", header: "i18n:govoplan-organizations.source_unit.2fbd8baa", minWidth: 190, render: (row) => optionalLabel(unitById.get(row.source_unit_id)?.name) },
|
||||||
{ id: "target", header: "i18n:govoplan-organizations.target_unit.a1507d86", minWidth: 190, render: (row) => optionalLabel(unitById.get(row.target_unit_id)?.name) },
|
{ id: "target", header: "i18n:govoplan-organizations.target_unit.a1507d86", minWidth: 190, render: (row) => optionalLabel(unitById.get(row.target_unit_id)?.name) },
|
||||||
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
||||||
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteUnits || busy, onClick: () => editRelation(row) }]} /> }
|
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteUnits || busy, disabledReason: organizationWriteReason(canWriteUnits, busy, "unit"), onClick: () => editRelation(row) }]} /> }
|
||||||
];
|
];
|
||||||
|
|
||||||
const functionTypeColumns: DataGridColumn<OrganizationFunctionTypeItem>[] = [
|
const functionTypeColumns: DataGridColumn<OrganizationFunctionTypeItem>[] = [
|
||||||
@@ -916,7 +941,7 @@ export default function OrganizationsPage({
|
|||||||
{ id: "delegable", header: "i18n:govoplan-organizations.delegable.b4f0137d", width: 120, render: (row) => activeStatus(row.delegable) },
|
{ id: "delegable", header: "i18n:govoplan-organizations.delegable.b4f0137d", width: 120, render: (row) => activeStatus(row.delegable) },
|
||||||
{ id: "actInPlace", header: "i18n:govoplan-organizations.act_in_place.49b942bd", width: 140, render: (row) => activeStatus(row.act_in_place_allowed) },
|
{ id: "actInPlace", header: "i18n:govoplan-organizations.act_in_place.49b942bd", width: 140, render: (row) => activeStatus(row.act_in_place_allowed) },
|
||||||
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
{ id: "status", header: "i18n:govoplan-organizations.status.bae7d5be", width: 120, render: (row) => activeStatus(row.is_active) },
|
||||||
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteModel || busy, onClick: () => editFunctionType(row) }]} /> }
|
{ id: "actions", header: "i18n:govoplan-core.actions.c3cd636a", width: 72, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "edit", label: "i18n:govoplan-organizations.edit.7dce1220", icon: <Edit3 size={16} aria-hidden="true" />, disabled: !canWriteModel || busy, disabledReason: organizationWriteReason(canWriteModel, busy, "model"), onClick: () => editFunctionType(row) }]} /> }
|
||||||
];
|
];
|
||||||
|
|
||||||
const functionColumns: DataGridColumn<OrganizationFunctionItem>[] = [
|
const functionColumns: DataGridColumn<OrganizationFunctionItem>[] = [
|
||||||
@@ -938,6 +963,7 @@ export default function OrganizationsPage({
|
|||||||
label: "i18n:govoplan-organizations.edit.7dce1220",
|
label: "i18n:govoplan-organizations.edit.7dce1220",
|
||||||
icon: <Edit3 size={16} aria-hidden="true" />,
|
icon: <Edit3 size={16} aria-hidden="true" />,
|
||||||
disabled: !canWriteFunctions || busy,
|
disabled: !canWriteFunctions || busy,
|
||||||
|
disabledReason: organizationWriteReason(canWriteFunctions, busy, "function"),
|
||||||
onClick: () => editFunction(row)
|
onClick: () => editFunction(row)
|
||||||
},
|
},
|
||||||
...functionActionContributions.map((contribution) => ({
|
...functionActionContributions.map((contribution) => ({
|
||||||
@@ -950,7 +976,26 @@ export default function OrganizationsPage({
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const canWriteActive = active === "model" ? canWriteModel : active === "units" || active === "relations" ? canWriteUnits : canWriteFunctions;
|
const activeWriteKind = active === "model"
|
||||||
|
? "model"
|
||||||
|
: active === "units" || active === "relations"
|
||||||
|
? "unit"
|
||||||
|
: "function";
|
||||||
|
const canWriteActive = activeWriteKind === "model"
|
||||||
|
? canWriteModel
|
||||||
|
: activeWriteKind === "unit"
|
||||||
|
? canWriteUnits
|
||||||
|
: canWriteFunctions;
|
||||||
|
const activeWriteReason = organizationWriteReason(
|
||||||
|
canWriteActive,
|
||||||
|
busy,
|
||||||
|
activeWriteKind
|
||||||
|
);
|
||||||
|
const reloadDisabledReason = loading
|
||||||
|
? ORGANIZATIONS_INTERFACE_I18N.loading
|
||||||
|
: busy
|
||||||
|
? ORGANIZATIONS_INTERFACE_I18N.busy
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<div className={`${mode === "workspace" ? "content-pad " : ""}organizations-page ${mode === "admin" ? "organizations-admin-page" : ""}`.trim()}>
|
<div className={`${mode === "workspace" ? "content-pad " : ""}organizations-page ${mode === "admin" ? "organizations-admin-page" : ""}`.trim()}>
|
||||||
@@ -960,7 +1005,14 @@ export default function OrganizationsPage({
|
|||||||
<p>{description}</p>
|
<p>{description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="organizations-toolbar">
|
<div className="organizations-toolbar">
|
||||||
<Button type="button" onClick={() => void loadModel()} disabled={loading || busy} title="i18n:govoplan-organizations.reload.cce71553">
|
<DocumentationHelpLink reference={ORGANIZATIONS_DOCUMENTATION} />
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => requestDiscard(() => void loadModel())}
|
||||||
|
disabled={Boolean(reloadDisabledReason)}
|
||||||
|
disabledReason={reloadDisabledReason}
|
||||||
|
title="i18n:govoplan-organizations.reload.cce71553"
|
||||||
|
>
|
||||||
<RefreshCw size={16} aria-hidden="true" /> i18n:govoplan-organizations.reload.cce71553
|
<RefreshCw size={16} aria-hidden="true" /> i18n:govoplan-organizations.reload.cce71553
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -968,7 +1020,23 @@ export default function OrganizationsPage({
|
|||||||
|
|
||||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||||
{success && !error && <DismissibleAlert tone="success" resetKey={success} floating>{success}</DismissibleAlert>}
|
{success && !error && <DismissibleAlert tone="success" resetKey={success} floating>{success}</DismissibleAlert>}
|
||||||
{!canWriteActive && <DismissibleAlert tone="warning" dismissible={false}>i18n:govoplan-organizations.write_permission_required.8b09fd67</DismissibleAlert>}
|
{!canWriteActive && (
|
||||||
|
<ActionBlockerHint
|
||||||
|
reason={{
|
||||||
|
summary: ORGANIZATIONS_INTERFACE_I18N.readOnlySummary,
|
||||||
|
details: activeWriteReason,
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<LoadingFrame loading={loading || busy} label="i18n:govoplan-organizations.loading_organization_model.846aa317">
|
<LoadingFrame loading={loading || busy} label="i18n:govoplan-organizations.loading_organization_model.846aa317">
|
||||||
{active === "model" && renderModelSection()}
|
{active === "model" && renderModelSection()}
|
||||||
@@ -984,7 +1052,11 @@ export default function OrganizationsPage({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="workspace organizations-workspace">
|
<div className="workspace organizations-workspace">
|
||||||
<ModuleSubnav active={active} groups={visibleSectionGroups} onSelect={setActive} />
|
<ModuleSubnav
|
||||||
|
active={active}
|
||||||
|
groups={visibleSectionGroups}
|
||||||
|
onSelect={(section) => requestDiscard(() => setActive(section))}
|
||||||
|
/>
|
||||||
<main className="workspace-content">
|
<main className="workspace-content">
|
||||||
{content}
|
{content}
|
||||||
</main>
|
</main>
|
||||||
@@ -994,16 +1066,16 @@ export default function OrganizationsPage({
|
|||||||
function renderModelSection() {
|
function renderModelSection() {
|
||||||
return (
|
return (
|
||||||
<div className="organizations-table-stack">
|
<div className="organizations-table-stack">
|
||||||
<Card title="i18n:govoplan-organizations.unit_types.c7afc174" actions={<AdminIconButton label="i18n:govoplan-organizations.add_unit_type.58f2c05a" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} onClick={openUnitTypeCreate} />}>
|
<Card title="i18n:govoplan-organizations.unit_types.c7afc174" actions={<AdminIconButton label="i18n:govoplan-organizations.add_unit_type.58f2c05a" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openUnitTypeCreate} />}>
|
||||||
<DataGrid id="organizations-unit-types" rows={model.unit_types} columns={unitTypeColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_unit_types_found.c81fb2a7" initialFit="container" />
|
<DataGrid id="organizations-unit-types" rows={model.unit_types} columns={unitTypeColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_unit_types_found.c81fb2a7" initialFit="container" />
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="i18n:govoplan-organizations.structures.f9b7f3b4" actions={<AdminIconButton label="i18n:govoplan-organizations.add_structure.b722042a" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} onClick={openStructureCreate} />}>
|
<Card title="i18n:govoplan-organizations.structures.f9b7f3b4" actions={<AdminIconButton label="i18n:govoplan-organizations.add_structure.b722042a" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openStructureCreate} />}>
|
||||||
<DataGrid id="organizations-structures" rows={model.structures} columns={structureColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_structures_found.20b382d0" initialFit="container" />
|
<DataGrid id="organizations-structures" rows={model.structures} columns={structureColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_structures_found.20b382d0" initialFit="container" />
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="i18n:govoplan-organizations.relation_types.e5890528" actions={<AdminIconButton label="i18n:govoplan-organizations.add_relation_type.2ad19d03" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} onClick={openRelationTypeCreate} />}>
|
<Card title="i18n:govoplan-organizations.relation_types.e5890528" actions={<AdminIconButton label="i18n:govoplan-organizations.add_relation_type.2ad19d03" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openRelationTypeCreate} />}>
|
||||||
<DataGrid id="organizations-relation-types" rows={model.relation_types} columns={relationTypeColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_relation_types_found.6f90bb81" initialFit="container" />
|
<DataGrid id="organizations-relation-types" rows={model.relation_types} columns={relationTypeColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_relation_types_found.6f90bb81" initialFit="container" />
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="i18n:govoplan-organizations.function_types.172c01fe" actions={<AdminIconButton label="i18n:govoplan-organizations.add_function_type.90d793f1" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} onClick={openFunctionTypeCreate} />}>
|
<Card title="i18n:govoplan-organizations.function_types.172c01fe" actions={<AdminIconButton label="i18n:govoplan-organizations.add_function_type.90d793f1" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} onClick={openFunctionTypeCreate} />}>
|
||||||
<DataGrid id="organizations-function-types" rows={model.function_types} columns={functionTypeColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_function_types_found.5eef31b1" initialFit="container" />
|
<DataGrid id="organizations-function-types" rows={model.function_types} columns={functionTypeColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_function_types_found.5eef31b1" initialFit="container" />
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -1013,9 +1085,9 @@ export default function OrganizationsPage({
|
|||||||
function renderUnitsSection() {
|
function renderUnitsSection() {
|
||||||
return (
|
return (
|
||||||
<div className="organizations-table-stack">
|
<div className="organizations-table-stack">
|
||||||
<Card title="i18n:govoplan-organizations.organization_tree.e5bfb195" actions={<AdminIconButton label="i18n:govoplan-organizations.add_root_unit.1ef8a9f5" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteUnits || busy} onClick={() => openUnitCreate()} />}>
|
<Card title="i18n:govoplan-organizations.organization_tree.e5bfb195" actions={<AdminIconButton label="i18n:govoplan-organizations.add_root_unit.1ef8a9f5" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteUnits || busy} disabledReason={organizationWriteReason(canWriteUnits, busy, "unit")} onClick={() => openUnitCreate()} />}>
|
||||||
<div className="explorer-tree-toolbar">
|
<div className="explorer-tree-toolbar">
|
||||||
{selectedUnitId && <Button type="button" variant="ghost" disabled={busy} onClick={() => setSelectedUnitId("")}>i18n:govoplan-organizations.none.334c4a4c</Button>}
|
{selectedUnitId && <Button type="button" variant="ghost" disabled={busy} disabledReason={busy ? ORGANIZATIONS_INTERFACE_I18N.busy : undefined} onClick={() => setSelectedUnitId("")}>i18n:govoplan-organizations.none.334c4a4c</Button>}
|
||||||
</div>
|
</div>
|
||||||
{model.units.length ? (
|
{model.units.length ? (
|
||||||
<ExplorerTree
|
<ExplorerTree
|
||||||
@@ -1041,13 +1113,14 @@ export default function OrganizationsPage({
|
|||||||
icon={<Plus size={16} aria-hidden="true" />}
|
icon={<Plus size={16} aria-hidden="true" />}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
disabled={!canWriteUnits || busy}
|
disabled={!canWriteUnits || busy}
|
||||||
|
disabledReason={organizationWriteReason(canWriteUnits, busy, "unit")}
|
||||||
onClick={() => addSubunit(unit)}
|
onClick={() => addSubunit(unit)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : <p className="muted small-note">i18n:govoplan-organizations.no_units_found.eea2dd0c</p>}
|
) : <p className="muted small-note">i18n:govoplan-organizations.no_units_found.eea2dd0c</p>}
|
||||||
</Card>
|
</Card>
|
||||||
<Card title="i18n:govoplan-organizations.units.e14d0d92" actions={<AdminIconButton label="i18n:govoplan-organizations.add_unit.8fa12fb1" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteUnits || busy} onClick={() => openUnitCreate()} />}>
|
<Card title="i18n:govoplan-organizations.units.e14d0d92" actions={<AdminIconButton label="i18n:govoplan-organizations.add_unit.8fa12fb1" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteUnits || busy} disabledReason={organizationWriteReason(canWriteUnits, busy, "unit")} onClick={() => openUnitCreate()} />}>
|
||||||
<DataGrid id="organizations-units" rows={model.units} columns={unitColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_units_found.eea2dd0c" initialFit="container" />
|
<DataGrid id="organizations-units" rows={model.units} columns={unitColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_units_found.eea2dd0c" initialFit="container" />
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -1057,7 +1130,7 @@ export default function OrganizationsPage({
|
|||||||
function renderRelationsSection() {
|
function renderRelationsSection() {
|
||||||
return (
|
return (
|
||||||
<div className="organizations-table-stack">
|
<div className="organizations-table-stack">
|
||||||
<Card title="i18n:govoplan-organizations.relations.1c796711" actions={<AdminIconButton label="i18n:govoplan-organizations.add_relation.6b6e67ea" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteUnits || busy} onClick={openRelationCreate} />}>
|
<Card title="i18n:govoplan-organizations.relations.1c796711" actions={<AdminIconButton label="i18n:govoplan-organizations.add_relation.6b6e67ea" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteUnits || busy} disabledReason={organizationWriteReason(canWriteUnits, busy, "unit")} onClick={openRelationCreate} />}>
|
||||||
<DataGrid id="organizations-relations" rows={model.relations} columns={relationColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_relations_found.4e75b11d" initialFit="container" />
|
<DataGrid id="organizations-relations" rows={model.relations} columns={relationColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_relations_found.4e75b11d" initialFit="container" />
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -1067,7 +1140,7 @@ export default function OrganizationsPage({
|
|||||||
function renderFunctionsSection() {
|
function renderFunctionsSection() {
|
||||||
return (
|
return (
|
||||||
<div className="organizations-table-stack">
|
<div className="organizations-table-stack">
|
||||||
<Card title="i18n:govoplan-organizations.functions.805dc49b" actions={<AdminIconButton label="i18n:govoplan-organizations.add_function.6abafee0" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteFunctions || busy} onClick={() => openFunctionCreate()} />}>
|
<Card title="i18n:govoplan-organizations.functions.805dc49b" actions={<AdminIconButton label="i18n:govoplan-organizations.add_function.6abafee0" icon={<Plus size={16} aria-hidden="true" />} variant="primary" disabled={!canWriteFunctions || busy} disabledReason={organizationWriteReason(canWriteFunctions, busy, "function")} onClick={() => openFunctionCreate()} />}>
|
||||||
<DataGrid id="organizations-functions" rows={model.functions} columns={functionColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_functions_found.54e759a0" initialFit="container" />
|
<DataGrid id="organizations-functions" rows={model.functions} columns={functionColumns} getRowKey={(row) => row.id} emptyText="i18n:govoplan-organizations.no_functions_found.54e759a0" initialFit="container" />
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -1077,7 +1150,11 @@ export default function OrganizationsPage({
|
|||||||
function renderChangeRequestField() {
|
function renderChangeRequestField() {
|
||||||
return (
|
return (
|
||||||
<div className="wide organizations-dialog-change-request">
|
<div className="wide organizations-dialog-change-request">
|
||||||
<FormField label="i18n:govoplan-organizations.change_request_id.a6e7fd6b">
|
<FormField
|
||||||
|
label="i18n:govoplan-organizations.change_request_id.a6e7fd6b"
|
||||||
|
help={busy ? ORGANIZATIONS_INTERFACE_I18N.busy : undefined}
|
||||||
|
documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}
|
||||||
|
>
|
||||||
<input value={changeRequestId} onChange={(event) => setChangeRequestId(event.target.value)} placeholder="cfgreq-..." disabled={busy} />
|
<input value={changeRequestId} onChange={(event) => setChangeRequestId(event.target.value)} placeholder="cfgreq-..." disabled={busy} />
|
||||||
</FormField>
|
</FormField>
|
||||||
<p className="organizations-field-note">i18n:govoplan-organizations.change_request_id_help.0c119a5d</p>
|
<p className="organizations-field-note">i18n:govoplan-organizations.change_request_id_help.0c119a5d</p>
|
||||||
@@ -1099,18 +1176,32 @@ export default function OrganizationsPage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function editorSubmitDisabled(): boolean {
|
function editorSubmitDisabled(): boolean {
|
||||||
if (busy || !activeEditor) return true;
|
return Boolean(editorSubmitDisabledReason());
|
||||||
if ((activeEditor === "unitType" || activeEditor === "structure" || activeEditor === "relationType" || activeEditor === "functionType") && !canWriteModel) return true;
|
}
|
||||||
if ((activeEditor === "unit" || activeEditor === "relation") && !canWriteUnits) return true;
|
|
||||||
if (activeEditor === "function" && !canWriteFunctions) return true;
|
function editorSubmitDisabledReason(): string | undefined {
|
||||||
if (activeEditor === "unitType") return !unitTypeDraft.name.trim();
|
if (busy) return ORGANIZATIONS_INTERFACE_I18N.busy;
|
||||||
if (activeEditor === "structure") return !structureDraft.name.trim();
|
if (!activeEditor) return ORGANIZATIONS_INTERFACE_I18N.incomplete;
|
||||||
if (activeEditor === "relationType") return !relationTypeDraft.name.trim();
|
if (activeEditor === "unitType" || activeEditor === "structure" || activeEditor === "relationType" || activeEditor === "functionType") {
|
||||||
if (activeEditor === "unit") return !unitDraft.name.trim();
|
const reason = organizationWriteReason(canWriteModel, busy, "model");
|
||||||
if (activeEditor === "relation") return !relationDraft.structure_id || !relationDraft.relation_type_id || !relationDraft.source_unit_id || !relationDraft.target_unit_id;
|
if (reason) return reason;
|
||||||
if (activeEditor === "functionType") return !functionTypeDraft.name.trim();
|
}
|
||||||
if (activeEditor === "function") return !functionDraft.name.trim() || !functionDraft.organization_unit_id;
|
if (activeEditor === "unit" || activeEditor === "relation") {
|
||||||
return true;
|
const reason = organizationWriteReason(canWriteUnits, busy, "unit");
|
||||||
|
if (reason) return reason;
|
||||||
|
}
|
||||||
|
if (activeEditor === "function") {
|
||||||
|
const reason = organizationWriteReason(canWriteFunctions, busy, "function");
|
||||||
|
if (reason) return reason;
|
||||||
|
}
|
||||||
|
if (activeEditor === "unitType" && !unitTypeDraft.name.trim()) return ORGANIZATIONS_INTERFACE_I18N.incomplete;
|
||||||
|
if (activeEditor === "structure" && !structureDraft.name.trim()) return ORGANIZATIONS_INTERFACE_I18N.incomplete;
|
||||||
|
if (activeEditor === "relationType" && !relationTypeDraft.name.trim()) return ORGANIZATIONS_INTERFACE_I18N.incomplete;
|
||||||
|
if (activeEditor === "unit" && !unitDraft.name.trim()) return ORGANIZATIONS_INTERFACE_I18N.incomplete;
|
||||||
|
if (activeEditor === "relation" && (!relationDraft.structure_id || !relationDraft.relation_type_id || !relationDraft.source_unit_id || !relationDraft.target_unit_id)) return ORGANIZATIONS_INTERFACE_I18N.incomplete;
|
||||||
|
if (activeEditor === "functionType" && !functionTypeDraft.name.trim()) return ORGANIZATIONS_INTERFACE_I18N.incomplete;
|
||||||
|
if (activeEditor === "function" && (!functionDraft.name.trim() || !functionDraft.organization_unit_id)) return ORGANIZATIONS_INTERFACE_I18N.incomplete;
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderEditorDialog() {
|
function renderEditorDialog() {
|
||||||
@@ -1127,16 +1218,38 @@ export default function OrganizationsPage({
|
|||||||
<Dialog
|
<Dialog
|
||||||
open={Boolean(activeEditor)}
|
open={Boolean(activeEditor)}
|
||||||
title={editorTitle()}
|
title={editorTitle()}
|
||||||
onClose={() => !busy && discardDrafts()}
|
onClose={() => {
|
||||||
|
if (busy) return;
|
||||||
|
if (hasDirtyDraft) requestDiscard(discardDrafts);
|
||||||
|
else discardDrafts();
|
||||||
|
}}
|
||||||
closeDisabled={busy}
|
closeDisabled={busy}
|
||||||
className="admin-dialog admin-dialog-wide organizations-editor-dialog"
|
className="admin-dialog admin-dialog-wide organizations-editor-dialog"
|
||||||
footer={(
|
footer={(
|
||||||
<>
|
<>
|
||||||
<Button type="button" onClick={discardDrafts} disabled={busy}>i18n:govoplan-organizations.cancel_edit.309c2a6f</Button>
|
<Button
|
||||||
<Button type="submit" form={formId} variant="primary" disabled={editorSubmitDisabled()}>{editorTitle()}</Button>
|
type="button"
|
||||||
|
onClick={() => hasDirtyDraft ? requestDiscard(discardDrafts) : discardDrafts()}
|
||||||
|
disabled={busy}
|
||||||
|
disabledReason={busy ? ORGANIZATIONS_INTERFACE_I18N.busy : undefined}
|
||||||
|
>
|
||||||
|
i18n:govoplan-organizations.cancel_edit.309c2a6f
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
form={formId}
|
||||||
|
variant="primary"
|
||||||
|
disabled={editorSubmitDisabled()}
|
||||||
|
disabledReason={editorSubmitDisabledReason()}
|
||||||
|
>
|
||||||
|
{editorTitle()}
|
||||||
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<div className="button-row organizations-dialog-help">
|
||||||
|
<DocumentationHelpLink reference={ORGANIZATIONS_FIELD_DOCUMENTATION} />
|
||||||
|
</div>
|
||||||
<form id={formId} className="admin-form-grid two-columns" onSubmit={(event) => void submit(event)}>
|
<form id={formId} className="admin-form-grid two-columns" onSubmit={(event) => void submit(event)}>
|
||||||
{renderEditorFields()}
|
{renderEditorFields()}
|
||||||
</form>
|
</form>
|
||||||
@@ -1148,7 +1261,7 @@ export default function OrganizationsPage({
|
|||||||
if (activeEditor === "unitType") {
|
if (activeEditor === "unitType") {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SluggedFields draft={unitTypeDraft} onChange={setUnitTypeDraft} disabled={!canWriteModel || busy} />
|
<SluggedFields draft={unitTypeDraft} onChange={setUnitTypeDraft} disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} />
|
||||||
{renderChangeRequestField()}
|
{renderChangeRequestField()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -1156,8 +1269,8 @@ export default function OrganizationsPage({
|
|||||||
if (activeEditor === "structure") {
|
if (activeEditor === "structure") {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SluggedFields draft={structureDraft} onChange={setStructureDraft} disabled={!canWriteModel || busy} />
|
<SluggedFields draft={structureDraft} onChange={setStructureDraft} disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} />
|
||||||
<FormField label="i18n:govoplan-organizations.kind.794c9d9c">
|
<FormField label="i18n:govoplan-organizations.kind.794c9d9c" help={organizationWriteReason(canWriteModel, busy, "model")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={structureDraft.structure_kind} disabled={!canWriteModel || busy} onChange={(event) => setStructureDraft({ ...structureDraft, structure_kind: event.target.value as OrganizationStructureKind })}>
|
<select value={structureDraft.structure_kind} disabled={!canWriteModel || busy} onChange={(event) => setStructureDraft({ ...structureDraft, structure_kind: event.target.value as OrganizationStructureKind })}>
|
||||||
{STRUCTURE_KINDS.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
|
{STRUCTURE_KINDS.map((item) => <option key={item.value} value={item.value}>{item.label}</option>)}
|
||||||
</select>
|
</select>
|
||||||
@@ -1169,28 +1282,28 @@ export default function OrganizationsPage({
|
|||||||
if (activeEditor === "relationType") {
|
if (activeEditor === "relationType") {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SluggedFields draft={relationTypeDraft} onChange={setRelationTypeDraft} disabled={!canWriteModel || busy} />
|
<SluggedFields draft={relationTypeDraft} onChange={setRelationTypeDraft} disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} />
|
||||||
<FormField label="i18n:govoplan-organizations.structure.7732fb0b">
|
<FormField label="i18n:govoplan-organizations.structure.7732fb0b" help={organizationWriteReason(canWriteModel, busy, "model")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={relationTypeDraft.structure_id} disabled={!canWriteModel || busy} onChange={(event) => setRelationTypeDraft({ ...relationTypeDraft, structure_id: event.target.value })}>
|
<select value={relationTypeDraft.structure_id} disabled={!canWriteModel || busy} onChange={(event) => setRelationTypeDraft({ ...relationTypeDraft, structure_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
||||||
{model.structures.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.structures.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="i18n:govoplan-organizations.source_unit.2fbd8baa">
|
<FormField label="i18n:govoplan-organizations.source_unit.2fbd8baa" help={organizationWriteReason(canWriteModel, busy, "model")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={relationTypeDraft.source_unit_type_id} disabled={!canWriteModel || busy} onChange={(event) => setRelationTypeDraft({ ...relationTypeDraft, source_unit_type_id: event.target.value })}>
|
<select value={relationTypeDraft.source_unit_type_id} disabled={!canWriteModel || busy} onChange={(event) => setRelationTypeDraft({ ...relationTypeDraft, source_unit_type_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
||||||
{model.unit_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.unit_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="i18n:govoplan-organizations.target_unit.a1507d86">
|
<FormField label="i18n:govoplan-organizations.target_unit.a1507d86" help={organizationWriteReason(canWriteModel, busy, "model")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={relationTypeDraft.target_unit_type_id} disabled={!canWriteModel || busy} onChange={(event) => setRelationTypeDraft({ ...relationTypeDraft, target_unit_type_id: event.target.value })}>
|
<select value={relationTypeDraft.target_unit_type_id} disabled={!canWriteModel || busy} onChange={(event) => setRelationTypeDraft({ ...relationTypeDraft, target_unit_type_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
||||||
{model.unit_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.unit_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<div className="organizations-check-list">
|
<div className="organizations-check-list">
|
||||||
<ToggleSwitch checked={relationTypeDraft.is_hierarchical} disabled={!canWriteModel || busy} onChange={(checked) => setRelationTypeDraft({ ...relationTypeDraft, is_hierarchical: checked })} label="i18n:govoplan-organizations.hierarchical.8964f313" />
|
<ToggleSwitch checked={relationTypeDraft.is_hierarchical} disabled={!canWriteModel || busy} help={organizationWriteReason(canWriteModel, busy, "model")} onChange={(checked) => setRelationTypeDraft({ ...relationTypeDraft, is_hierarchical: checked })} label="i18n:govoplan-organizations.hierarchical.8964f313" />
|
||||||
<ToggleSwitch checked={relationTypeDraft.allow_cycles} disabled={!canWriteModel || busy} onChange={(checked) => setRelationTypeDraft({ ...relationTypeDraft, allow_cycles: checked })} label="i18n:govoplan-organizations.allow_cycles.31327578" />
|
<ToggleSwitch checked={relationTypeDraft.allow_cycles} disabled={!canWriteModel || busy} help={organizationWriteReason(canWriteModel, busy, "model")} onChange={(checked) => setRelationTypeDraft({ ...relationTypeDraft, allow_cycles: checked })} label="i18n:govoplan-organizations.allow_cycles.31327578" />
|
||||||
</div>
|
</div>
|
||||||
{renderChangeRequestField()}
|
{renderChangeRequestField()}
|
||||||
</>
|
</>
|
||||||
@@ -1199,14 +1312,14 @@ export default function OrganizationsPage({
|
|||||||
if (activeEditor === "unit") {
|
if (activeEditor === "unit") {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SluggedFields draft={unitDraft} onChange={setUnitDraft} disabled={!canWriteUnits || busy} />
|
<SluggedFields draft={unitDraft} onChange={setUnitDraft} disabled={!canWriteUnits || busy} disabledReason={organizationWriteReason(canWriteUnits, busy, "unit")} />
|
||||||
<FormField label="i18n:govoplan-organizations.unit_type.9e62810d">
|
<FormField label="i18n:govoplan-organizations.unit_type.9e62810d" help={organizationWriteReason(canWriteUnits, busy, "unit")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={unitDraft.unit_type_id} disabled={!canWriteUnits || busy} onChange={(event) => setUnitDraft({ ...unitDraft, unit_type_id: event.target.value })}>
|
<select value={unitDraft.unit_type_id} disabled={!canWriteUnits || busy} onChange={(event) => setUnitDraft({ ...unitDraft, unit_type_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
||||||
{model.unit_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.unit_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="i18n:govoplan-organizations.parent_unit.1986c35e">
|
<FormField label="i18n:govoplan-organizations.parent_unit.1986c35e" help={organizationWriteReason(canWriteUnits, busy, "unit")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={unitDraft.parent_id} disabled={!canWriteUnits || busy} onChange={(event) => setUnitDraft({ ...unitDraft, parent_id: event.target.value })}>
|
<select value={unitDraft.parent_id} disabled={!canWriteUnits || busy} onChange={(event) => setUnitDraft({ ...unitDraft, parent_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
||||||
{model.units.filter((item) => item.id !== editingUnitId).map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.units.filter((item) => item.id !== editingUnitId).map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
@@ -1224,25 +1337,25 @@ export default function OrganizationsPage({
|
|||||||
if (activeEditor === "relation") {
|
if (activeEditor === "relation") {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormField label="i18n:govoplan-organizations.structure.7732fb0b">
|
<FormField label="i18n:govoplan-organizations.structure.7732fb0b" help={organizationWriteReason(canWriteUnits, busy, "unit")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={relationDraft.structure_id} disabled={!canWriteUnits || busy} onChange={(event) => setRelationDraft({ ...relationDraft, structure_id: event.target.value })}>
|
<select value={relationDraft.structure_id} disabled={!canWriteUnits || busy} onChange={(event) => setRelationDraft({ ...relationDraft, structure_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.select_structure.c10f551c</option>
|
<option value="">i18n:govoplan-organizations.select_structure.c10f551c</option>
|
||||||
{model.structures.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.structures.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="i18n:govoplan-organizations.relation_type.d0aee2e7">
|
<FormField label="i18n:govoplan-organizations.relation_type.d0aee2e7" help={organizationWriteReason(canWriteUnits, busy, "unit")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={relationDraft.relation_type_id} disabled={!canWriteUnits || busy} onChange={(event) => setRelationDraft({ ...relationDraft, relation_type_id: event.target.value })}>
|
<select value={relationDraft.relation_type_id} disabled={!canWriteUnits || busy} onChange={(event) => setRelationDraft({ ...relationDraft, relation_type_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.select_relation_type.73f92478</option>
|
<option value="">i18n:govoplan-organizations.select_relation_type.73f92478</option>
|
||||||
{model.relation_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.relation_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="i18n:govoplan-organizations.source_unit.2fbd8baa">
|
<FormField label="i18n:govoplan-organizations.source_unit.2fbd8baa" help={organizationWriteReason(canWriteUnits, busy, "unit")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={relationDraft.source_unit_id} disabled={!canWriteUnits || busy} onChange={(event) => setRelationDraft({ ...relationDraft, source_unit_id: event.target.value })}>
|
<select value={relationDraft.source_unit_id} disabled={!canWriteUnits || busy} onChange={(event) => setRelationDraft({ ...relationDraft, source_unit_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.select_source_unit.9b5a29c8</option>
|
<option value="">i18n:govoplan-organizations.select_source_unit.9b5a29c8</option>
|
||||||
{model.units.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.units.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="i18n:govoplan-organizations.target_unit.a1507d86">
|
<FormField label="i18n:govoplan-organizations.target_unit.a1507d86" help={organizationWriteReason(canWriteUnits, busy, "unit")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={relationDraft.target_unit_id} disabled={!canWriteUnits || busy} onChange={(event) => setRelationDraft({ ...relationDraft, target_unit_id: event.target.value })}>
|
<select value={relationDraft.target_unit_id} disabled={!canWriteUnits || busy} onChange={(event) => setRelationDraft({ ...relationDraft, target_unit_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.select_target_unit.8d607541</option>
|
<option value="">i18n:govoplan-organizations.select_target_unit.8d607541</option>
|
||||||
{model.units.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.units.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
@@ -1252,6 +1365,7 @@ export default function OrganizationsPage({
|
|||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
checked={relationDraft.is_active}
|
checked={relationDraft.is_active}
|
||||||
disabled={!canWriteUnits || busy}
|
disabled={!canWriteUnits || busy}
|
||||||
|
help={organizationWriteReason(canWriteUnits, busy, "unit")}
|
||||||
onChange={(checked) => setRelationDraft({ ...relationDraft, is_active: checked })}
|
onChange={(checked) => setRelationDraft({ ...relationDraft, is_active: checked })}
|
||||||
label="i18n:govoplan-organizations.active.a733b809"
|
label="i18n:govoplan-organizations.active.a733b809"
|
||||||
/>
|
/>
|
||||||
@@ -1263,16 +1377,16 @@ export default function OrganizationsPage({
|
|||||||
if (activeEditor === "functionType") {
|
if (activeEditor === "functionType") {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SluggedFields draft={functionTypeDraft} onChange={setFunctionTypeDraft} disabled={!canWriteModel || busy} />
|
<SluggedFields draft={functionTypeDraft} onChange={setFunctionTypeDraft} disabled={!canWriteModel || busy} disabledReason={organizationWriteReason(canWriteModel, busy, "model")} />
|
||||||
<FormField label="i18n:govoplan-organizations.unit_type.9e62810d">
|
<FormField label="i18n:govoplan-organizations.unit_type.9e62810d" help={organizationWriteReason(canWriteModel, busy, "model")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={functionTypeDraft.organization_unit_type_id} disabled={!canWriteModel || busy} onChange={(event) => setFunctionTypeDraft({ ...functionTypeDraft, organization_unit_type_id: event.target.value })}>
|
<select value={functionTypeDraft.organization_unit_type_id} disabled={!canWriteModel || busy} onChange={(event) => setFunctionTypeDraft({ ...functionTypeDraft, organization_unit_type_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
||||||
{model.unit_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.unit_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<div className="organizations-check-list">
|
<div className="organizations-check-list">
|
||||||
<ToggleSwitch checked={functionTypeDraft.delegable} disabled={!canWriteModel || busy} onChange={(checked) => setFunctionTypeDraft({ ...functionTypeDraft, delegable: checked })} label="i18n:govoplan-organizations.delegable.b4f0137d" />
|
<ToggleSwitch checked={functionTypeDraft.delegable} disabled={!canWriteModel || busy} help={organizationWriteReason(canWriteModel, busy, "model")} onChange={(checked) => setFunctionTypeDraft({ ...functionTypeDraft, delegable: checked })} label="i18n:govoplan-organizations.delegable.b4f0137d" />
|
||||||
<ToggleSwitch checked={functionTypeDraft.act_in_place_allowed} disabled={!canWriteModel || busy} onChange={(checked) => setFunctionTypeDraft({ ...functionTypeDraft, act_in_place_allowed: checked })} label="i18n:govoplan-organizations.act_in_place.49b942bd" />
|
<ToggleSwitch checked={functionTypeDraft.act_in_place_allowed} disabled={!canWriteModel || busy} help={organizationWriteReason(canWriteModel, busy, "model")} onChange={(checked) => setFunctionTypeDraft({ ...functionTypeDraft, act_in_place_allowed: checked })} label="i18n:govoplan-organizations.act_in_place.49b942bd" />
|
||||||
</div>
|
</div>
|
||||||
{renderChangeRequestField()}
|
{renderChangeRequestField()}
|
||||||
</>
|
</>
|
||||||
@@ -1280,22 +1394,22 @@ export default function OrganizationsPage({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SluggedFields draft={functionDraft} onChange={setFunctionDraft} disabled={!canWriteFunctions || busy} />
|
<SluggedFields draft={functionDraft} onChange={setFunctionDraft} disabled={!canWriteFunctions || busy} disabledReason={organizationWriteReason(canWriteFunctions, busy, "function")} />
|
||||||
<FormField label="i18n:govoplan-organizations.unit.8fe4d595">
|
<FormField label="i18n:govoplan-organizations.unit.8fe4d595" help={organizationWriteReason(canWriteFunctions, busy, "function")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={functionDraft.organization_unit_id} disabled={!canWriteFunctions || busy} onChange={(event) => setFunctionDraft({ ...functionDraft, organization_unit_id: event.target.value })}>
|
<select value={functionDraft.organization_unit_id} disabled={!canWriteFunctions || busy} onChange={(event) => setFunctionDraft({ ...functionDraft, organization_unit_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.select_unit.013bf13a</option>
|
<option value="">i18n:govoplan-organizations.select_unit.013bf13a</option>
|
||||||
{model.units.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.units.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField label="i18n:govoplan-organizations.function_type.501fe7c0">
|
<FormField label="i18n:govoplan-organizations.function_type.501fe7c0" help={organizationWriteReason(canWriteFunctions, busy, "function")} documentation={ORGANIZATIONS_FIELD_DOCUMENTATION}>
|
||||||
<select value={functionDraft.function_type_id} disabled={!canWriteFunctions || busy} onChange={(event) => setFunctionDraft({ ...functionDraft, function_type_id: event.target.value })}>
|
<select value={functionDraft.function_type_id} disabled={!canWriteFunctions || busy} onChange={(event) => setFunctionDraft({ ...functionDraft, function_type_id: event.target.value })}>
|
||||||
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
<option value="">i18n:govoplan-organizations.none.334c4a4c</option>
|
||||||
{model.function_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
{model.function_types.map((item) => <option key={item.id} value={item.id}>{item.name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</FormField>
|
</FormField>
|
||||||
<div className="organizations-check-list">
|
<div className="organizations-check-list">
|
||||||
<ToggleSwitch checked={functionDraft.delegable} disabled={!canWriteFunctions || busy} onChange={(checked) => setFunctionDraft({ ...functionDraft, delegable: checked })} label="i18n:govoplan-organizations.delegable.b4f0137d" />
|
<ToggleSwitch checked={functionDraft.delegable} disabled={!canWriteFunctions || busy} help={organizationWriteReason(canWriteFunctions, busy, "function")} onChange={(checked) => setFunctionDraft({ ...functionDraft, delegable: checked })} label="i18n:govoplan-organizations.delegable.b4f0137d" />
|
||||||
<ToggleSwitch checked={functionDraft.act_in_place_allowed} disabled={!canWriteFunctions || busy} onChange={(checked) => setFunctionDraft({ ...functionDraft, act_in_place_allowed: checked })} label="i18n:govoplan-organizations.act_in_place.49b942bd" />
|
<ToggleSwitch checked={functionDraft.act_in_place_allowed} disabled={!canWriteFunctions || busy} help={organizationWriteReason(canWriteFunctions, busy, "function")} onChange={(checked) => setFunctionDraft({ ...functionDraft, act_in_place_allowed: checked })} label="i18n:govoplan-organizations.act_in_place.49b942bd" />
|
||||||
</div>
|
</div>
|
||||||
{renderChangeRequestField()}
|
{renderChangeRequestField()}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import type { DocumentationHelpReference } from "@govoplan/core-webui";
|
||||||
|
|
||||||
|
export const ORGANIZATIONS_DOCUMENTATION = {
|
||||||
|
topicId: "organizations.model",
|
||||||
|
documentationType: "admin"
|
||||||
|
} satisfies DocumentationHelpReference;
|
||||||
|
|
||||||
|
export const ORGANIZATIONS_FIELD_DOCUMENTATION = {
|
||||||
|
topicId: "organizations.reference.fields-and-consequences",
|
||||||
|
documentationType: "admin"
|
||||||
|
} satisfies DocumentationHelpReference;
|
||||||
|
|
||||||
|
export const ORGANIZATIONS_INTERFACE_I18N = {
|
||||||
|
loading: "i18n:govoplan-organizations.loading_reason",
|
||||||
|
busy: "i18n:govoplan-organizations.busy_reason",
|
||||||
|
noChanges: "i18n:govoplan-organizations.no_changes_reason",
|
||||||
|
modelWrite: "i18n:govoplan-organizations.model_write_reason",
|
||||||
|
unitWrite: "i18n:govoplan-organizations.unit_write_reason",
|
||||||
|
functionWrite: "i18n:govoplan-organizations.function_write_reason",
|
||||||
|
settingsWrite: "i18n:govoplan-organizations.settings_write_reason",
|
||||||
|
incomplete: "i18n:govoplan-organizations.incomplete_editor_reason",
|
||||||
|
readOnlySummary: "i18n:govoplan-organizations.read_only_summary",
|
||||||
|
requiredAction: "i18n:govoplan-organizations.required_action",
|
||||||
|
actor: "i18n:govoplan-organizations.responsible_actor",
|
||||||
|
destination: "i18n:govoplan-organizations.destination",
|
||||||
|
permissionGuidance: "i18n:govoplan-organizations.request_write_permission",
|
||||||
|
administrator: "i18n:govoplan-organizations.organization_or_access_administrator",
|
||||||
|
administrationTarget: "i18n:govoplan-organizations.administration_roles_target"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export function organizationWriteReason(
|
||||||
|
permitted: boolean,
|
||||||
|
busy: boolean,
|
||||||
|
kind: "model" | "unit" | "function" | "settings"
|
||||||
|
): string | undefined {
|
||||||
|
if (busy) return ORGANIZATIONS_INTERFACE_I18N.busy;
|
||||||
|
if (permitted) return undefined;
|
||||||
|
return {
|
||||||
|
model: ORGANIZATIONS_INTERFACE_I18N.modelWrite,
|
||||||
|
unit: ORGANIZATIONS_INTERFACE_I18N.unitWrite,
|
||||||
|
function: ORGANIZATIONS_INTERFACE_I18N.functionWrite,
|
||||||
|
settings: ORGANIZATIONS_INTERFACE_I18N.settingsWrite
|
||||||
|
}[kind];
|
||||||
|
}
|
||||||
@@ -122,7 +122,23 @@ export const generatedTranslations: PlatformTranslations = {
|
|||||||
"i18n:govoplan-organizations.update_structure.b2e25446": "Update structure",
|
"i18n:govoplan-organizations.update_structure.b2e25446": "Update structure",
|
||||||
"i18n:govoplan-organizations.update_unit.67e2500f": "Update unit",
|
"i18n:govoplan-organizations.update_unit.67e2500f": "Update unit",
|
||||||
"i18n:govoplan-organizations.update_unit_type.b96fc0ad": "Update unit type",
|
"i18n:govoplan-organizations.update_unit_type.b96fc0ad": "Update unit type",
|
||||||
"i18n:govoplan-organizations.write_permission_required.8b09fd67": "You do not have write permission for this organization section."
|
"i18n:govoplan-organizations.write_permission_required.8b09fd67": "You do not have write permission for this organization section.",
|
||||||
|
"i18n:govoplan-organizations.organizations_administration": "Organizations administration",
|
||||||
|
"i18n:govoplan-organizations.loading_reason": "Organization data is loading.",
|
||||||
|
"i18n:govoplan-organizations.busy_reason": "An organization change is in progress.",
|
||||||
|
"i18n:govoplan-organizations.no_changes_reason": "Make a change before saving.",
|
||||||
|
"i18n:govoplan-organizations.model_write_reason": "Organization model write permission is required.",
|
||||||
|
"i18n:govoplan-organizations.unit_write_reason": "Organization unit write permission is required.",
|
||||||
|
"i18n:govoplan-organizations.function_write_reason": "Organization function write permission is required.",
|
||||||
|
"i18n:govoplan-organizations.settings_write_reason": "Organization settings write permission is required.",
|
||||||
|
"i18n:govoplan-organizations.incomplete_editor_reason": "Complete the required fields before saving.",
|
||||||
|
"i18n:govoplan-organizations.read_only_summary": "This organization section is read-only.",
|
||||||
|
"i18n:govoplan-organizations.required_action": "Required action",
|
||||||
|
"i18n:govoplan-organizations.responsible_actor": "Responsible actor",
|
||||||
|
"i18n:govoplan-organizations.destination": "Destination",
|
||||||
|
"i18n:govoplan-organizations.request_write_permission": "Request the matching organization model, unit, function, or settings write permission.",
|
||||||
|
"i18n:govoplan-organizations.organization_or_access_administrator": "An organization or Access administrator",
|
||||||
|
"i18n:govoplan-organizations.administration_roles_target": "Administration > Role templates and function mappings"
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
"i18n:govoplan-organizations.act_in_place.49b942bd": "In Vertretung handeln",
|
"i18n:govoplan-organizations.act_in_place.49b942bd": "In Vertretung handeln",
|
||||||
@@ -245,6 +261,22 @@ export const generatedTranslations: PlatformTranslations = {
|
|||||||
"i18n:govoplan-organizations.update_structure.b2e25446": "Struktur aktualisieren",
|
"i18n:govoplan-organizations.update_structure.b2e25446": "Struktur aktualisieren",
|
||||||
"i18n:govoplan-organizations.update_unit.67e2500f": "Einheit aktualisieren",
|
"i18n:govoplan-organizations.update_unit.67e2500f": "Einheit aktualisieren",
|
||||||
"i18n:govoplan-organizations.update_unit_type.b96fc0ad": "Einheitstyp aktualisieren",
|
"i18n:govoplan-organizations.update_unit_type.b96fc0ad": "Einheitstyp aktualisieren",
|
||||||
"i18n:govoplan-organizations.write_permission_required.8b09fd67": "Du hast keine Schreibberechtigung für diesen Organisationsbereich."
|
"i18n:govoplan-organizations.write_permission_required.8b09fd67": "Du hast keine Schreibberechtigung für diesen Organisationsbereich.",
|
||||||
|
"i18n:govoplan-organizations.organizations_administration": "Organisationsverwaltung",
|
||||||
|
"i18n:govoplan-organizations.loading_reason": "Organisationsdaten werden geladen.",
|
||||||
|
"i18n:govoplan-organizations.busy_reason": "Eine Organisationsänderung wird verarbeitet.",
|
||||||
|
"i18n:govoplan-organizations.no_changes_reason": "Nehmen Sie vor dem Speichern eine Änderung vor.",
|
||||||
|
"i18n:govoplan-organizations.model_write_reason": "Eine Schreibberechtigung für das Organisationsmodell ist erforderlich.",
|
||||||
|
"i18n:govoplan-organizations.unit_write_reason": "Eine Schreibberechtigung für Organisationseinheiten ist erforderlich.",
|
||||||
|
"i18n:govoplan-organizations.function_write_reason": "Eine Schreibberechtigung für Organisationsfunktionen ist erforderlich.",
|
||||||
|
"i18n:govoplan-organizations.settings_write_reason": "Eine Schreibberechtigung für Organisationseinstellungen ist erforderlich.",
|
||||||
|
"i18n:govoplan-organizations.incomplete_editor_reason": "Füllen Sie vor dem Speichern die Pflichtfelder aus.",
|
||||||
|
"i18n:govoplan-organizations.read_only_summary": "Dieser Organisationsbereich ist schreibgeschützt.",
|
||||||
|
"i18n:govoplan-organizations.required_action": "Erforderliche Aktion",
|
||||||
|
"i18n:govoplan-organizations.responsible_actor": "Verantwortliche Stelle",
|
||||||
|
"i18n:govoplan-organizations.destination": "Ziel",
|
||||||
|
"i18n:govoplan-organizations.request_write_permission": "Fordern Sie die passende Schreibberechtigung für Organisationsmodell, Einheiten, Funktionen oder Einstellungen an.",
|
||||||
|
"i18n:govoplan-organizations.organization_or_access_administrator": "Eine Organisations- oder Access-Administration",
|
||||||
|
"i18n:govoplan-organizations.administration_roles_target": "Administration > Rollenvorlagen und Funktionszuordnungen"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-2
@@ -44,7 +44,7 @@ const organizationFunctionPicker: OrganizationFunctionPickerUiCapability = {
|
|||||||
export const organizationsModule: PlatformWebModule = {
|
export const organizationsModule: PlatformWebModule = {
|
||||||
id: "organizations",
|
id: "organizations",
|
||||||
label: "i18n:govoplan-organizations.organizations.220edf64",
|
label: "i18n:govoplan-organizations.organizations.220edf64",
|
||||||
version: "1.0.0",
|
version: "0.1.8",
|
||||||
dependencies: ["access"],
|
dependencies: ["access"],
|
||||||
optionalDependencies: ["admin"],
|
optionalDependencies: ["admin"],
|
||||||
translations,
|
translations,
|
||||||
@@ -53,7 +53,7 @@ export const organizationsModule: PlatformWebModule = {
|
|||||||
id: "organizations.admin.tenant",
|
id: "organizations.admin.tenant",
|
||||||
moduleId: "organizations",
|
moduleId: "organizations",
|
||||||
kind: "section",
|
kind: "section",
|
||||||
label: "Organizations administration",
|
label: "i18n:govoplan-organizations.organizations_administration",
|
||||||
order: 85
|
order: 85
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user