Workflow Approval Page¶
Overview¶
The Workflow Approval page (/declarations/:id/workflow) provides the interface for approvers to review a declaration and either approve, reject, or send it back for revision. The page is implemented in client/src/pages/WorkflowApprovalPage.tsx.
Page context: Wraps content in <PageEntityProvider entity={{ kind: 'workflow_for_declaration', declarationId: asDeclarationId(declaration.id) }}>.
The :id in the URL is the declaration ID (not a workflow instance ID). The page resolves the linked workflow instance via workflowInstances(declarationId) query.
Page Layout¶
Header¶
PageHeader component:
- Title: "Workflow Approval"
- Subtitle: "Declaration #[id] - [Rep Name]" (from declaration.rep_name or form_data.personal_info.full_name)
- Back link: "/dashboard"
- Breadcrumbs: Dashboard > Workflows > Approval #[id]
- Action buttons: PDF export, DOCX export (both call FastAPI export endpoints)
Workflow Stepper (Prominent Top)¶
If the workflow instance has steps, the WorkflowStepper component renders in a card with a blue top border:
Step 1 Step 2 Step 3 Step 4
-------> -------> -------> ------->
[Approved] [Current] [Pending] [Future]
Each step displays:
- Circle: colored by status — green with checkmark (approved), blue pulsing ring (current), gray (pending future), red with X (rejected), orange (sent back)
- Initial: first letter of approver name (or step number if name unavailable; fallback uses index step_index + 1)
- Label: approver name (or "Approver N")
- Status text: "Approved" / "Current Reviewer" / "Approver" / "Rejected" / "Sent Back"
- Acted-at date: short SG format (if step has acted_at)
- Current step extras: "Waiting for: [Approver Name]" badge + "Since: [date]" + idle-day indicator (green 0-3d, amber 4-7d, red 8+d)
- Connector lines: green gradient (completed), red gradient (rejected), orange (sent-back), gray (future)
Idle days are computed from the previous step's acted_at or the submittedAt prop. The workflow is considered "terminal" (no current reviewer) when any step is sent_back or rejected, or when all steps are approved.
Declaration Summary Card¶
Two-column grid with:
Left column:
- Representative: personal_info.full_name or declaration.rep_name
- Company: general_info.principal_company_name
- Rep Type: general_info.rep_type
Right column:
- Form Type: via formTypeLabel() utility
- Created: SG-locale datetime
- Submitted: SG-locale datetime
Above both columns: StatusBadge showing current declaration status.
Footer: "View full declaration form" link navigating to the Form 3A page in view-only mode (/reps/:rep_id/form3a with state: { declarationId, viewOnly: true }).
Approval Comments Section¶
When workflow events with comments exist, rendered in a card with MessageSquare icon heading. Each comment is a colored bordered card:
| Event Type | Card Colors | Icon Badge |
|---|---|---|
step_approved / approved / approve |
Green success background + green border | Green circle with CheckCircle2 + "Approved" |
step_sent_back / sent_back / send_back |
Orange background + orange border | Orange circle with RotateCcw + "Sent Back" |
| Other (rejected events) | Red danger background + red border | Red circle with XCircle + "Rejected" |
Each comment card shows: - Avatar: initials of actor name - Actor name (bold) + status badge - Timestamp (right-aligned, SG-locale) - Comment text below (with left padding for alignment)
Comments are sorted chronologically (oldest first).
Your Decision Section (Conditional)¶
Shown only when the current user is the active pending approver. The isCurrentApprover check: the workflow exists, and there is a step where step_index === workflow.current_step_index AND approver_user_id === dbUser.id AND status === 'pending'.
Amber top border card with:
- AlertCircle icon in amber circle + "Your Decision" heading
- "Review the declaration and provide your decision with a comment." subtitle
- Mandatory comment textarea (required field, red asterisk label)
- Three action buttons:
1. Approve: green background, CheckCircle2 icon, bg-success-600 hover:bg-success-700
2. Request Changes (send-back): orange outline, RotateCcw icon, border-orange-400 text-orange-700
3. Reject: red danger variant, XCircle icon
Validation: comment must not be empty before Approve or Reject. An inline error message "Comment is required before approving or rejecting." (rendered as red text below the comment textarea) blocks the action modal from opening.
On Approve/Reject click: opens confirmation modal with declaration summary and comment preview. On confirm: calls approveWorkflowStep or rejectWorkflowStep mutation, refetches all data, clears comment.
On Send Back click: opens send-back modal with "What changes are needed?" textarea. On submit: calls sendBackWorkflowStep mutation, navigates back to /dashboard.
Status Info Card (When NOT Current Approver)¶
When the user is not the current approver, a card shows the workflow status:
| Workflow Status | Icon | Message | Border Color |
|---|---|---|---|
rejected |
XCircle (red) | "This declaration has been rejected." | Red left border |
sent_back |
RotateCcw (orange) | "This declaration was sent back for revision." | Orange left border |
approved / completed |
CheckCheck (blue) | "This workflow has been completed. All approvers have acted." | Blue left border |
| Other (pending, not user's turn) | Clock (blue) | "You are not the current approver for this declaration. Please wait for your turn." | Blue left border |
Action Modals¶
Approve/Reject Confirmation Modal¶
Triggered by Approve or Reject buttons. Shows: - Declaration summary (ID, rep, form type, user's comment) - Confirm/Cancel buttons - On confirm: executes mutation, refetches, shows success toast
Send-Back Modal¶
Triggered by "Request Changes" button. Shows:
- Declaration summary (ID, rep)
- "What changes are needed?" mandatory textarea
- Cancel / Send Back buttons (orange-themed)
- On submit: executes sendBackWorkflowStep mutation, navigates to /dashboard
GraphQL Data Sources¶
| Query/Mutation | Purpose | Key Parameters |
|---|---|---|
declaration(id) |
Fetch declaration data | id: number |
workflowInstances(declarationId) |
Fetch linked workflow (first instance used) | declarationId: number |
workflowEvents(declarationId) |
Fetch all events for the declaration | declarationId: number |
approveWorkflowStep |
Approve current step | instanceId, stepIndex, comment |
rejectWorkflowStep |
Reject current step | instanceId, stepIndex, comment |
sendBackWorkflowStep |
Send back for revision | instanceId, stepIndex, comment |
Sequential Constraint¶
The workflow enforces step-by-step sequential progression. Step N+1 cannot begin until Step N is APPROVED. This constraint is enforced server-side by the NestJS workflow service. The frontend only displays approval actions for the user assigned to the current pending step.
What Happens After Each Action¶
- Approve: advances
current_step_indexby 1. If it was the last step, workflow status changes tocompleted. - Reject: all prior approvals are discarded. Declaration status reverts; the representative must start over.
- Send Back (Request Changes): returns the declaration to the representative with
REVISION_REQUESTEDstatus. Prior approvals are retained — when the rep resubmits, the workflow resumes at the step that sent it back.
Escalation Paths¶
The page itself does not implement reassignment or override. Escalation is handled externally:
- If an approver is unavailable (on leave, unresponsive), an ORG_ADMIN or HR_ADMIN can reassign the workflow template's approver at /workflows (WorkflowManagementPage) — but this change applies to new declarations only, not existing instances
- There is no in-page "reassign approver" button — the chatbot or an admin must use direct database/API operations for mid-workflow reassignment
Page Context Details¶
The page publishes { kind: 'workflow_for_declaration', declaration_id } to the chatbot. When responding to page-context queries, the chatbot should:
- Read the workflow state to identify the current pending approver
- Calculate idle days to determine urgency
- Report which step is stuck and at which approver
- Suggest appropriate next steps: nudge the approver (if still responsive), escalate to admin (if >5 days), or advise on send-back reasons