351 lines
10 KiB
Dart
351 lines
10 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/export_file_result.dart';
|
|
import '../../../../shared/models/user_management_models.dart';
|
|
import '../../data/repositories/user_repository_impl.dart';
|
|
import '../../domain/usecases/user_usecases.dart';
|
|
|
|
class UsersListState {
|
|
const UsersListState({
|
|
this.users = const [],
|
|
this.summary,
|
|
this.filters,
|
|
this.query = const UserListQuery(),
|
|
this.total = 0,
|
|
this.totalPages = 1,
|
|
this.isRefreshing = false,
|
|
this.isExporting = false,
|
|
this.actionError,
|
|
this.actionSuccess,
|
|
});
|
|
|
|
final List<ManagedUserModel> users;
|
|
final UserSummaryModel? summary;
|
|
final UserFiltersModel? filters;
|
|
final UserListQuery query;
|
|
final int total;
|
|
final int totalPages;
|
|
final bool isRefreshing;
|
|
final bool isExporting;
|
|
final String? actionError;
|
|
final String? actionSuccess;
|
|
|
|
UsersListState copyWith({
|
|
List<ManagedUserModel>? users,
|
|
UserSummaryModel? summary,
|
|
UserFiltersModel? filters,
|
|
UserListQuery? query,
|
|
int? total,
|
|
int? totalPages,
|
|
bool? isRefreshing,
|
|
bool? isExporting,
|
|
String? actionError,
|
|
String? actionSuccess,
|
|
bool clearMessages = false,
|
|
}) {
|
|
return UsersListState(
|
|
users: users ?? this.users,
|
|
summary: summary ?? this.summary,
|
|
filters: filters ?? this.filters,
|
|
query: query ?? this.query,
|
|
total: total ?? this.total,
|
|
totalPages: totalPages ?? this.totalPages,
|
|
isRefreshing: isRefreshing ?? this.isRefreshing,
|
|
isExporting: isExporting ?? this.isExporting,
|
|
actionError: clearMessages ? null : actionError ?? this.actionError,
|
|
actionSuccess: clearMessages ? null : actionSuccess ?? this.actionSuccess,
|
|
);
|
|
}
|
|
}
|
|
|
|
final getUsersUseCaseProvider = Provider((ref) {
|
|
return GetUsersUseCase(ref.watch(userRepositoryProvider));
|
|
});
|
|
|
|
final getUserSummaryUseCaseProvider = Provider((ref) {
|
|
return GetUserSummaryUseCase(ref.watch(userRepositoryProvider));
|
|
});
|
|
|
|
final getUserFiltersUseCaseProvider = Provider((ref) {
|
|
return GetUserFiltersUseCase(ref.watch(userRepositoryProvider));
|
|
});
|
|
|
|
final createUserUseCaseProvider = Provider((ref) {
|
|
return CreateUserUseCase(ref.watch(userRepositoryProvider));
|
|
});
|
|
|
|
final updateUserUseCaseProvider = Provider((ref) {
|
|
return UpdateUserUseCase(ref.watch(userRepositoryProvider));
|
|
});
|
|
|
|
final deleteUserUseCaseProvider = Provider((ref) {
|
|
return DeleteUserUseCase(ref.watch(userRepositoryProvider));
|
|
});
|
|
|
|
final updateProfileUseCaseProvider = Provider((ref) {
|
|
return UpdateProfileUseCase(ref.watch(userRepositoryProvider));
|
|
});
|
|
|
|
final getUserByIdUseCaseProvider = Provider((ref) {
|
|
return GetUserByIdUseCase(ref.watch(userRepositoryProvider));
|
|
});
|
|
|
|
final exportUsersUseCaseProvider = Provider((ref) {
|
|
return ExportUsersUseCase(ref.watch(userRepositoryProvider));
|
|
});
|
|
|
|
final usersListProvider =
|
|
AsyncNotifierProvider<UsersListNotifier, UsersListState>(UsersListNotifier.new);
|
|
|
|
class UsersListNotifier extends AsyncNotifier<UsersListState> {
|
|
final _columnSearch = ColumnSearchPaging(defaultLimit: 10);
|
|
|
|
@override
|
|
Future<UsersListState> build() async {
|
|
ref.keepAlive();
|
|
return _loadAll(const UserListQuery(limit: 10));
|
|
}
|
|
|
|
Future<UsersListState> _loadAll(UserListQuery query) async {
|
|
final summaryResult = await ref.read(getUserSummaryUseCaseProvider)();
|
|
final filtersResult = await ref.read(getUserFiltersUseCaseProvider)();
|
|
final usersResult = await ref.read(getUsersUseCaseProvider)(query);
|
|
|
|
if (usersResult.failure != null) throw usersResult.failure!;
|
|
|
|
final page = usersResult.data!;
|
|
return UsersListState(
|
|
users: page.items,
|
|
summary: summaryResult.data,
|
|
filters: filtersResult.data,
|
|
query: query,
|
|
total: page.total,
|
|
totalPages: page.totalPages,
|
|
);
|
|
}
|
|
|
|
Future<void> refresh() async {
|
|
final current = state.valueOrNull ?? const UsersListState();
|
|
state = AsyncData(current.copyWith(isRefreshing: true, clearMessages: true));
|
|
try {
|
|
state = AsyncData(await _loadAll(current.query));
|
|
} catch (e, st) {
|
|
state = AsyncError(e, st);
|
|
}
|
|
}
|
|
|
|
Future<void> applyQuery(UserListQuery query) async {
|
|
final previous = state.valueOrNull;
|
|
if (previous == null) {
|
|
state = const AsyncLoading();
|
|
}
|
|
try {
|
|
state = AsyncData(await _loadAll(query));
|
|
} catch (e, st) {
|
|
state = AsyncError(e, st);
|
|
}
|
|
}
|
|
|
|
void setSearch(String search) {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return;
|
|
applyQuery(current.query.copyWith(search: TableSearch.normalize(search), page: 1));
|
|
}
|
|
|
|
Future<void> ensureColumnSearchDataset() async {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return;
|
|
final limit = _columnSearch.beginFullDataset(
|
|
currentLimit: current.query.limit,
|
|
total: current.total,
|
|
);
|
|
if (limit == null) return;
|
|
await applyQuery(
|
|
current.query.copyWith(search: null, page: 1, limit: limit),
|
|
);
|
|
}
|
|
|
|
void clearColumnSearchDataset() {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return;
|
|
final limit = _columnSearch.endFullDataset();
|
|
if (limit == null) return;
|
|
applyQuery(current.query.copyWith(search: null, page: 1, limit: limit));
|
|
}
|
|
|
|
void setPage(int page) {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return;
|
|
applyQuery(current.query.copyWith(page: page));
|
|
}
|
|
|
|
void setPageSize(int limit) {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return;
|
|
applyQuery(current.query.copyWith(limit: limit, page: 1));
|
|
}
|
|
|
|
void setSort(String sortBy, String sortOrder) {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return;
|
|
applyQuery(current.query.copyWith(sortBy: sortBy, sortOrder: sortOrder, page: 1));
|
|
}
|
|
|
|
void setStatusFilter(String? status) {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return;
|
|
applyQuery(current.query.copyWith(status: status, page: 1));
|
|
}
|
|
|
|
void setRoleFilter(int? roleId) {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return;
|
|
applyQuery(current.query.copyWith(roleId: roleId, page: 1));
|
|
}
|
|
|
|
void setDepartmentFilter(int? departmentId) {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return;
|
|
applyQuery(current.query.copyWith(departmentId: departmentId, page: 1));
|
|
}
|
|
|
|
Future<void> resetFilters() async {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return;
|
|
|
|
final clearedQuery = UserListQuery(limit: current.query.limit);
|
|
final query = current.query;
|
|
final hasActiveFilters = (query.search?.isNotEmpty ?? false) ||
|
|
query.roleId != null ||
|
|
query.departmentId != null ||
|
|
query.status != null;
|
|
|
|
if (!hasActiveFilters &&
|
|
query.page == clearedQuery.page &&
|
|
query.limit == clearedQuery.limit) {
|
|
return;
|
|
}
|
|
|
|
await applyQuery(clearedQuery);
|
|
}
|
|
|
|
Future<ExportFileResult?> exportUsers() async {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return null;
|
|
|
|
state = AsyncData(current.copyWith(isExporting: true, clearMessages: true));
|
|
|
|
final result = await ref.read(exportUsersUseCaseProvider)(current.query);
|
|
final latest = state.valueOrNull ?? current;
|
|
|
|
if (result.failure != null) {
|
|
state = AsyncData(
|
|
latest.copyWith(
|
|
isExporting: false,
|
|
actionError: result.failure!.message,
|
|
),
|
|
);
|
|
return null;
|
|
}
|
|
|
|
state = AsyncData(latest.copyWith(isExporting: false));
|
|
return result.data;
|
|
}
|
|
|
|
Future<bool> deactivateUser(String id) async {
|
|
final result = await ref.read(deleteUserUseCaseProvider)(id);
|
|
if (result.failure != null) {
|
|
final current = state.valueOrNull;
|
|
if (current != null) {
|
|
state = AsyncData(
|
|
current.copyWith(actionError: result.failure!.message),
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
await refresh();
|
|
return true;
|
|
}
|
|
|
|
Future<bool> toggleUserStatus(ManagedUserModel user) async {
|
|
final updateUseCase = ref.read(updateUserUseCaseProvider);
|
|
final activating = user.status != 'active';
|
|
final result = await updateUseCase(
|
|
user.id,
|
|
UpdateUserRequest(
|
|
status: activating ? 'active' : 'inactive',
|
|
isActive: activating,
|
|
),
|
|
);
|
|
if (result.failure != null) {
|
|
final current = state.valueOrNull;
|
|
if (current != null) {
|
|
state = AsyncData(
|
|
current.copyWith(actionError: result.failure!.message),
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
await refresh();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
final userDetailProvider = AsyncNotifierProvider.family<UserDetailNotifier, ManagedUserModel, String>(
|
|
UserDetailNotifier.new,
|
|
);
|
|
|
|
class UserDetailNotifier extends FamilyAsyncNotifier<ManagedUserModel, String> {
|
|
@override
|
|
Future<ManagedUserModel> build(String arg) async {
|
|
final useCase = ref.read(getUserByIdUseCaseProvider);
|
|
final result = await useCase(arg);
|
|
if (result.failure != null) throw result.failure!;
|
|
return result.data!;
|
|
}
|
|
|
|
Future<bool> deactivate() async {
|
|
final deleteUseCase = ref.read(deleteUserUseCaseProvider);
|
|
final result = await deleteUseCase(arg);
|
|
if (result.failure != null) return false;
|
|
ref.invalidateSelf();
|
|
await future;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
final userFormProvider = AsyncNotifierProvider.family<UserFormNotifier, ManagedUserModel?, String?>(
|
|
UserFormNotifier.new,
|
|
);
|
|
|
|
class UserFormNotifier extends FamilyAsyncNotifier<ManagedUserModel?, String?> {
|
|
@override
|
|
Future<ManagedUserModel?> build(String? arg) async {
|
|
if (arg == null) return null;
|
|
final useCase = ref.read(getUserByIdUseCaseProvider);
|
|
final result = await useCase(arg);
|
|
if (result.failure != null) throw result.failure!;
|
|
return result.data;
|
|
}
|
|
|
|
Future<ManagedUserModel?> submitCreate(CreateUserRequest request) async {
|
|
final useCase = ref.read(createUserUseCaseProvider);
|
|
final result = await useCase(request);
|
|
if (result.failure != null) throw result.failure!;
|
|
ref.invalidate(usersListProvider);
|
|
return result.data;
|
|
}
|
|
|
|
Future<ManagedUserModel?> submitUpdate(String id, UpdateUserRequest request) async {
|
|
final useCase = ref.read(updateUserUseCaseProvider);
|
|
final result = await useCase(id, request);
|
|
if (result.failure != null) throw result.failure!;
|
|
ref.invalidate(usersListProvider);
|
|
ref.invalidate(userDetailProvider(id));
|
|
return result.data;
|
|
}
|
|
}
|