The slot booking page is one number field per slot tier, stacked, and little else. SlotBookingForm::buildForm() loops the slot's tiers and emits quantityField() for each; that field is a number element whose #title is the price category's label and whose #description is the price and the word Available. Everything a buyer decides on is therefore helper text under a spinner.
Concrete problems, in rough order of how much they hurt:
- The tariff matrix is flattened into labels. Sites price a seating grade by a tariff family, so the tiers are a cross-product ("Full price - Category 1", "Youth - Category 1", ...). The page renders them as N sibling spinners and the grouping survives only as a string inside each label. A hall with four grades and three tariff families is twelve stacked spinners with no rows, no columns and no headings.
- Price has the lowest visual weight on the page. It is part of
#description, joined to the availability word with a dash. There is no price column, no per-line total, and the only total is a bare paragraph at the bottom. - Availability is a single word. Deliberately so, because the raw count is racy under concurrent booking, but the result is that scarcity is invisible: nothing distinguishes a hall with two seats left from a hall with two thousand. A banded hint ("a few left") would keep the honesty and restore the signal.
- The maximum is enforced silently. The field's
#maxis the held quantity plus what is free, so the spinner simply refuses to go higher with no explanation of the ceiling. - The order block re-implements the summary, worse. It is an
item_listof bare place labels, so a line reads "A15" with no tariff, no area and no price. The module already has a richer renderer:TransactionSummaryproduces tier, seat, area and price per line through a theme hook, and the placement module already contributes the area name to it. Worse, a printed seat name is only unique within its area, so on a real venue several different seats are all printed "A15" and the line does not identify a seat at all. - Nothing connects a tariff to where the seats are. The page never says which area a grade corresponds to, and the map is a separate page, so a buyer choosing between two prices is choosing between two numbers.
- Two submit buttons, no explanation. Update basket and Continue to checkout sit side by side, and the order block only reflects a change after the first one is pressed.
What a reshape should probably do: group the tiers by their tariff family and their grade rather than emitting siblings, give price and availability real positions instead of helper text, render the order through TransactionSummary so the lines say what they are, and show enough area context that a price means something. Whether the quantity control stays a spinner is an open question; for a small party a stepper or a select reads better and avoids the silent cap.
Scope note: fixing only the order block (routing it through TransactionSummary, which also restores the per-line prices and the area) is a small, self-contained improvement and could land first if the wider reshape needs design discussion.
Issue fork yoyaku-3614127
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
mably commentedA design direction worth aiming at, from a mainstream ticketing layout: one collapsible panel per tariff family (subscription, full price, reduced), collapsed by default with one open; inside the open panel, one card per seating grade; the price set large and right-aligned with a smaller fee note beneath it; and a minus/value/plus stepper rather than a number spinner, which also removes the silently-enforced maximum described above.
The fee note has no model behind it today, so it is filed separately as #3614129: Add a per-ticket booking fee to the price shown and charged and is not part of this reshape. This issue should assume the price is the only amount per line.
Comment #4
mably commentedComment #6
mably commentedComment #8
mably commentedMerged to 1.x as
3d740e8.The page is now offer cards rather than a column of spinners. Each names its tariff and the place grades it prices, gives the price its own position instead of helper text, and says what is left in the words the resource allows: the exact count where it publishes one, a band where it does not, stated once and excluding what the visitor already holds.
Quantities are held as the visitor clicks, through a minus/value/plus stepper that debounces so several presses are one request. It runs the same reconcile the visible buttons use, so there is one booking path rather than a second one written for the browser, and it degrades to a real submit with no JavaScript.
Tariffs can now be grouped:
yoyaku_category_groupis a heading over the categories of one resource, and the page offers one collapsible panel per group. Groups are optional and a resource that defines none is offered as a plain list, exactly as before, which is covered by its own test.The basket block is gone from this page, since the cart page owns it, and the remaining actions sit on one row. The page title names the event and the line beneath it names the venue.
Accessibility was treated as part of the feature: the stepper's buttons are named after their offer, a changed quantity is announced, the ceiling is marked with aria-disabled rather than disabled so focus is not lost, focus returns to the pressed button after the rebuild, and contrast was computed rather than eyeballed. Twenty browser tests cover it.
Two follow-ups came out of the work: #3614328: Rename a tier's capacity to quota, because it is not a capacity to rename a tier's capacity to quota, because it is a selling limit and not a capacity, and #3614282: Show how long a cart is held, warn before it lapses, and let the visitor extend it to tell a visitor how long their cart is held.