Build Admin KPI Stat Widget component
epic-admin-portal-foundation-task-009 — Implement the AdminStatWidget reusable Flutter widget that accepts a label, value, trend indicator, and accent colour. The widget displays a single KPI metric card with loading skeleton, error state, and accessible semantics label. It must conform to the design token system and WCAG 2.2 AA contrast requirements.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 6 - 158 tasks
Can start after Tier 5 completes
Implementation Notes
Keep the widget purely presentational — it must not reference any BLoC, Riverpod provider, or repository directly. The parent screen is responsible for passing the resolved value string and loading/error states. Define AdminStatTrend as a Dart enum with extension methods for icon data and semantic description string (e.g., AdminStatTrend.up.semanticLabel returns 'trending up'). Use the project's existing design token constants for all styling — do not hardcode color hex values or pixel sizes.
For the WCAG contrast requirement: place text on the card's background color (not on the accentColor); use accentColor only for decorative elements like a 4px left border. The shimmer effect should use a grey gradient animation consistent with other loading states in the app. Reference the existing AppButton or similar reusable widgets in the codebase for code style conventions.
Testing Requirements
Widget tests (flutter_test): (1) Renders label and value correctly in normal state. (2) Renders shimmer skeleton when isLoading=true and hides value text. (3) Renders errorMessage when provided and hides value text. (4) Trend.up renders upward icon; Trend.down renders downward icon; Trend.neutral renders dash.
(5) Semantics test: find semantics node with combined label string. (6) Golden tests for all three states (normal, loading, error) at 1.0 font scale. (7) Overflow test: render with a 12-character label and 8-digit value at font scale 2.0 — no overflow errors. Accessibility audit: use flutter_accessibility_test or manual VoiceOver check to confirm the Semantics label is read correctly.
Missing RLS policies on one or more tables (e.g., a newly added join table or a Supabase view) could expose cross-org data to org_admin queries, creating a GDPR-reportable data breach.
Mitigation & Contingency
Mitigation: Enumerate all tables and views accessed by admin queries before writing any policy. Create an automated test that attempts a cross-org query for each table from an org_admin JWT and asserts an empty result set.
Contingency: If a gap is discovered post-deployment, immediately disable the affected query surface and deploy a hotfix policy before re-enabling. Log the incident and notify DPO if any cross-org data was returned.
The recursive CTE for NHF's deeply nested org tree (up to 5 levels, 1,400 local chapters) may exceed the 2-second dashboard load target when resolving large subtrees on every request.
Mitigation & Contingency
Mitigation: Benchmark the recursive CTE against a synthetic NHF-scale dataset during development. Introduce a short-TTL server-side cache for subtree resolution results. Index the parent_id column on the organisations table.
Contingency: If CTE performance remains insufficient, materialise the org subtree as a precomputed closure table updated on org structure changes, and switch the RLS guard to query the closure table instead.
Incorrect JWT claim injection in AdminRlsGuard (e.g., wrong claim key name or missing refresh on org switch) could silently apply the wrong org scope, causing org_admin to see a different organisation's data without an explicit error.
Mitigation & Contingency
Mitigation: Write unit tests for the guard that verify the injected claim value against the authenticated user's org_id for every admin route. Add a server-side assertion that the claim matches the user's database record before executing any query.
Contingency: Roll back the guard to a deny-all fallback, invalidate active admin sessions, and re-issue corrected JWTs. Audit query logs to identify any sessions that received incorrect scope.