Add staff reliability stats & shift location

Introduce staff reliability statistics and location fields across domain, data, and UI. Changes include:

- New API endpoint StaffEndpoints.profileStats ('/staff/profile/stats').
- New domain entity StaffReliabilityStats with JSON (de)serialization and export.
- Profile repository: getReliabilityStats implementation and interface addition.
- New GetReliabilityStatsUseCase and DI registration in StaffProfileModule.
- ProfileCubit/state: load and store reliabilityStats; UI wired to display ReliabilityStatsCard and ReliabilityScoreBar using state values.
- Coverage/shift updates: Added AssignedWorker.hasReview to track if a worker was reviewed; added locationName/locationAddress to ShiftWithWorkers and show location in ShiftHeader; hide rate button if worker.hasReview.
- Clock-in handling: treat backend ALREADY_CLOCKED_IN (409) as idempotent by re-fetching attendance and emitting success when appropriate.

These changes wire backend stats through repository/usecase/cubit to the profile UI and add shift location and review-awareness to client views.
This commit is contained in:
Achintha Isuru
2026-03-19 10:53:37 -04:00
parent 88a319da4f
commit 493891eea0
15 changed files with 247 additions and 37 deletions

View File

@@ -228,12 +228,20 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
event: event,
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.
} on AppException catch (e) {
// The backend returns 409 ALREADY_CLOCKED_IN when the worker has
// an active attendance session. This is a normal idempotency
// signal — re-fetch the authoritative status and emit success
// without surfacing an error snackbar.
final bool isAlreadyClockedIn =
e is ApiException && e.apiCode == 'ALREADY_CLOCKED_IN';
// Re-fetch attendance status to reconcile local state with
// the backend (handles both ALREADY_CLOCKED_IN and legacy
// Postgres constraint 23505 duplicates).
final AttendanceStatus currentStatus = await _getAttendanceStatus();
if (currentStatus.isClockedIn) {
if (isAlreadyClockedIn || currentStatus.isClockedIn) {
emit(state.copyWith(
status: ClockInStatus.success,
attendance: currentStatus,