Skip to content
Vince.
All case studies

Product · Reservations · Menu

Coffee shop

Reservations that know a table is busy for ninety minutes

Table reservations with turn times and table combining, plus a menu whose modifiers actually affect the price. Slots are offered only when a real table can seat the party for as long as it will be sitting there.

  • TypeScript
  • React
  • Vitest

In short

  • Turn time scales with party size, so a booking blocks the slots it spills into
  • Smallest table that fits, and combining only when nothing single will do
  • Unavailable slots are shown with a reason rather than hidden, so a busy service does not look like a closed one
  • Menu modifiers fold into the unit price before quantity, so two large lattes cost twice the large price

A booking is longer than the time it names

A reservation form that offers every half hour the shop is open, and blocks only the exact time somebody booked, will double-seat the same table before lunch. A party of four sits for ninety minutes; a party of nine sits for two and a half hours. The booking occupies a range, and every slot overlapping that range has to close with it.

Once turn times vary by party size, the available slots depend on who is asking. The same 7pm can be free for two and impossible for eight — not because of a rule, but because of what will physically fit.

Seating two people at the table for eight

Any free table with enough seats is a legal answer, and the largest one is the worst. It burns the only table that could have taken the next big booking, and it does so invisibly: the reservation succeeds, the guests are seated, and the party of eight that rings an hour later is turned away for no reason anybody can see.

The allocator takes the smallest single table that fits, and only tries combining when nothing single will do — then takes the smallest workable combination for the same reason. Tables can only combine with the ones they physically adjoin, which is a property of the room rather than the software, so it lives in the data.

Show the closed doors

Unavailable slots are returned with a reason rather than filtered out. A list that silently skips 7pm looks like a restaurant that does not open until 7:30; a struck-through 7pm marked "fully booked" tells a diner the place is worth queueing for.

It is a one-line difference in the return type and it changes what the interface is able to say.

Modifiers are pricing, not decoration

An oat-milk latte in a large size is one item with two price adjustments, and the adjustments have to survive being multiplied by a quantity. Folding them into the unit price before multiplying is the only order that gives the right answer — three large lattes is three times the large price, not three times the base price plus a single upgrade.

A required modifier group with nothing chosen is an incomplete order rather than a zero-cost one. Defaulting silently is how a customer is charged for a size they never picked.

Source

  • src/lib/dining/reservations.ts
  • src/lib/dining/menu.ts
  • src/lib/dining/dining.test.ts