high priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Widget renders preset buttons for Q1, Q2, Q3, Q4, Full Year, and Custom Range
Tapping a preset immediately invokes onPeriodSelected(DateTimeRange) with the correct start/end dates for the current calendar year
Tapping 'Custom Range' opens a date range picker (showDateRangePicker or equivalent) allowing the user to select arbitrary start and end dates
The initially selected period is visually highlighted when initialValue is provided
onPeriodSelected is never called with an invalid range (end before start)
All interactive elements have semantic labels readable by VoiceOver and TalkBack screen readers
Minimum touch target for each preset button is 44Γ—44 dp per WCAG 2.2 AA
Text contrast ratio for labels against button background meets WCAG 2.2 AA (β‰₯ 4.5:1)
Widget uses only design token values for colours, spacing, typography, and border radii β€” no hardcoded hex/dp values
Widget is entirely stateless β€” no setState; any selection state is owned by the parent via initialValue + onPeriodSelected
Widget tests verify correct DateTimeRange values for each preset and that onPeriodSelected is called exactly once per tap
Widget renders correctly at 320dp minimum screen width without horizontal overflow

Technical Requirements

frameworks
Flutter
flutter_test (widget testing)
Design token system (AppColors, AppSpacing, AppTextStyles, AppRadii)
apis
Flutter Material showDateRangePicker (for custom range)
data models
DateTimeRange (Flutter SDK)
ExportPeriodPreset (enum: q1, q2, q3, q4, fullYear, custom)
performance requirements
Widget build method must be pure and O(1) β€” no async calls, no database access
Preset buttons render in a single Row/Wrap β€” no lazy loading required
security requirements
No PII or session data handled inside this widget
Date values are standard Dart DateTime β€” no serialisation or network calls
ui components
ExportPeriodPresetButton (reusable chip-style button per preset)
Custom date range picker via Flutter Material showDateRangePicker

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Implement as a StatelessWidget accepting: `final DateTimeRange? initialValue`, `final void Function(DateTimeRange) onPeriodSelected`, `final BuildContext context` (needed for showDateRangePicker). Derive preset DateTimeRange values from `DateTime.now().year` so Q1 always references the current calendar year β€” use a static helper `_presetRange(ExportPeriodPreset preset, int year)`. For the custom range, call `showDateRangePicker(context: context, firstDate: DateTime(2020), lastDate: DateTime.now())` and invoke `onPeriodSelected` in the `.then()` callback only if the result is non-null.

Lay out the presets in a `Wrap` widget with `spacing: AppSpacing.sm` and `runSpacing: AppSpacing.sm` so it reflows gracefully on narrow screens. Each preset chip should use `AppColors.surfaceElevated` as default background and `AppColors.brandPrimary` when selected (comparing with initialValue). Ensure each chip's `Semantics` widget sets `label: 'Select ${preset.displayName} period'` and `selected: isSelected`.

Testing Requirements

Widget tests (flutter_test) covering: (1) all 6 preset buttons render; (2) tapping Q1 calls onPeriodSelected with Jan 1–Mar 31 of current year; (3) tapping Q2/Q3/Q4 produce correct date ranges; (4) tapping Full Year calls onPeriodSelected with Jan 1–Dec 31; (5) initialValue highlights the matching preset; (6) onPeriodSelected called exactly once per preset tap with no duplicates; (7) widget renders without overflow at 320dp width. Accessibility test: pump widget in flutter_test with AccessibilityGuidelines.androidTapTargetGuideline and AccessibilityGuidelines.iOSTapTargetGuideline assertions. Custom range picker interaction can be tested by verifying the navigator push occurs β€” actual date picker interaction is a manual test.

Component
Export Period Picker
ui low
Epic Risks (3)
high impact medium prob technical

NHF's three-level hierarchy (national / region / chapter) with 1,400 chapters may have edge cases such as chapters belonging to multiple regions, orphaned nodes, or missing parent links in the database. Incorrect scope expansion would silently under- or over-report activities, which could invalidate a Bufdir submission.

Mitigation & Contingency

Mitigation: Obtain a full hierarchy fixture export from NHF before implementation begins. Write exhaustive unit tests covering boundary cases: single chapter, full national roll-up, chapters with no activities, and chapters assigned to multiple regions. Validate resolver output against a known-good manual count.

Contingency: If hierarchy data quality is too poor for automated resolution at launch, implement a manual scope override in the coordinator UI that allows the coordinator to explicitly select org units from a tree picker, bypassing the resolver.

medium impact high prob dependency

The activity_type_configuration table may not cover all activity types currently in use, leaving a subset unmapped at launch. Bufdir submissions with unmapped categories will be incomplete and may be rejected by Bufdir.

Mitigation & Contingency

Mitigation: Run a query against production activity data before implementation to enumerate all distinct activity type IDs. Cross-reference with Bufdir's published category schema (request from Norse Digital Products). Flag every gap as a known issue and build the warning surface into the preview panel.

Contingency: Implement a fallback 'Other' category bucket for unmapped types and surface a prominent warning in the export preview requiring coordinator acknowledgement before proceeding. Log unmapped types for post-launch cleanup.

high impact low prob security

Supabase RLS policies on generated_reports and the storage bucket must enforce strict org isolation. A misconfigured policy could allow a coordinator from one organisation to read another organisation's export files, creating a serious data breach with GDPR implications.

Mitigation & Contingency

Mitigation: Write RLS integration tests that attempt cross-org reads with explicitly different JWT tokens and assert that all attempts return empty sets or 403 errors. Include RLS policy review in the pull request checklist. Use Supabase's built-in policy tester during development.

Contingency: If a policy gap is discovered post-deployment, immediately revoke all signed URLs for affected exports, audit the access log for unauthorised reads, and issue a coordinated disclosure to affected organisations per GDPR breach notification requirements.