diff --git a/webui/src/features/approvals/ApprovalsPage.tsx b/webui/src/features/approvals/ApprovalsPage.tsx index 44dfa9b..52302f1 100644 --- a/webui/src/features/approvals/ApprovalsPage.tsx +++ b/webui/src/features/approvals/ApprovalsPage.tsx @@ -1,6 +1,6 @@ import { AlarmClock, Check, Plus, RefreshCw, X } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; -import { ActionToolbar, ActionBlockerHint, Button, ConfirmDialog, Dialog, DocumentationHelpLink, DismissibleAlert, FormField, IconButton, LoadingIndicator, PageScrollViewport, StatusBadge, WorkspaceLayout, hasScope, usePlatformLanguage, useUnsavedChanges, useUnsavedDraftGuard, type PlatformRouteContext } from "@govoplan/core-webui"; +import { ActionToolbar, ActionBlockerHint, Button, ConfirmDialog, Dialog, DocumentationHelpLink, DismissibleAlert, FormField, IconButton, LoadingIndicator, MetricCard, MetricGrid, PageScrollViewport, SelectionList, SelectionListItem, SelectionListItemContent, StatePanel, StatusBadge, WorkspaceLayout, hasScope, usePlatformLanguage, useUnsavedChanges, useUnsavedDraftGuard, type PlatformRouteContext } from "@govoplan/core-webui"; import { approvalHistory, decideApproval, escalateApproval, getApproval, listApprovals, type ApprovalEvent, type ApprovalRequest } from "../../api/approvals"; import ApprovalRequestDialog from "./ApprovalRequestDialog"; import { APPROVALS_DOCUMENTATION, APPROVALS_FIELD_DOCUMENTATION, APPROVALS_I18N } from "./interfacePatterns"; @@ -59,6 +59,7 @@ export default function ApprovalsPage({ settings, auth }: PlatformRouteContext) primarySize="compact" primaryScrollable={false} contentScrollable={false} + surface="contained" className="approvals-shell" primaryClassName="approvals-list-panel" contentClassName="approvals-workspace" @@ -68,20 +69,20 @@ export default function ApprovalsPage({ settings, auth }: PlatformRouteContext) helpContextId="approvals.workspace" helpModuleId="approvals" primary={<> - } disabled={loading || busy} disabledReason={loading ? APPROVALS_I18N.loading : busy ? APPROVALS_I18N.busy : undefined} onClick={() => void load()} /> - {loading && }
{items.map((item) => )}
{!loading && items.length === 0 &&
No approval requests
}
+ } disabled={loading || busy} disabledReason={loading ? APPROVALS_I18N.loading : busy ? APPROVALS_I18N.busy : undefined} onClick={() => void load()} /> + {loading && }{items.map((item) => setSelectedId(item.id)}>)}{!loading && items.length === 0 && } } > {error && {error}} {!canWrite && } {selected &&

{selected.title}

{selected.subject_module} / {selected.subject_type} / {selected.subject_id}{selected.subject_version ? ` @ ${selected.subject_version}` : ""}
{canAdmin && selected.state === "pending" && }
-
+ {selected.description &&

{selected.description}

}

Approval chain

{selected.steps.map((step, index) =>
{index + 1}{step.label}{step.required_approvals} required / {step.selectors.map((item) => `${humanize(item.kind)}: ${item.label || item.value}`).join(", ")}{step.signature_required && Signature}
)}

History

{history.map((event) =>
{event.sequence}{humanize(event.event_type)}
)}
} - {!selected && !loading &&
Select or create an approval request.
} + {!selected && !loading && } {creating && setCreating(false)} onSaved={(item) => { setCreating(false); setSelectedId(item.id); void load(undefined, item.id); }} />} {selected && decision && setDecision(null)} onConfirm={async (reason, signatureId) => { setBusy(true); setError(""); try { await decideApproval(settings, selected, decision, reason, signatureId ? { owner_module: "signatures", object_id: signatureId } : undefined); setDecision(null); await reload(selected.id); return true; } catch (failure) { setError(text(failure, "The Approval decision could not be recorded.")); return false; } finally { setBusy(false); } }} />} @@ -100,7 +101,6 @@ function DecisionDialog({ outcome, busy, signatureRequired, onClose, onConfirm } return }>