Foodops
All docs

Counter · Updated 2026-08-24 · va332e72

Dine-In & Table Management

Audio narration for this article is generated with the weekly docs refresh.

Plain-English summary

The Dine-In page shows a visual floor plan of your restaurant — tables laid out as you've arranged them. Staff tap a table to open a new order or see what's already on it. It's how a waiter opens "Table 3 wants two coffees" without typing anything — just tap the table, add items, and the order is tied to that seat for the whole visit.

When you'd use this

Sit-down service where the same order grows over time (starters → mains → desserts → bill). The floor plan replaces the physical paper tickets and makes it obvious who's still eating versus who's ready to pay. Takeaway and delivery don't need this view — they use the Counter directly.

How to access

  • URL: /tables
  • Sidebar: the dining-table icon (4th icon from the top in the Foodops sidebar — shown in red when you're on this page)
  • From POS Counter: click the Dine-in button in the top-right of /orders/index

Dine-In landing — Ground Floor with 4 tables, zoom controls, and right-side order panel

Prerequisites

  • At least one floor configured for the outlet (a default "Ground Floor" exists on setup)
  • At least one table on that floor (created in Settings → Outlets → Tables)
  • An open register session — dine-in orders can't be created without one. Register access is coordinated across devices via SignalR — see Sales Sessions for details.

The test org has 4 tables on Ground Floor (Table 1 through Table 4), which is the state shown in the screenshots.

Walkthrough — open a dine-in order

1. Open the Dine-In page

Click the dining-table sidebar icon or navigate to /tables. The floor plan loads with tables shown in their configured positions.

2. Understand the floor plan

The main area is a canvas-rendered floor plan — tables are drawn on a grid, not as HTML elements. This matters for two reasons:

  • Drag-to-rearrange works directly on the canvas (in floor-config mode)
  • Automated scripts need pixel coordinates, not DOM selectors, to click a table

Controls at the top:

  • Floor tabs (left): "Ground Floor" and any other floors you've created
  • Zoom controls (right): −/+ buttons, percentage display (default 65%), fullscreen toggle, pan/drag mode

Table colour states:

  • Grey — Available (no active order)
  • Pink/red — Selected or occupied with an active order
  • Other colour variants exist for Reserved and Dirty depending on theme (note: the Reserved state is effectively unused at the July-10 launch — reservations is a retired legacy feature, see Reservations)

Chair highlighting (NEW 2026-04-30): Individual chair icons within a table are now highlighted based on occupiedSeats — the count of seats with active orders assigned (commit 31ec27d). A table with 6 seats and only 4 orders shows 4 highlighted chairs and 2 unlit, giving floor staff at-a-glance partial occupancy visibility without tapping the table.

Verified 2026-05-04 (Playwright) — the floor plan renders the 4 tables (Table 1–4) on Ground Floor with all tables in the available (grey) state — no active orders. Each table shows chair icons in an unlit state. The right-side panel shows "Select Table / No orders found" until a table is selected.

Dine-In page — Ground Floor with Table 1–4 in available state, right panel showing Select Table / No orders found

Note: Occupied-chair highlighting (commit 31ec27d): the exact color/fill/stroke of highlighted vs. available chairs could not be verified in the 2026-05-04 playwright pass — the E2E test org had no active dine-in orders. The available (unlit) chair style is confirmed as above. Re-verify with an active dine-in order to capture the occupied state.

3. Select a table

Click a table on the floor plan. The right panel updates to show:

  • Table name (e.g., "Table 1")
  • A + New Order button in the top-right of the panel — shown when the table can accept a new order (i.e. the table is vacant, or the outlet has "Allow multiple active orders per table" enabled). On an occupied table with that setting OFF (the default), the button is hidden. [code: foodops.counter.app/src/pages/DineInPage.tsx:762-873]
  • The list of active orders on that table, or "No orders found / This table has no active orders"

Table 1 selected — highlighted pink on the floor plan, right panel shows table header and New Order button

4. Create the order

Click New Order. Foodops navigates to /orders/create?tableId={guid}&originView=dinein. This is the Counter order form, but with dine-in-specific fields pre-filled:

Dine-In order creation — Order Type locked to Dine In, Covers and Server fields appear alongside Floor Table and Customer

Field What it does Dine-in specific?
Order Type Locked to "Dine In"
Covers Number of guests at the table (defaults to 1)
Floor Table Auto-filled with "Ground Floor/Table 1" — tells the kitchen and receipt which seat
Server Auto-filled with the logged-in staff member (e.g., "Claude Tester") — tracks who handled service
Customer Walk In Customer by default; change to link to a saved customer Shared
Notes Visible on the receipt and kitchen ticket Shared

5. Add items and save

From here the flow is identical to POS takeaway:

  • Click categories on the left → click item cards to add to the order
  • Use −/qty/+ on the right to adjust quantities
  • Save & Accept — order goes to kitchen, stays Open on the table, card goes to KDS
  • Go to Payment — accept and settle now (rare for dine-in mid-service)
  • Save as draft — hold the order without sending to the kitchen

When saved, the table on the floor plan changes to the "occupied" colour so every staff member knows it's active. The occupiedSeats count updates, which may additionally highlight individual chair icons on the table.

6. Continue the service

Dine-in orders usually grow. Tap the same table again and you'll see the existing order in the right panel — choose it to add more items, move to ready, settle the bill, or fire a course to the kitchen. The order lifecycle is:

New → Open → Preparing → Ready → Closed (or → Cancelled)

Closing the order (via payment) moves the table back to Available on the floor plan.

Key concepts

  • Floor — a named section of the restaurant's physical layout (Ground Floor, First Floor, Rooftop, Terrace). Each outlet can have multiple floors on higher plans; the Starter plan caps at 1 floor.
  • Table — a specific seating location on a floor, with a number and seat capacity. Configured in Settings → Outlets → Tables. Every table has a persistent GUID that the order form uses in its URL.
  • Covers — restaurant jargon for "number of guests". Matters for reporting (revenue per cover) and for kitchen capacity planning. Separate from "party size" in reservations because walk-ins may not be booked.
  • Server — the staff member assigned to the order. Used for tipping reports, performance reporting, and splitting service charges.
  • Occupied / Available / Reserved / Dirty — the canonical states a table can be in. Dirty indicates a just-vacated table awaiting cleanup — useful for host-stand triage on busy services. Reserved depended on the reservations feature, which is retired in the new stack (see Reservations), so in practice only Available/Occupied/Dirty are exercised at launch.
  • Canvas floor plan — the rendering approach. Tables are drawn on an HTML5 canvas, which means automated scripts (e.g., for QA) need pixel-coordinate clicks rather than CSS selectors.
  • occupiedSeats — a per-table count of seats with active order assignments, calculated by DineInPage and passed to FloorPlanCanvas (introduced 2026-04-30, commit 31ec27d). Used to highlight individual chair icons within a table so staff can see partial occupancy without selecting the table.
  • Dine-in without a table — an outlet can enable enableDineInWithoutTable, which lets staff start a dine-in order without picking a table by routing it to a system/virtual "Unassigned" table (unassignedTableId on a configured unassignedTableFloorId). This is the supported way to run dine-in for venues that don't seat by table (counters, food courts). These system/virtual tables are always exempt from the one-active-order-per-table limit. [code: foodops.counter.app/src/pages/DineInPage.tsx:283-285 (enableDineInWithoutTable, unassignedTableId, unassignedTableFloorId read from the session snapshot)]

Common questions

Q: I clicked Dine-in at the Counter but nothing happened — where does it go? A: Dine-in from the Counter routes to /tables/viewlayout?originView=counter — the floor plan with a "back to counter" affordance. If the floor plan doesn't load, check that (a) the outlet has at least one table configured and (b) the register session is open.

Q: How do I add more floors/tables? A: Settings → Outlets → [your outlet] → Floors and Tables. Add a floor name, then add tables with number and seat count. Rearrange positions by dragging on the canvas in edit mode. The number of floors per outlet is plan-limited and rises with the tier; see Pricing.

Q: Can multiple orders be open on the same table? A: It depends on an outlet setting. By default this is OFF — a dine-in table can hold only one active order at a time; trying to start a second order on an already-occupied table is blocked with "Table is not vacant", and the "+ New Order" button is hidden on that table until the existing order is closed or cancelled. An outlet can turn on "Allow multiple active orders per table" (org setting AllowMultipleActiveOrdersPerTable), after which the right panel lists all active orders on the selected table and a second order can be started — useful when one diner wants a separate bill. System/virtual tables (used by "enable dine-in without table") are always exempt from the one-order limit. [code: hubits-api-sales/Hubits.Sales.Api/Services/Counter/Orders/OrderCreationService.cs:157-188; foodops.counter.app/src/pages/DineInPage.tsx:762-873]

Q: Can I split a bill across multiple payment methods? A: At payment time, you can apply multiple settlements (cash + card, for example) to the same order. Splitting the check into two separate orders is done by transferring items to a new order on the same table.

Q: What happens if a table is selected but an order is already in progress? A: The right panel shows the existing order instead of "No orders found". Click the order to view/edit it. Whether you can also start a second order on the same table depends on the "Allow multiple active orders per table" outlet setting — see the multiple-orders question above. With the setting OFF (the default), the "+ New Order" button is hidden while an active order exists on the table; with it ON, the button stays available for separate bills. [code: foodops.counter.app/src/pages/DineInPage.tsx:762-873]

Q: Can I change which table an order is on? A: Yes — transfer orders between tables from the order detail view (Actions → Transfer). The floor plan updates immediately.

Q: Why is the Covers field important? A: Reporting. "Revenue per cover" is a standard restaurant metric — total sales ÷ total guests. Leaving Covers at 1 when 4 people ate skews the data and hides over- or under-performing shifts.

Q: What's the difference between Server and the user who created the order? A: They're often the same but not always. Server is the waiter assigned to the table. The "created by" user is whoever typed the order into the POS (could be a host entering it, or a manager covering a break). The Server field is used for tipping and performance reports.

Q: Can I start a dine-in order without assigning a table? A: Yes, if the outlet has dine-in without a table enabled (enableDineInWithoutTable). The order is attached to a system/virtual "Unassigned" table so you can skip the floor-plan table pick entirely — useful for counter-style or food-court venues. Those system tables also bypass the one-active-order-per-table limit. [code: foodops.counter.app/src/pages/DineInPage.tsx:283-285]

Q: Can I see a reservation alongside the floor plan? A: No — table reservations are a retired legacy-monolith feature with no equivalent in the new stack (see Reservations). The floor plan does not show booking state, and the "Reserved" colour is effectively unused at launch because nothing populates reservations.

Q: My tables aren't appearing even though I created them in Settings. A: After adding tables in Settings, click Reload Cache (bottom of the Counter or order-creation page). The POS caches the menu + outlet config locally; changes only appear after a reload.

Q: Why are only some chairs on a table highlighted? A: The floor plan tracks occupiedSeats — the number of seats with active orders. Highlighted chairs indicate occupied seats; unlit chairs are still available. This shows partial occupancy (e.g., a 6-top with only 4 seats ordered) at a glance.

Edge cases and known issues

Canvas-rendered floor plan breaks automation

  • Symptom: Test scripts that use DOM selectors (e.g., document.querySelector('[data-table-id]')) fail to click tables
  • Cause: Tables are drawn on an HTML5 canvas, not as HTML elements
  • Workaround: For automation, use pixel-coordinate clicks. Capture a baseline screenshot and map table number → (x,y). For tests that must be resilient to layout changes, drive the order flow via the URL directly (/orders/create?tableId={guid}&originView=dinein) rather than through the UI.

"Select a table to view orders" stays even after clicking

  • Symptom: Click a table but the right panel keeps showing the empty state
  • Cause: Click landed outside the canvas hit-zone for the table (common at low zoom levels, or when the table is near a floor edge)
  • Workaround: Zoom in with the + button, click the centre of the table

Floor count limits surprise mid-setup

  • Symptom: You create a second floor and get an upgrade prompt
  • Cause: the plan's floor-per-outlet limit has been reached
  • Workaround: Put all tables on Ground Floor and use naming conventions (T-GF-01, T-BAR-02) as a workaround, or upgrade

Canvas drag-to-rearrange loses position on refresh

  • Symptom: You drag a table to a new position, reload, and it's back where it was
  • Cause: Floor-config mode needs an explicit Save before position changes persist
  • Workaround: After rearranging, click Save in the floor-config toolbar. The layout is saved per floor, not per browser session.

Plan limits

Plan tiers and their limits are not listed here — they are DB-backed and change with the lineup. See Pricing for the live catalogue.

Table count per floor is unlimited on all plans — only the floor count is gated.

API and integration notes

  • Table layout is persisted server-side per floor; each table has a GUID (tableId) used in order-creation URLs
  • Order creation URL pattern: /orders/create?tableId={guid}&originView=dinein
  • The originView parameter is counter, dinein, or delivery depending on entry path — used for "Back" button behaviour
  • Floor-plan canvas rendering uses an internal coordinate system; hit testing runs client-side on canvas click events
  • Table status (Available / Occupied / Dirty) is derived on the server from active orders. (The legacy Reserved-from-reservations input is gone — reservations is retired in the new stack.)
  • occupiedSeats is calculated client-side on DineInPage from active order assignments and passed to FloorPlanCanvas as a prop (commit 31ec27d)

Related

  • POS Takeaway Order — the shared order-creation flow (now offline-first)

  • Sales Sessions & Register Management — session lifecycle; cross-device sync via SignalR (NEW 2026-05)

  • Kitchen Display — where dine-in orders appear for the kitchen

  • Settings Overview — where floors and tables are configured

  • User Stories: POS — US-POS-001 (open dine-in order), US-POS-017 (transfer table), US-POS-018 (split bill)

  • 2026-08-10 — codebase: foodops.counter.app 8c411fc..5de51d4 (commit subjects include 'chore: hide Take Out / Delivery buttons on dine-in page for now' plus receipt/KOT timezone and session-badge fixes unrelated to this entry). Two items worth tracking, neither contradicts a documented claim: (1) DineInPage.tsx's post-session getOrderButtonConfig handler now hardcodes setShowDelivery(false) / setShowTakeout(false) regardless of the returned enableDelivery/enableTakeOut flags, with a comment noting Take Out/Delivery is 'hidden for now (not wired up yet)' — this entry never documented Take Out/Delivery buttons on the Dine-In page, so nothing to correct, but flag before writing any copy about ordering-type buttons on this page since they're intentionally suppressed pending wiring. (2) The void-bin drag-and-drop feature flagged as 'still settling' in the 2026-08-05 note continues to evolve: the drop-target occupied check now trusts the live activeOrderCount over the potentially-stale isOccupied flag, item moves gained an undoItemMove affordance surfaced via the move-success toast, and FloorPlanCanvas's drag-highlight switched from a stroked border to a 1.12x scale-up of the table (per Figma) — reinforces the prior recommendation to wait for a live Playwright pass before drafting Key concept/Q&A copy for void bin + drag-and-drop transfer. None of this touches the tracked New Order button visibility (DineInPage.tsx:762-873), one-order-per-table default, dine-in-without-table (:283-285), or occupiedSeats chair-highlighting code paths, which remain accurate. (auto-applied by the truth pipeline; adversarially gated)

  • 2026-08-05 — codebase: foodops.counter.app f52b6dc..5167f45 (commit subjects: 'void bin offline plumbing', 'void-to-bin engine + immediate cancel wiring', 'void bin reason-capture snackbar', 'void bin discard buttons + read-only panel + live badge on dine-in') adds a void bin feature to the Dine-In page not yet documented here. DineInPage.tsx gains a discard panel (showDiscardPanel, VoidBinPanel, DiscardActionButtons with DROP_DISCARD/DROP_TAKEOUT/DROP_DELIVERY drop zones), a live void-bin count badge (voidBinCount via getSessionVoidBinEntries), and offline actions voidOrderToBin/voidReduceItem/moveOrderToTable/moveItemToTable, gated by an enableVoidBin session flag. FloorPlanCanvas was extended with a forwardRef + hitTestTable imperative handle and a highlightTableId prop ('table under a drag, highlighted while dragging'), implying floor-plan tables are now valid drag-and-drop targets for moving/voiding orders — a second interaction path alongside the documented Actions → Transfer flow. Commit subjects reference a 'synced-void pending bug' fix and split test files, suggesting the feature is still settling; recommend a live Playwright pass before writing a full Key concept / Q&A section on void bin + drag-and-drop transfer. None of this diff touches the code paths behind this entry's existing tracked claims (New Order button visibility at DineInPage.tsx:762-873, one-order-per-table default, dine-in-without-table at :283-285, occupiedSeats highlighting), which remain accurate. (auto-applied by the truth pipeline; adversarially gated)

Note: Code + Playwright verified 2026-05-04 · codebase + live E2E (partial) Verified_by: playwright · Last verified: 2026-05-04 Playwright pass (2026-05-04) confirmed: /tables loads Ground Floor with Tables 1–4 in available (grey) state; right panel shows "Select Table / No orders found"; zoom controls and floor tabs render correctly. Screenshot 150 captured. Chair highlighting for occupied seats (commit 31ec27d) verified at code level only — no active dine-in orders in E2E test org. Status remains needs-review until occupied-chair visual style is verified with an active order.

2026-06-04 — content re-verified against hubits-api-sales@5274891 + foodops.counter.app@e002374 (main). REAL CHANGE: one-active-order-per-table is now the default, gated by the org setting AllowMultipleActiveOrdersPerTable (introduced 2026-05-04 16c2084, "expose & enforce"). With the setting OFF, OrderCreationService.cs:157-188 blocks a second active order on a non-system dine-in table ("Table is not vacant"), and DineInPage.tsx:762-873 hides the "+ New Order" button on an occupied table. Corrected the two "multiple orders per table = yes" Q&A and the New-Order-button walkthrough copy. NOTE on the task's cited driver 5565dd2 ("feat: include SalesOrderStatus in table order info query"): it is a 1-line .Include(t => t.SalesOrderStatus) added to TablesController.GetFloorPlanData() so the floor-plan order list reflects status (fixes tables painted vacant while orders still showed) — it is NOT a multiple-orders-per-table feature and has no separate user-facing copy to change. occupiedSeats chair-highlighting still code-only verified (no active dine-in order in E2E org).

2026-06-03 — source_repos re-pointed for the 2026-06-03 backend microservice split. ADDED hubits-api-sales (tables/dine-in/server/bill-split API — TablesController.cs + ServersController.cs + BillSplitController.cs) pinned to main HEAD 5274891, and haafai.app.foodops (reservations/tables monolith — TablesController.cs + Views/Tables/** + ReservationsController.cs + Views/Reservations/**, which back the floor/table + reservation config surfaces) pinned to main HEAD bf7c9d2. All added paths verified present on main via gh api. Existing foodops.counter.app dine-in UI globs left unchanged (playwright-verified at e002374; all still resolve on main). No fallback/broken globs found.

2026-06-29 — codebase: NEW-STACK launch re-verification (July-10). Dropped the retired haafai.app.foodops source_repos glob (TablesController/Views/Tables + ReservationsController/Views/Reservations — monolith gone); re-pinned foodops.counter.app@32669a9f3a + hubits-api-sales@19190b9a04 (TablesController/ServersController/BillSplitController + OrderCreationService all present on main). CONFIRMED DELTA — dine-in without a table: DineInPage.tsx:283-285 reads enableDineInWithoutTable + unassignedTableId/unassignedTableFloorId from the session snapshot, routing table-less dine-in orders to a system/virtual "Unassigned" table; added a Key concept + Q&A. RESERVATION-DEPENDENT claims corrected: reservations is retired in the new stack (verified zero reservation code in counter/admin/sales on main), so the Reserved table state is effectively unused at launch — fixed the colour-states note, the table-states Key concept, the floor-plan reservation Q&A, and the API note ("derived from active orders + reservations" → active orders only). One-active-order-per-table default + AllowMultipleActiveOrdersPerTable, occupiedSeats chair highlighting, canvas floor plan, transfer/split flows unchanged.

Additional verification events are appended here by the weekly KB sync and by manual re-verification passes. See _meta/failure-log.md for the global failure index.

Go beyond POS.
Choose the total solution.

Focus on creating the best experience for your guests while we handle the rest.