Split availability windows into bookable lesson-length slots; weekly calendar views; 12-hour times #50

Merged
thatguygriff merged 2 commits from feature/slot-splitting-and-week-view into main 2026-07-05 19:14:51 +00:00
Owner

Closes #49

Problem

An availability window was stored and served as a single bookable row, so:

  • A 2026-07-06 09:00–16:00 window with 60-minute lessons showed to students as one "09:00–16:00 (60 min)" entry, and booking it consumed the whole day.
  • A month-long window (2026-06-01 19:522026-06-30 19:52) rendered as a nonsense "19:52–19:52" block on a past date — nothing filtered past slots or prevented multi-day windows.
  • Times rendered in 24-hour form everywhere.

Changes

Slot splitting

  • AvailabilitySlot::splitByDuration() chunks a window into consecutive lesson-length slots (a trailing remainder shorter than a lesson is dropped); AvailabilityRepository::createFromWindow() persists one row per chunk — used by both the REST endpoint and the admin form, so the existing claim/booking flow is untouched.
  • Weekly recurrence creates a series per chunk, so "reserve this time weekly" still holds the same hour each week.
  • Windows must start and end on the same day and fit at least one lesson (400 invalid_window; the admin form is a no-op, matching its handling of other invalid input). POST /availability now always returns {"ids": [...]}.

Past-slot filtering

  • findAvailable() always floors results at current_time('mysql') — a slot whose start has passed is never returned regardless of query params.

Data migration

  • Plugin::boot re-runs the Installer when the stored plugin version differs from USC_VERSION (bumped to 1.0.0-rc.2), and the installer runs splitOversizedWindows(): unbooked same-day windows longer than their lesson length are rewritten in place as lesson-length rows. Multi-day legacy rows are left alone (hidden from students by the past filter; deletable from My Availability).

12-hour AM/PM times

  • Booking page JS, wp-admin availability list, admin student detail, and the Gutenberg editor previews all render g:i A style times.

Weekly calendar views

  • Student booking page: List | Week toggle; the week view shows seven day columns of bookable time buttons with previous/next-week navigation, defaulting to the week of the earliest open slot.
  • My Availability: the same toggle via usc_view=week / usc_week=Y-m-d (bookmarkable); the grid shows each day's slots with Booked markers or Delete buttons.
  • Both honour the site's start_of_week option through the new pure WeekCalendar helper (admin) and the usScheduler JS config (frontend).

Testing

  • New: AvailabilityEndpointTest (window validation, chunked create), WeekCalendarTest (week resolution, bucketing), plus splitByDuration, createFromWindow, splitOversizedWindows, and past-floor coverage in the existing suites.
  • composer test (241 tests), composer lint (PHPStan level 10), and composer cs all pass.

🤖 Generated with Claude Code

Closes #49 ## Problem An availability window was stored and served as a **single bookable row**, so: - A `2026-07-06 09:00–16:00` window with 60-minute lessons showed to students as one "09:00–16:00 (60 min)" entry, and booking it consumed the whole day. - A month-long window (`2026-06-01 19:52` – `2026-06-30 19:52`) rendered as a nonsense "19:52–19:52" block on a past date — nothing filtered past slots or prevented multi-day windows. - Times rendered in 24-hour form everywhere. ## Changes ### Slot splitting - `AvailabilitySlot::splitByDuration()` chunks a window into consecutive lesson-length slots (a trailing remainder shorter than a lesson is dropped); `AvailabilityRepository::createFromWindow()` persists one row per chunk — used by both the REST endpoint and the admin form, so the existing claim/booking flow is untouched. - Weekly recurrence creates a series **per chunk**, so "reserve this time weekly" still holds the same hour each week. - Windows must start and end on the same day and fit at least one lesson (`400 invalid_window`; the admin form is a no-op, matching its handling of other invalid input). `POST /availability` now always returns `{"ids": [...]}`. ### Past-slot filtering - `findAvailable()` always floors results at `current_time('mysql')` — a slot whose start has passed is never returned regardless of query params. ### Data migration - `Plugin::boot` re-runs the `Installer` when the stored plugin version differs from `USC_VERSION` (bumped to 1.0.0-rc.2), and the installer runs `splitOversizedWindows()`: unbooked same-day windows longer than their lesson length are rewritten in place as lesson-length rows. Multi-day legacy rows are left alone (hidden from students by the past filter; deletable from My Availability). ### 12-hour AM/PM times - Booking page JS, wp-admin availability list, admin student detail, and the Gutenberg editor previews all render `g:i A` style times. ### Weekly calendar views - **Student booking page**: List | Week toggle; the week view shows seven day columns of bookable time buttons with previous/next-week navigation, defaulting to the week of the earliest open slot. - **My Availability**: the same toggle via `usc_view=week` / `usc_week=Y-m-d` (bookmarkable); the grid shows each day's slots with Booked markers or Delete buttons. - Both honour the site's `start_of_week` option through the new pure `WeekCalendar` helper (admin) and the `usScheduler` JS config (frontend). ## Testing - New: `AvailabilityEndpointTest` (window validation, chunked create), `WeekCalendarTest` (week resolution, bucketing), plus `splitByDuration`, `createFromWindow`, `splitOversizedWindows`, and past-floor coverage in the existing suites. - `composer test` (241 tests), `composer lint` (PHPStan level 10), and `composer cs` all pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
thatguygriff added 1 commit 2026-07-05 19:07:04 +00:00
Split availability windows into bookable lesson-length slots with weekly calendar views
CI / Build Plugin Zip (pull_request) Has been skipped
CI / Tests (PHP 8.2) (pull_request) Successful in 45s
CI / PHPStan (pull_request) Successful in 2m48s
CI / Tests (PHP 8.1) (pull_request) Successful in 41s
CI / No Debug Code (pull_request) Failing after 2s
CI / Coding Standards (pull_request) Successful in 52s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
b7d5e3039e
Availability windows were stored and served as a single bookable row, so a
9:00 AM-4:00 PM window showed to students as one giant slot and booking it
consumed the whole day; past and multi-day windows also leaked into the
booking page as nonsense entries.

- Split windows into consecutive lesson-length slots on save (REST and admin
  form); each chunk is independently bookable and weekly recurrence creates a
  series per chunk so "reserve this time weekly" holds the same hour each week
- Reject windows spanning multiple days or shorter than the lesson length
  (400 invalid_window)
- Never return slots whose start has passed from GET /availability
- Migrate pre-split rows: Plugin::boot re-runs the Installer on version change
  and AvailabilityRepository::splitOversizedWindows() rewrites unbooked
  same-day oversized windows in place
- Display all times in 12-hour AM/PM form (booking page, wp-admin lists,
  editor previews)
- Add a List | Week view toggle to the student booking page and the
  instructor availability page, with previous/next-week navigation honouring
  the site's start_of_week option (new WeekCalendar helper)

Co-Authored-By: Claude Fable 5 <[email protected]>
thatguygriff added 1 commit 2026-07-05 19:12:40 +00:00
Word-bound the no-debug CI grep so method calls like ->add() don't match dd(
CI / Tests (PHP 8.2) (pull_request) Successful in 38s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 1m20s
CI / PHPStan (pull_request) Successful in 1m43s
CI / Build Plugin Zip (pull_request) Has been skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m5s
dbf61e8593
The unanchored dd\( pattern matched the substring in DateTimeImmutable::add(),
failing the check on non-debug code.

Co-Authored-By: Claude Fable 5 <[email protected]>
thatguygriff merged commit 43497503b9 into main 2026-07-05 19:14:51 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Unsupervised/unsupervised-scheduler#50