@fayz-ai/plugin-tables 0.10.2 → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/ComandaSettingsView-ZB5RK7TP.js +176 -0
  2. package/dist/ComandaSettingsView-ZB5RK7TP.js.map +1 -0
  3. package/dist/TablesPage-VVDNK3GN.js +1145 -0
  4. package/dist/TablesPage-VVDNK3GN.js.map +1 -0
  5. package/dist/chunk-XHRPH676.js +12 -0
  6. package/dist/chunk-XHRPH676.js.map +1 -0
  7. package/dist/context.d.ts +10 -0
  8. package/dist/context.d.ts.map +1 -1
  9. package/dist/data/core-supabase.d.ts +32 -0
  10. package/dist/data/core-supabase.d.ts.map +1 -0
  11. package/dist/data/mock.d.ts.map +1 -1
  12. package/dist/data/registries.d.ts.map +1 -1
  13. package/dist/data/types.d.ts +39 -1
  14. package/dist/data/types.d.ts.map +1 -1
  15. package/dist/index.d.ts +7 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1234 -21
  18. package/dist/index.js.map +1 -1
  19. package/dist/locales/en.d.ts.map +1 -1
  20. package/dist/locales/pt-BR.d.ts.map +1 -1
  21. package/dist/migrations/index.d.ts +7 -0
  22. package/dist/migrations/index.d.ts.map +1 -0
  23. package/dist/store.d.ts +40 -1
  24. package/dist/store.d.ts.map +1 -1
  25. package/dist/types.d.ts +138 -0
  26. package/dist/types.d.ts.map +1 -1
  27. package/dist/views/ComandaActions.d.ts +63 -0
  28. package/dist/views/ComandaActions.d.ts.map +1 -0
  29. package/dist/views/ComandaSettingsView.d.ts +3 -0
  30. package/dist/views/ComandaSettingsView.d.ts.map +1 -0
  31. package/dist/views/FloorPlanView.d.ts.map +1 -1
  32. package/dist/views/TableRailPage.d.ts +3 -0
  33. package/dist/views/TableRailPage.d.ts.map +1 -0
  34. package/package.json +4 -4
  35. package/dist/TablesPage-GKTE4SD2.js +0 -274
  36. package/dist/TablesPage-GKTE4SD2.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
+ import { TablesContextProvider } from './chunk-XHRPH676.js';
1
2
  import React from 'react';
2
3
  import { createStore } from 'zustand/vanilla';
3
4
  import { PluginSettingsPanel, dedup } from '@fayz-ai/admin';
4
5
  import { toast } from 'sonner';
6
+ import { registerComponent, getSupabaseClientOptional } from '@fayz-ai/core';
5
7
  import { createFayzClient } from '@fayz-ai/sdk';
6
8
 
7
- // src/index.ts
8
-
9
9
  // src/data/mock.ts
10
10
  var uid = 0;
11
11
  function nextId(prefix) {
@@ -60,13 +60,43 @@ function seedTables(zones) {
60
60
  tables.push(makeTable(nextId("table"), 12, 2, bar.id, bar.name, "bar", 1, 0));
61
61
  return tables;
62
62
  }
63
+ var MENU_CATEGORIES = [
64
+ { id: "c-mains", name: "Pratos" },
65
+ { id: "c-starters", name: "Entradas" },
66
+ { id: "c-drinks", name: "Bebidas" },
67
+ { id: "c-desserts", name: "Sobremesas" }
68
+ ];
69
+ var MENU = [
70
+ { id: "m-1", name: "Picanha na chapa", price: 129.9, categoryId: "c-mains", categoryName: "Pratos" },
71
+ { id: "m-2", name: "Moqueca de peixe", price: 98, categoryId: "c-mains", categoryName: "Pratos" },
72
+ { id: "m-3", name: "Bolinho de bacalhau", price: 42, categoryId: "c-starters", categoryName: "Entradas" },
73
+ { id: "m-4", name: "P\xE3o de alho", price: 18, categoryId: "c-starters", categoryName: "Entradas" },
74
+ { id: "m-5", name: "Chopp 300ml", price: 14, categoryId: "c-drinks", categoryName: "Bebidas" },
75
+ { id: "m-6", name: "Suco de laranja", price: 12, categoryId: "c-drinks", categoryName: "Bebidas" },
76
+ { id: "m-7", name: "Pudim", price: 22, categoryId: "c-desserts", categoryName: "Sobremesas" }
77
+ ];
63
78
  function createMockTablesProvider() {
64
79
  const zones = seedZones();
65
80
  const tables = seedTables(zones);
66
81
  const sessions = [];
82
+ const linesBySession = /* @__PURE__ */ new Map();
67
83
  function findTable(id) {
68
84
  return tables.find((t) => t.id === id) ?? null;
69
85
  }
86
+ function linesOf(sessionId) {
87
+ let lines = linesBySession.get(sessionId);
88
+ if (!lines) {
89
+ lines = [];
90
+ linesBySession.set(sessionId, lines);
91
+ }
92
+ return lines;
93
+ }
94
+ function recalc(sessionId) {
95
+ const total = linesOf(sessionId).reduce((sum, l) => sum + l.total, 0);
96
+ const session = sessions.find((s) => s.id === sessionId);
97
+ const table = session ? findTable(session.tableId) : null;
98
+ if (table) table.currentTotal = total;
99
+ }
70
100
  function matchesQuery(table, query) {
71
101
  if (!query) return true;
72
102
  if (query.zone && table.zone !== query.zone) return false;
@@ -156,6 +186,8 @@ function createMockTablesProvider() {
156
186
  sessions.push(session);
157
187
  table.status = "occupied";
158
188
  table.currentSessionId = session.id;
189
+ table.currentOrderId = session.id;
190
+ table.currentOrderReference = `#${session.id}`;
159
191
  table.currentGuests = input.guests;
160
192
  table.currentWaiterName = session.waiterName;
161
193
  table.currentElapsedMinutes = 0;
@@ -168,6 +200,7 @@ function createMockTablesProvider() {
168
200
  if (!session) throw new Error(`Session ${sessionId} not found`);
169
201
  session.status = "closed";
170
202
  session.closedAt = (/* @__PURE__ */ new Date()).toISOString();
203
+ linesBySession.delete(session.id);
171
204
  const table = findTable(session.tableId);
172
205
  if (table) {
173
206
  table.status = "cleaning";
@@ -216,6 +249,84 @@ function createMockTablesProvider() {
216
249
  const idx = zones.findIndex((z) => z.id === id);
217
250
  if (idx !== -1) zones.splice(idx, 1);
218
251
  },
252
+ // ---- The comanda ----
253
+ async getTableOrder(tableId) {
254
+ const session = sessions.find((s) => s.tableId === tableId && s.status === "active");
255
+ if (!session) return null;
256
+ const lines = linesOf(session.id);
257
+ const subtotal = lines.reduce((sum, l) => sum + l.total, 0);
258
+ return {
259
+ id: session.id,
260
+ reference: `#${session.id}`,
261
+ status: "new",
262
+ guests: session.guests,
263
+ seatedAt: session.seatedAt,
264
+ notes: session.notes,
265
+ waiterId: session.waiterId,
266
+ waiterName: session.waiterName,
267
+ lines,
268
+ // O provider de demonstração não divide conta: participante é o eixo
269
+ // que só faz sentido com dado real atrás, e uma lista falsa de "Pessoa
270
+ // 1, Pessoa 2" ensinaria a tela errada a quem está avaliando o produto.
271
+ participants: [],
272
+ subtotal,
273
+ discount: 0,
274
+ tax: 0,
275
+ total: subtotal,
276
+ paid: 0,
277
+ due: subtotal,
278
+ currency: "BRL"
279
+ };
280
+ },
281
+ async getMenuCategories() {
282
+ return MENU_CATEGORIES;
283
+ },
284
+ async getMenuItems(search, categoryId) {
285
+ const term = search?.toLowerCase();
286
+ return MENU.filter((m) => (!term || m.name.toLowerCase().includes(term)) && (!categoryId || m.categoryId === categoryId));
287
+ },
288
+ async addOrderLine(sessionId, input) {
289
+ linesOf(sessionId).push({
290
+ id: nextId("line"),
291
+ productId: input.productId,
292
+ name: input.name,
293
+ quantity: input.quantity,
294
+ unitPrice: input.unitPrice,
295
+ discount: 0,
296
+ total: input.unitPrice * input.quantity,
297
+ notes: input.notes
298
+ });
299
+ recalc(sessionId);
300
+ },
301
+ async setOrderLineQuantity(lineId, quantity) {
302
+ for (const [sessionId, lines] of linesBySession) {
303
+ const idx = lines.findIndex((l) => l.id === lineId);
304
+ if (idx === -1) continue;
305
+ if (quantity <= 0) lines.splice(idx, 1);
306
+ else {
307
+ lines[idx].quantity = quantity;
308
+ lines[idx].total = lines[idx].unitPrice * quantity;
309
+ }
310
+ recalc(sessionId);
311
+ return;
312
+ }
313
+ },
314
+ async removeOrderLine(lineId) {
315
+ for (const [sessionId, lines] of linesBySession) {
316
+ const idx = lines.findIndex((l) => l.id === lineId);
317
+ if (idx === -1) continue;
318
+ lines.splice(idx, 1);
319
+ recalc(sessionId);
320
+ return;
321
+ }
322
+ },
323
+ async updateSessionGuests(sessionId, guests) {
324
+ const session = sessions.find((s) => s.id === sessionId);
325
+ if (!session) return;
326
+ session.guests = guests;
327
+ const table = findTable(session.tableId);
328
+ if (table) table.currentGuests = guests;
329
+ },
219
330
  // ---- Summary ----
220
331
  async getSummary() {
221
332
  const available = tables.filter((t) => t.status === "available").length;
@@ -260,6 +371,14 @@ function createTablesStore(provider) {
260
371
  summary: null,
261
372
  summaryLoading: false,
262
373
  selectedTableId: null,
374
+ openOrder: null,
375
+ openOrderLoading: false,
376
+ activeGuestId: null,
377
+ settings: null,
378
+ cancelReasons: [],
379
+ transfers: [],
380
+ comandaSupported: typeof provider.getTableOrder === "function",
381
+ menuCategories: [],
263
382
  async fetchTables(query) {
264
383
  return dedup("tables:tables:" + JSON.stringify(query), async () => {
265
384
  set({ tablesLoading: true });
@@ -295,8 +414,217 @@ function createTablesStore(provider) {
295
414
  set({ summary, summaryLoading: false });
296
415
  });
297
416
  },
417
+ /** After a write. Deliberately not `fetchTables`: that one dedups, and a
418
+ * refresh that joins an in-flight read shows the state before the write. */
419
+ async refreshAfterWrite() {
420
+ const tableId = get().selectedTableId;
421
+ const [tables, summary] = await Promise.all([
422
+ provider.getTables(),
423
+ provider.getSummary()
424
+ ]);
425
+ set({ tables, summary });
426
+ if (tableId) await get().fetchOpenOrder(tableId);
427
+ },
298
428
  selectTable(id) {
299
- set({ selectedTableId: id });
429
+ set({ selectedTableId: id, openOrder: null, activeGuestId: null, transfers: [] });
430
+ if (id) void get().fetchOpenOrder(id);
431
+ },
432
+ async fetchOpenOrder(tableId) {
433
+ if (!provider.getTableOrder) return;
434
+ set({ openOrderLoading: true });
435
+ try {
436
+ const openOrder = await provider.getTableOrder(tableId);
437
+ if (get().selectedTableId !== tableId) return;
438
+ set({ openOrder });
439
+ } catch (err) {
440
+ set({ openOrder: null });
441
+ toast.error("N\xE3o foi poss\xEDvel ler a comanda", { description: err?.message });
442
+ } finally {
443
+ set({ openOrderLoading: false });
444
+ }
445
+ },
446
+ async fetchMenuCategories() {
447
+ if (!provider.getMenuCategories) return;
448
+ return dedup("tables:menuCategories", async () => {
449
+ try {
450
+ set({ menuCategories: await provider.getMenuCategories() });
451
+ } catch {
452
+ set({ menuCategories: [] });
453
+ }
454
+ });
455
+ },
456
+ async addOrderLine(input) {
457
+ const order = get().openOrder;
458
+ if (!order || !provider.addOrderLine) return;
459
+ try {
460
+ await provider.addOrderLine(order.id, input);
461
+ await get().refreshAfterWrite();
462
+ } catch (err) {
463
+ toast.error("N\xE3o foi poss\xEDvel lan\xE7ar o item", { description: err?.message });
464
+ throw err;
465
+ }
466
+ },
467
+ async setOrderLineQuantity(lineId, quantity) {
468
+ if (!provider.setOrderLineQuantity) return;
469
+ try {
470
+ await provider.setOrderLineQuantity(lineId, quantity);
471
+ await get().refreshAfterWrite();
472
+ } catch (err) {
473
+ toast.error("N\xE3o foi poss\xEDvel alterar o item", { description: err?.message });
474
+ }
475
+ },
476
+ selectGuest(guestId) {
477
+ set({ activeGuestId: guestId });
478
+ },
479
+ async fetchSettings() {
480
+ if (!provider.getSettings) return;
481
+ try {
482
+ set({ settings: await provider.getSettings() });
483
+ } catch {
484
+ set({ settings: null });
485
+ }
486
+ },
487
+ async saveSettings(patch) {
488
+ if (!provider.saveSettings) return;
489
+ try {
490
+ await provider.saveSettings(patch);
491
+ await get().fetchSettings();
492
+ await get().refreshAfterWrite();
493
+ toast.success("Configura\xE7\xF5es salvas");
494
+ } catch (err) {
495
+ toast.error("N\xE3o foi poss\xEDvel salvar", { description: err?.message });
496
+ }
497
+ },
498
+ async addGuest(label) {
499
+ const order = get().openOrder;
500
+ if (!order || !provider.addGuest) return;
501
+ try {
502
+ const guest = await provider.addGuest(order.id, label);
503
+ set({ activeGuestId: guest.id });
504
+ await get().refreshAfterWrite();
505
+ } catch (err) {
506
+ toast.error("N\xE3o foi poss\xEDvel acrescentar o participante", { description: err?.message });
507
+ }
508
+ },
509
+ async removeGuest(guestId) {
510
+ if (!provider.removeGuest) return;
511
+ try {
512
+ await provider.removeGuest(guestId);
513
+ if (get().activeGuestId === guestId) set({ activeGuestId: null });
514
+ await get().refreshAfterWrite();
515
+ } catch (err) {
516
+ toast.error("N\xE3o foi poss\xEDvel remover o participante", { description: err?.message });
517
+ }
518
+ },
519
+ async setGuestServiceCharge(guestId, percent) {
520
+ if (!provider.setGuestServiceCharge) return;
521
+ try {
522
+ await provider.setGuestServiceCharge(guestId, percent);
523
+ await get().refreshAfterWrite();
524
+ } catch (err) {
525
+ toast.error("N\xE3o foi poss\xEDvel mudar a taxa", { description: err?.message });
526
+ }
527
+ },
528
+ async setOrderServiceCharge(percent) {
529
+ const order = get().openOrder;
530
+ if (!order || !provider.setOrderServiceCharge) return;
531
+ try {
532
+ await provider.setOrderServiceCharge(order.id, percent);
533
+ await get().refreshAfterWrite();
534
+ } catch (err) {
535
+ toast.error("N\xE3o foi poss\xEDvel mudar a taxa", { description: err?.message });
536
+ }
537
+ },
538
+ async discountLines(input) {
539
+ const order = get().openOrder;
540
+ if (!order || !provider.discountLines) return;
541
+ try {
542
+ await provider.discountLines(order.id, input);
543
+ await get().refreshAfterWrite();
544
+ toast.success("Desconto aplicado");
545
+ } catch (err) {
546
+ toast.error("N\xE3o foi poss\xEDvel aplicar o desconto", { description: err?.message });
547
+ }
548
+ },
549
+ async fetchCancelReasons() {
550
+ if (!provider.getCancelReasons) return;
551
+ try {
552
+ set({ cancelReasons: await provider.getCancelReasons() });
553
+ } catch {
554
+ set({ cancelReasons: [] });
555
+ }
556
+ },
557
+ async addCancelReason(label) {
558
+ if (!provider.addCancelReason) return;
559
+ try {
560
+ await provider.addCancelReason(label);
561
+ await get().fetchCancelReasons();
562
+ } catch (err) {
563
+ toast.error("N\xE3o foi poss\xEDvel criar o motivo", { description: err?.message });
564
+ }
565
+ },
566
+ async updateCancelReason(id, patch) {
567
+ if (!provider.updateCancelReason) return;
568
+ try {
569
+ await provider.updateCancelReason(id, patch);
570
+ await get().fetchCancelReasons();
571
+ } catch (err) {
572
+ toast.error("N\xE3o foi poss\xEDvel alterar o motivo", { description: err?.message });
573
+ }
574
+ },
575
+ async deleteCancelReason(id) {
576
+ if (!provider.deleteCancelReason) return;
577
+ try {
578
+ await provider.deleteCancelReason(id);
579
+ await get().fetchCancelReasons();
580
+ } catch (err) {
581
+ toast.error("N\xE3o foi poss\xEDvel excluir o motivo", { description: err?.message });
582
+ }
583
+ },
584
+ async cancelLines(input) {
585
+ const order = get().openOrder;
586
+ if (!order || !provider.cancelLines) return;
587
+ try {
588
+ await provider.cancelLines(order.id, input);
589
+ await get().refreshAfterWrite();
590
+ toast.success(input.lineIds.length === 1 ? "Item cancelado" : `${input.lineIds.length} itens cancelados`);
591
+ } catch (err) {
592
+ toast.error("N\xE3o foi poss\xEDvel cancelar", { description: err?.message });
593
+ }
594
+ },
595
+ async transfer(input) {
596
+ const order = get().openOrder;
597
+ if (!order || !provider.transfer) return;
598
+ try {
599
+ await provider.transfer(order.id, input);
600
+ await get().refreshAfterWrite();
601
+ await get().fetchTransfers();
602
+ toast.success("Transfer\xEAncia feita");
603
+ } catch (err) {
604
+ toast.error("N\xE3o foi poss\xEDvel transferir", { description: err?.message });
605
+ }
606
+ },
607
+ async fetchTransfers() {
608
+ const order = get().openOrder;
609
+ if (!order || !provider.getTransfers) {
610
+ set({ transfers: [] });
611
+ return;
612
+ }
613
+ try {
614
+ set({ transfers: await provider.getTransfers(order.id) });
615
+ } catch {
616
+ set({ transfers: [] });
617
+ }
618
+ },
619
+ async setSessionGuests(guests) {
620
+ const order = get().openOrder;
621
+ if (!order || !provider.updateSessionGuests) return;
622
+ try {
623
+ await provider.updateSessionGuests(order.id, guests);
624
+ await get().refreshAfterWrite();
625
+ } catch (err) {
626
+ toast.error("N\xE3o foi poss\xEDvel mudar o n\xFAmero de pessoas", { description: err?.message });
627
+ }
300
628
  },
301
629
  async seatGuests(input) {
302
630
  try {
@@ -307,10 +635,12 @@ function createTablesStore(provider) {
307
635
  provider.getSummary()
308
636
  ]);
309
637
  set({ tables, activeSessions, summary });
310
- toast.success("Guests seated");
638
+ const tableId = get().selectedTableId;
639
+ if (tableId) await get().fetchOpenOrder(tableId);
640
+ toast.success("Mesa aberta");
311
641
  return session;
312
642
  } catch (err) {
313
- toast.error("Failed to seat guests", { description: err?.message });
643
+ toast.error("N\xE3o foi poss\xEDvel abrir a mesa", { description: err?.message });
314
644
  throw err;
315
645
  }
316
646
  },
@@ -322,10 +652,10 @@ function createTablesStore(provider) {
322
652
  provider.getActiveSessions(),
323
653
  provider.getSummary()
324
654
  ]);
325
- set({ tables, activeSessions, summary });
326
- toast.success("Session closed");
655
+ set({ tables, activeSessions, summary, openOrder: null });
656
+ toast.success("Conta fechada");
327
657
  } catch (err) {
328
- toast.error("Failed to close session", { description: err?.message });
658
+ toast.error("N\xE3o foi poss\xEDvel fechar a conta", { description: err?.message });
329
659
  throw err;
330
660
  }
331
661
  },
@@ -337,9 +667,9 @@ function createTablesStore(provider) {
337
667
  provider.getSummary()
338
668
  ]);
339
669
  set({ tables, summary });
340
- toast.success("Table status updated");
670
+ toast.success("Mesa atualizada");
341
671
  } catch (err) {
342
- toast.error("Failed to update table status", { description: err?.message });
672
+ toast.error("N\xE3o foi poss\xEDvel atualizar a mesa", { description: err?.message });
343
673
  throw err;
344
674
  }
345
675
  },
@@ -351,10 +681,10 @@ function createTablesStore(provider) {
351
681
  provider.getSummary()
352
682
  ]);
353
683
  set({ tables, summary });
354
- toast.success("Table created");
684
+ toast.success("Mesa criada");
355
685
  return table;
356
686
  } catch (err) {
357
- toast.error("Failed to create table", { description: err?.message });
687
+ toast.error("N\xE3o foi poss\xEDvel criar a mesa", { description: err?.message });
358
688
  throw err;
359
689
  }
360
690
  }
@@ -370,12 +700,67 @@ var zoneEntity = {
370
700
  defaultSort: "sortOrder",
371
701
  fields: [
372
702
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
373
- { key: "color", label: "Color", type: "text", showInTable: true },
703
+ { key: "color", label: "Color", type: "color", showInTable: true },
374
704
  { key: "sortOrder", label: "Order", type: "number", showInTable: true, defaultValue: 0 },
375
705
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
376
706
  ],
377
- data: { table: "restaurant_zones", tenantScoped: true }
707
+ data: { table: "plg_tables_zones", tenantScoped: true }
378
708
  };
709
+ var tableEntity = {
710
+ name: "Table",
711
+ namePlural: "Tables",
712
+ icon: "Utensils",
713
+ displayField: "name",
714
+ defaultSort: "number",
715
+ fields: [
716
+ { key: "number", label: "Number", type: "number", required: true, showInTable: true },
717
+ { key: "name", label: "Name", type: "text", required: true, showInTable: true },
718
+ { key: "seats", label: "Seats", type: "number", showInTable: true, defaultValue: 2 },
719
+ // The zone is a real FK, so it is picked from the zones registry rather
720
+ // than typed: the salão a mesa belongs to has to be one that exists, and
721
+ // `v_tables` joins on this id to group the floor plan.
722
+ {
723
+ key: "zoneId",
724
+ label: "Zone",
725
+ type: "relation",
726
+ relation: { table: "plg_tables_zones", labelField: "name" },
727
+ showInTable: true
728
+ },
729
+ {
730
+ key: "shape",
731
+ label: "Shape",
732
+ type: "select",
733
+ showInTable: true,
734
+ defaultValue: "square",
735
+ options: [
736
+ { value: "square", label: "Square" },
737
+ { value: "round", label: "Round" },
738
+ { value: "rectangle", label: "Rectangle" },
739
+ { value: "bar", label: "Bar" }
740
+ ]
741
+ },
742
+ // `occupied` is deliberately absent: a mesa is occupied when a comanda is
743
+ // open on it, which `v_tables` derives. Offering it here would let someone
744
+ // type a state the floor plan then contradicts.
745
+ {
746
+ key: "status",
747
+ label: "Status",
748
+ type: "select",
749
+ showInTable: true,
750
+ defaultValue: "available",
751
+ options: [
752
+ { value: "available", label: "Available" },
753
+ { value: "reserved", label: "Reserved" },
754
+ { value: "cleaning", label: "Cleaning" }
755
+ ]
756
+ },
757
+ { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
758
+ ],
759
+ data: { table: "plg_tables_tables", tenantScoped: true }
760
+ };
761
+ zoneEntity.relatedLists = [
762
+ { entity: tableEntity, foreignKey: "zoneId", label: "Tables", icon: "Utensils" }
763
+ ];
379
764
  var tablesRegistries = [
380
765
  {
381
766
  id: "zones",
@@ -383,11 +768,20 @@ var tablesRegistries = [
383
768
  icon: "MapPin",
384
769
  description: "Floor plan zones (indoor, outdoor, bar, etc.)",
385
770
  display: "table",
771
+ // No seed ids: the primary key is a uuid the database generates, and the
772
+ // hand-written 'z-indoor' this used to carry could never be inserted.
386
773
  seedData: [
387
- { id: "z-indoor", name: "Indoor", color: "#3b82f6", sortOrder: 0, isActive: true },
388
- { id: "z-outdoor", name: "Outdoor", color: "#22c55e", sortOrder: 1, isActive: true },
389
- { id: "z-bar", name: "Bar", color: "#f59e0b", sortOrder: 2, isActive: true }
774
+ { name: "Indoor", color: "#3b82f6", sortOrder: 0, isActive: true },
775
+ { name: "Outdoor", color: "#22c55e", sortOrder: 1, isActive: true },
776
+ { name: "Bar", color: "#f59e0b", sortOrder: 2, isActive: true }
390
777
  ]
778
+ },
779
+ {
780
+ id: "tables",
781
+ entity: tableEntity,
782
+ icon: "Utensils",
783
+ description: "The mesas themselves \u2014 number, seats and which zone they sit in.",
784
+ display: "table"
391
785
  }
392
786
  ];
393
787
 
@@ -398,6 +792,7 @@ var en = {
398
792
  "tables.nav.zones": "Zones",
399
793
  "tables.nav.history": "Session History",
400
794
  "tables.floorPlan.title": "Floor Plan",
795
+ "tables.floorPlan.manage": "Manage tables and zones",
401
796
  "tables.floorPlan.subtitle": "Manage table layout and seating",
402
797
  "tables.floorPlan.available": "Available",
403
798
  "tables.floorPlan.occupied": "Occupied",
@@ -435,7 +830,53 @@ var en = {
435
830
  "tables.history.waiter": "Waiter",
436
831
  "tables.history.duration": "Duration",
437
832
  "tables.history.noSessions": "No session history yet",
438
- "tables.settings.title": "Tables Settings"
833
+ "tables.settings.title": "Tables Settings",
834
+ // -- The table's panel (right rail) ----------------------------------------
835
+ "tables.panel.ticket": "Ticket",
836
+ "tables.panel.ticketLoading": "Reading the ticket\u2026",
837
+ "tables.panel.ticketEmpty": "Empty ticket",
838
+ "tables.panel.ticketEmptyDesc": "Nothing has been rung up for this table yet.",
839
+ "tables.panel.noReader": "This install does not read tickets from the floor plan \u2014 open the order under Orders.",
840
+ "tables.panel.addItem": "Add item",
841
+ "tables.panel.searchMenu": "Search the menu",
842
+ "tables.panel.menuEmpty": "Nothing by that name on the menu.",
843
+ "tables.panel.each": "each",
844
+ "tables.panel.addOne": "Add {item}",
845
+ "tables.panel.removeOne": "Remove one {item}",
846
+ "tables.panel.removeItem": "Remove {item}",
847
+ "tables.panel.howManyGuests": "How many guests",
848
+ "tables.panel.guestsAtTable": "Guests at the table",
849
+ "tables.panel.oneLess": "One guest fewer",
850
+ "tables.panel.oneMore": "One guest more",
851
+ "tables.panel.overCapacity": "This table seats {seats}, for {guests} guests.",
852
+ "tables.panel.tableNote": "Table note",
853
+ "tables.panel.cleaningDesc": "The table is out of service for cleaning. Mark it ready to free it up.",
854
+ "tables.panel.subtotal": "Subtotal",
855
+ "tables.panel.service": "Service {percent}%",
856
+ "tables.panel.serviceMixed": "Service (mixed rates)",
857
+ "tables.panel.cancelledTotal": "Cancelled",
858
+ "tables.panel.paid": "Paid",
859
+ "tables.panel.due": "Balance due",
860
+ "tables.panel.discount": "Discount",
861
+ "tables.panel.total": "Total",
862
+ "tables.panel.perPerson": "Per person ({guests})",
863
+ "tables.panel.openTable": "Open table",
864
+ "tables.panel.cancelReservation": "Cancel reservation",
865
+ "tables.panel.seat": "Seat",
866
+ "tables.panel.closeBill": "Close bill",
867
+ "tables.panel.backToTicket": "Back to the ticket",
868
+ "tables.panel.closingEmpty": "This ticket has nothing on it.",
869
+ "tables.panel.closingSummary": "{count} items \xB7 {total}",
870
+ "tables.panel.closingSummaryOne": "1 item \xB7 {total}",
871
+ "tables.panel.closingPerPerson": "{amount} per person",
872
+ "tables.panel.confirmClose": "Confirm close",
873
+ "tables.panel.guestOne": "guest",
874
+ "tables.panel.guestMany": "guests",
875
+ "tables.panel.pickerTitle": "Pick items",
876
+ "tables.panel.allCategories": "All",
877
+ "tables.panel.columnItem": "Item",
878
+ "tables.panel.columnCategory": "Category",
879
+ "tables.panel.columnPrice": "Price"
439
880
  };
440
881
 
441
882
  // src/locales/pt-BR.ts
@@ -445,6 +886,7 @@ var ptBR = {
445
886
  "tables.nav.zones": "\xC1reas",
446
887
  "tables.nav.history": "Hist\xF3rico",
447
888
  "tables.floorPlan.title": "Mapa de Mesas",
889
+ "tables.floorPlan.manage": "Cadastrar mesas e sal\xF5es",
448
890
  "tables.floorPlan.subtitle": "Gerencie o layout e a ocupa\xE7\xE3o das mesas",
449
891
  "tables.floorPlan.available": "Dispon\xEDvel",
450
892
  "tables.floorPlan.occupied": "Ocupada",
@@ -482,7 +924,53 @@ var ptBR = {
482
924
  "tables.history.waiter": "Gar\xE7om",
483
925
  "tables.history.duration": "Dura\xE7\xE3o",
484
926
  "tables.history.noSessions": "Nenhum hist\xF3rico de sess\xF5es",
485
- "tables.settings.title": "Configura\xE7\xF5es de Mesas"
927
+ "tables.settings.title": "Configura\xE7\xF5es de Mesas",
928
+ // -- The mesa's panel (right rail) -----------------------------------------
929
+ "tables.panel.ticket": "Comanda",
930
+ "tables.panel.ticketLoading": "Lendo a comanda\u2026",
931
+ "tables.panel.ticketEmpty": "Comanda vazia",
932
+ "tables.panel.ticketEmptyDesc": "Nada foi lan\xE7ado ainda para esta mesa.",
933
+ "tables.panel.noReader": "Esta instala\xE7\xE3o n\xE3o l\xEA comandas pelo mapa de mesas \u2014 abra o pedido em Pedidos.",
934
+ "tables.panel.addItem": "Lan\xE7ar item",
935
+ "tables.panel.searchMenu": "Buscar no card\xE1pio",
936
+ "tables.panel.menuEmpty": "Nada com esse nome no card\xE1pio.",
937
+ "tables.panel.each": "cada",
938
+ "tables.panel.addOne": "Adicionar {item}",
939
+ "tables.panel.removeOne": "Tirar um {item}",
940
+ "tables.panel.removeItem": "Remover {item}",
941
+ "tables.panel.howManyGuests": "Quantas pessoas",
942
+ "tables.panel.guestsAtTable": "Pessoas na mesa",
943
+ "tables.panel.oneLess": "Menos uma pessoa",
944
+ "tables.panel.oneMore": "Mais uma pessoa",
945
+ "tables.panel.overCapacity": "Esta mesa tem {seats} lugares para {guests} pessoas.",
946
+ "tables.panel.tableNote": "Observa\xE7\xE3o da mesa",
947
+ "tables.panel.cleaningDesc": "A mesa saiu de servi\xE7o e est\xE1 em limpeza. Marque como pronta para liber\xE1-la.",
948
+ "tables.panel.subtotal": "Subtotal",
949
+ "tables.panel.service": "Servi\xE7o {percent}%",
950
+ "tables.panel.serviceMixed": "Servi\xE7o (taxas diferentes)",
951
+ "tables.panel.cancelledTotal": "Cancelado",
952
+ "tables.panel.paid": "J\xE1 pago",
953
+ "tables.panel.due": "Saldo devedor",
954
+ "tables.panel.discount": "Desconto",
955
+ "tables.panel.total": "Total",
956
+ "tables.panel.perPerson": "Por pessoa ({guests})",
957
+ "tables.panel.openTable": "Abrir mesa",
958
+ "tables.panel.cancelReservation": "Cancelar reserva",
959
+ "tables.panel.seat": "Sentar",
960
+ "tables.panel.closeBill": "Fechar conta",
961
+ "tables.panel.backToTicket": "Voltar \xE0 comanda",
962
+ "tables.panel.closingEmpty": "Esta comanda n\xE3o tem nenhum item lan\xE7ado.",
963
+ "tables.panel.closingSummary": "{count} itens \xB7 {total}",
964
+ "tables.panel.closingSummaryOne": "1 item \xB7 {total}",
965
+ "tables.panel.closingPerPerson": "{amount} por pessoa",
966
+ "tables.panel.confirmClose": "Confirmar fechamento",
967
+ "tables.panel.guestOne": "pessoa",
968
+ "tables.panel.guestMany": "pessoas",
969
+ "tables.panel.pickerTitle": "Selecionar itens",
970
+ "tables.panel.allCategories": "Tudo",
971
+ "tables.panel.columnItem": "Item",
972
+ "tables.panel.columnCategory": "Categoria",
973
+ "tables.panel.columnPrice": "Pre\xE7o"
486
974
  };
487
975
 
488
976
  // src/locales/index.ts
@@ -752,9 +1240,702 @@ function createFayzTablesProvider(options = {}) {
752
1240
  }
753
1241
  };
754
1242
  }
1243
+ var ZONES = "plg_tables_zones";
1244
+ var TABLES = "plg_tables_tables";
1245
+ var ORDER_EXT = "plg_tables_order_extensions";
1246
+ var VIEW = "v_tables";
1247
+ var LINES = "items";
1248
+ var CATALOG = "products";
1249
+ var SETTINGS = "plg_tables_settings";
1250
+ var GUESTS = "plg_tables_order_guests";
1251
+ var ITEM_GUESTS = "plg_tables_item_guests";
1252
+ var CANCEL_REASONS = "plg_tables_cancel_reasons";
1253
+ var TRANSFERS = "plg_tables_order_transfers";
1254
+ var GUESTS_VIEW = "v_comanda_guests";
1255
+ var BALANCES = "v_invoice_balances";
1256
+ var SECTIONS = "plg_menu_sections";
1257
+ var PLACEMENTS = "plg_menu_placements";
1258
+ var SEATING_KIND = "dine_in";
1259
+ var READ_ONLY = "This provider reads the floor plan; pass `writes` to let the app that owns it change tables.";
1260
+ function createCoreTablesProvider(options) {
1261
+ const db = () => {
1262
+ const client = getSupabaseClientOptional();
1263
+ if (!client) throw new Error("Supabase client is not configured");
1264
+ return client;
1265
+ };
1266
+ function requireWrites() {
1267
+ if (!options?.writes) throw new Error(READ_ONLY);
1268
+ return options.writes;
1269
+ }
1270
+ async function nextReference() {
1271
+ const spec = options?.writes?.reference;
1272
+ if (!spec) return void 0;
1273
+ try {
1274
+ const { data, error } = await db().rpc("next_sequence_authorized", {
1275
+ p_kind: spec.sequenceKind,
1276
+ p_permission: spec.permission ?? "orders.create"
1277
+ });
1278
+ if (error) throw new Error(error.message);
1279
+ return `${spec.prefix ?? ""}${String(data).padStart(spec.pad ?? 6, "0")}`;
1280
+ } catch (err) {
1281
+ console.warn("[tables] next_sequence_authorized unavailable:", err?.message);
1282
+ return void 0;
1283
+ }
1284
+ }
1285
+ function tenant() {
1286
+ const id = requireWrites().tenantId();
1287
+ if (!id) throw new Error("No active organization: the floor plan cannot be changed without one");
1288
+ return id;
1289
+ }
1290
+ function shortRef(orderId) {
1291
+ return orderId ? `#${String(orderId).slice(0, 8)}` : void 0;
1292
+ }
1293
+ function num(value) {
1294
+ return Number(value) || 0;
1295
+ }
1296
+ function rowToLine(r) {
1297
+ const meta = r.metadata ?? {};
1298
+ return {
1299
+ id: r.id,
1300
+ productId: r.product_id ?? void 0,
1301
+ name: r.name ?? "",
1302
+ quantity: num(r.quantity),
1303
+ unitPrice: num(r.unit_price),
1304
+ discount: num(r.discount),
1305
+ total: num(r.total),
1306
+ notes: typeof meta.kitchenNotes === "string" ? meta.kitchenNotes : void 0,
1307
+ guestId: r.guest_id ?? void 0,
1308
+ cancelledAt: r.cancelled_at ?? void 0,
1309
+ cancelReason: r.cancel_reason ?? void 0
1310
+ };
1311
+ }
1312
+ function rowToGuest(r) {
1313
+ return {
1314
+ id: r.id,
1315
+ seatNo: Number(r.seat_no) || 0,
1316
+ label: r.label ?? void 0,
1317
+ ownServiceChargePercent: r.own_service_charge_percent == null ? void 0 : num(r.own_service_charge_percent),
1318
+ serviceChargePercent: num(r.service_charge_percent),
1319
+ subtotal: num(r.subtotal),
1320
+ serviceCharge: num(r.service_charge),
1321
+ cancelledTotal: num(r.cancelled_total),
1322
+ itemCount: Number(r.item_count) || 0
1323
+ };
1324
+ }
1325
+ async function recalc(orderId) {
1326
+ const { error } = await db().rpc("comanda_recalc", { p_order_id: orderId });
1327
+ if (error) throw new Error(error.message);
1328
+ }
1329
+ function rowToTable2(r) {
1330
+ const elapsed = r.seated_at ? Math.max(0, Math.round((Date.now() - new Date(r.seated_at).getTime()) / 6e4)) : void 0;
1331
+ return {
1332
+ id: r.id,
1333
+ name: r.name,
1334
+ number: Number(r.number) || 0,
1335
+ seats: Number(r.seats) || 0,
1336
+ status: r.status ?? "available",
1337
+ zone: r.zone_id ?? "",
1338
+ zoneName: r.zone_name ?? void 0,
1339
+ shape: r.shape ?? "square",
1340
+ gridCol: Number(r.grid_col) || 0,
1341
+ gridRow: Number(r.grid_row) || 0,
1342
+ isActive: r.is_active !== false,
1343
+ // The session IS the comanda, so the two ids are deliberately the same:
1344
+ // closing one closes the other, and there is no third row to disagree.
1345
+ currentSessionId: r.current_order_id ?? void 0,
1346
+ currentOrderId: r.current_order_id ?? void 0,
1347
+ // A comanda opened by `seatGuests` has no reference_number — nothing in
1348
+ // the pool numbers one — so the short id stands in. It is still shorter
1349
+ // than the uuid the panel used to print in full.
1350
+ currentOrderReference: r.current_order_reference ?? shortRef(r.current_order_id),
1351
+ currentGuests: r.current_guests ?? void 0,
1352
+ currentElapsedMinutes: r.current_order_id ? elapsed : void 0,
1353
+ currentTotal: r.current_order_id ? Number(r.current_total) || 0 : void 0,
1354
+ tenantId: r.tenant_id,
1355
+ createdAt: r.created_at,
1356
+ updatedAt: r.updated_at
1357
+ };
1358
+ }
1359
+ async function loadTables(query) {
1360
+ let q = db().from(VIEW).select("*").order("number");
1361
+ if (query?.zone) q = q.eq("zone_id", query.zone);
1362
+ if (query?.search) q = q.ilike("name", `%${query.search}%`);
1363
+ const { data, error } = await q;
1364
+ if (error) throw new Error(error.message);
1365
+ let tables = (data ?? []).map(rowToTable2);
1366
+ if (query?.status) {
1367
+ const wanted = Array.isArray(query.status) ? query.status : [query.status];
1368
+ tables = tables.filter((t) => wanted.includes(t.status));
1369
+ }
1370
+ return tables;
1371
+ }
1372
+ async function tableById(id) {
1373
+ const { data, error } = await db().from(VIEW).select("*").eq("id", id).maybeSingle();
1374
+ if (error) throw new Error(error.message);
1375
+ if (!data) throw new Error("Table not found");
1376
+ return rowToTable2(data);
1377
+ }
1378
+ async function openSeatings(tableId) {
1379
+ let q = db().from(ORDER_EXT).select("order_id, table_id, guests, seated_at, tenant_id, orders!inner(id, status, notes, assignee_id)").not("orders.status", "in", '("completed","cancelled")');
1380
+ const { data, error } = await q;
1381
+ if (error) throw new Error(error.message);
1382
+ return data ?? [];
1383
+ }
1384
+ function rowToSession(r, tableName) {
1385
+ const order = Array.isArray(r.orders) ? r.orders[0] : r.orders;
1386
+ const closed = order && ["completed", "cancelled"].includes(order.status);
1387
+ return {
1388
+ id: r.order_id,
1389
+ tableId: r.table_id,
1390
+ tableName,
1391
+ orderId: r.order_id,
1392
+ guests: r.guests ?? 0,
1393
+ waiterId: order?.assignee_id ?? void 0,
1394
+ seatedAt: r.seated_at,
1395
+ status: closed ? "closed" : "active",
1396
+ notes: order?.notes ?? void 0,
1397
+ tenantId: r.tenant_id,
1398
+ createdAt: r.seated_at
1399
+ };
1400
+ }
1401
+ return {
1402
+ getTables: (query) => loadTables(query),
1403
+ async getTableById(id) {
1404
+ const { data, error } = await db().from(VIEW).select("*").eq("id", id).maybeSingle();
1405
+ if (error) throw new Error(error.message);
1406
+ return data ? rowToTable2(data) : null;
1407
+ },
1408
+ async createTable(input) {
1409
+ const tenantId = tenant();
1410
+ const { data, error } = await db().from(TABLES).insert({
1411
+ tenant_id: tenantId,
1412
+ zone_id: input.zone || null,
1413
+ name: input.name,
1414
+ number: input.number,
1415
+ seats: input.seats,
1416
+ shape: input.shape ?? "square",
1417
+ grid_col: input.gridCol ?? 0,
1418
+ grid_row: input.gridRow ?? 0
1419
+ }).select("id").single();
1420
+ if (error) throw new Error(error.message);
1421
+ return tableById(data.id);
1422
+ },
1423
+ async updateTable(id, data) {
1424
+ requireWrites();
1425
+ const patch = {};
1426
+ if (data.name !== void 0) patch.name = data.name;
1427
+ if (data.number !== void 0) patch.number = data.number;
1428
+ if (data.seats !== void 0) patch.seats = data.seats;
1429
+ if (data.shape !== void 0) patch.shape = data.shape;
1430
+ if (data.zone !== void 0) patch.zone_id = data.zone || null;
1431
+ if (data.gridCol !== void 0) patch.grid_col = data.gridCol;
1432
+ if (data.gridRow !== void 0) patch.grid_row = data.gridRow;
1433
+ if (data.isActive !== void 0) patch.is_active = data.isActive;
1434
+ if (data.status !== void 0 && data.status !== "occupied") patch.status = data.status;
1435
+ if (Object.keys(patch).length) {
1436
+ const { error } = await db().from(TABLES).update(patch).eq("id", id);
1437
+ if (error) throw new Error(error.message);
1438
+ }
1439
+ return tableById(id);
1440
+ },
1441
+ async deleteTable(id) {
1442
+ requireWrites();
1443
+ const { error } = await db().from(TABLES).delete().eq("id", id);
1444
+ if (error) throw new Error(error.message);
1445
+ },
1446
+ async updateTableStatus(input) {
1447
+ requireWrites();
1448
+ if (input.status === "occupied") {
1449
+ throw new Error('Uma mesa fica ocupada ao abrir a comanda \u2014 use "sentar" em vez de marcar o estado.');
1450
+ }
1451
+ const { error } = await db().from(TABLES).update({ status: input.status }).eq("id", input.tableId);
1452
+ if (error) throw new Error(error.message);
1453
+ return tableById(input.tableId);
1454
+ },
1455
+ async updateTablePositions(positions) {
1456
+ requireWrites();
1457
+ for (const p of positions) {
1458
+ const { error } = await db().from(TABLES).update({ grid_col: p.gridCol, grid_row: p.gridRow }).eq("id", p.id);
1459
+ if (error) throw new Error(error.message);
1460
+ }
1461
+ },
1462
+ async seatGuests(input) {
1463
+ const tenantId = tenant();
1464
+ const table = await tableById(input.tableId);
1465
+ if (table.status === "occupied") throw new Error(`${table.name} j\xE1 tem uma comanda aberta`);
1466
+ const reference = await nextReference();
1467
+ const { data: order, error } = await db().from("orders").insert({
1468
+ tenant_id: tenantId,
1469
+ kind: SEATING_KIND,
1470
+ status: "new",
1471
+ channel: "dine_in",
1472
+ ...reference ? { reference_number: reference } : {},
1473
+ ...input.waiterId ? { assignee_id: input.waiterId } : {},
1474
+ ...input.notes ? { notes: input.notes } : {},
1475
+ subtotal: 0,
1476
+ total: 0,
1477
+ currency: "BRL"
1478
+ }).select("id, status, notes, assignee_id").single();
1479
+ if (error) throw new Error(error.message);
1480
+ const { data: ext, error: extError } = await db().from(ORDER_EXT).insert({
1481
+ order_id: order.id,
1482
+ tenant_id: tenantId,
1483
+ table_id: input.tableId,
1484
+ guests: input.guests
1485
+ }).select("order_id, table_id, guests, seated_at, tenant_id").single();
1486
+ if (extError) {
1487
+ await db().from("orders").delete().eq("id", order.id);
1488
+ throw new Error(extError.message);
1489
+ }
1490
+ return rowToSession({ ...ext, orders: order }, table.name);
1491
+ },
1492
+ async closeSession(sessionId) {
1493
+ requireWrites();
1494
+ const { data: order, error } = await db().from("orders").update({ status: "completed" }).eq("id", sessionId).select("id, status, notes, assignee_id").single();
1495
+ if (error) throw new Error(error.message);
1496
+ const { data: ext } = await db().from(ORDER_EXT).select("order_id, table_id, guests, seated_at, tenant_id").eq("order_id", sessionId).maybeSingle();
1497
+ return rowToSession({ ...ext ?? { order_id: sessionId }, orders: order });
1498
+ },
1499
+ async getActiveSessions() {
1500
+ const [rows, tables] = await Promise.all([openSeatings(), loadTables()]);
1501
+ const names = new Map(tables.map((t) => [t.id, t.name]));
1502
+ return rows.map((r) => rowToSession(r, names.get(r.table_id)));
1503
+ },
1504
+ async getSessionHistory(tableId) {
1505
+ let q = db().from(ORDER_EXT).select("order_id, table_id, guests, seated_at, tenant_id, orders(id, status, notes, assignee_id)").order("seated_at", { ascending: false }).limit(200);
1506
+ if (tableId) q = q.eq("table_id", tableId);
1507
+ const { data, error } = await q;
1508
+ if (error) throw new Error(error.message);
1509
+ const names = new Map((await loadTables()).map((t) => [t.id, t.name]));
1510
+ return (data ?? []).map((r) => rowToSession(r, names.get(r.table_id)));
1511
+ },
1512
+ async getZones() {
1513
+ const { data, error } = await db().from(ZONES).select("*").order("sort_order");
1514
+ if (error) throw new Error(error.message);
1515
+ return (data ?? []).map((z) => ({
1516
+ id: z.id,
1517
+ name: z.name,
1518
+ color: z.color ?? void 0,
1519
+ sortOrder: z.sort_order ?? 0,
1520
+ isActive: z.is_active !== false,
1521
+ tenantId: z.tenant_id,
1522
+ createdAt: z.created_at
1523
+ }));
1524
+ },
1525
+ async createZone(data) {
1526
+ const tenantId = tenant();
1527
+ const { data: row, error } = await db().from(ZONES).insert({
1528
+ tenant_id: tenantId,
1529
+ name: data.name ?? "Sal\xE3o",
1530
+ color: data.color ?? null,
1531
+ sort_order: data.sortOrder ?? 0
1532
+ }).select("*").single();
1533
+ if (error) throw new Error(error.message);
1534
+ return {
1535
+ id: row.id,
1536
+ name: row.name,
1537
+ color: row.color ?? void 0,
1538
+ sortOrder: row.sort_order ?? 0,
1539
+ isActive: true,
1540
+ tenantId: row.tenant_id,
1541
+ createdAt: row.created_at
1542
+ };
1543
+ },
1544
+ async updateZone(id, data) {
1545
+ requireWrites();
1546
+ const patch = {};
1547
+ if (data.name !== void 0) patch.name = data.name;
1548
+ if (data.color !== void 0) patch.color = data.color;
1549
+ if (data.sortOrder !== void 0) patch.sort_order = data.sortOrder;
1550
+ if (data.isActive !== void 0) patch.is_active = data.isActive;
1551
+ const { data: row, error } = await db().from(ZONES).update(patch).eq("id", id).select("*").single();
1552
+ if (error) throw new Error(error.message);
1553
+ return {
1554
+ id: row.id,
1555
+ name: row.name,
1556
+ color: row.color ?? void 0,
1557
+ sortOrder: row.sort_order ?? 0,
1558
+ isActive: row.is_active !== false,
1559
+ tenantId: row.tenant_id,
1560
+ createdAt: row.created_at
1561
+ };
1562
+ },
1563
+ async deleteZone(id) {
1564
+ requireWrites();
1565
+ const { error } = await db().from(ZONES).delete().eq("id", id);
1566
+ if (error) throw new Error(error.message);
1567
+ },
1568
+ // -- The comanda ---------------------------------------------------------
1569
+ async getTableOrder(tableId) {
1570
+ const { data, error } = await db().from(ORDER_EXT).select("order_id, guests, seated_at, service_charge_percent, orders!inner(id, reference_number, status, notes, assignee_id, party_id, subtotal, discount, tax, total, currency)").eq("table_id", tableId).not("orders.status", "in", '("completed","cancelled")').order("seated_at", { ascending: false }).limit(1);
1571
+ if (error) throw new Error(error.message);
1572
+ const row = (data ?? [])[0];
1573
+ if (!row) return null;
1574
+ const order = Array.isArray(row.orders) ? row.orders[0] : row.orders;
1575
+ const [
1576
+ { data: lines, error: linesError },
1577
+ { data: people },
1578
+ { data: guestRows },
1579
+ { data: itemGuests },
1580
+ { data: balance }
1581
+ ] = await Promise.all([
1582
+ // `cancelled_at` e `cancel_reason` entram no select: a linha cancelada
1583
+ // CONTINUA na comanda, riscada. Filtrá-la aqui esconderia justamente o
1584
+ // que o gerente abre a comanda para ver.
1585
+ db().from(LINES).select("id, product_id, name, quantity, unit_price, discount, total, sort_order, metadata, cancelled_at, cancel_reason").eq("order_id", order.id).order("sort_order"),
1586
+ order.assignee_id || order.party_id ? db().from("people").select("id, name").in("id", [order.assignee_id, order.party_id].filter(Boolean)) : Promise.resolve({ data: [] }),
1587
+ db().from(GUESTS_VIEW).select("*").eq("order_id", order.id).order("seat_no"),
1588
+ db().from(ITEM_GUESTS).select("item_id, guest_id"),
1589
+ db().from(BALANCES).select("paid, balance").eq("invoice_id", order.id).maybeSingle()
1590
+ ]);
1591
+ if (linesError) throw new Error(linesError.message);
1592
+ const nameById = new Map((people ?? []).map((p) => [p.id, p.name]));
1593
+ const guestOfItem = new Map(
1594
+ (itemGuests ?? []).map((r) => [r.item_id, r.guest_id])
1595
+ );
1596
+ return {
1597
+ id: order.id,
1598
+ reference: order.reference_number ?? shortRef(order.id),
1599
+ status: order.status,
1600
+ guests: row.guests ?? 0,
1601
+ seatedAt: row.seated_at,
1602
+ notes: order.notes ?? void 0,
1603
+ waiterId: order.assignee_id ?? void 0,
1604
+ waiterName: order.assignee_id ? nameById.get(order.assignee_id) : void 0,
1605
+ partyName: order.party_id ? nameById.get(order.party_id) : void 0,
1606
+ lines: (lines ?? []).map((l) => rowToLine({ ...l, guest_id: guestOfItem.get(l.id) ?? null })),
1607
+ participants: (guestRows ?? []).map(rowToGuest),
1608
+ serviceChargePercent: row.service_charge_percent == null ? void 0 : num(row.service_charge_percent),
1609
+ subtotal: num(order.subtotal),
1610
+ discount: num(order.discount),
1611
+ tax: num(order.tax),
1612
+ total: num(order.total),
1613
+ // Sem fatura levantada ainda, nada foi pago e tudo é devido. É a
1614
+ // resposta certa e não um buraco: a comanda aberta é, por definição,
1615
+ // dinheiro a receber.
1616
+ paid: num(balance?.paid),
1617
+ due: balance ? num(balance.balance) : num(order.total),
1618
+ currency: order.currency ?? "BRL"
1619
+ };
1620
+ },
1621
+ async getMenuCategories() {
1622
+ const sections = await db().from(SECTIONS).select("id, name, sort_order").eq("is_active", true).order("sort_order");
1623
+ if (!sections.error && (sections.data ?? []).length > 0) {
1624
+ return (sections.data ?? []).map((r) => ({ id: r.id, name: r.name }));
1625
+ }
1626
+ const { data, error } = await db().from("categories").select("id, name").order("name");
1627
+ if (error) throw new Error(error.message);
1628
+ return (data ?? []).map((c) => ({ id: c.id, name: c.name }));
1629
+ },
1630
+ async getMenuItems(search, categoryId) {
1631
+ let sectionFilter = null;
1632
+ if (categoryId) {
1633
+ const placed = await db().from(PLACEMENTS).select("item_id").eq("section_id", categoryId).eq("is_active", true);
1634
+ if (!placed.error) {
1635
+ const placedIds = (placed.data ?? []).map((r) => r.item_id);
1636
+ if (placedIds.length === 0) return [];
1637
+ sectionFilter = placedIds;
1638
+ }
1639
+ }
1640
+ let q = db().from(CATALOG).select("id, name, price, category_id, image_url").eq("is_active", true).order("name").limit(60);
1641
+ if (search) q = q.ilike("name", `%${search}%`);
1642
+ if (sectionFilter) q = q.in("id", sectionFilter);
1643
+ else if (categoryId) q = q.eq("category_id", categoryId);
1644
+ const { data, error } = await q;
1645
+ if (error) throw new Error(error.message);
1646
+ const rows = data ?? [];
1647
+ if (rows.length === 0) return [];
1648
+ const ids = rows.map((r) => r.id);
1649
+ const placements = await db().from(PLACEMENTS).select("item_id, section_id").in("item_id", ids).eq("is_active", true);
1650
+ const sectionByProduct = /* @__PURE__ */ new Map();
1651
+ if (!placements.error && (placements.data ?? []).length > 0) {
1652
+ const sectionIds = [...new Set((placements.data ?? []).map((r) => r.section_id).filter(Boolean))];
1653
+ const { data: sections } = await db().from(SECTIONS).select("id, name").in("id", sectionIds);
1654
+ const nameById = new Map((sections ?? []).map((r) => [r.id, r.name]));
1655
+ for (const row of placements.data ?? []) {
1656
+ const name = nameById.get(row.section_id);
1657
+ if (name) sectionByProduct.set(row.item_id, name);
1658
+ }
1659
+ }
1660
+ const categoryIds = [...new Set(rows.map((r) => r.category_id).filter(Boolean))];
1661
+ const { data: categories } = categoryIds.length ? await db().from("categories").select("id, name").in("id", categoryIds) : { data: [] };
1662
+ const categoryById = new Map((categories ?? []).map((c) => [c.id, c.name]));
1663
+ return rows.map((r) => ({
1664
+ id: r.id,
1665
+ name: r.name,
1666
+ price: num(r.price),
1667
+ categoryId: r.category_id ?? void 0,
1668
+ // The section wins where there is one: it is the word the Cardápio
1669
+ // screen shows, and the two lists have to say the same thing.
1670
+ categoryName: sectionByProduct.get(r.id) ?? (r.category_id ? categoryById.get(r.category_id) : void 0),
1671
+ imageUrl: r.image_url ?? void 0
1672
+ }));
1673
+ },
1674
+ async addOrderLine(orderId, input) {
1675
+ const tenantId = tenant();
1676
+ const { count } = await db().from(LINES).select("id", { count: "exact", head: true }).eq("order_id", orderId);
1677
+ const { data, error } = await db().from(LINES).insert({
1678
+ tenant_id: tenantId,
1679
+ order_id: orderId,
1680
+ product_id: input.productId ?? null,
1681
+ name: input.name,
1682
+ quantity: input.quantity,
1683
+ unit_price: input.unitPrice,
1684
+ discount: 0,
1685
+ total: input.unitPrice * input.quantity,
1686
+ sort_order: count ?? 0,
1687
+ metadata: input.notes ? { kitchenNotes: input.notes } : {}
1688
+ }).select("id").single();
1689
+ if (error) throw new Error(error.message);
1690
+ if (input.guestId && data?.id) {
1691
+ const { error: linkError } = await db().from(ITEM_GUESTS).insert({
1692
+ item_id: data.id,
1693
+ guest_id: input.guestId,
1694
+ tenant_id: tenantId
1695
+ });
1696
+ if (linkError) throw new Error(linkError.message);
1697
+ }
1698
+ await recalc(orderId);
1699
+ },
1700
+ async setOrderLineQuantity(lineId, quantity) {
1701
+ requireWrites();
1702
+ const { data: line, error } = await db().from(LINES).select("id, order_id, unit_price, discount").eq("id", lineId).maybeSingle();
1703
+ if (error) throw new Error(error.message);
1704
+ if (!line) throw new Error("Item n\xE3o encontrado na comanda");
1705
+ if (quantity <= 0) {
1706
+ const { error: delError } = await db().from(LINES).delete().eq("id", lineId);
1707
+ if (delError) throw new Error(delError.message);
1708
+ } else {
1709
+ const { error: upError } = await db().from(LINES).update({
1710
+ quantity,
1711
+ total: num(line.unit_price) * quantity - num(line.discount)
1712
+ }).eq("id", lineId);
1713
+ if (upError) throw new Error(upError.message);
1714
+ }
1715
+ await recalc(line.order_id);
1716
+ },
1717
+ async removeOrderLine(lineId) {
1718
+ requireWrites();
1719
+ const { data: line, error } = await db().from(LINES).select("order_id").eq("id", lineId).maybeSingle();
1720
+ if (error) throw new Error(error.message);
1721
+ if (!line) return;
1722
+ const { error: delError } = await db().from(LINES).delete().eq("id", lineId);
1723
+ if (delError) throw new Error(delError.message);
1724
+ await recalc(line.order_id);
1725
+ },
1726
+ // -- A política da casa ---------------------------------------------------
1727
+ async getSettings() {
1728
+ const { data, error } = await db().from(SETTINGS).select("service_charge_percent, cancel_requires_pin, operational_day_start").maybeSingle();
1729
+ if (error) throw new Error(error.message);
1730
+ return {
1731
+ serviceChargePercent: num(data?.service_charge_percent),
1732
+ cancelRequiresPin: data?.cancel_requires_pin === true,
1733
+ operationalDayStart: data?.operational_day_start ?? "06:00"
1734
+ };
1735
+ },
1736
+ async saveSettings(patch) {
1737
+ const tenantId = tenant();
1738
+ const row = { tenant_id: tenantId };
1739
+ if (patch.serviceChargePercent != null) row.service_charge_percent = patch.serviceChargePercent;
1740
+ if (patch.cancelRequiresPin != null) row.cancel_requires_pin = patch.cancelRequiresPin;
1741
+ if (patch.operationalDayStart != null) row.operational_day_start = patch.operationalDayStart;
1742
+ const { error } = await db().from(SETTINGS).upsert(row, { onConflict: "tenant_id" });
1743
+ if (error) throw new Error(error.message);
1744
+ },
1745
+ // -- Participantes --------------------------------------------------------
1746
+ async addGuest(orderId, label) {
1747
+ const tenantId = tenant();
1748
+ const { data: taken } = await db().from(GUESTS).select("seat_no").eq("order_id", orderId).order("seat_no");
1749
+ const used = new Set((taken ?? []).map((r) => Number(r.seat_no)));
1750
+ let seat = 1;
1751
+ while (used.has(seat)) seat += 1;
1752
+ const { data, error } = await db().from(GUESTS).insert({
1753
+ tenant_id: tenantId,
1754
+ order_id: orderId,
1755
+ seat_no: seat,
1756
+ label: label ?? null
1757
+ }).select("id, seat_no, label").single();
1758
+ if (error) throw new Error(error.message);
1759
+ return {
1760
+ id: data.id,
1761
+ seatNo: Number(data.seat_no),
1762
+ label: data.label ?? void 0,
1763
+ serviceChargePercent: 0,
1764
+ subtotal: 0,
1765
+ serviceCharge: 0,
1766
+ cancelledTotal: 0,
1767
+ itemCount: 0
1768
+ };
1769
+ },
1770
+ async removeGuest(guestId) {
1771
+ requireWrites();
1772
+ const { data: guest } = await db().from(GUESTS).select("order_id").eq("id", guestId).maybeSingle();
1773
+ const { error } = await db().from(GUESTS).delete().eq("id", guestId);
1774
+ if (error) throw new Error(error.message);
1775
+ if (guest?.order_id) await recalc(guest.order_id);
1776
+ },
1777
+ async setGuestServiceCharge(guestId, percent) {
1778
+ requireWrites();
1779
+ const { data: guest } = await db().from(GUESTS).select("order_id").eq("id", guestId).maybeSingle();
1780
+ const { error } = await db().from(GUESTS).update({ service_charge_percent: percent }).eq("id", guestId);
1781
+ if (error) throw new Error(error.message);
1782
+ if (guest?.order_id) await recalc(guest.order_id);
1783
+ },
1784
+ async setOrderServiceCharge(orderId, percent) {
1785
+ requireWrites();
1786
+ const { error } = await db().from(ORDER_EXT).update({ service_charge_percent: percent }).eq("order_id", orderId);
1787
+ if (error) throw new Error(error.message);
1788
+ await recalc(orderId);
1789
+ },
1790
+ // -- Desconto -------------------------------------------------------------
1791
+ async discountLines(orderId, input) {
1792
+ requireWrites();
1793
+ if (input.lineIds.length === 0) return;
1794
+ const { data: lines, error } = await db().from(LINES).select("id, quantity, unit_price").in("id", input.lineIds);
1795
+ if (error) throw new Error(error.message);
1796
+ for (const line of lines ?? []) {
1797
+ const gross = num(line.unit_price) * num(line.quantity);
1798
+ const raw = input.mode === "percent" ? gross * (input.value / 100) : input.value;
1799
+ const discount = Math.min(Math.max(raw, 0), gross);
1800
+ const { error: upError } = await db().from(LINES).update({ discount, total: gross - discount }).eq("id", line.id);
1801
+ if (upError) throw new Error(upError.message);
1802
+ }
1803
+ await recalc(orderId);
1804
+ },
1805
+ // -- Cancelamento ---------------------------------------------------------
1806
+ async getCancelReasons() {
1807
+ const { data, error } = await db().from(CANCEL_REASONS).select("id, label, is_active, sort_order").order("sort_order");
1808
+ if (error) throw new Error(error.message);
1809
+ return (data ?? []).map((r) => ({
1810
+ id: r.id,
1811
+ label: r.label,
1812
+ isActive: r.is_active !== false,
1813
+ sortOrder: Number(r.sort_order) || 0
1814
+ }));
1815
+ },
1816
+ async addCancelReason(label) {
1817
+ const tenantId = tenant();
1818
+ const { count } = await db().from(CANCEL_REASONS).select("id", { count: "exact", head: true });
1819
+ const { data, error } = await db().from(CANCEL_REASONS).insert({ tenant_id: tenantId, label, sort_order: count ?? 0 }).select("id, label, is_active, sort_order").single();
1820
+ if (error) throw new Error(error.message);
1821
+ return { id: data.id, label: data.label, isActive: data.is_active !== false, sortOrder: Number(data.sort_order) || 0 };
1822
+ },
1823
+ async updateCancelReason(id, patch) {
1824
+ requireWrites();
1825
+ const row = {};
1826
+ if (patch.label != null) row.label = patch.label;
1827
+ if (patch.isActive != null) row.is_active = patch.isActive;
1828
+ if (patch.sortOrder != null) row.sort_order = patch.sortOrder;
1829
+ const { error } = await db().from(CANCEL_REASONS).update(row).eq("id", id);
1830
+ if (error) throw new Error(error.message);
1831
+ },
1832
+ async deleteCancelReason(id) {
1833
+ requireWrites();
1834
+ const { error } = await db().from(CANCEL_REASONS).delete().eq("id", id);
1835
+ if (error) throw new Error(error.message);
1836
+ },
1837
+ async cancelLines(orderId, input) {
1838
+ requireWrites();
1839
+ if (input.lineIds.length === 0) return;
1840
+ const { error } = await db().from(LINES).update({ cancelled_at: (/* @__PURE__ */ new Date()).toISOString(), cancel_reason: input.reason }).in("id", input.lineIds);
1841
+ if (error) throw new Error(error.message);
1842
+ await recalc(orderId);
1843
+ },
1844
+ // -- Transferência --------------------------------------------------------
1845
+ async transfer(orderId, input) {
1846
+ const tenantId = tenant();
1847
+ const audit = {
1848
+ tenant_id: tenantId,
1849
+ order_id: orderId,
1850
+ kind: input.kind,
1851
+ note: input.note ?? null,
1852
+ item_ids: input.lineIds ?? []
1853
+ };
1854
+ if (input.kind === "table") {
1855
+ if (!input.toTableId) throw new Error("Escolha a mesa de destino");
1856
+ const { data: current } = await db().from(ORDER_EXT).select("table_id").eq("order_id", orderId).maybeSingle();
1857
+ const { data: busy } = await db().from(ORDER_EXT).select("order_id, orders!inner(status)").eq("table_id", input.toTableId).not("orders.status", "in", '("completed","cancelled")').limit(1);
1858
+ if ((busy ?? []).length > 0) throw new Error("A mesa de destino j\xE1 tem comanda aberta");
1859
+ const { error } = await db().from(ORDER_EXT).update({ table_id: input.toTableId }).eq("order_id", orderId);
1860
+ if (error) throw new Error(error.message);
1861
+ audit.from_table_id = current?.table_id ?? null;
1862
+ audit.to_table_id = input.toTableId;
1863
+ } else if (input.kind === "guest") {
1864
+ if (!input.guestId || !input.toOrderId) throw new Error("Escolha o participante e a comanda de destino");
1865
+ const { error } = await db().from(GUESTS).update({ order_id: input.toOrderId }).eq("id", input.guestId);
1866
+ if (error) throw new Error(error.message);
1867
+ const { data: moved } = await db().from(ITEM_GUESTS).select("item_id").eq("guest_id", input.guestId);
1868
+ const ids = (moved ?? []).map((r) => r.item_id);
1869
+ if (ids.length > 0) {
1870
+ const { error: itemError } = await db().from(LINES).update({ order_id: input.toOrderId }).in("id", ids);
1871
+ if (itemError) throw new Error(itemError.message);
1872
+ }
1873
+ audit.guest_id = input.guestId;
1874
+ audit.target_order_id = input.toOrderId;
1875
+ audit.item_ids = ids;
1876
+ await recalc(input.toOrderId);
1877
+ } else {
1878
+ if (!input.lineIds || input.lineIds.length === 0) throw new Error("Escolha os itens");
1879
+ const { error: delError } = await db().from(ITEM_GUESTS).delete().in("item_id", input.lineIds);
1880
+ if (delError) throw new Error(delError.message);
1881
+ if (input.toGuestId) {
1882
+ const { error: insError } = await db().from(ITEM_GUESTS).insert(
1883
+ input.lineIds.map((id) => ({ item_id: id, guest_id: input.toGuestId, tenant_id: tenantId }))
1884
+ );
1885
+ if (insError) throw new Error(insError.message);
1886
+ }
1887
+ audit.guest_id = input.toGuestId ?? null;
1888
+ }
1889
+ const { error: auditError } = await db().from(TRANSFERS).insert(audit);
1890
+ if (auditError) throw new Error(auditError.message);
1891
+ await recalc(orderId);
1892
+ },
1893
+ async getTransfers(orderId) {
1894
+ const { data, error } = await db().from(TRANSFERS).select("id, kind, from_table_id, to_table_id, guest_id, item_ids, note, created_at").eq("order_id", orderId).order("created_at", { ascending: false });
1895
+ if (error) throw new Error(error.message);
1896
+ const rows = data ?? [];
1897
+ const tableIds = Array.from(new Set(
1898
+ rows.flatMap((r) => [r.from_table_id, r.to_table_id]).filter(Boolean)
1899
+ ));
1900
+ const { data: tables } = tableIds.length ? await db().from(TABLES).select("id, number").in("id", tableIds) : { data: [] };
1901
+ const numberById = new Map((tables ?? []).map((t) => [t.id, t.number]));
1902
+ const named = (id) => id && numberById.has(id) ? `Mesa ${numberById.get(id)}` : void 0;
1903
+ return rows.map((r) => ({
1904
+ id: r.id,
1905
+ kind: r.kind,
1906
+ fromTableName: named(r.from_table_id),
1907
+ toTableName: named(r.to_table_id),
1908
+ itemCount: (r.item_ids ?? []).length,
1909
+ note: r.note ?? void 0,
1910
+ createdAt: r.created_at
1911
+ }));
1912
+ },
1913
+ async updateSessionGuests(orderId, guests) {
1914
+ requireWrites();
1915
+ const { error } = await db().from(ORDER_EXT).update({ guests }).eq("order_id", orderId);
1916
+ if (error) throw new Error(error.message);
1917
+ },
1918
+ async getSummary() {
1919
+ const tables = await loadTables();
1920
+ const count = (status) => tables.filter((t) => t.status === status).length;
1921
+ const elapsed = tables.map((t) => t.currentElapsedMinutes).filter((m) => typeof m === "number");
1922
+ return {
1923
+ totalTables: tables.length,
1924
+ availableCount: count("available"),
1925
+ occupiedCount: count("occupied"),
1926
+ reservedCount: count("reserved"),
1927
+ cleaningCount: count("cleaning"),
1928
+ totalSeats: tables.reduce((sum, t) => sum + t.seats, 0),
1929
+ occupiedSeats: tables.filter((t) => t.status === "occupied").reduce((sum, t) => sum + t.seats, 0),
1930
+ averageSessionMinutes: elapsed.length ? Math.round(elapsed.reduce((a, b) => a + b, 0) / elapsed.length) : 0
1931
+ };
1932
+ }
1933
+ };
1934
+ }
755
1935
 
756
1936
  // src/index.ts
757
- var TablesPage = React.lazy(() => import('./TablesPage-GKTE4SD2.js').then((m) => ({ default: m.TablesPage })));
1937
+ var TablesPage = React.lazy(() => import('./TablesPage-VVDNK3GN.js').then((m) => ({ default: m.TablesPage })));
1938
+ var ComandaSettingsView = React.lazy(() => import('./ComandaSettingsView-ZB5RK7TP.js').then((m) => ({ default: m.ComandaSettingsView })));
758
1939
  var DEFAULT_LABELS = {
759
1940
  pageTitle: "Tables",
760
1941
  floorPlan: "Floor Plan",
@@ -773,10 +1954,12 @@ function resolveConfig(options) {
773
1954
  { name: "Outdoor", color: "#22c55e" },
774
1955
  { name: "Bar", color: "#f59e0b" }
775
1956
  ],
1957
+ serviceChargePercent: options?.serviceChargePercent ?? 0,
776
1958
  onTableSeated: options?.onTableSeated,
777
1959
  onTableClosed: options?.onTableClosed
778
1960
  };
779
1961
  }
1962
+ var FLOOR_PLAN_COMPONENT_ID = "tables.floor-plan";
780
1963
  function createTablesPlugin(options) {
781
1964
  const config = resolveConfig(options);
782
1965
  const provider = options?.dataProvider ?? createMockTablesProvider();
@@ -786,6 +1969,9 @@ function createTablesPlugin(options) {
786
1969
  { fallback: null },
787
1970
  React.createElement(TablesPage, { config, provider, store, registries: tablesRegistries })
788
1971
  );
1972
+ registerComponent(FLOOR_PLAN_COMPONENT_ID, PageComponent);
1973
+ const ContextBoundary = ({ children }) => React.createElement(TablesContextProvider, { config, provider, store }, children);
1974
+ ContextBoundary.displayName = "TablesContextBoundary";
789
1975
  return {
790
1976
  id: "tables",
791
1977
  defaultAgentRole: "operations",
@@ -869,12 +2055,39 @@ function createTablesPlugin(options) {
869
2055
  })(),
870
2056
  order: 21,
871
2057
  permission: { feature: "tables", action: "read" }
2058
+ },
2059
+ // Duas abas, e cada uma responde uma pergunta diferente: a de cima é o
2060
+ // CADASTRO do salão (praças, mesas — trabalho de implantação), esta é a
2061
+ // POLÍTICA da comanda (taxa, cancelamento, dia operacional — decisões do
2062
+ // dono). Juntá-las daria uma tela em que quem vem mudar a taxa passa
2063
+ // primeiro pelas mesas.
2064
+ {
2065
+ id: "comanda",
2066
+ label: "Comanda",
2067
+ icon: "Receipt",
2068
+ component: (() => {
2069
+ const ComandaSettingsTab = () => React.createElement(
2070
+ ContextBoundary,
2071
+ null,
2072
+ React.createElement(
2073
+ React.Suspense,
2074
+ { fallback: null },
2075
+ React.createElement(ComandaSettingsView)
2076
+ )
2077
+ );
2078
+ ComandaSettingsTab.displayName = "ComandaSettingsTab";
2079
+ return ComandaSettingsTab;
2080
+ })(),
2081
+ order: 22,
2082
+ // A política é do dono, não do garçom: quem só atende mesa não decide a
2083
+ // taxa da casa nem apaga motivo de cancelamento.
2084
+ permission: { feature: "tables", action: "edit" }
872
2085
  }
873
2086
  ],
874
2087
  locales: tablesLocales
875
2088
  };
876
2089
  }
877
2090
 
878
- export { createFayzTablesProvider, createTablesPlugin };
2091
+ export { FLOOR_PLAN_COMPONENT_ID, createCoreTablesProvider, createFayzTablesProvider, createTablesPlugin };
879
2092
  //# sourceMappingURL=index.js.map
880
2093
  //# sourceMappingURL=index.js.map