import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/utils/column_search_paging.dart'; import '../../../../core/utils/table_search.dart'; import '../../../../shared/models/permission_matrix_models.dart'; import '../../../../shared/models/user_management_models.dart'; import '../../data/repositories/role_repository_impl.dart'; import '../../domain/usecases/role_usecases.dart'; class RolesListState { const RolesListState({ this.roles = const [], this.search = '', this.page = 1, this.limit = 10, }); final List roles; final String search; final int page; final int limit; List get filteredRoles { // Roles are already filtered by the API when [search] is set. return roles; } int get total => filteredRoles.length; int get totalPages { if (total == 0) return 1; return (total / limit).ceil(); } List get pagedRoles { final all = filteredRoles; if (all.isEmpty) return const []; final start = ((page - 1) * limit).clamp(0, all.length); final end = (start + limit).clamp(0, all.length); return all.sublist(start, end); } RolesListState copyWith({ List? roles, String? search, int? page, int? limit, }) { return RolesListState( roles: roles ?? this.roles, search: search ?? this.search, page: page ?? this.page, limit: limit ?? this.limit, ); } } final getRoleCardsUseCaseProvider = Provider((ref) { return GetRoleCardsUseCase(ref.watch(roleRepositoryProvider)); }); final deleteRoleUseCaseProvider = Provider((ref) { return DeleteRoleUseCase(ref.watch(roleRepositoryProvider)); }); final getPermissionCatalogUseCaseProvider = Provider((ref) { return GetPermissionCatalogUseCase(ref.watch(roleRepositoryProvider)); }); final getPermissionMatrixUseCaseProvider = Provider((ref) { return GetPermissionMatrixUseCase(ref.watch(roleRepositoryProvider)); }); final savePermissionMatrixUseCaseProvider = Provider((ref) { return SavePermissionMatrixUseCase(ref.watch(roleRepositoryProvider)); }); final permissionCatalogProvider = FutureProvider>((ref) async { final result = await ref.read(getPermissionCatalogUseCaseProvider)(); if (result.failure != null) throw result.failure!; return result.data ?? const []; }); final rolesListProvider = AsyncNotifierProvider(RolesListNotifier.new); class RolesListNotifier extends AsyncNotifier { final _columnSearch = ColumnSearchPaging(defaultLimit: 10); @override Future build() async { ref.keepAlive(); return _load(); } Future _load({String? search}) async { final result = await ref.read(getRoleCardsUseCaseProvider)(search: search); if (result.failure != null) throw result.failure!; return RolesListState(roles: result.data ?? [], search: search ?? ''); } Future refresh() async { final current = state.valueOrNull; if (current == null) { state = const AsyncLoading(); } try { state = AsyncData(await _load(search: current?.search)); } catch (e, st) { state = AsyncError(e, st); } } Future deleteRole(String id) async { final result = await ref.read(deleteRoleUseCaseProvider)(id); if (result.failure != null) return false; await refresh(); return true; } Future setSearch(String search) async { final current = state.valueOrNull; if (current == null) return; final normalized = TableSearch.normalize(search); state = const AsyncLoading(); try { final loaded = await _load(search: normalized); state = AsyncData(loaded.copyWith(page: 1, limit: current.limit)); } catch (e, st) { state = AsyncError(e, st); } } Future ensureColumnSearchDataset() async { final current = state.valueOrNull; if (current == null) return; final limit = _columnSearch.beginFullDataset( currentLimit: current.limit, total: current.total, ); if (limit == null) return; state = const AsyncLoading(); try { final loaded = await _load(search: ''); state = AsyncData( loaded.copyWith( page: 1, limit: loaded.total > 0 ? loaded.total : limit, ), ); } catch (e, st) { state = AsyncError(e, st); } } void clearColumnSearchDataset() { final current = state.valueOrNull; if (current == null) return; final limit = _columnSearch.endFullDataset(); if (limit == null) return; state = AsyncData(current.copyWith(page: 1, limit: limit, search: '')); } void setPage(int page) { final current = state.valueOrNull; if (current == null) return; state = AsyncData(current.copyWith(page: page)); } void setPageSize(int limit) { final current = state.valueOrNull; if (current == null) return; state = AsyncData(current.copyWith(limit: limit, page: 1)); } } final permissionMatrixProvider = AsyncNotifierProvider.family< PermissionMatrixNotifier, RolePermissionMatrix, String>(PermissionMatrixNotifier.new); class PermissionMatrixNotifier extends FamilyAsyncNotifier { @override Future build(String arg) async { final useCase = ref.read(getPermissionMatrixUseCaseProvider); final result = await useCase(arg); if (result.failure != null) throw result.failure!; return result.data!; } void toggleAction(String moduleId, String action, bool value) { final current = state.valueOrNull; if (current == null) return; final normalizedAction = action.toLowerCase(); final updated = current.modules.map((row) { if (row.moduleId != moduleId) return row; if (!isPermissionActionApplicable( row.code, action, columnActions: current.actionColumns, )) { return row; } final granted = Map.from(row.granted); granted[normalizedAction] = value; // CREATE / EDIT / DELETE / APPROVE / EXPORT imply VIEW. const impliesView = { 'create', 'edit', 'delete', 'approve', 'export', }; if (value && impliesView.contains(normalizedAction) && isPermissionActionApplicable( row.code, 'view', columnActions: current.actionColumns, )) { granted['view'] = true; } return row.copyWith(granted: granted); }).toList(); state = AsyncData(current.copyWith(modules: updated)); } Future save() async { final current = state.valueOrNull; if (current == null) return false; final useCase = ref.read(savePermissionMatrixUseCaseProvider); final result = await useCase(arg, current); if (result.failure != null) return false; ref.invalidateSelf(); await future; return true; } }