266 lines
8.0 KiB
Dart
266 lines
8.0 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/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<AuditLogEntryModel> 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<AuditLogEntryModel>? 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, AuditLogsListState>(
|
|
AuditLogsListNotifier.new,
|
|
);
|
|
|
|
class AuditLogsListNotifier extends AsyncNotifier<AuditLogsListState> {
|
|
final _columnSearch = ColumnSearchPaging();
|
|
|
|
@override
|
|
Future<AuditLogsListState> build() async {
|
|
ref.keepAlive();
|
|
return _loadAll(const AuditLogListQuery(limit: 20));
|
|
}
|
|
|
|
Future<AuditLogsListState> _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<void> 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<void> 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<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 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<void> resetFilters() async {
|
|
final current = state.valueOrNull;
|
|
if (current == null) return;
|
|
await applyQuery(AuditLogListQuery(limit: current.query.limit));
|
|
}
|
|
|
|
Future<ExportFileResult?> 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<AuditLogDetailModel, String> {
|
|
@override
|
|
Future<AuditLogDetailModel> build(String arg) async {
|
|
final result = await ref.read(getAuditLogByIdUseCaseProvider)(arg);
|
|
if (result.failure != null) throw result.failure!;
|
|
return result.data!;
|
|
}
|
|
}
|