bharat_erp/lib/modules/roles/presentation/providers/roles_provider.dart
2026-07-22 18:17:43 +05:30

247 lines
6.9 KiB
Dart

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<RoleCardModel> roles;
final String search;
final int page;
final int limit;
List<RoleCardModel> 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<RoleCardModel> 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<RoleCardModel>? 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<List<PermissionModuleCatalog>>((ref) async {
final result = await ref.read(getPermissionCatalogUseCaseProvider)();
if (result.failure != null) throw result.failure!;
return result.data ?? const [];
});
final rolesListProvider =
AsyncNotifierProvider<RolesListNotifier, RolesListState>(RolesListNotifier.new);
class RolesListNotifier extends AsyncNotifier<RolesListState> {
final _columnSearch = ColumnSearchPaging(defaultLimit: 10);
@override
Future<RolesListState> build() async {
ref.keepAlive();
return _load();
}
Future<RolesListState> _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<void> 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<bool> deleteRole(String id) async {
final result = await ref.read(deleteRoleUseCaseProvider)(id);
if (result.failure != null) return false;
await refresh();
return true;
}
Future<void> 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<void> 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<RolePermissionMatrix, String> {
@override
Future<RolePermissionMatrix> 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<String, bool>.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<bool> 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;
}
}