Commit graph

40 commits

Author SHA1 Message Date
96674c58c8 Fix Rooms Accessible View missing yesterday's still-in-house bookings
fetchRoomOccupancy() only queried NewBook for today..today, trusting its
period_from/period_to filter to include a booking that arrived yesterday
and hasn't checked out. Per hvac's scheduler (which learned this the hard
way), that overlap isn't reliable server-side — widen the fetch window to
yesterday..today and filter locally against each booking's own
arrival/departure dates instead. A room with a guest still in "arrived"
status from yesterday was showing as free/available today.

Also fixed the unrelated `?unoccupied=true` task filter, which still
called fetchOccupiedSiteIds() — a function renamed to fetchRoomOccupancy()
during the July occupied/arriving split (25d6323) but never updated at
this second call site, so it would throw if ever exercised.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:26:14 +00:00
2d2541576d Merge asset recurring templates into the main Recurring/Summary pages
Assets.tsx had its own bare-bones template-creation form (title/interval/
next_due only, no description/priority-choice/assignee/schedule_mode/
lead_days/group) with zero edit or delete path — a linked recurring task
could only ever be created from here, never managed. The backend already
sourced the asset detail's tasks/templates straight from the main tasks
and task_templates tables (no separate storage), so the fix is entirely
frontend: stop duplicating the editor, link back to the real one.

- Assets.tsx: removed the bespoke ServiceTaskForm entirely. Each linked
  template row now navigates to /recurring?edit=<id> (opens that
  template's full edit form there, including delete). "+ Add" navigates
  to /recurring?new_asset=&new_location= for a prefilled New Template
  form. Added a matching "+ Report fault" action navigating to
  /summary?new_asset=&new_location= for one-off tasks, since there was
  previously no way to create a plain task from the Assets page at all.
  Existing tasks still open in-place via TaskModal, unchanged.
- Recurring.tsx / Summary.tsx: read those query params on mount, open
  the appropriate form/modal prefilled, then strip the params from the
  URL (replace, no history pollution).
- NewTaskModal.tsx + Recurring.tsx template form: asset picker is no
  longer gated behind picking a location first — it always shows the
  full register (grouped by location) or the location-filtered list
  once one's chosen, and selecting an asset now back-fills its location
  automatically. Makes tagging routine servicing to a specific asset
  (fixed or floating recurring) the natural way in, for both one-off
  and recurring tasks.
- Asset list/detail counts (open tasks, recurring templates) are
  unchanged — still sourced from the same main-table queries.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 15:09:24 +00:00
d9b01a5981 Add lead time to recurring templates — generate before actually due
task_templates.lead_days (default 0): scheduler now spawns the task
when (next_due - lead_days) <= today instead of next_due <= today, but
due_date on the created task is still the real next_due — so overdue
and priority handling are unaffected, the task just becomes visible
earlier for prep/booking. Small jobs (AC filter clean) can stay at 0;
bigger ones needing advance notice (septic tank empty-out, contractor
booking) can set 2-3 days or 30 days as needed.

- db.js: lead_days INT NOT NULL DEFAULT 0 (inline for fresh installs,
  ALTER for existing).
- scheduler.js: WHERE (next_due - lead_days) <= $1 — next_due itself
  and the fixed/floating rollover logic are untouched by this.
- templates.js: accept + validate lead_days (>= 0) on create/update.
- Recurring.tsx: lead time field in the template form with contextual
  hint text, plus a small 'generates Nd early' note on list rows that
  have one set.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 12:20:50 +00:00
763e7be6ca Add delete action for recurring templates
There was no way to remove a template once created (e.g. test/scratch
recurring tasks) - only edit/pause via active. DELETE /api/templates/:id
detaches any tasks already spawned from it (template_id cleared) rather
than blocking on the FK or cascading, so existing task history survives
as ordinary one-off tasks while the recurring schedule itself is removed.
Delete button in the edit modal footer, only shown when editing an
existing template - not on new/duplicate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 11:58:52 +00:00
690de73172 Fix duplicate template due date being silently cleared
duplicate() blanked next_due along with location/asset, but title/
description/notes/etc. all stayed filled in — so a duplicated
template looked fully populated while due date was actually empty,
and the combined 'Title and first due date are required' error didn't
say which field was missing. Adding another room to an existing
schedule normally wants the same due date as the rest of the group
anyway, so carry it over from the source template instead of clearing
it; only location/asset genuinely need re-picking. Error messages now
name the specific missing field.
2026-08-26 11:53:54 +00:00
ee23cb1b16 Add template groups: bulk multi-location creation + duplicate action
For recurring jobs that repeat identically across many locations (e.g.
clean AC filter in every room), one task_templates row per location is
still required — schedule/completion tracking is per-location, which
matters especially for floating mode — but creating and organising
that many rows by hand didn't scale.

- New template_groups table (id, unique name) + task_templates.group_id.
  POST /api/template-groups upserts by name so picking an existing
  group from the UI never creates a duplicate folder.
- Recurring template form: optional group name (datalist-autocompleted
  from existing groups) plus a "create for multiple locations" toggle
  that reveals a checkbox list of locations; saving loops one
  createTemplate() call per selected location, all sharing the
  resolved group_id.
- Duplicate button on each template row pre-fills the New Template
  form from that row (location/asset/next-due cleared for repicking),
  landing in the same group automatically — the lightweight path for
  adding one more room later without the bulk picker.
- Recurring.tsx list now groups rows under a collapsible folder header
  per group_name; ungrouped templates render as flat rows as before.
2026-08-26 10:33:19 +00:00
c740b87f1b Show hotel name on login screen (was missing despite VITE_HOTEL_NAME plumbing)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 10:22:04 +00:00
a661a06a8f Add fixed/floating schedule mode to recurring task templates
Fixed mode keeps the existing behavior: next_due always advances from
the previous scheduled date, regardless of when a task is actually
completed. Floating mode instead computes the next occurrence from
the completion date of the current task, so early/late completion
shifts the whole schedule.

- task_templates gains schedule_mode ('fixed'|'floating', default
  'fixed') and pending_task_id, tracking the open task spawned from a
  floating template so the hourly sweep doesn't respawn duplicates
  before it's resolved.
- scheduler.js: floating templates set pending_task_id instead of
  advancing next_due at spawn time.
- tasks.js /resolve: floating templates recompute next_due from
  completed_at once the task is marked fixed (not temporary_fix,
  since that isn't really done yet); pending_task_id is always
  cleared on fixed resolution so a stale flag can't wedge the
  template if the mode changed mid-cycle.
- addInterval/isoDate pulled out of scheduler.js into a shared
  lib/recurrence.js used by both the scheduler and the resolve route.
- Recurring.tsx: schedule mode toggle in the template form, and the
  list shows "awaiting completion" instead of a stale next-due date
  for floating templates with an open occurrence.
2026-08-26 10:14:03 +00:00
aaf31f26b7 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
71b10736fa Use full "Maintenance" as PWA short_name instead of "Maint." 2026-07-29 17:23:19 +00:00
ffeff92406 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:03 +00:00
5901d61853 Revert Open category back to Operations (autocorrect typo) 2026-07-26 12:48:01 +00:00
ccf0dc0f78 Move maintenance into Open sidebar category with noticeboard/calendar 2026-07-25 21:43:48 +00:00
3740727950 Fix session-expiry redirect breaking standalone PWA out of its shell
Two compounding issues were kicking installed/standalone maintenance
PWA sessions out into the portal's framed browser view on re-login:

- manifest scope was the app's own base path instead of "/", so any
  same-origin navigation outside it (like the old redirect to
  /login) dropped the standalone window into a regular browser tab
- AuthGate unconditionally hard-navigated window.top to the central
  /login on session expiry, even when not embedded in the portal
  iframe, which is exactly the navigation the doc warns against

AuthGate now only bounces to central login when actually embedded
(and passes ?from= so it returns to this app afterwards); standalone
or directly-opened tabs get an in-app login form and never navigate
away. Also wired up the previously-dead inactivity auto-logout timer
(disabled for installed PWAs, configurable per device otherwise).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 17:06:55 +00:00
9f402bd2d9 Show upload progress — spinner placeholder in photo grid, status text on submit
TaskModal: grey placeholder thumb with spinning loader appears in the photo grid immediately on file pick, replaced by the real thumb once upload+resize completes.
NewTaskModal: submit button shows spinning icon with 'Creating task…' then 'Uploading photo N of M…' instead of just greying out.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 11:12:00 +00:00
b3f4adeb3e Add name, email and sign out button to sidebar footer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 11:10:05 +00:00
626d4d4d72 Replace timeline photo thumbnail with compact 'view' link
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 11:06:30 +00:00
5d62050745 Photo lightbox, timeline photo previews, subtle edit/delete footer
- Photos open in a full-screen lightbox overlay instead of navigating to a new tab (fixes PWA back-button issue)
- DB migration adds photo_id to task_events; photo upload logs the reference so activity timeline can show a clickable thumbnail for each 'Photo added' event
- Edit and Delete buttons moved to a quiet ghost-button footer at the bottom-right of the modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 10:59:51 +00:00
4ab9eb4035 Compress and resize photos on upload via sharp
Buffer upload → auto-rotate (EXIF) → resize to 1800px max → JPEG 82% progressive. Typical phone photo drops from 4-8 MB to ~200-400 KB. sharp prebuilt binaries include libvips so no Alpine package changes needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 10:48:24 +00:00
ca006f9aea Add edit and delete to task modal
Edit button (update cap): inline form for title, description, priority, unusable flag, due date. Delete button (admin only): hard delete with confirm step, cascades photos/events and cleans files from disk. Backend adds DELETE /api/tasks/:id route.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 10:17:57 +00:00
03d1cd2bb6 Restore collapsible filter accordions alongside new sort chip
Status and Filters dropdowns are back as expanding chips. Sort chip sits inline to the right of the two accordion headers as a peer element in a flex filter-row.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 09:53:17 +00:00
513565ecc8 Replace collapsible filter accordions with flat horizontal chip bar + add sort chip
Status and filter sections now always visible in one scrollable chip-bar. Sort chip cycles through: Priority high→low, Priority low→high, Age oldest→newest, Age newest→oldest.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 09:02:55 +00:00
43809b7460 Add Web Push notifications for task assignment
When a task is created or reassigned to a staff member, a push notification
is sent to any devices they have subscribed on. Keys are auto-generated on
first boot and stored in the config table (no manual VAPID setup needed).

- Backend: web-push dep, push_subscriptions table, lib/push.js (VAPID +
  send), routes/push.js (vapid-key / subscribe / unsubscribe endpoints),
  push notify wired into task-core createTask and tasks PATCH reassignment
- Frontend: switched vite-plugin-pwa to injectManifest strategy, custom
  sw.js handles precache + push event + notificationclick, usePushSubscription
  hook requests permission and registers subscription on login

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-20 22:59:16 +00:00
fc993c63bc Collapse status and filter chip bars on mobile
Both chip bars now start collapsed with a summary header showing
the active selection (e.g. "In Progress" / "Rooms view · Mine only").
Tap the header to expand and change filters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-20 22:35:30 +00:00
06d1d01f91 Add PWA manifest + update banner to maintenance app
Wire up vite-plugin-pwa so Docker builds generate the web manifest
and service worker (was missing, causing installs to show portal icon).
Adds UpdateBanner with version-based polling via health endpoint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-20 22:28:08 +00:00
79eba484e0 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
a3b458abca Fix maintenance /assets 403 and remove vite-plugin-pwa
- nginx try_files: remove $uri/ so the Vite assets/ build directory
  doesn't shadow the /assets React route and trigger a 403
- Remove VitePWA plugin (leftover from scaffold, not needed for internal app)
- Strip PWA-specific nginx location blocks (manifest, sw.js, registerSW.js)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 08:45:32 +00:00
c43a23b00d Configurable inactivity timeout — disabled automatically in PWA/standalone mode 2026-07-13 12:29:00 +00:00
3641ab0e6d Add shared device inactivity timeout to AuthGate 2026-07-13 11:53:05 +00:00
13ab682b23 Replace letter icons with Lucide PWA icons 2026-07-11 20:30:09 +00:00
ced0754fcc Make maintenance installable as a PWA
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-11 11:27:00 +00:00
83ac37e4a1 Fix expired session showing EmbeddedFallback instead of login page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-10 11:44:00 +00:00
965f28186f Add group-by-location toggle to maintenance summary filters
Layers chip in the second chip bar groups tasks by location alphabetically
with section headers. Location name is suppressed from task card meta when
grouping is active to avoid redundancy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-10 11:19:53 +00:00
25d6323aa4 Differentiate arrived vs arriving-today in Rooms Accessible View
A confirmed booking for today that hasn't checked in yet is accessible —
staff can get in before the guest arrives. Previously it was lumped with
in-room guests and greyed out.

Backend: classifyBookingStatus() splits NewBook 'staying' bookings by
status string (normalised). Statuses containing 'arriv', 'inhouse' or
'checkedin' → occupied; everything else → arriving. /api/occupancy now
returns both occupied_site_ids and arriving_site_ids.

Frontend:
- Occupied rooms: greyed out + 'Occupied' badge (guest physically in room)
- Arriving today: normal display + amber 'Arrival today' badge (access now,
  prioritise before guest checks in)
- Chip label shows 'Rooms (X free • Y arriving • Z occupied)' breakdown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-09 12:29:26 +00:00
3fc23eaecc Fix #5 category in search results, rename rooms chip (#6)
- #5: SearchSelect now shows the area/category as a muted right-aligned
  label on every dropdown item — visible at a glance whether a result is
  a Room, Kitchen, Garden etc. Typing the category name already filtered
  correctly; the label makes it clear without needing to read the group header.
  Applies to both the new-task location picker and the asset location picker.
- #6: Rename 'Rooms view' chip to 'Rooms Accessible View' to better convey
  that it shows which rooms staff can actually access right now.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 12:34:22 +00:00
2a2b14029a Fix issues #2 #5 #6 #8: fuzzy search, rooms-view, asset edit button
- #2 #5: SearchSelect now splits query into words — each word must appear
  anywhere in label or group, so '101' matches 'Room 101' and 'room 101'
  matches too (multi-word, order-independent)
- #6: Rooms-view chip replaces 'Unoccupied rooms only'. When active:
  filters task list to rooms category only, fetches live occupancy, and
  shows occupied-room tasks greyed out with an 'Occupied' badge — so small
  jobs (bulb changes etc.) are still visible without being buried. Category
  chips hide while rooms-view is on to avoid conflict.
- #8: Rooms-view chip shows total rooms count + free count
  e.g. 'Rooms (5 • 3 free)'. Category chip counts stay as-is (full
  count) and hide when rooms-view is active.
- #3/#4 follow-up: Add pencil edit button on every asset table row so
  users can edit an asset directly without opening the detail modal first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 12:26:43 +00:00
859874f614 Fix issues #2-#6 and #8: searchable pickers, occupancy filter, asset UX, counts
- #2 #5: Replace plain <select> location pickers in NewTask and Assets with
  SearchSelect combobox — full-text search across name and category group
- #3: Add "Unknown" checkbox on asset install date — disables the field and
  saves null instead of leaving an ambiguous blank
- #4: Add inline "Add service task" form in asset detail modal — creates a
  recurring template pre-linked to the asset without leaving the page
- #6: Fix unoccupied rooms filter — was always empty because (a) list_type
  was 'all' instead of 'staying' and (b) only checked b.site_id but NewBook
  returns booking_site_id on most account configs
- #8: Add open-task count badges to category filter chips on Summary page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 12:09:57 +00:00
2a3a226131 Fix 400 on NewBook sync and run-due: send empty JSON body
Fastify rejects POST requests that carry Content-Type: application/json
but an empty body (JSON.parse('') throws). Both no-body POSTs now send {}.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 11:10:14 +00:00
3289a31027 Remove NewBook room blocking — app never writes to NewBook
Unsellable flag is in-app visibility only; staff mark rooms out of
order in NewBook through their own process. NewBook use is now
read-only (room sync + occupancy filter).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 21:37:52 +00:00
6ca395097e Maintenance log book app — initial scaffold
Multi-department fault log: NewBook-synced room locations + manual
locations with categories, six-state task flow (submitted/in progress/
hold-parts/hold-later/temporary fix/fixed), photos per stage, priorities
with unusable flag and per-task NewBook out-of-order push, costs on
resolve, comment/audit thread, recurring task templates with
note-to-template carryover, asset register, contractor register with
document attachments, staff/contractor allocation, occupancy-aware
summary filter, searchable history with CSV export, email notifications.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 21:28:57 +00:00