feat(flutter/admin): requests screen with approve/reject

AdminRequestsScreen renders rows from adminRequestsProvider, joining
requester user_id → username via adminUsersProvider for display
(falls back to UUID prefix when the users list hasn't loaded yet).
Approve fires immediately; Reject confirms via dialog. Both are
optimistic with rollback in the controller.

Adds AdminUsersController build-only (the read side) so the row join
works; mutation methods land in Slice 3 alongside the Users screen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 21:54:58 -04:00
parent 8bb192fe54
commit bd1bcab12b
4 changed files with 257 additions and 0 deletions
@@ -6,6 +6,7 @@ import '../api/endpoints/admin_requests.dart';
import '../api/endpoints/admin_users.dart';
import '../library/library_providers.dart' show dioProvider;
import '../models/admin_request.dart';
import '../models/admin_user.dart';
final adminRequestsApiProvider = FutureProvider<AdminRequestsApi>((ref) async {
return AdminRequestsApi(await ref.watch(dioProvider.future));
@@ -89,3 +90,19 @@ class AdminRequestsController extends AsyncNotifier<List<AdminRequest>> {
final adminRequestsProvider =
AsyncNotifierProvider<AdminRequestsController, List<AdminRequest>>(
AdminRequestsController.new);
/// Read side defined here so the Requests screen can join requester
/// UUID → username for display. Mutation methods (setAdmin,
/// setAutoApprove, resetPassword, delete) are appended in Slice 3
/// alongside the Users screen.
class AdminUsersController extends AsyncNotifier<List<AdminUser>> {
@override
Future<List<AdminUser>> build() async {
final api = await ref.watch(adminUsersApiProvider.future);
return api.list();
}
}
final adminUsersProvider =
AsyncNotifierProvider<AdminUsersController, List<AdminUser>>(
AdminUsersController.new);