feat(clock_in): add messages for already clocked in/out states and handle API call failures
This commit is contained in:
@@ -1479,7 +1479,9 @@
|
|||||||
},
|
},
|
||||||
"clock_in": {
|
"clock_in": {
|
||||||
"location_verification_required": "Please wait for location verification before clocking in.",
|
"location_verification_required": "Please wait for location verification before clocking in.",
|
||||||
"notes_required_for_timeout": "Please add a note explaining why your location can't be verified."
|
"notes_required_for_timeout": "Please add a note explaining why your location can't be verified.",
|
||||||
|
"already_clocked_in": "You're already clocked in to this shift.",
|
||||||
|
"already_clocked_out": "You've already clocked out of this shift."
|
||||||
},
|
},
|
||||||
"generic": {
|
"generic": {
|
||||||
"unknown": "Something went wrong. Please try again.",
|
"unknown": "Something went wrong. Please try again.",
|
||||||
|
|||||||
@@ -1474,7 +1474,9 @@
|
|||||||
},
|
},
|
||||||
"clock_in": {
|
"clock_in": {
|
||||||
"location_verification_required": "Por favor, espera la verificaci\u00f3n de ubicaci\u00f3n antes de registrar entrada.",
|
"location_verification_required": "Por favor, espera la verificaci\u00f3n de ubicaci\u00f3n antes de registrar entrada.",
|
||||||
"notes_required_for_timeout": "Por favor, agrega una nota explicando por qu\u00e9 no se puede verificar tu ubicaci\u00f3n."
|
"notes_required_for_timeout": "Por favor, agrega una nota explicando por qu\u00e9 no se puede verificar tu ubicaci\u00f3n.",
|
||||||
|
"already_clocked_in": "Ya est\u00e1s registrado en este turno.",
|
||||||
|
"already_clocked_out": "Ya registraste tu salida de este turno."
|
||||||
},
|
},
|
||||||
"generic": {
|
"generic": {
|
||||||
"unknown": "Algo sali\u00f3 mal. Por favor, intenta de nuevo.",
|
"unknown": "Algo sali\u00f3 mal. Por favor, intenta de nuevo.",
|
||||||
|
|||||||
@@ -204,29 +204,49 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
|
|||||||
action: () async {
|
action: () async {
|
||||||
final DeviceLocation? location = geofenceState.currentLocation;
|
final DeviceLocation? location = geofenceState.currentLocation;
|
||||||
|
|
||||||
final AttendanceStatus newStatus = await _clockIn(
|
try {
|
||||||
ClockInArguments(
|
final AttendanceStatus newStatus = await _clockIn(
|
||||||
shiftId: event.shiftId,
|
ClockInArguments(
|
||||||
notes: event.notes,
|
shiftId: event.shiftId,
|
||||||
latitude: location?.latitude,
|
notes: event.notes,
|
||||||
longitude: location?.longitude,
|
latitude: location?.latitude,
|
||||||
accuracyMeters: location?.accuracy,
|
longitude: location?.longitude,
|
||||||
capturedAt: location?.timestamp,
|
accuracyMeters: location?.accuracy,
|
||||||
overrideReason: geofenceState.isGeofenceOverridden
|
capturedAt: location?.timestamp,
|
||||||
? geofenceState.overrideNotes
|
overrideReason: geofenceState.isGeofenceOverridden
|
||||||
: null,
|
? geofenceState.overrideNotes
|
||||||
),
|
: null,
|
||||||
);
|
),
|
||||||
emit(state.copyWith(
|
);
|
||||||
status: ClockInStatus.success,
|
emit(state.copyWith(
|
||||||
attendance: newStatus,
|
status: ClockInStatus.success,
|
||||||
));
|
attendance: newStatus,
|
||||||
|
));
|
||||||
|
|
||||||
// Start background tracking after successful clock-in.
|
// Start background tracking after successful clock-in.
|
||||||
_dispatchBackgroundTrackingStarted(
|
_dispatchBackgroundTrackingStarted(
|
||||||
event: event,
|
event: event,
|
||||||
activeShiftId: newStatus.activeShiftId,
|
activeShiftId: newStatus.activeShiftId,
|
||||||
);
|
);
|
||||||
|
} on AppException catch (_) {
|
||||||
|
// The clock-in API call failed. Re-fetch attendance status to
|
||||||
|
// reconcile: if the worker is already clocked in (e.g. duplicate
|
||||||
|
// session from Postgres constraint 23505), treat it as success.
|
||||||
|
final AttendanceStatus currentStatus = await _getAttendanceStatus();
|
||||||
|
if (currentStatus.isClockedIn) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: ClockInStatus.success,
|
||||||
|
attendance: currentStatus,
|
||||||
|
));
|
||||||
|
_dispatchBackgroundTrackingStarted(
|
||||||
|
event: event,
|
||||||
|
activeShiftId: currentStatus.activeShiftId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Worker is genuinely not clocked in — surface the error.
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (String errorKey) => state.copyWith(
|
onError: (String errorKey) => state.copyWith(
|
||||||
status: ClockInStatus.failure,
|
status: ClockInStatus.failure,
|
||||||
@@ -261,29 +281,51 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
|
|||||||
final GeofenceState currentGeofence = _geofenceBloc.state;
|
final GeofenceState currentGeofence = _geofenceBloc.state;
|
||||||
final DeviceLocation? location = currentGeofence.currentLocation;
|
final DeviceLocation? location = currentGeofence.currentLocation;
|
||||||
|
|
||||||
final AttendanceStatus newStatus = await _clockOut(
|
try {
|
||||||
ClockOutArguments(
|
final AttendanceStatus newStatus = await _clockOut(
|
||||||
notes: event.notes,
|
ClockOutArguments(
|
||||||
breakTimeMinutes: event.breakTimeMinutes,
|
notes: event.notes,
|
||||||
shiftId: activeShiftId,
|
breakTimeMinutes: event.breakTimeMinutes,
|
||||||
latitude: location?.latitude,
|
shiftId: activeShiftId,
|
||||||
longitude: location?.longitude,
|
latitude: location?.latitude,
|
||||||
accuracyMeters: location?.accuracy,
|
longitude: location?.longitude,
|
||||||
capturedAt: location?.timestamp,
|
accuracyMeters: location?.accuracy,
|
||||||
),
|
capturedAt: location?.timestamp,
|
||||||
);
|
),
|
||||||
emit(state.copyWith(
|
);
|
||||||
status: ClockInStatus.success,
|
emit(state.copyWith(
|
||||||
attendance: newStatus,
|
status: ClockInStatus.success,
|
||||||
));
|
attendance: newStatus,
|
||||||
|
));
|
||||||
|
|
||||||
// Stop background tracking after successful clock-out.
|
// Stop background tracking after successful clock-out.
|
||||||
_geofenceBloc.add(
|
_geofenceBloc.add(
|
||||||
BackgroundTrackingStopped(
|
BackgroundTrackingStopped(
|
||||||
clockOutTitle: event.clockOutTitle,
|
clockOutTitle: event.clockOutTitle,
|
||||||
clockOutBody: event.clockOutBody,
|
clockOutBody: event.clockOutBody,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} on AppException catch (_) {
|
||||||
|
// The clock-out API call failed. Re-fetch attendance status to
|
||||||
|
// reconcile: if the worker is already clocked out (e.g. duplicate
|
||||||
|
// end-session), treat it as success.
|
||||||
|
final AttendanceStatus currentStatus = await _getAttendanceStatus();
|
||||||
|
if (!currentStatus.isClockedIn) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
status: ClockInStatus.success,
|
||||||
|
attendance: currentStatus,
|
||||||
|
));
|
||||||
|
_geofenceBloc.add(
|
||||||
|
BackgroundTrackingStopped(
|
||||||
|
clockOutTitle: event.clockOutTitle,
|
||||||
|
clockOutBody: event.clockOutBody,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Worker is still clocked in — surface the error.
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (String errorKey) => state.copyWith(
|
onError: (String errorKey) => state.copyWith(
|
||||||
status: ClockInStatus.failure,
|
status: ClockInStatus.failure,
|
||||||
|
|||||||
Reference in New Issue
Block a user