247 lines
6.9 KiB
Dart
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;
|
|
}
|
|
}
|