high priority medium complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

An abstract BarChartAdapter interface is defined with methods: buildBarGroups(List<MonthlyActivityData>), buildTitlesData(), buildGridData(), buildBorderData(), and onBarTouched(BarTouchResponse) — none of these expose fl_chart types in their signatures
A concrete FlChartBarChartAdapter implements BarChartAdapter and internally uses fl_chart BarChart, BarChartGroupData, BarChartRodData, and FlTitlesData
The adapter maps MonthlyActivityData (month label + count per activity type) to BarChartGroupData with one rod per activity type when a filter is active, or a single rod when unfiltered
X-axis bottom titles display abbreviated month names (Jan, Feb, … Dec) with correct locale formatting for Norwegian (nb_NO)
Y-axis left titles display integer activity counts; the top value is rounded up to the nearest 5 or 10 for visual padding
Touch callbacks invoke an onBarTapped(month, activityType, count) callback supplied to the adapter without leaking BarTouchResponse outside the adapter
Entry animation runs on first render with a 400 ms duration and easeOut curve; re-renders on data change re-trigger the animation
Grouped bars for multiple activity types use colors sourced exclusively from the design token palette — no hardcoded hex values
The adapter is registered via dependency injection (Riverpod provider or BLoC-level injection) so it can be swapped in tests
Unit tests confirm that given a list of MonthlyActivityData the adapter produces the correct number of BarChartGroupData objects and that rod counts match input counts

Technical Requirements

frameworks
Flutter
fl_chart (^0.68 or latest stable)
Riverpod or BLoC for DI
data models
MonthlyActivityData (month: String, counts: Map<String, int>)
ActivityTypeSummary
performance requirements
Adapter construction must complete in under 2 ms for datasets up to 24 months × 6 activity types (144 data points)
No widget rebuilds triggered inside the adapter — it is a pure data transformer
security requirements
No user PII is passed to or stored in the chart adapter
Design tokens are the sole source of color values — prevents accidental use of non-accessible colors

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Define the BarChartAdapter as a pure Dart abstract class in `lib/features/statistics/adapters/bar_chart_adapter.dart`. The concrete FlChartBarChartAdapter lives in the same directory and is the only file that imports `package:fl_chart/fl_chart.dart`. Expose a Riverpod `barChartAdapterProvider` (or inject via BLoC constructor) so tests can substitute a fake adapter. For grouped bars, order rods consistently by activity type ID to avoid color flicker across rebuilds.

Retrieve all colors via `AppColors.activityTypeColor(typeId)` design token accessor — never use Color(0x…) literals. The animation controller should be owned by the consuming widget (MonthlyActivityBarChart), not the adapter; the adapter only returns static configuration. Use `BarChartData.copyWith` for incremental updates to avoid full data reconstruction on filter changes.

Testing Requirements

Unit tests (flutter_test): (1) Adapter maps empty input to empty BarGroups without throwing. (2) Adapter maps 12 months of single-type data to 12 single-rod groups with correct rod values. (3) Adapter maps 12 months of 3-type data to 12 three-rod groups. (4) Touch callback receives correct month/type/count when onBarTouched fires.

(5) Color resolution uses design tokens — mock the token provider and assert no raw hex strings appear in rod data. Widget golden test: adapter wired to a test BarChart renders without overflow on a 375 × 300 canvas. No integration tests required at adapter level.

Component
Monthly Activity Bar Chart
ui medium
Epic Risks (4)
medium impact medium prob technical

PeerMentorStatsList must handle rosters of 40+ mentors efficiently. A naive ListView implementation that re-renders all rows on filter change may cause frame drops on mid-range devices, degrading the experience for coordinators managing large chapters.

Mitigation & Contingency

Mitigation: Use ListView.builder with const constructors for row widgets. Profile the list with Flutter DevTools on a release build against a 60-mentor dataset before submitting for review. Implement sort in the BLoC layer, not in the widget.

Contingency: If frame drops persist, introduce pagination (load 20 mentors, scroll to load more) and add a search filter to reduce visible row count in practice.

medium impact low prob integration

The ActivityTypeDonutChart must render org-configured activity type labels from the org-labels system. If the terminology provider is not available or returns stale labels, chart segments will display raw key strings instead of human-readable organisation-specific names, confusing coordinators.

Mitigation & Contingency

Mitigation: Always resolve labels through the OrganizationLabelsProvider before passing data to the donut chart widget. Implement a fallback that formats the raw key as a readable string (e.g., 'peer_support' → 'Peer Support') if the provider returns null.

Contingency: If the org-labels system is unavailable, display the formatted fallback label and log a warning. Do not block chart rendering on label resolution — render with fallbacks immediately.

high impact high prob technical

fl_chart widgets do not natively expose semantic labels for individual bars and donut segments. Without explicit Semantics wrappers, VoiceOver and TalkBack users will receive no meaningful chart information, failing accessibility requirements critical for Blindeforbundet and NHF deployments.

Mitigation & Contingency

Mitigation: Wrap each fl_chart widget in a Semantics widget with a descriptive label summarising the chart data. Implement the data table fallback toggle from the start, not as an afterthought. Validate with VoiceOver on iOS and TalkBack on Android during development.

Contingency: If fl_chart's rendering pipeline prevents semantic overlay from working reliably, replace chart widgets with a custom Canvas-based implementation that has full semantic control, or use a different charting library with better accessibility support.

medium impact medium prob technical

The period filter state must be preserved when navigating away from the dashboard (e.g., drilling into a mentor's detail screen and pressing back). If the BLoC is re-created on navigation, the filter resets to default, forcing coordinators to re-select their period every time they drill down, degrading operational workflow efficiency.

Mitigation & Contingency

Mitigation: Provide the StatsBloc at a route level above the dashboard screen using a BlocProvider scoped to the statistics navigation shell, not inside the screen widget itself. Verify persistence with a widget integration test that navigates away and back.

Contingency: If route-level scoping is not achievable within the current navigation architecture, persist the last-used filter to a local session store and restore it on screen re-entry.