Commit graph

17 commits

Author SHA1 Message Date
d0ff70eb86 Summary: folder tabs, priority filter badges, sort dropdown; add Hold — Booked
- Status filter replaced by folder tabs (All open / Overdue / In progress /
  Temp fix / On hold) with live count badges; tabs, priority and category
  filters now applied client-side so counts always match
- Priority filter badges with counts; sort is now a dropdown list
- New hold_booked status (contractor booked), grouped with in progress, with
  optional booked date stored in hold_until; hold_until cleared when leaving
  the date-bearing holds

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 13:25:19 +00:00
e3e7e60594 Show Arrival today alongside Departing for back-to-back turnovers
fetchRoomOccupancy() was dropping a site from `arriving` as soon as it
was also `departing`, on the assumption departing always wins. But a
departing room can have a same-day incoming booking already confirmed —
worth flagging on its own, not silently swallowed. Site now stays in
both sets when that's the case; the Rooms Accessible View card renders
both the purple "Departing" and amber "Arrival today" badges together.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:38:26 +00:00
8ddb99d2d7 Add a Departing state to the Rooms Accessible View
A guest checking out today but not yet gone was previously lumped in
with ordinary stayovers — fully greyed out, no way to tell "the guest is
gone in 20 minutes" apart from "the guest isn't leaving at all". Split
that out into its own bucket:

Backend: classifyBookingStatus() now also recognises 'depart'/'checkedout'
statuses (already checked out — room's free regardless of the date
window). fetchRoomOccupancy() returns occupied/departing/arriving; an
in-house booking whose departure date is today lands in 'departing'
instead of 'occupied'. The ?unoccupied=true task filter and /api/occupancy
both updated for the new three-way split.

Frontend: departing rooms get a light dim (opacity only, no greyscale)
instead of the hard grey-out, plus a purple 'Departing' badge — matching
the booked=green/occupied=blue/departing=purple colour language hvac
already uses for NewBook room states. Chip label breakdown now reads
"X free • Y arriving • Z departing • W occupied".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 09:33:57 +00:00
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
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
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
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
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
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
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
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
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
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