feat: Centralized Error Handling & Crash Fixes

This commit is contained in:
2026-02-11 18:52:23 +05:30
parent ea06510474
commit c1112ac01c
51 changed files with 2104 additions and 960 deletions

View File

@@ -23,43 +23,56 @@ class CoverageRepositoryImpl implements CoverageRepository {
/// Fetches shifts for a specific date.
@override
Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async {
final String? businessId =
dc.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return <CoverageShift>[];
try {
final String? businessId =
dc.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return <CoverageShift>[];
}
final DateTime start = DateTime(date.year, date.month, date.day);
final DateTime end =
DateTime(date.year, date.month, date.day, 23, 59, 59, 999);
final fdc.QueryResult<
dc.ListShiftRolesByBusinessAndDateRangeData,
dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult =
await _dataConnect
.listShiftRolesByBusinessAndDateRange(
businessId: businessId,
start: _toTimestamp(start),
end: _toTimestamp(end),
)
.execute();
final fdc.QueryResult<
dc.ListStaffsApplicationsByBusinessForDayData,
dc.ListStaffsApplicationsByBusinessForDayVariables> applicationsResult =
await _dataConnect
.listStaffsApplicationsByBusinessForDay(
businessId: businessId,
dayStart: _toTimestamp(start),
dayEnd: _toTimestamp(end),
)
.execute();
return _mapCoverageShifts(
shiftRolesResult.data.shiftRoles,
applicationsResult.data.applications,
date,
);
} catch (e) {
final String error = e.toString().toLowerCase();
if (error.contains('network') ||
error.contains('connection') ||
error.contains('unavailable') ||
error.contains('offline') ||
error.contains('socket') ||
error.contains('failed host lookup')) {
throw NetworkException(technicalMessage: 'Coverage fetch failed: $e');
}
throw ServerException(technicalMessage: 'Coverage fetch failed: $e');
}
final DateTime start = DateTime(date.year, date.month, date.day);
final DateTime end =
DateTime(date.year, date.month, date.day, 23, 59, 59, 999);
final fdc.QueryResult<
dc.ListShiftRolesByBusinessAndDateRangeData,
dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult =
await _dataConnect
.listShiftRolesByBusinessAndDateRange(
businessId: businessId,
start: _toTimestamp(start),
end: _toTimestamp(end),
)
.execute();
final fdc.QueryResult<
dc.ListStaffsApplicationsByBusinessForDayData,
dc.ListStaffsApplicationsByBusinessForDayVariables> applicationsResult =
await _dataConnect
.listStaffsApplicationsByBusinessForDay(
businessId: businessId,
dayStart: _toTimestamp(start),
dayEnd: _toTimestamp(end),
)
.execute();
return _mapCoverageShifts(
shiftRolesResult.data.shiftRoles,
applicationsResult.data.applications,
date,
);
}
/// Fetches coverage statistics for a specific date.
@@ -180,6 +193,7 @@ class CoverageRepositoryImpl implements CoverageRepository {
case dc.ApplicationStatus.REJECTED:
return CoverageWorkerStatus.rejected;
case dc.ApplicationStatus.CONFIRMED:
case dc.ApplicationStatus.ACCEPTED:
return CoverageWorkerStatus.confirmed;
case dc.ApplicationStatus.CHECKED_IN:
return CoverageWorkerStatus.checkedIn;

View File

@@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart';
import 'package:krow_core/core.dart';
import 'package:core_localization/core_localization.dart';
import '../blocs/coverage_bloc.dart';
import '../blocs/coverage_event.dart';
import '../blocs/coverage_state.dart';
@@ -57,7 +58,16 @@ class _CoveragePageState extends State<CoveragePage> {
create: (BuildContext context) => Modular.get<CoverageBloc>()
..add(CoverageLoadRequested(date: DateTime.now())),
child: Scaffold(
body: BlocBuilder<CoverageBloc, CoverageState>(
body: BlocConsumer<CoverageBloc, CoverageState>(
listener: (BuildContext context, CoverageState state) {
if (state.status == CoverageStatus.failure && state.errorMessage != null) {
UiSnackbar.show(
context,
message: translateErrorKey(state.errorMessage!),
type: UiSnackbarType.error,
);
}
},
builder: (BuildContext context, CoverageState state) {
final DateTime selectedDate = state.selectedDate ?? DateTime.now();
@@ -226,43 +236,45 @@ class _CoveragePageState extends State<CoveragePage> {
required BuildContext context,
required CoverageState state,
}) {
if (state.status == CoverageStatus.loading) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (state.shifts.isEmpty) {
if (state.status == CoverageStatus.loading) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (state.status == CoverageStatus.failure) {
return Center(
child: Padding(
padding: const EdgeInsets.all(UiConstants.space6),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(
UiIcons.warning,
size: UiConstants.space12,
color: UiColors.destructive,
),
const SizedBox(height: UiConstants.space4),
Text(
'Failed to load coverage data',
style: UiTypography.title2m.copyWith(
color: UiColors.textPrimary,
if (state.status == CoverageStatus.failure) {
return Center(
child: Padding(
padding: const EdgeInsets.all(UiConstants.space6),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(
UiIcons.error,
size: 48,
color: UiColors.error,
),
),
const SizedBox(height: UiConstants.space2),
Text(
state.errorMessage ?? 'An unknown error occurred',
style: UiTypography.body2r.copyWith(
color: UiColors.mutedForeground,
const SizedBox(height: UiConstants.space4),
Text(
state.errorMessage != null
? translateErrorKey(state.errorMessage!)
: 'An error occurred',
style: UiTypography.body1m.textError,
textAlign: TextAlign.center,
),
textAlign: TextAlign.center,
),
],
const SizedBox(height: UiConstants.space4),
UiButton.secondary(
text: 'Retry',
onPressed: () => BlocProvider.of<CoverageBloc>(context).add(
const CoverageRefreshRequested(),
),
),
],
),
),
),
);
);
}
}
return Padding(