Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11eca4ed28 |
@@ -354,6 +354,8 @@ manifest = ModuleManifest(
|
||||
title="Governed approval chains",
|
||||
summary="Create exact-subject approval chains with delegation, separation of duties, escalation, and signature evidence.",
|
||||
body=(
|
||||
"Documentation books are beside the Approval requests and template headings and beside "
|
||||
"request or decision dialog titles; field help stays with its label. "
|
||||
"An Approval request freezes its subject revision, ordered steps, eligible selectors, quorum, rejection policy, signature requirement, and governance references. "
|
||||
"Decisions are append-only, tenant-bound, optimistic-concurrency protected, and replay safe. Consuming modules verify the exact subject through the capability rather than reading Approval tables. "
|
||||
"When Tasks is enabled, a pending step appears in the common work inbox only for a principal who currently passes the exact decision eligibility checks. "
|
||||
@@ -394,6 +396,9 @@ manifest = ModuleManifest(
|
||||
"Genehmigungsketten für exakt bezeichnete Gegenstände mit Delegation, Funktionstrennung, Eskalation und Signaturnachweisen erstellen."
|
||||
),
|
||||
"body": (
|
||||
"Dokumentationsbücher stehen neben den Überschriften der Genehmigungsanfragen und Vorlagen "
|
||||
"sowie neben den Titeln der Anfrage- und Entscheidungsdialoge; Feldhilfe bleibt bei der "
|
||||
"Feldbezeichnung. "
|
||||
"Ein Genehmigungsantrag fixiert Gegenstandsrevision, geordnete Schritte, zulässige Selektoren, Quorum, "
|
||||
"Ablehnungsregel, Signaturanforderung und Governance-Verweise. Entscheidungen werden nur angefügt, sind "
|
||||
"mandantengebunden, durch optimistische Nebenläufigkeit geschützt und wiederholungssicher. Verbrauchende "
|
||||
|
||||
@@ -41,9 +41,8 @@ export default function ApprovalRequestDialog({ settings, onClose, onSaved }: {
|
||||
else onClose();
|
||||
}
|
||||
|
||||
return <Dialog open title="New Approval request" onClose={requestClose} closeDisabled={busy} portal className="approval-request-dialog" footer={<><Button onClick={requestClose} disabled={busy} disabledReason={busy ? APPROVALS_I18N.busy : undefined}>Cancel</Button><Button variant="primary" disabled={busy || !valid} disabledReason={busy ? APPROVALS_I18N.busy : !valid ? APPROVALS_I18N.incomplete : undefined} onClick={() => void save()}>{busy ? "Creating" : "Create request"}</Button></>}>
|
||||
return <Dialog open title="New Approval request" titleHelp={<DocumentationHelpLink reference={APPROVALS_FIELD_DOCUMENTATION} />} onClose={requestClose} closeDisabled={busy} portal className="approval-request-dialog" footer={<><Button onClick={requestClose} disabled={busy} disabledReason={busy ? APPROVALS_I18N.busy : undefined}>Cancel</Button><Button variant="primary" disabled={busy || !valid} disabledReason={busy ? APPROVALS_I18N.busy : !valid ? APPROVALS_I18N.incomplete : undefined} onClick={() => void save()}>{busy ? "Creating" : "Create request"}</Button></>}>
|
||||
<div className="approval-editor">
|
||||
<div className="approval-editor-help"><DocumentationHelpLink reference={APPROVALS_FIELD_DOCUMENTATION} /></div>
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
<FormGrid columns={2} gap="compact" collapseAt="workspace">
|
||||
<FormField label="Title" documentation={APPROVALS_FIELD_DOCUMENTATION}><input value={draft.title} disabled={busy} onChange={(event) => setDraft({ ...draft, title: event.target.value })} /></FormField>
|
||||
|
||||
@@ -190,7 +190,7 @@ export default function ApprovalTemplatesPanel({
|
||||
);
|
||||
|
||||
return <>
|
||||
<AdminPageLayout title="Approval templates" description="Define reusable, immutable approval chains and compare every published or draft revision." loading={loading} error={error} success={success} actions={<><DocumentationHelpLink reference={APPROVALS_DOCUMENTATION} /><Button onClick={() => void load()} disabled={loading}>Reload</Button><AdminIconButton label="Create approval template" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canAdmin} disabledReason={!canAdmin ? "Approval administration permission is required." : undefined} /></>}>
|
||||
<AdminPageLayout title="Approval templates" titleHelp={<DocumentationHelpLink reference={APPROVALS_DOCUMENTATION} />} description="Define reusable, immutable approval chains and compare every published or draft revision." loading={loading} error={error} success={success} actions={<><Button onClick={() => void load()} disabled={loading}>Reload</Button><AdminIconButton label="Create approval template" icon={<Plus />} variant="primary" onClick={openCreate} disabled={!canAdmin} disabledReason={!canAdmin ? "Approval administration permission is required." : undefined} /></>}>
|
||||
<div className="admin-table-surface"><DataGrid id="approval-templates-v1" rows={templates} columns={columns} initialFit="container" getRowKey={(row) => row.id} emptyText="No approval templates found." /></div>
|
||||
</AdminPageLayout>
|
||||
|
||||
|
||||
@@ -69,7 +69,9 @@ export default function ApprovalsPage({ settings, auth }: PlatformRouteContext)
|
||||
helpContextId="approvals.workspace"
|
||||
helpModuleId="approvals"
|
||||
primary={<>
|
||||
<WorkspaceActionBar scope="collection-pane" variant="collection" refreshable reloadAction={{ onReload: () => void load(), loading: loading || busy, label: "Refresh approvals" }} className="approvals-toolbar" createAction={<Button variant="primary" disabled={!canWrite} disabledReason={!canWrite ? APPROVALS_I18N.writeReason : undefined} onClick={() => setCreating(true)}><Plus size={16} aria-hidden="true" />New request</Button>} helpAction={<DocumentationHelpLink reference={APPROVALS_DOCUMENTATION} />} />
|
||||
<WorkspaceActionBar
|
||||
title="Approval requests"
|
||||
titleHelp={<DocumentationHelpLink reference={APPROVALS_DOCUMENTATION} />} scope="collection-pane" variant="collection" refreshable reloadAction={{ onReload: () => void load(), loading: loading || busy, label: "Refresh approvals" }} className="approvals-toolbar" createAction={<Button variant="primary" disabled={!canWrite} disabledReason={!canWrite ? APPROVALS_I18N.writeReason : undefined} onClick={() => setCreating(true)}><Plus size={16} aria-hidden="true" />New request</Button>} />
|
||||
<PageScrollViewport className="approvals-list-viewport">{loading && <LoadingIndicator label="Loading approvals" />}<SelectionList label="Approval requests" variant="navigation">{items.map((item) => <SelectionListItem key={item.id} selected={item.id === selectedId} onClick={() => setSelectedId(item.id)}><SelectionListItemContent title={item.title} description={`${item.subject_module} / ${item.subject_type}`} /><StatusBadge status={tone(item.state)} label={humanize(item.state)} /></SelectionListItem>)}</SelectionList>{!loading && items.length === 0 && <StatePanel size="compact" description="No approval requests" />}</PageScrollViewport>
|
||||
</>}
|
||||
>
|
||||
@@ -98,7 +100,7 @@ function DecisionDialog({ outcome, busy, signatureRequired, onClose, onConfirm }
|
||||
const valid = Boolean(reason.trim() && (!signatureRequired || signatureId.trim()));
|
||||
useUnsavedDraftGuard({ dirty, onSave: async () => valid && onConfirm(reason.trim(), signatureId.trim()), onDiscard: () => { setReason(""); setSignatureId(""); }, title: "i18n:govoplan-approvals.unsaved_title", message: "i18n:govoplan-approvals.unsaved_message" });
|
||||
const requestClose = () => { if (busy) return; if (dirty) requestDiscard(onClose); else onClose(); };
|
||||
return <Dialog open title={`${humanize(outcome)} request`} onClose={requestClose} closeDisabled={busy} portal footer={<><Button onClick={requestClose} disabled={busy} disabledReason={busy ? APPROVALS_I18N.busy : undefined}>Cancel</Button><Button variant={outcome === "approved" ? "primary" : "danger"} disabled={busy || !valid} disabledReason={busy ? APPROVALS_I18N.busy : !valid ? APPROVALS_I18N.incomplete : undefined} onClick={() => void onConfirm(reason.trim(), signatureId.trim())}>Confirm</Button></>}><div className="approval-decision-form"><div className="approval-editor-help"><DocumentationHelpLink reference={APPROVALS_FIELD_DOCUMENTATION} /></div><FormField label="Reason" documentation={APPROVALS_FIELD_DOCUMENTATION}><textarea rows={5} value={reason} disabled={busy} onChange={(event) => setReason(event.target.value)} /></FormField>{signatureRequired && <FormField label="Signature reference" documentation={APPROVALS_FIELD_DOCUMENTATION}><input value={signatureId} disabled={busy} onChange={(event) => setSignatureId(event.target.value)} /></FormField>}</div></Dialog>;
|
||||
return <Dialog open title={`${humanize(outcome)} request`} titleHelp={<DocumentationHelpLink reference={APPROVALS_FIELD_DOCUMENTATION} />} onClose={requestClose} closeDisabled={busy} portal footer={<><Button onClick={requestClose} disabled={busy} disabledReason={busy ? APPROVALS_I18N.busy : undefined}>Cancel</Button><Button variant={outcome === "approved" ? "primary" : "danger"} disabled={busy || !valid} disabledReason={busy ? APPROVALS_I18N.busy : !valid ? APPROVALS_I18N.incomplete : undefined} onClick={() => void onConfirm(reason.trim(), signatureId.trim())}>Confirm</Button></>}><div className="approval-decision-form"><FormField label="Reason" documentation={APPROVALS_FIELD_DOCUMENTATION}><textarea rows={5} value={reason} disabled={busy} onChange={(event) => setReason(event.target.value)} /></FormField>{signatureRequired && <FormField label="Signature reference" documentation={APPROVALS_FIELD_DOCUMENTATION}><input value={signatureId} disabled={busy} onChange={(event) => setSignatureId(event.target.value)} /></FormField>}</div></Dialog>;
|
||||
}
|
||||
|
||||
function tone(state: ApprovalRequest["state"]): "active" | "inactive" | "warning" { if (state === "approved") return "active"; if (["rejected", "cancelled", "expired"].includes(state)) return "inactive"; return "warning"; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { PlatformTranslations } from "@govoplan/core-webui";
|
||||
|
||||
const en = {
|
||||
"Approval requests": "Approval requests",
|
||||
"i18n:govoplan-approvals.approvals": "Approvals",
|
||||
"i18n:govoplan-approvals.loading_reason": "Approval data is still loading.",
|
||||
"i18n:govoplan-approvals.busy_reason": "Another Approval action is still running.",
|
||||
@@ -64,6 +65,7 @@ const en = {
|
||||
} as const;
|
||||
|
||||
const de: Record<keyof typeof en, string> = {
|
||||
"Approval requests": "Genehmigungsanträge",
|
||||
"i18n:govoplan-approvals.approvals": "Genehmigungen",
|
||||
"i18n:govoplan-approvals.loading_reason": "Genehmigungsdaten werden noch geladen.",
|
||||
"i18n:govoplan-approvals.busy_reason": "Eine andere Genehmigungsaktion läuft noch.",
|
||||
|
||||
Reference in New Issue
Block a user