diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json index b4525aca..6a2c8eea 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json +++ b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json @@ -982,7 +982,8 @@ "override_hint": "Enter your justification...", "override_submit": "Clock In", "overridden_title": "Location Not Verified", - "overridden_desc": "You are clocking in without location verification. Your justification has been recorded." + "overridden_desc": "You are clocking in without location verification. Your justification has been recorded.", + "outside_work_area_warning": "You've moved away from the work area" } }, "availability": { diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json b/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json index 027aa800..710a935f 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json +++ b/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json @@ -977,7 +977,8 @@ "override_hint": "Ingrese su justificación...", "override_submit": "Registrar Entrada", "overridden_title": "Ubicación No Verificada", - "overridden_desc": "Está registrando entrada sin verificación de ubicación. Su justificación ha sido registrada." + "overridden_desc": "Está registrando entrada sin verificación de ubicación. Su justificación ha sido registrada.", + "outside_work_area_warning": "Te has alejado del área de trabajo" } }, "availability": { 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 a075096c..49a44979 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 @@ -107,7 +107,7 @@ class ClockInActionSection extends StatelessWidget { return Column( mainAxisSize: MainAxisSize.min, children: [ - const GeofenceStatusBanner(), + GeofenceStatusBanner(isClockedIn: isCheckedIn), const SizedBox(height: UiConstants.space3), EarlyCheckInBanner( availabilityTime: checkInAvailabilityTime ?? soonLabel, @@ -120,7 +120,7 @@ class ClockInActionSection extends StatelessWidget { return Column( mainAxisSize: MainAxisSize.min, children: [ - const GeofenceStatusBanner(), + GeofenceStatusBanner(isClockedIn: isCheckedIn), const SizedBox(height: UiConstants.space3), EarlyCheckOutBanner( availabilityTime: checkOutAvailabilityTime ?? soonLabel, @@ -146,7 +146,7 @@ class ClockInActionSection extends StatelessWidget { mainAxisSize: MainAxisSize.min, spacing: UiConstants.space4, children: [ - const GeofenceStatusBanner(), + GeofenceStatusBanner(isClockedIn: isCheckedIn), _currentInteraction.buildActionWidget( isCheckedIn: isCheckedIn, isDisabled: isGeofenceBlocking, diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_status_banner.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_status_banner.dart index 115bb840..f6c56f17 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_status_banner.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_status_banner.dart @@ -4,6 +4,7 @@ import 'package:krow_domain/krow_domain.dart'; import '../../bloc/geofence/geofence_bloc.dart'; import '../../bloc/geofence/geofence_state.dart'; +import 'outside_work_area_banner.dart'; import 'permission_denied_banner.dart'; import 'permission_denied_forever_banner.dart'; import 'service_disabled_banner.dart'; @@ -17,9 +18,18 @@ import 'verifying_banner.dart'; /// /// Reads [GeofenceBloc] state directly and renders the appropriate /// banner variant based on permission, location, and verification conditions. +/// When [isClockedIn] is true and the worker is too far, a non-blocking +/// informational banner is shown instead of the override flow. class GeofenceStatusBanner extends StatelessWidget { /// Creates a [GeofenceStatusBanner]. - const GeofenceStatusBanner({super.key}); + const GeofenceStatusBanner({this.isClockedIn = false, super.key}); + + /// Whether the worker is currently clocked in. + /// + /// When true and the device is outside the geofence, a lightweight + /// [OutsideWorkAreaBanner] is shown instead of [TooFarBanner] so that + /// the clock-out slider remains accessible. + final bool isClockedIn; @override Widget build(BuildContext context) { @@ -77,6 +87,12 @@ class GeofenceStatusBanner extends StatelessWidget { if (!state.isLocationVerified && !state.isLocationTimedOut && state.distanceFromTarget != null) { + // When already clocked in, show a non-blocking informational banner + // instead of the "Clock in anyway" override flow so the clock-out + // slider remains accessible. + if (isClockedIn) { + return const OutsideWorkAreaBanner(); + } return TooFarBanner(distanceMeters: state.distanceFromTarget!); } diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/outside_work_area_banner.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/outside_work_area_banner.dart new file mode 100644 index 00000000..392cd716 --- /dev/null +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/outside_work_area_banner.dart @@ -0,0 +1,28 @@ +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// Non-blocking warning banner shown when the worker is already clocked in +/// but has moved outside the geofence radius. +/// +/// Unlike [TooFarBanner], this banner does not include a "Clock in anyway" +/// action button and does not block the clock-out slider. +class OutsideWorkAreaBanner extends StatelessWidget { + /// Creates an [OutsideWorkAreaBanner]. + const OutsideWorkAreaBanner({super.key}); + + @override + Widget build(BuildContext context) { + final TranslationsStaffClockInGeofenceEn i18n = Translations.of( + context, + ).staff.clock_in.geofence; + + return UiNoticeBanner( + backgroundColor: UiColors.tagPending, + icon: UiIcons.warning, + iconColor: UiColors.textWarning, + title: i18n.outside_work_area_warning, + titleColor: UiColors.textWarning, + ); + } +}