import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/utils/column_search_paging.dart'; import '../../../../core/utils/table_search.dart'; import '../../../../shared/models/audit_log_model.dart'; import '../../../../shared/models/export_file_result.dart'; import '../../data/repositories/audit_repository_impl.dart'; import '../../domain/usecases/audit_usecases.dart'; class AuditLogsListState { const AuditLogsListState({ this.items = const [], this.filters = const AuditLogFilterOptions(), this.query = const AuditLogListQuery(), this.total = 0, this.totalPages = 1, this.filtersRequired = true, this.isRefreshing = false, this.isExporting = false, this.actionError, this.actionSuccess, }); final List items; final AuditLogFilterOptions filters; final AuditLogListQuery query; final int total; final int totalPages; final bool filtersRequired; final bool isRefreshing; final bool isExporting; final String? actionError; final String? actionSuccess; AuditLogsListState copyWith({ List? items, AuditLogFilterOptions? filters, AuditLogListQuery? query, int? total, int? totalPages, bool? filtersRequired, bool? isRefreshing, bool? isExporting, String? actionError, String? actionSuccess, bool clearMessages = false, }) { return AuditLogsListState( items: items ?? this.items, filters: filters ?? this.filters, query: query ?? this.query, total: total ?? this.total, totalPages: totalPages ?? this.totalPages, filtersRequired: filtersRequired ?? this.filtersRequired, isRefreshing: isRefreshing ?? this.isRefreshing, isExporting: isExporting ?? this.isExporting, actionError: clearMessages ? null : actionError ?? this.actionError, actionSuccess: clearMessages ? null : actionSuccess ?? this.actionSuccess, ); } } final getAuditFiltersUseCaseProvider = Provider((ref) { return GetAuditFiltersUseCase(ref.watch(auditRepositoryProvider)); }); final getAuditLogsUseCaseProvider = Provider((ref) { return GetAuditLogsUseCase(ref.watch(auditRepositoryProvider)); }); final getAuditLogByIdUseCaseProvider = Provider((ref) { return GetAuditLogByIdUseCase(ref.watch(auditRepositoryProvider)); }); final exportAuditLogsUseCaseProvider = Provider((ref) { return ExportAuditLogsUseCase(ref.watch(auditRepositoryProvider)); }); final auditLogsListProvider = AsyncNotifierProvider( AuditLogsListNotifier.new, ); class AuditLogsListNotifier extends AsyncNotifier { final _columnSearch = ColumnSearchPaging(); @override Future build() async { ref.keepAlive(); return _loadAll(const AuditLogListQuery(limit: 20)); } Future _loadAll(AuditLogListQuery query) async { final filtersResult = await ref.read(getAuditFiltersUseCaseProvider)(); final listResult = await ref.read(getAuditLogsUseCaseProvider)(query); if (listResult.failure != null) throw listResult.failure!; final page = listResult.data!; return AuditLogsListState( items: page.items, filters: filtersResult.data ?? const AuditLogFilterOptions(), query: query, total: page.total, totalPages: page.totalPages, filtersRequired: page.filtersRequired || !query.hasActiveFilter, ); } Future refresh() async { final current = state.valueOrNull ?? const AuditLogsListState(); state = AsyncData(current.copyWith(isRefreshing: true, clearMessages: true)); try { state = AsyncData(await _loadAll(current.query)); } catch (e, st) { state = AsyncError(e, st); } } Future applyQuery(AuditLogListQuery query) async { final previous = state.valueOrNull; if (previous == null) { state = const AsyncLoading(); } else { state = AsyncData(previous.copyWith(query: query, clearMessages: true)); } try { final filters = previous?.filters ?? const AuditLogFilterOptions(); final listResult = await ref.read(getAuditLogsUseCaseProvider)(query); if (listResult.failure != null) throw listResult.failure!; final page = listResult.data!; state = AsyncData( AuditLogsListState( items: page.items, filters: filters, query: query, total: page.total, totalPages: page.totalPages, filtersRequired: page.filtersRequired || !query.hasActiveFilter, ), ); } 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 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 setTableName(String? tableName) { final current = state.valueOrNull; if (current == null) return; applyQuery(current.query.copyWith(tableName: tableName, page: 1)); } void setAction(String? action) { final current = state.valueOrNull; if (current == null) return; applyQuery(current.query.copyWith(action: action, page: 1)); } void setPerformedBy(int? performedBy) { final current = state.valueOrNull; if (current == null) return; applyQuery(current.query.copyWith(performedBy: performedBy, page: 1)); } void setDateRange(DateTime? dateFrom, DateTime? dateTo) { final current = state.valueOrNull; if (current == null) return; applyQuery( current.query.copyWith(dateFrom: dateFrom, dateTo: dateTo, page: 1), ); } Future resetFilters() async { final current = state.valueOrNull; if (current == null) return; await applyQuery(AuditLogListQuery(limit: current.query.limit)); } Future exportLogs() async { final current = state.valueOrNull; if (current == null) return null; if (!current.query.hasActiveFilter) { state = AsyncData( current.copyWith( actionError: 'Apply at least one filter before exporting.', ), ); return null; } state = AsyncData(current.copyWith(isExporting: true, clearMessages: true)); final result = await ref.read(exportAuditLogsUseCaseProvider)(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; } } final auditLogDetailProvider = AsyncNotifierProvider.family< AuditLogDetailNotifier, AuditLogDetailModel, String>( AuditLogDetailNotifier.new, ); class AuditLogDetailNotifier extends FamilyAsyncNotifier { @override Future build(String arg) async { final result = await ref.read(getAuditLogByIdUseCaseProvider)(arg); if (result.failure != null) throw result.failure!; return result.data!; } }