Wire getAggregatedStatsForMentor to dashboard BLoC
epic-membership-recruitment-coordinator-badge-task-002 — Create the CoordinatorRecruitmentDashboardBloc (or Cubit) that calls ReferralAttributionService.getAggregatedStatsForMentor() for every peer mentor in the coordinator's scope. Handle loading, error, and success states. Support date-range filtering by passing the selected range to the service call and emitting updated state on filter change.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Use `bloc_concurrency` package's `restartable()` transformer on the FilterChanged event handler to implement cancellation: `on
The initial filter should default to `DateRangeFilter.lastThirtyDays()` (defined in task-001). Consider using a Cubit instead of full BLoC if the event set remains small (LoadDashboard + FilterChanged) — Cubit's method-based API is less boilerplate for this pattern.
Testing Requirements
Write bloc_test unit tests covering: (1) LoadDashboard emits [DashboardLoading, DashboardLoaded] on successful service response, (2) LoadDashboard emits [DashboardLoading, DashboardError] when service throws, (3) FilterChanged re-emits [DashboardLoading, DashboardLoaded] with updated stats, (4) rapid FilterChanged events result in only one final DashboardLoaded (restartable transformer cancels previous), (5) empty mentor list emits DashboardLoaded with empty mentorStats, (6) parallel fetch correctness — verify all mentor IDs are passed to the service. Mock ReferralAttributionService with mocktail. Do not test Supabase directly in BLoC tests — that is the service layer's concern.
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.
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.
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.