Commit graph

8 commits

Author SHA1 Message Date
9bfd14a082 Add timesheet sync: pull actual hours, daily cron, per-cell source indicators
- fetchTimesheetShifts: hits /api/v2/shifts, subtracts breaks, carries PENDING/APPROVED status
- syncJobs.js: shared runRotaSync/runTimesheetSync logic used by routes and cron
- Rota sync now skips dates already marked source='timesheet'
- POST /api/workforce/sync-timesheets: on-demand pull of last 7 days actual hours
- Daily 6am cron: rota sync then timesheet sync, each logged independently
- upsertWorkforceShifts now writes source per row (workforce or timesheet)
- Frontend: Pull timesheets button alongside Sync rota button
- Clock icon on timesheet cells — amber for PENDING, green for APPROVED
- Both buttons disabled while either sync is in progress

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 18:25:02 +00:00
18e24efbeb Add sync error logging, extend error flash, fix unload saves
- Log workforce sync errors to backend stdout so they appear in docker logs
- Extend error flash from 5s to 15s so errors are readable
- Replace sendBeacon (POST-only) with keepalive fetch (PUT) on unload —
  sendBeacon was hitting 404s on all three unload saves

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 14:19:38 +00:00
c7066521ff Rework workforce sync to per-date storage with rolling window
Replaces the single workforce_rota config snapshot (which was overwritten
on each sync, losing data when switching weeks) with a workforce_daily_shifts
table keyed by date. Sync now covers a rolling today-7 to today+28 window
unconditionally — no date params needed. Each date's record carries a
synced_at timestamp so the UI shows the age of the oldest date in view.
Mid-week viewing works naturally since data is stored per date not per week.
source column reserved for future timesheet replacement of past dates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 14:06:56 +00:00
1a8d8d9a2b Show muted icons on intermediate rota footer rows
vs Booked, with Recurring Tasks, and with Pickup now show ⚠/✓/✗
in muted grey using the same amber thresholds as the final row,
giving a quick visual cue without the coloured emphasis.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 13:41:07 +00:00
75ac7591d2 Add 5-tier warning thresholds to staff rota footer
Replaces the 2-row (vs Booked / vs with Pickup) footer with a progressive 5-row breakdown:
- Total Available (unchanged)
- vs Booked — plain diff, room cleaning only
- with Recurring Tasks — plain diff, adds recurring tasks
- with Pickup — plain diff, adds pickup hours
- with Adjustments — bold coloured final row with configurable thresholds

New 5-tier icon system: red ⚠ / amber ⚠ / green ✓ / amber ✗ / red ✗, with defaults of
4h / 1h / 1h / 2h spare or short. Thresholds are editable in Settings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 13:38:46 +00:00
fee388db4c Fix week navigation using ref to eliminate stale closure entirely
Previous wsArg approach still suffered from useCallback memoisation
interacting with React batching — loadAll could fire with the render-
before-last week start. Switch to weekStartRef (written before every
loadAll call and on every render) so loadAll always reads the live
value regardless of when the memoised function was created.

Also removes unused putWarningThresholds import (added externally).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 13:35:34 +00:00
f1ba176195 Debounce date input loads to prevent mid-type API calls
Typing a date manually (e.g. "21") fires onChange on each keystroke,
so "2" would trigger a load for the 2nd before "21" was complete.
Added a 600ms debounce on the week-start and since-date inputs so the
load only fires after the user stops typing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 12:59:26 +00:00
0b63543e1f Fix date selector loading stale week — pass new dates directly to loadAll
`loadAll` captured `weekStart` in a stale closure when called via
`setTimeout`. Selecting a date would show the correct value in the input
but load data for the previously-shown week. Replaced all deferred
`setTimeout(() => loadAll(true), 0)` calls with direct `loadAll(true, newDate)`
calls, passing the updated date as an explicit argument.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 12:26:35 +00:00