From 10bd61b25087634e9cf177f09e99771b86bb3ea6 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Sat, 14 Mar 2026 20:21:10 -0400 Subject: [PATCH] feat(clock_in): add error handling support to check-in interactions --- .../strategies/check_in_interaction.dart | 1 + .../strategies/nfc_check_in_interaction.dart | 1 + .../swipe_check_in_interaction.dart | 2 + .../widgets/clock_in_action_section.dart | 40 ++++++++++--------- .../presentation/widgets/clock_in_body.dart | 1 + .../widgets/swipe_to_check_in.dart | 12 ++++-- 6 files changed, 36 insertions(+), 21 deletions(-) diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/check_in_interaction.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/check_in_interaction.dart index 707d055f..6f218f90 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/check_in_interaction.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/check_in_interaction.dart @@ -16,6 +16,7 @@ abstract class CheckInInteraction { required bool isCheckedIn, required bool isDisabled, required bool isLoading, + required bool hasError, required VoidCallback onCheckIn, required VoidCallback onCheckOut, }); diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/nfc_check_in_interaction.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/nfc_check_in_interaction.dart index f479ac77..efa2e0e3 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/nfc_check_in_interaction.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/nfc_check_in_interaction.dart @@ -21,6 +21,7 @@ class NfcCheckInInteraction implements CheckInInteraction { required bool isCheckedIn, required bool isDisabled, required bool isLoading, + required bool hasError, required VoidCallback onCheckIn, required VoidCallback onCheckOut, }) { diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/swipe_check_in_interaction.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/swipe_check_in_interaction.dart index 21273af9..4e27e0e6 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/swipe_check_in_interaction.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/strategies/swipe_check_in_interaction.dart @@ -16,6 +16,7 @@ class SwipeCheckInInteraction implements CheckInInteraction { required bool isCheckedIn, required bool isDisabled, required bool isLoading, + required bool hasError, required VoidCallback onCheckIn, required VoidCallback onCheckOut, }) { @@ -23,6 +24,7 @@ class SwipeCheckInInteraction implements CheckInInteraction { isCheckedIn: isCheckedIn, isDisabled: isDisabled, isLoading: isLoading, + hasError: hasError, onCheckIn: onCheckIn, onCheckOut: onCheckOut, ); diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_action_section.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_action_section.dart index 99c348d1..c1791c0b 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_action_section.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_action_section.dart @@ -6,16 +6,15 @@ import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; import 'package:krow_domain/krow_domain.dart'; +import '../../domain/validators/clock_in_validation_context.dart'; +import '../../domain/validators/validators/time_window_validator.dart'; import '../bloc/clock_in/clock_in_bloc.dart'; import '../bloc/clock_in/clock_in_event.dart'; import '../bloc/geofence/geofence_bloc.dart'; import '../bloc/geofence/geofence_state.dart'; -import '../../domain/validators/clock_in_validation_context.dart'; -import '../../domain/validators/validators/time_window_validator.dart'; import '../strategies/check_in_interaction.dart'; import '../strategies/nfc_check_in_interaction.dart'; import '../strategies/swipe_check_in_interaction.dart'; -import 'early_check_in_banner.dart'; import 'geofence_status_banner/geofence_status_banner.dart'; import 'lunch_break_modal.dart'; import 'no_shifts_banner.dart'; @@ -35,6 +34,7 @@ class ClockInActionSection extends StatelessWidget { required this.checkOutTime, required this.checkInMode, required this.isActionInProgress, + this.hasError = false, super.key, }); @@ -60,6 +60,9 @@ class ClockInActionSection extends StatelessWidget { /// Whether a check-in or check-out action is currently in progress. final bool isActionInProgress; + /// Whether the last action attempt resulted in an error. + final bool hasError; + /// Resolves the [CheckInInteraction] for the current mode. /// /// Falls back to [SwipeCheckInInteraction] if the mode is unrecognized. @@ -81,21 +84,21 @@ class ClockInActionSection extends StatelessWidget { /// Builds the action widget for an active (not completed) shift. Widget _buildActiveShiftAction(BuildContext context) { - if (!isCheckedIn && !_isCheckInAllowed(selectedShift!)) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - const GeofenceStatusBanner(), - const SizedBox(height: UiConstants.space3), - EarlyCheckInBanner( - availabilityTime: _getAvailabilityTimeText( - selectedShift!, - context, - ), - ), - ], - ); - } + // if (!isCheckedIn && !_isCheckInAllowed(selectedShift!)) { + // return Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // const GeofenceStatusBanner(), + // const SizedBox(height: UiConstants.space3), + // EarlyCheckInBanner( + // availabilityTime: _getAvailabilityTimeText( + // selectedShift!, + // context, + // ), + // ), + // ], + // ); + // } return BlocBuilder( builder: (BuildContext context, GeofenceState geofenceState) { @@ -119,6 +122,7 @@ class ClockInActionSection extends StatelessWidget { isCheckedIn: isCheckedIn, isDisabled: isGeofenceBlocking, isLoading: isActionInProgress, + hasError: hasError, onCheckIn: () => _handleCheckIn(context), onCheckOut: () => _handleCheckOut(context), ), diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_body.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_body.dart index 8b0032f4..05f1f7cc 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_body.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_body.dart @@ -111,6 +111,7 @@ class _ClockInBodyState extends State { checkInMode: state.checkInMode, isActionInProgress: state.status == ClockInStatus.actionInProgress, + hasError: state.status == ClockInStatus.failure, ), // checked-in banner (only when checked in to the selected shift) diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/swipe_to_check_in.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/swipe_to_check_in.dart index 906cca30..2ed7cc7b 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/swipe_to_check_in.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/swipe_to_check_in.dart @@ -17,6 +17,7 @@ class SwipeToCheckIn extends StatefulWidget { this.isLoading = false, this.isCheckedIn = false, this.isDisabled = false, + this.hasError = false, }); /// Called when the user completes the swipe to check in. @@ -34,6 +35,9 @@ class SwipeToCheckIn extends StatefulWidget { /// Whether the slider is disabled (e.g. geofence blocking). final bool isDisabled; + /// Whether an error occurred during the last action attempt. + final bool hasError; + @override State createState() => _SwipeToCheckInState(); } @@ -54,9 +58,11 @@ class _SwipeToCheckInState extends State _dragValue = 0.0; }); } - // Reset on error: loading finished but check-in state didn't change. - if (oldWidget.isLoading && !widget.isLoading && - widget.isCheckedIn == oldWidget.isCheckedIn && _isComplete) { + // Reset on error: loading finished without state change, or validation error. + if (_isComplete && + widget.isCheckedIn == oldWidget.isCheckedIn && + ((oldWidget.isLoading && !widget.isLoading) || + (!oldWidget.hasError && widget.hasError))) { setState(() { _isComplete = false; _dragValue = 0.0;