critical priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

Screen is a BlocConsumer (or BlocBuilder + BlocListener) that rebuilds layout based on AccountingExportScreenState
In ConfigLoaded state: ExportDateRangePicker and export trigger button are visible for coordinator and org_admin roles; ExportHistoryPanel is always visible
In LoadingConfig state: a full-screen or section-level loading indicator is shown; export controls are not rendered
In Exporting state: export trigger button is replaced by a loading indicator with label 'Exporting…'; date picker is disabled
In ExportSuccess state: a success snackbar or inline banner shows 'Export complete' with a 'Download' action that dispatches DownloadFile event; history panel refreshes automatically
In ExportError state: an error banner with the error message and a 'Retry' button is shown below the date picker; banner uses the error color token
In DuplicateWarning state: the ExportConfirmationDialog is shown automatically (triggered via BlocListener) with the duplicate warning banner visible
Org-specific exporter label ('Export to Xledger' or 'Export to Microsoft Dynamics 365') is displayed on the trigger button using the exporterType from ConfigLoaded state
Peer mentor role sees only the ExportHistoryPanel with a read-only header 'Export History' β€” no date picker or trigger button rendered
All spacing uses design token spacing constants β€” no hardcoded pixel values except for standard Flutter defaults
All text colors pass WCAG 2.2 AA (4.5:1 contrast ratio) verified against the dark/light theme background tokens
Screen layout is responsive: renders correctly on screen widths from 320px to 428px (small to large phone) without horizontal overflow

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
data models
annual_summary
bufdir_export_audit_log
performance requirements
Screen must reach first meaningful paint within 400ms of navigation push β€” LoadConfig event dispatched in initState
ExportHistoryPanel lazy-loads β€” do not block screen render waiting for history data
BlocListener side effects (show dialog, show snackbar) must not cause frame drops β€” use WidgetsBinding.instance.addPostFrameCallback if needed
security requirements
Role guard must read role from the authenticated session provider β€” not from any URL parameter or local state that could be manipulated
Export trigger button is conditionally rendered (not just hidden via Visibility) to prevent accessibility tree exposure of the hidden control
ui components
AccountingExportScreen (BlocConsumer<AccountingExportScreenBloc, AccountingExportState>)
ExportActionSection (hosts date picker + trigger button, conditionally rendered based on role)
ExportTriggerButton (shows org-specific label from BLoC state, disabled during Exporting)
ExportErrorBanner (error message + retry button)
ExportSuccessBanner (success message + download action)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use BlocConsumer splitting the builder (UI rebuild) and listener (side effects like dialogs and snackbars) cleanly β€” do not show dialogs inside the builder. Dispatch LoadConfig in the BlocProvider's create callback (not in initState) to keep the widget stateless. Use Scaffold with a SingleChildScrollView wrapping a Column for the main layout β€” avoid ListView for this screen since content length is bounded. The ExportConfirmationDialog should be shown with showDialog() inside the BlocListener β€” pass the DuplicateWarning state's data as constructor arguments to the dialog.

After ExportSuccess, call context.read().refresh() (or equivalent Riverpod invalidate) to trigger a history panel reload. Use the design token spacing extension (e.g., AppSpacing.md, AppSpacing.lg) for all padding/margin values consistent with the rest of the app.

Testing Requirements

Widget tests: pump AccountingExportScreen with a mock BLoC in each state and verify: (1) Correct widgets rendered per state; (2) Trigger button label matches exporterType; (3) Peer mentor role renders only history panel; (4) BlocListener shows dialog on DuplicateWarning state; (5) Success snackbar appears on ExportSuccess state. Golden tests: render screen in ConfigLoaded state for coordinator and peer mentor roles. Accessibility test: verify no missing Semantics labels using the flutter_test semantics API; verify trigger button has correct role and label. Responsive test: set MediaQuery width to 320px and verify no RenderFlex overflow errors.

Integration test: full flow from LoadConfig β†’ InitiateExport β†’ ExportSuccess using a fake BLoC that progresses through all states. Target 85%+ widget test coverage.

Component
Accounting Export Screen
ui medium
Dependencies (4)
Build the ExportDateRangePicker Flutter widget with accessible date selection controls. Must support keyboard navigation, VoiceOver/TalkBack screen reader labels, and WCAG 2.2 AA contrast. Expose a callback with the selected DateTimeRange. Include preset options (current month, last month, custom range) and validate that start date is not after end date. epic-accounting-system-export-ui-task-001 Build the ExportConfirmationDialog widget that displays the export summary before the coordinator commits. Must show: selected date range, total approved claims count, skipped claims count with reasons (already exported, outside range), and the target exporter name (Xledger or Dynamics). Include a duplicate-export warning state that renders a distinct caution banner when the guard detects an existing export for the same period. Dialog must be fully accessible with semantic labels and focus trapping. epic-accounting-system-export-ui-task-002 Build the ExportHistoryPanel widget that fetches and displays past export runs for the current organisation. Each row shows: export date, period covered, exporter type (Xledger/Dynamics), claim count, status (success/partial/failed), and a download button that triggers file download. Panel must support pull-to-refresh and an empty-state illustration when no exports exist. Depends on the ExportRunRepository (323) being available via Riverpod provider. epic-accounting-system-export-ui-task-003 Create the AccountingExportScreenBloc (BLoC pattern) with states: Idle, LoadingConfig, ConfigLoaded(exporterType, orgName), Exporting, ExportSuccess(exportRunId, fileUrl), ExportError(message), and DuplicateWarning(existingRunId). Events: LoadConfig, InitiateExport(dateRange), ConfirmExport, CancelExport, DownloadFile. BLoC coordinates calls to the Export Edge Function client and Double-Export Guard. Guard-detected duplicates must transition to DuplicateWarning state before proceeding. epic-accounting-system-export-ui-task-004
Epic Risks (2)
medium impact medium prob technical

Export operations may take several seconds, and the UI must handle all intermediate states (loading, partial success, failure, duplicate warning) without leaving the coordinator on a blank or unresponsive screen. Missing state handling causes confusion and potentially double-submissions.

Mitigation & Contingency

Mitigation: Design the BLoC state machine with explicit states for each transition before writing any widget code: ExportIdle, ExportDuplicateWarning, ExportInProgress, ExportSuccess, ExportPartialSuccess, ExportFailed. Each state maps to a distinct UI. Widget tests cover all states.

Contingency: If a loading state is missed in production, surface a generic error state with a retry action rather than leaving the UI stuck. Add a timeout on the Edge Function call (default 30 seconds) that transitions to ExportFailed with a user-readable message.

high impact medium prob technical

The custom Export Date Range Picker may not be fully navigable with VoiceOver if the underlying Flutter date widgets do not expose the correct semantic tree. This is a critical accessibility failure for Blindeforbundet users who rely on screen readers.

Mitigation & Contingency

Mitigation: Use Flutter's built-in DateRangePicker as the base and wrap with explicit Semantics nodes for start and end labels. Test with VoiceOver on a physical iOS device as part of the definition of done for this component. Reference the existing AccessibilityTestHarness pattern used elsewhere in the app.

Contingency: If the custom picker fails accessibility audit, replace it with two independent DatePicker fields (start and end) using Flutter's standard accessible date input, which has broader VoiceOver support than range variants.