Commit graph

43 commits

Author SHA1 Message Date
00cc28062b Add iOS install hint + Apple PWA meta tags
iOS Safari has no beforeinstallprompt API, so the portal's Install
button (?install=1) silently did nothing there. IosInstallHint now
shows Share -> Add to Home Screen steps on Safari, or a prompt to
switch to Safari first if opened in another iOS browser/in-app
webview (those can't install PWAs on iOS at all).

Also added apple-mobile-web-app-title + apple-touch-icon so the
home screen icon isn't a page screenshot.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 14:12:12 +00:00
47bd54afec Fix week nav anchor, timesheet sync range, and in-progress clock entries
- Week Prev/Next/Today now anchor to this Monday instead of today, so
  navigation stays week-aligned
- Timesheet sync no longer pulls into the future — capped at today
  while shift-fetching still covers the full displayed week
- Roster pivot skips entries that are still clocked in with no
  clock-out yet, instead of showing them as 0 hours

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 14:11:40 +00:00
36ca4e1996 Scope PWA manifest to app's own path instead of origin root
Every installed PWA claimed scope "/", so Android link-capturing routed
navigation for any app on the shared origin into whichever PWA had it.
2026-07-29 13:47:08 +00:00
e085fd3e88 Fix PWA manifest scope to / per install-architecture doc
Scope was set to the app's own base path, which breaks the installed
PWA out of standalone mode into a regular browser tab on any
same-origin navigation outside that path (e.g. the auth redirect).
scope: "/" keeps navigation inside the installed app's window.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 17:06:46 +00:00
cae0f48d79 Fix today gap + future date visual drop after pull timesheets
runRotaSync: only protect today's timesheet row when it has real
clocked-out hours (hours > 0), so stale ?/0h rows and empty rows
are healed by the next rota sync without needing manual intervention.

runTimesheetSync: revert to SKIP for today when no completed shifts —
writing an empty row was overwriting the rota schedule with nothing.

Frontend: fetch wfShifts for the full week after a timesheet pull
(not just up to today), so future rota rows aren't dropped from state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 12:37:43 +00:00
cbc9db67bc Default week view to Monday of current week
Adds thisMonday() helper and uses it as the fallback wherever the
week start was previously today. The Today button also snaps back
to this week's Monday rather than the literal current date.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 12:34:31 +00:00
b194cf1278 Fix ? / 0.00h display for in-progress shifts
Frontend: pivotWfShifts now skips entries where times='?' and hours=0
— immediately removes the visual glitch from any stale DB rows.

Backend: when today's timesheet pull finds no completed shifts, write
a source='workforce' row instead of skipping, so the stale timesheet
entry is overwritten and rota sync can restore schedule data for today.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 12:21:31 +00:00
e3c8f143ef Refine today's timesheet handling — skip in-progress shifts
fetchTimesheetShifts: filter out today's shifts with no finish time
(still clocked in), so only completed shifts contribute hours.

runTimesheetSync: if today's row has no completed shifts at all,
skip writing it — leaves rota schedule data in place rather than
overwriting with an empty timesheet row while staff are still working.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 12:10:52 +00:00
c19460ecb3 Fix timesheet sync overwriting future rota dates with empty data
Pull timesheets was writing source='timesheet' rows for future dates
(no clock-in data yet), giving ? / 0.00h and preventing rota sync
from ever healing those dates.

Three-part fix:
- Backend runTimesheetSync: cap end date to today before looping
- Frontend syncTimesheetData: cap end date to today before calling API
- Backend runRotaSync: only skip timesheet-sourced dates that are <= today,
  so any future dates accidentally marked as timesheet are self-healed
  next time rota sync runs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 12:08:36 +00:00
ffefa1914f Scope manual sync buttons to current view dates only
Sync rota and pull timesheets now operate on the 7-day view window rather
than rolling default windows — reduces API calls and allows backdating.
Cron job still uses rolling defaults via runRotaSync()/runTimesheetSync().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 20:08:43 +00:00
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
109a4ea194 Fix duplicate const cur declaration in sync route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 14:25:06 +00:00
69720b778a Fix workforce sync: split 35-day window into 7-day API chunks
Workforce schedules API enforces a hard 7-day limit per request.
Split the rolling window into up to 5 weekly chunks, fetch each
sequentially, then merge by staff id before pivoting to per-date rows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 14:22:32 +00:00
09a8158a1e Add .gitignore and remove accidentally committed node_modules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 14:19:59 +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
872251250e Fix logout (add missing nginx auth proxy) + show name/email in sidebar footer
The /hk-planner/api/auth/ → central auth nginx block was missing, causing
logout to hit the app backend. Also corrects the logout URL and switches from
window.location.href='/' to reload(). Shows name + email in sidebar footer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 11:10:02 +00:00
1a5de8d3fc Remove break deductions from shift hours — show raw on-site time
The planner needs to know who is available and for how long (scheduling),
not paid hours after payroll deductions. Workforce was returning explicit
break data for some shifts (e.g. a 30-min break on a 12:00–14:00 shift),
causing those hours to display short. Strip all break logic and use raw
start→finish duration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-21 15:50:33 +00:00
e0b0f2f275 Fix shift hours overcounting automatic breaks + add version-check banner
Workforce's automatic_break_length is a payroll deduction setting, not a
scheduling field — applying it when no explicit breaks are present was silently
shortening all shifts (e.g. a 2h shift showing as 1.5h). Now only deducts
explicit break records from s.breaks. Also adds BUILD_VERSION to /health and
an UpdateBanner that prompts staff to reload when a new deploy lands.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-21 15:30:17 +00:00
ba23d59b3d Force desktop frame via useFrameViewport hook in Layout — applies to all pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-21 09:05:30 +00:00
8c6a79f222 Force desktop viewport (width=1280) — data-heavy display not suited to responsive layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-21 08:14:57 +00:00
2c0180ce9a Sync shared components — sidebar scrollbar, Layout, AuthGate updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 12:07:11 +00:00
e0ff800c6b Add shared device inactivity timeout to AuthGate 2026-07-13 11:53:04 +00:00
e4e6958ff9 Gate planner config write endpoints with requireCap('planner')
Six PUT /api/config/* routes were missing per-route capability checks —
any authenticated hk-planner user could modify staff, time requirements,
pickup data, tasks, adjustments, and last-reviewed regardless of their cap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-13 09:24:55 +00:00
2400e224f1 Redirect to login on mid-session 401 instead of silently failing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 20:09:16 +00:00
1b8e04039d Replace letter icons with Lucide PWA icons 2026-07-11 20:30:07 +00:00
fbda399d21 Make hk-planner installable as a PWA
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-11 11:27:00 +00:00
c0b1d2a62e Move general tasks + time reqs to Settings; add date adjustments
- Rename 'Category Settings' to 'Settings' (nav, page title, component)
- Move Recurring General Tasks and Time Requirements editing to Settings page
- Planner still loads both for calcRequired calculations
- Add date-specific Adjustments section to Planner: per-date +/- hour
  offsets keyed by YYYY-MM-DD (non-recurring); feed into Required Hours total
- Adjustments row shown in Required Hours tfoot when non-zero

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:11:21 +00:00
b803b01ae1 Add Workforce rota integration to HK Planner
Pulls HK staff shifts from Workforce.com API into the staff rota section.
Rota rows (read-only, WF badge, times+hours per day) appear above manual rows;
manual name inputs get a datalist populated from WF staff. Sync triggered via
button with stale-week detection. Dept selection stored per-app in CategorySettings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 20:47:09 +00:00
490a9a558b Rename app to Rota Check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 13:19:40 +00:00
e7975db4a3 Show hours to 2 decimal places (0.75h instead of 0.8h)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 15:43:40 +00:00
74d8951fe2 Center D/S/A values and header in their column
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 15:28:34 +00:00
087553054e Move booking delta ticker above occupied count in Rooms cell
▲/▼ indicators relate to occupied rooms, not D/S/A breakdown,
so move them to the top of the Rooms cell in both CatRows and TotalRows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 15:04:55 +00:00
97e99735ce Summary table: stronger day borders, weekend bg, and WP-accurate tooltips
- 2px left border on Rooms th/td marks each day boundary clearly
- Saturday/Sunday columns get a subtle blue-tinted background
- Occupied number shows pickup-change context + prior week stats on hover
- '+' pickup button shows prior week line (matching original WP plugin logic)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:44:35 +00:00
31fd73ce1e Summary table: split Rooms and D/S/A into separate columns per day
Match the original WP plugin layout — left cell has occupied count, vac,
and pickup controls; right cell has departs/stays/arrivals stacked
vertically, right-aligned, with delta indicators above them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:34:21 +00:00
135f1f6cf0 Add CalendarClock icon, planner/settings capabilities
- backend/src/auth.js: extract caps from JWT (planner/settings), hasCap/requireCap
- backend/src/routes/config.js: gate categories + test-connection on requireCap('settings')
- frontend/src/types.ts: add caps[] to User, export can() helper
- frontend/src/App.tsx: gate /settings route on can(user, 'settings')
- frontend/src/components/Layout.tsx: gate nav item on can(user, 'settings')

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:29:58 +00:00
773d323060 Fix compose: use default bridge network, match cashup service names
The frontend nginx proxies to 'backend:3001', so services must be named
backend/frontend. Drop the external hnf_net network (each app runs in its
own LXC) and add apparmor=unconfined for docker-in-LXC.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 14:03:33 +00:00
4abae5eda1 Initial commit: HK Planner app
Housekeeping workload and hours planning app — port of the WP hotel
housekeeping hours calculator plugin. 7-day occupancy planner with
Newbook PMS integration, staff rota, time requirements, and pickup tracking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 09:24:14 +00:00