Students could not tell which of the studio's private-lesson types a given open time supports until they clicked it — a slot tied to an offering takes that offering only, and a generic slot only takes types whose length fits.
What changed
Lesson-type filter above the booking calendar — a checkbox per active private-lesson type, from GET /offerings?kind=private_lesson (fetched once per page load and reused by the registration form, which previously refetched per instructor). Instructor names appear on the labels only when the catalog spans more than one instructor, and the whole control is hidden when there are fewer than two types.
Calendar narrows to the matching times. Both the week and list views show only the slots bookable as a ticked type. Changing the filter re-anchors the week view on the earliest matching slot, so you never land on an empty week. No ticks = no filter; Show all types clears it.
Registration form follows the filter. The Lesson type picker offers only the filtered types that fit the slot. When exactly one type remains it is rendered pre-selected with no placeholder and its intake questions load immediately — this also covers the pre-existing case where only one type fits the slot with no filter set.
Bookability is decided by offeringFitsSlot() in assets/js/booking.js, the client-side mirror of the rule POST /bookings already enforces (same instructor; the tied offering when there is one; otherwise matching duration_minutes). The filter is a browsing aid only — no server changes, and every booking is still validated server-side.
Docs: new Lesson-Type Filter section in docs/features/lesson-booking.md, a pointer from docs/features/availability-management.md, and a CHANGELOG entry under 1.2.2. No schema change, so no version bump.
Tests
composer test — 629 tests, 1808 assertions, OK
composer lint — no errors
composer cs — clean
PHP is untouched by this change. Since the repo has no JS test harness, the front-end behaviour was verified by driving assets/js/booking.js through a throwaway stub-DOM/fetch harness (not committed): filter rendering, slot narrowing per ticked type, the clear control, auto-select of a lone type (including that submit posts the auto-selected offering_id and loads its questions), the two-type picker excluding a non-fitting length, and tied slots still rendering their locked picker — 15/15 pass.
Closes #117.
Students could not tell which of the studio's private-lesson types a given open time supports until they clicked it — a slot tied to an offering takes that offering only, and a generic slot only takes types whose length fits.
## What changed
- **Lesson-type filter above the booking calendar** — a checkbox per active private-lesson type, from `GET /offerings?kind=private_lesson` (fetched once per page load and reused by the registration form, which previously refetched per instructor). Instructor names appear on the labels only when the catalog spans more than one instructor, and the whole control is hidden when there are fewer than two types.
- **Calendar narrows to the matching times.** Both the week and list views show only the slots bookable as a ticked type. Changing the filter re-anchors the week view on the earliest matching slot, so you never land on an empty week. No ticks = no filter; **Show all types** clears it.
- **Registration form follows the filter.** The **Lesson type** picker offers only the filtered types that fit the slot. When exactly one type remains it is rendered pre-selected with no placeholder and its intake questions load immediately — this also covers the pre-existing case where only one type fits the slot with no filter set.
- Bookability is decided by `offeringFitsSlot()` in `assets/js/booking.js`, the client-side mirror of the rule `POST /bookings` already enforces (same instructor; the tied offering when there is one; otherwise matching `duration_minutes`). The filter is a browsing aid only — no server changes, and every booking is still validated server-side.
Docs: new **Lesson-Type Filter** section in `docs/features/lesson-booking.md`, a pointer from `docs/features/availability-management.md`, and a CHANGELOG entry under 1.2.2. No schema change, so no version bump.
## Tests
- `composer test` — 629 tests, 1808 assertions, OK
- `composer lint` — no errors
- `composer cs` — clean
PHP is untouched by this change. Since the repo has no JS test harness, the front-end behaviour was verified by driving `assets/js/booking.js` through a throwaway stub-DOM/fetch harness (not committed): filter rendering, slot narrowing per ticked type, the clear control, auto-select of a lone type (including that submit posts the auto-selected `offering_id` and loads its questions), the two-type picker excluding a non-fitting length, and tied slots still rendering their locked picker — 15/15 pass.
Not every open time can be booked as every private-lesson type: a slot tied
to an offering takes that offering only, and a generic slot only takes types
whose length fits. Students had no way to see that before clicking a time.
The booking calendar now carries a lesson-type filter — a checkbox per active
private-lesson type, fetched once from GET /offerings?kind=private_lesson.
Ticking types narrows the calendar to the times bookable as one of them and
re-anchors the week view on the earliest match. The registration form's
Lesson type picker is narrowed the same way, and a lone remaining type is
pre-selected with its intake questions loaded.
Bookability is decided by offeringFitsSlot(), the client-side mirror of the
rule POST /bookings enforces; the filter is a browsing aid and the server
still validates every booking. No ticks means no filter, and the whole
control is hidden when the studio offers fewer than two private-lesson types.
Closes#117
Co-Authored-By: Claude Opus 5 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #117.
Students could not tell which of the studio's private-lesson types a given open time supports until they clicked it — a slot tied to an offering takes that offering only, and a generic slot only takes types whose length fits.
What changed
GET /offerings?kind=private_lesson(fetched once per page load and reused by the registration form, which previously refetched per instructor). Instructor names appear on the labels only when the catalog spans more than one instructor, and the whole control is hidden when there are fewer than two types.offeringFitsSlot()inassets/js/booking.js, the client-side mirror of the rulePOST /bookingsalready enforces (same instructor; the tied offering when there is one; otherwise matchingduration_minutes). The filter is a browsing aid only — no server changes, and every booking is still validated server-side.Docs: new Lesson-Type Filter section in
docs/features/lesson-booking.md, a pointer fromdocs/features/availability-management.md, and a CHANGELOG entry under 1.2.2. No schema change, so no version bump.Tests
composer test— 629 tests, 1808 assertions, OKcomposer lint— no errorscomposer cs— cleanPHP is untouched by this change. Since the repo has no JS test harness, the front-end behaviour was verified by driving
assets/js/booking.jsthrough a throwaway stub-DOM/fetch harness (not committed): filter rendering, slot narrowing per ticked type, the clear control, auto-select of a lone type (including that submit posts the auto-selectedoffering_idand loads its questions), the two-type picker excluding a non-fitting length, and tied slots still rendering their locked picker — 15/15 pass.