critical priority medium complexity frontend pending frontend specialist Tier 3

Acceptance Criteria

ActivityTypeFormScreen is a StatefulWidget located at lib/screens/admin/activity_type_form_screen.dart (or equivalent admin screens directory)
GoRouter route `/admin/activity-types/new` renders the screen in creation mode with all fields empty or at defaults
GoRouter route `/admin/activity-types/:activityTypeId/edit` renders the screen in edit mode with all fields pre-populated from the fetched ActivityType
A role guard redirects non-coordinator and non-admin users to the no-access screen before the form renders
In edit mode, the screen fetches the ActivityType from Supabase using the activityTypeId path parameter and displays a loading indicator while fetching
If the edit-mode fetch fails (e.g., 404 or network error), an error state with a retry action is shown instead of a blank or broken form
The AppBar or page header shows 'New Activity Type' in creation mode and 'Edit [displayName]' in edit mode
A cancel/back action navigates back to the activity types list without saving
The screen uses the project's existing design token system for spacing, typography, and colours — no hardcoded pixel values or colour literals
The BLoC for this screen (ActivityTypeFormBloc) is provided via BlocProvider at the screen level — not globally
The screen is accessible via the coordinator admin settings navigation (task-001 admin list screen links to this screen)
Keyboard safe area is respected — the form scrolls when the keyboard is open, no fields are obscured

Technical Requirements

frameworks
Flutter
BLoC
GoRouter
apis
Supabase REST API (GET /activity_types/:id for edit mode pre-population)
data models
ActivityType (task-002)
BufdirCategory (task-003)
performance requirements
Initial screen render (skeleton or loading state) must appear within one frame — do not block UI on async fetch
Edit mode fetch must complete and populate fields within 2 seconds on a standard mobile connection
security requirements
Role guard must evaluate server-side role from the JWT claim — not a locally cached role that could be stale
activityTypeId from the route must be validated as a valid UUID before sending to Supabase to prevent injection
ui components
AppBar / PageHeader widget (existing project component)
SingleChildScrollView with KeyboardDismissOnScroll
CircularProgressIndicator (loading state)
ErrorStateWidget with retry button (existing or new)
BlocProvider<ActivityTypeFormBloc>

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Mirror the pattern of existing admin form screens in the project (e.g., edit contact screen) — use the same BlocProvider placement, same GoRouter extra/pathParameter conventions, and same loading/error widget approach. For the role guard, use GoRouter's redirect callback or a dedicated RoleGuard widget already in the project — do not duplicate the guard logic. In edit mode, pass the full ActivityType object as a GoRouter `extra` if navigating from the list screen (avoids a redundant network fetch); only fall back to fetching by ID when navigating via deep link. Use a CustomScrollView with SliverList so the AppBar collapses naturally on scroll — match the UX of other form screens.

The ActivityTypeFormBloc should be initialised with the ActivityType? argument in initState, not in the constructor, to support GoRouter's hot reload correctly.

Testing Requirements

Write widget tests in test/screens/admin/activity_type_form_screen_test.dart: (1) creation mode renders with correct AppBar title 'New Activity Type'; (2) edit mode renders with AppBar title containing the activity type display name after fetch; (3) a loading indicator is shown while the edit-mode fetch is in progress; (4) an error message and retry button are shown when the fetch fails; (5) a user without coordinator role is redirected to the no-access screen; (6) tapping cancel triggers navigation pop without saving; (7) the BLoC is properly provided and the screen rebuilds on state changes. Use a mock Supabase client for these widget tests. Write a GoRouter integration test asserting both routes resolve to ActivityTypeFormScreen.

Component
Activity Type Form Screen
ui medium
Epic Risks (3)
high impact medium prob dependency

The Bufdir reporting category list is defined externally by Bufdir and may change between reporting years. If the dropdown in ActivityTypeFormScreen is hardcoded, existing activity type mappings could become invalid after a Bufdir schema update, breaking export validation for all organisations.

Mitigation & Contingency

Mitigation: Store the valid Bufdir category list in a Supabase configuration table (bufdir_categories) rather than as a Dart constant, so it can be updated by an admin without a mobile app release. Load the list in the form screen via a lightweight repository call cached locally.

Contingency: If the Bufdir category list cannot be externalised before the admin screen ships, expose a manual override field that allows coordinators to enter a raw Bufdir category code as a fallback, and schedule the configuration table migration as a follow-up task.

medium impact medium prob technical

Reusing ActivityTypeFormScreen for both creation and editing requires careful Riverpod provider scoping. If the form provider is not properly reset between navigation events, stale values from a previously edited type may pre-populate a new creation form, leading to incorrect data being saved.

Mitigation & Contingency

Mitigation: Scope the form state provider to the route using Riverpod's autoDispose modifier, ensuring the state is torn down when the screen is popped. Write a widget test that navigates to edit type A, pops, navigates to create new, and asserts all fields are empty.

Contingency: If provider scoping proves complex with the current router setup, fall back to separate widget implementations for create and edit that share a common form widget but maintain independent provider instances.

high impact low prob integration

Archiving an activity type must not break historical Bufdir export queries that filter activities by type. If the export pipeline performs an INNER JOIN against only active activity types, archived types will cause historical activities to be silently excluded from exports, producing incorrect reporting data.

Mitigation & Contingency

Mitigation: Audit all downstream query builders (Bufdir export, stats aggregation) before shipping the archive feature to confirm they join against all activity types regardless of is_active status. Add an integration test that archives a type, then asserts historical activity records for that type still appear in export queries.

Contingency: If a downstream query is discovered to filter on is_active post-launch, apply a targeted Supabase view fix that unions active and archived types for export contexts without requiring a mobile app update.