feat: Centralized Error Handling & Crash Fixes
This commit is contained in:
@@ -14,6 +14,7 @@ class AvailabilityRepositoryImpl
|
||||
implements AvailabilityRepository {
|
||||
final dc.ExampleConnector _dataConnect;
|
||||
final firebase.FirebaseAuth _firebaseAuth;
|
||||
String? _cachedStaffId;
|
||||
|
||||
AvailabilityRepositoryImpl({
|
||||
required dc.ExampleConnector dataConnect,
|
||||
@@ -22,6 +23,8 @@ class AvailabilityRepositoryImpl
|
||||
_firebaseAuth = firebaseAuth;
|
||||
|
||||
Future<String> _getStaffId() async {
|
||||
if (_cachedStaffId != null) return _cachedStaffId!;
|
||||
|
||||
final firebase.User? user = _firebaseAuth.currentUser;
|
||||
if (user == null) {
|
||||
throw NotAuthenticatedException(
|
||||
@@ -33,7 +36,8 @@ class AvailabilityRepositoryImpl
|
||||
if (result.data.staffs.isEmpty) {
|
||||
throw const ServerException(technicalMessage: 'Staff profile not found');
|
||||
}
|
||||
return result.data.staffs.first.id;
|
||||
_cachedStaffId = result.data.staffs.first.id;
|
||||
return _cachedStaffId!;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -149,48 +153,51 @@ class AvailabilityRepositoryImpl
|
||||
final Set<dc.DayOfWeek> processedDays = {};
|
||||
final List<DayAvailability> resultDays = [];
|
||||
|
||||
final List<Future<void>> futures = [];
|
||||
|
||||
for (int i = 0; i <= dayCount; i++) {
|
||||
final DateTime date = start.add(Duration(days: i));
|
||||
final dc.DayOfWeek dow = _toBackendDay(date.weekday);
|
||||
|
||||
// Logic to determine if enabled based on type
|
||||
bool enableDay = false;
|
||||
if (type == 'all') enableDay = true;
|
||||
else if (type == 'clear') enableDay = false;
|
||||
else if (type == 'weekdays') {
|
||||
enableDay = (dow != dc.DayOfWeek.SATURDAY && dow != dc.DayOfWeek.SUNDAY);
|
||||
} else if (type == 'weekends') {
|
||||
enableDay = (dow == dc.DayOfWeek.SATURDAY || dow == dc.DayOfWeek.SUNDAY);
|
||||
}
|
||||
final DateTime date = start.add(Duration(days: i));
|
||||
final dc.DayOfWeek dow = _toBackendDay(date.weekday);
|
||||
|
||||
// Only update backend once per DayOfWeek (since it's recurring)
|
||||
// to avoid redundant calls if range > 1 week.
|
||||
if (!processedDays.contains(dow)) {
|
||||
processedDays.add(dow);
|
||||
|
||||
final dc.AvailabilityStatus status = _boolToStatus(enableDay);
|
||||
|
||||
await Future.wait([
|
||||
_upsertSlot(staffId, dow, dc.AvailabilitySlot.MORNING, status),
|
||||
_upsertSlot(staffId, dow, dc.AvailabilitySlot.AFTERNOON, status),
|
||||
_upsertSlot(staffId, dow, dc.AvailabilitySlot.EVENING, status),
|
||||
]);
|
||||
}
|
||||
|
||||
// Prepare return object
|
||||
final slots = [
|
||||
AvailabilityAdapter.fromPrimitive('MORNING', isAvailable: enableDay),
|
||||
AvailabilityAdapter.fromPrimitive('AFTERNOON', isAvailable: enableDay),
|
||||
AvailabilityAdapter.fromPrimitive('EVENING', isAvailable: enableDay),
|
||||
];
|
||||
// Logic to determine if enabled based on type
|
||||
bool enableDay = false;
|
||||
if (type == 'all') {
|
||||
enableDay = true;
|
||||
} else if (type == 'clear') {
|
||||
enableDay = false;
|
||||
} else if (type == 'weekdays') {
|
||||
enableDay = (dow != dc.DayOfWeek.SATURDAY && dow != dc.DayOfWeek.SUNDAY);
|
||||
} else if (type == 'weekends') {
|
||||
enableDay = (dow == dc.DayOfWeek.SATURDAY || dow == dc.DayOfWeek.SUNDAY);
|
||||
}
|
||||
|
||||
resultDays.add(DayAvailability(
|
||||
date: date,
|
||||
isAvailable: enableDay,
|
||||
slots: slots,
|
||||
));
|
||||
// Only update backend once per DayOfWeek (since it's recurring)
|
||||
if (!processedDays.contains(dow)) {
|
||||
processedDays.add(dow);
|
||||
final dc.AvailabilityStatus status = _boolToStatus(enableDay);
|
||||
|
||||
futures.add(_upsertSlot(staffId, dow, dc.AvailabilitySlot.MORNING, status));
|
||||
futures.add(_upsertSlot(staffId, dow, dc.AvailabilitySlot.AFTERNOON, status));
|
||||
futures.add(_upsertSlot(staffId, dow, dc.AvailabilitySlot.EVENING, status));
|
||||
}
|
||||
|
||||
// Prepare return object
|
||||
final slots = [
|
||||
AvailabilityAdapter.fromPrimitive('MORNING', isAvailable: enableDay),
|
||||
AvailabilityAdapter.fromPrimitive('AFTERNOON', isAvailable: enableDay),
|
||||
AvailabilityAdapter.fromPrimitive('EVENING', isAvailable: enableDay),
|
||||
];
|
||||
|
||||
resultDays.add(DayAvailability(
|
||||
date: date,
|
||||
isAvailable: enableDay,
|
||||
slots: slots,
|
||||
));
|
||||
}
|
||||
|
||||
// Execute all updates in parallel
|
||||
await Future.wait(futures);
|
||||
|
||||
return resultDays;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,12 +42,12 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Translations.of(context);
|
||||
final i18n = Translations.of(context).staff.availability;
|
||||
return BlocProvider.value(
|
||||
value: _bloc,
|
||||
child: Scaffold(
|
||||
appBar: UiAppBar(
|
||||
title: 'My Availability',
|
||||
title: i18n.title,
|
||||
centerTitle: false,
|
||||
showBackButton: true,
|
||||
),
|
||||
@@ -64,17 +64,9 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
if (state is AvailabilityError) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: state.message,
|
||||
message: translateErrorKey(state.message),
|
||||
type: UiSnackbarType.error,
|
||||
);
|
||||
} else if (state is AvailabilityError) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(translateErrorKey(state.message)),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<AvailabilityBloc, AvailabilityState>(
|
||||
@@ -110,7 +102,12 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
),
|
||||
),
|
||||
if (state.isActionInProgress)
|
||||
const UiLoadingPage(), // Show loading overlay during actions
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
color: UiColors.white.withValues(alpha: 0.5),
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (state is AvailabilityError) {
|
||||
@@ -128,7 +125,6 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
@@ -140,6 +136,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
}
|
||||
|
||||
Widget _buildQuickSet(BuildContext context) {
|
||||
final i18n = Translations.of(context).staff.availability;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
@@ -150,26 +147,28 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Quick Set Availability',
|
||||
i18n.quick_set_title,
|
||||
style: UiTypography.body2b,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _buildQuickSetButton(context, 'All Week', 'all')),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Expanded(
|
||||
child: _buildQuickSetButton(context, 'Weekdays', 'weekdays'),
|
||||
child: _buildQuickSetButton(context, i18n.all_week, 'all'),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Expanded(
|
||||
child: _buildQuickSetButton(context, 'Weekends', 'weekends'),
|
||||
child: _buildQuickSetButton(context, i18n.weekdays, 'weekdays'),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Expanded(
|
||||
child: _buildQuickSetButton(context, i18n.weekends, 'weekends'),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Expanded(
|
||||
child: _buildQuickSetButton(
|
||||
context,
|
||||
'Clear All',
|
||||
i18n.clear_all,
|
||||
'clear',
|
||||
isDestructive: true,
|
||||
),
|
||||
@@ -388,7 +387,15 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
style: UiTypography.title2b,
|
||||
),
|
||||
Text(
|
||||
isAvailable ? 'You are available' : 'Not available',
|
||||
isAvailable
|
||||
? Translations.of(context)
|
||||
.staff
|
||||
.availability
|
||||
.available_status
|
||||
: Translations.of(context)
|
||||
.staff
|
||||
.availability
|
||||
.not_available_status,
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
),
|
||||
],
|
||||
@@ -560,6 +567,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
}
|
||||
|
||||
Widget _buildInfoCard() {
|
||||
final i18n = Translations.of(context).staff.availability;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
@@ -577,11 +585,11 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
spacing: UiConstants.space1,
|
||||
children: [
|
||||
Text(
|
||||
'Auto-Match uses your availability',
|
||||
i18n.auto_match_title,
|
||||
style: UiTypography.body2m,
|
||||
),
|
||||
Text(
|
||||
"When enabled, you'll only be matched with shifts during your available times.",
|
||||
i18n.auto_match_description,
|
||||
style: UiTypography.body3r.textSecondary,
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user