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.
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>
- 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>
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>
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>
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>
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>