critical priority medium complexity integration pending integration specialist Tier 3

Acceptance Criteria

Tapping 'Confirm Approval' calls ApprovalWorkflowService.approveExpenseClaim(claimId) with the correct claim identifier
Tapping 'Confirm Rejection' calls ApprovalWorkflowService.rejectExpenseClaim(claimId, comment) with trimmed rejection comment
While the service call is in-flight, the confirmation buttons are replaced with a loading indicator (CircularProgressIndicator) and all interaction is disabled
On successful service response, the sheet closes via Navigator.pop with a result value indicating success (e.g., ApprovalResult.approved or ApprovalResult.rejected)
On service failure, the sheet remains open, displays an inline error message below the action summary, and re-enables the confirmation button
The inline error message for service failure is distinct from validation errors — uses a error banner pattern, not field-level error style
Network timeout or connectivity errors display a user-friendly message ('Could not process approval. Please check your connection and try again.')
The service call includes the ClaimEvent entity creation (from_status transition) via Supabase with RLS enforcement
BLoC state (ApprovalWorkflowBloc or equivalent) is used to manage loading/success/error states — no direct async logic in the widget
Parent screen is notified of successful approval/rejection to trigger list refresh without full page reload

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
Supabase PostgreSQL 15
Supabase Edge Functions (Deno)
data models
claim_event
activity
performance requirements
Service call must complete within 5 seconds on a standard 4G connection before timeout UI is shown
Optimistic UI not required — wait for server confirmation before closing sheet
security requirements
Supabase client call must use authenticated session JWT — never service role key from mobile client
Rejection comment must be sanitized (trim, max 1000 chars) before transmission
RLS policies must enforce that only coordinator role can approve/reject claims for their organization
ui components
CircularProgressIndicator (loading state overlay on confirmation buttons)
ErrorBannerWidget (inline service error display)
BLoC event/state: ApproveClaimRequested, RejectClaimRequested, ApprovalInProgress, ApprovalSuccess, ApprovalFailure

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use BlocConsumer in ApprovalActionSheet to react to state changes: listen block handles navigation (pop on success) and error announcements; builder block handles UI rendering. Pass the BLoC in via context (provided by parent screen's BlocProvider) rather than creating it inside the sheet — this allows the parent to react to success and refresh its own list. For the service layer, ApprovalWorkflowService.approveExpenseClaim should insert a ClaimEvent row via Supabase with the appropriate status transition and let a Postgres trigger or Edge Function handle the actual expense_claims status update — this keeps audit trail complete. Rejection comment max length enforcement: add inputFormatters: [LengthLimitingTextInputFormatter(1000)] to the comment field.

Testing Requirements

Unit test ApprovalWorkflowBloc: mock ApprovalWorkflowService and verify correct events produce correct state transitions (InProgress → Success, InProgress → Failure). Widget test ApprovalActionSheet: mock BLoC, pump ApprovalInProgress state and verify buttons are disabled and loader is visible; pump ApprovalSuccess and verify Navigator.pop is called; pump ApprovalFailure and verify error banner widget appears and sheet is still in tree. Integration test: use Supabase local emulator or test environment to verify actual ClaimEvent row is created with correct from_status/to_status on approve.

Component
Approval Action Bottom Sheet
ui medium
Epic Risks (2)
medium impact medium prob scope

If a bulk approval batch partially fails (some claims approved, some failed), the UI must communicate which specific claims failed without overwhelming the coordinator. A poorly designed error display could cause coordinators to re-approve already-approved claims or miss claims that still need attention.

Mitigation & Contingency

Mitigation: Design the BulkApprovalResult display to show a clear summary (e.g., '14 approved, 2 failed') with a collapsible list of failed claims including their IDs and submitter names. Failed claims should remain selected in the queue so the coordinator can retry them individually.

Contingency: If the summary UI proves insufficient, add a dedicated 'bulk action history' sheet showing the last bulk operation result, accessible from the queue screen header.

medium impact low prob technical

If the app is backgrounded or the network drops while the coordinator has the ApprovalActionSheet open mid-decision, the typed comment could be lost and the transition state could be ambiguous, potentially causing a coordinator to believe they approved a claim that was never submitted.

Mitigation & Contingency

Mitigation: Persist the in-progress action sheet state (selected action + comment text) to a local draft store keyed on claim ID. On sheet re-open for the same claim, restore the draft. After confirmed submission, verify the resulting claim status from the server before dismissing the sheet.

Contingency: On network error during submission, display a persistent retry banner within the sheet rather than dismissing it, so the coordinator can resubmit without re-entering their comment.