Skip to content
App Crafters
All projects

Our own tool · Restaurant group · 2025

Scheduling against tables instead of covers

A widget that counts covers will seat two people at a six-top on a Friday, then refuse a party of eight that two adjacent tables would have taken. Both are decided at the moment of booking, which makes them the software's job. This is the allocator that makes them properly.

4

Turn-time bands derived from party size, because one length either loses the late sitting or holds tables empty

  • Next.js
  • TypeScript
  • Postgres
  • Viber

Try it here

This is the same code that runs on the sector page, not a recording of it. Put your own numbers in.

  1. Pick a party size and book it - see which table it lands on.
  2. Try a party of eight with combining off, and see which tables it would have needed.
  3. Hold the two-tops for walk-ins and see what that costs in bookings.

0/3

table-allocatorinteractive

Real allocation, running in your browser - turn times, tightest fit, table combining, seats wasted, all genuinely computed. Nothing is sent anywhere. The plan and the timeline are two views of the same service. Six tables and one evening is a simplification of a real floor; the logic is not.

The numbers

2
Combinable pairs on this floor, and the allocator reaches for them last rather than first
1
Number nobody was measuring: seats left empty per booking

The problem

A group with three rooms takes bookings through a widget that counts covers against a limit.

So a party of two gets a six-top on a Friday because the cover count allowed it, and a party of eight is refused while two adjacent four-tops sit empty, because the widget has no concept of pushing tables together. Both are decisions made at the moment of booking, which means no amount of reporting afterwards recovers them.

What we did

  1. Turn time is derived from party size rather than set once. A single turn length applied to every booking either loses the late sitting or holds tables empty, and which of the two it does depends on where the number was set - so it is not a setting, it is a curve.

  2. Assignment is tightest-fit first and combinations only as a fallback, because pushing two tables together costs the flexibility of having two. The allocator will take a worse fit rather than break up a pair it might need later.

  3. A refusal names the constraint that caused it. "Fully booked" is useless to a manager; "tables three and four together would seat them, but combining is switched off" is a setting somebody can change while the guest is still on the phone.

  4. Seats wasted is surfaced per booking and totalled per service, because it is the number that makes the case for the whole system and nobody was measuring it.

Where it got to

The allocator on the restaurants page is this logic, running in your browser with six tables and one service. Switch combining off and try a party of eight to see the refusal that matters.

A worked example

The allocator on the restaurants page is this logic. One Friday service, six tables, combining off then on:

Covers booked at the start
22
Floor in use
26%
Party of 8 at 21:30, combining off
refused
Reason
tables 3 + 4 would seat them
Same booking, combining on
30 covers, 40%
Two more pairs at 22:00
34 covers, 4 seats empty, 48%

The second pair took a six-top because nothing tighter was free, and the allocator said so. A widget that counts covers reports none of this.

Try it here

Want something like this for your business?

A 20-minute call with the two people who would build it. You tell us what you need; we tell you whether it is worth building and roughly what it costs.

The full page: Restaurants & groups
Book a call