medium priority low complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

DateRangeFilterBar renders four selectable options: 'Last 7 days', 'Last 30 days', 'Last 90 days', 'Custom'
The active/selected option is visually distinguished (e.g. filled chip vs outlined chip) using design tokens
Tapping a preset option dispatches a DateRangeChanged event to the RecruitmentDashboardBloc with computed start/end dates
Tapping 'Custom' opens a date range picker dialog; on confirm, dispatches DateRangeChanged with user-selected start/end dates
Tapping 'Custom' then cancelling the picker leaves the previously active range unchanged
Filter bar renders consistently with the existing 452-stats-period-filter-bar component — same chip shape, typography, and spacing tokens
The filter bar correctly reads the active range from BLoC state on rebuild — does not maintain its own local state for which option is selected
All filter options are accessible: each chip has a Semantics label including selected state (e.g. 'Last 30 days, selected')
Filter bar is horizontally scrollable on narrow screens without truncating chip labels
Dispatching a date range change event triggers the BLoC to reload referral data — verified by widget test

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc)
performance requirements
Filter bar state update must not cause a full screen rebuild — use BlocSelector scoped to active date range only
security requirements
Custom date range must be validated: end date must not be before start date, range must not exceed 365 days
ui components
DateRangeFilterBar (reuse/extend 452-stats-period-filter-bar pattern)
FilterChip (design system chip component)
DateRangePickerDialog (Flutter showDateRangePicker or custom modal)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Extend or copy the pattern from 452-stats-period-filter-bar rather than building from scratch — check if it can be parameterized to accept a different set of preset options. If it cannot, create DateRangeFilterBar as a new widget that follows the same visual and behavioral contract. Date computation for presets: use DateTime.now() minus Duration(days: N) at dispatch time, not at widget construction time, to ensure accuracy. Store the active DateRange in the BLoC state, not in widget state — this ensures the filter bar reflects state correctly after hot restart or screen pop.

For the custom range picker, use Flutter's built-in showDateRangePicker which handles accessibility and locale automatically.

Testing Requirements

Widget tests (flutter_test): (1) tapping each preset chip dispatches the correct DateRangeChanged event with the correct computed date range, (2) active chip reflects BLoC state — not internal widget state, (3) cancelling custom picker does not dispatch an event, (4) custom picker confirm dispatches correct start/end dates, (5) chips are scrollable without overflow on a 320px width constraint. Use MockBloc and verify add() calls.

Component
Coordinator Recruitment Dashboard
ui medium
Epic Risks (3)
medium impact high prob dependency

BadgeCriteriaIntegration must reference specific badge definition IDs from the badge-definition-repository for recruitment badges. If those badge definitions have not been created in the database when this epic is implemented, the integration will silently fail to award badges.

Mitigation & Contingency

Mitigation: As the first task of this epic, create the four recruitment badge definitions (seed data migration) with known, stable IDs. BadgeCriteriaIntegration hardcodes these IDs as constants. Include an assertion in the integration tests that verifies the badge definition records exist in the test database.

Contingency: If the badge definitions system does not support seeding at migration time, store the badge definition IDs in a feature-flag-style config table and look them up at runtime, falling back to a no-op with a warning log if they are absent.

medium impact medium prob technical

The coordinator dashboard aggregates referral stats across all peer mentors in an organisation. For large organisations (HLF has many peer mentors nationally), the aggregation query may be slow, causing the dashboard to feel unresponsive.

Mitigation & Contingency

Mitigation: Implement the aggregation as a Supabase database view or RPC that runs server-side with appropriate indexes on (mentor_id, org_id, created_at, event_type). Add a composite index on referral_events during the foundation epic's migration. Cache the result in the Riverpod provider with a 5-minute TTL.

Contingency: If query performance remains unacceptable at scale, materialise the aggregation in a nightly pg_cron job into a stats_cache table, and serve the dashboard from the cache with a 'last updated' timestamp shown to the coordinator.

medium impact low prob integration

The existing badge award service is implemented by the achievement-badges feature. If that feature's public API (BadgeAwardService interface) changes while this epic is in progress, the BadgeCriteriaIntegration will break at compile time or behave incorrectly at runtime.

Mitigation & Contingency

Mitigation: Confirm the BadgeAwardService interface is stable and document the exact method signatures this integration depends on. Write a narrow integration test that constructs the real BadgeAwardService against a test database to detect breaking changes immediately.

Contingency: If the badge service interface changes, adapt the BadgeCriteriaIntegration adapter class to match the new contract. The adapter pattern used here isolates the change to a single class.