Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11eca4ed28 | ||
|
|
36781c86f7 |
@@ -73,3 +73,21 @@ From the core checkout, labels can be synced once a local `GITEA_TOKEN` is avail
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
/mnt/DATA/git/govoplan/tools/gitea/gitea-sync-labels.py --root /mnt/DATA/git/govoplan-approvals --apply
|
||||
```
|
||||
|
||||
## Git-source WebUI package
|
||||
|
||||
The repository root exposes `@govoplan/approvals-webui` for Git-tagged release
|
||||
dependencies. It mirrors the owning `webui/package.json` version, public
|
||||
TypeScript/CSS exports and peer requirements, with entry paths under
|
||||
`webui/src`. Consumers provide the shared Core/React peers; the facade runs no
|
||||
development or install scripts. The source archive contains `webui/src`, this
|
||||
README and any repository license file. Run module development checks from `webui/`; Python
|
||||
installation remains governed by `pyproject.toml`.
|
||||
|
||||
Das Repository stellt `@govoplan/approvals-webui` am Wurzelpfad für versionierte
|
||||
Git-Abhängigkeiten bereit. Version, öffentliche TypeScript-/CSS-Exporte und
|
||||
Peer-Anforderungen entsprechen `webui/package.json`; die Einstiegspfade liegen
|
||||
unter `webui/src`. Gemeinsame Core-/React-Peers stellt die einbindende Anwendung
|
||||
bereit. Die Fassade führt keine Entwicklungs- oder Installationsskripte aus.
|
||||
Entwicklungsprüfungen bleiben in `webui/`, die Python-Installation weiterhin in
|
||||
`pyproject.toml` definiert.
|
||||
|
||||
+28
-3
@@ -1,8 +1,33 @@
|
||||
{
|
||||
"name": "@govoplan/approvals",
|
||||
"version": "0.1.20",
|
||||
"name": "@govoplan/approvals-webui",
|
||||
"version": "0.1.21",
|
||||
"private": true,
|
||||
"description": "Governed approval chains, decisions, delegation, and escalation for GovOPlaN.",
|
||||
"type": "module",
|
||||
"peerDependencies": {}
|
||||
"peerDependencies": {
|
||||
"@govoplan/core-webui": "^0.1.18",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": ">=19.2.7 <20",
|
||||
"react-dom": ">=19.2.7 <20"
|
||||
},
|
||||
"main": "webui/src/index.ts",
|
||||
"module": "webui/src/index.ts",
|
||||
"types": "webui/src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./webui/src/index.ts",
|
||||
"import": "./webui/src/index.ts"
|
||||
},
|
||||
"./styles/approvals.css": "./webui/src/styles/approvals.css"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@govoplan/core-webui": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"webui/src",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "govoplan-approvals"
|
||||
version = "0.1.20"
|
||||
version = "0.1.21"
|
||||
description = "Governed approval chains, decisions, delegation, and escalation for GovOPlaN."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
@@ -41,7 +41,7 @@ from govoplan_approvals.backend.service import SqlApprovalRequests
|
||||
|
||||
MODULE_ID = "approvals"
|
||||
MODULE_NAME = "Approvals"
|
||||
MODULE_VERSION = "0.1.20"
|
||||
MODULE_VERSION = "0.1.21"
|
||||
READ_SCOPE = "approvals:workspace:read"
|
||||
WRITE_SCOPE = "approvals:workspace:write"
|
||||
DECIDE_SCOPE = "approvals:workspace:decide"
|
||||
@@ -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 "
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/approvals-webui",
|
||||
"version": "0.1.20",
|
||||
"version": "0.1.21",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -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