@fayz-ai/pos 0.20.0

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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/dist/app/config.d.ts +238 -0
  4. package/dist/app/config.d.ts.map +1 -0
  5. package/dist/index.d.ts +24 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +2638 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/keyboard/index.d.ts +3 -0
  10. package/dist/keyboard/index.d.ts.map +1 -0
  11. package/dist/lib/haptics.d.ts +3 -0
  12. package/dist/lib/haptics.d.ts.map +1 -0
  13. package/dist/lib/useSwipeAway.d.ts +18 -0
  14. package/dist/lib/useSwipeAway.d.ts.map +1 -0
  15. package/dist/screen/DeliveryBoard.d.ts +12 -0
  16. package/dist/screen/DeliveryBoard.d.ts.map +1 -0
  17. package/dist/screen/DispatchRail.d.ts +13 -0
  18. package/dist/screen/DispatchRail.d.ts.map +1 -0
  19. package/dist/screen/IdentitySheet.d.ts +7 -0
  20. package/dist/screen/IdentitySheet.d.ts.map +1 -0
  21. package/dist/screen/ModifierSheet.d.ts +13 -0
  22. package/dist/screen/ModifierSheet.d.ts.map +1 -0
  23. package/dist/screen/Notice.d.ts +21 -0
  24. package/dist/screen/Notice.d.ts.map +1 -0
  25. package/dist/screen/OpenTableSheet.d.ts +13 -0
  26. package/dist/screen/OpenTableSheet.d.ts.map +1 -0
  27. package/dist/screen/PinSheet.d.ts +9 -0
  28. package/dist/screen/PinSheet.d.ts.map +1 -0
  29. package/dist/screen/PosScreen.d.ts +96 -0
  30. package/dist/screen/PosScreen.d.ts.map +1 -0
  31. package/dist/screen/ReservationsView.d.ts +11 -0
  32. package/dist/screen/ReservationsView.d.ts.map +1 -0
  33. package/dist/screen/ShortcutsOverlay.d.ts +6 -0
  34. package/dist/screen/ShortcutsOverlay.d.ts.map +1 -0
  35. package/dist/screen/SplitSheet.d.ts +9 -0
  36. package/dist/screen/SplitSheet.d.ts.map +1 -0
  37. package/dist/screen/TicketRail.d.ts +36 -0
  38. package/dist/screen/TicketRail.d.ts.map +1 -0
  39. package/dist/screen/money.d.ts +11 -0
  40. package/dist/screen/money.d.ts.map +1 -0
  41. package/dist/screen/session.d.ts +105 -0
  42. package/dist/screen/session.d.ts.map +1 -0
  43. package/dist/screen/views.d.ts +51 -0
  44. package/dist/screen/views.d.ts.map +1 -0
  45. package/dist/styles.css +1664 -0
  46. package/dist/theme/index.d.ts +2 -0
  47. package/dist/theme/index.d.ts.map +1 -0
  48. package/dist/theme/index.js +53 -0
  49. package/dist/theme/index.js.map +1 -0
  50. package/dist/theme/tokens.d.ts +62 -0
  51. package/dist/theme/tokens.d.ts.map +1 -0
  52. package/package.json +61 -0
package/dist/index.js ADDED
@@ -0,0 +1,2638 @@
1
+ import { haptic, createKeymap, directionOf, moveFocus } from '@fayz-ai/core/interaction';
2
+ export { comboOf, createKeymap, haptic, setHapticsEnabled } from '@fayz-ai/core/interaction';
3
+ import { useState, useRef, useCallback, useEffect, useMemo, useReducer } from 'react';
4
+ import { Pencil, Receipt, AlertTriangle, ChefHat, ChevronLeft, QrCode, CreditCard, Banknote, Trash2, Minus, Plus, Bike, Package, CheckCheck, Clock, MapPin, ShieldCheck, Keyboard, X, CalendarClock, Users, Phone, Check, UserRound, User, Delete, UtensilsCrossed, Info, Search, LayoutGrid, ClipboardList, Utensils, CheckCircle2, CookingPot, CircleDashed } from 'lucide-react';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+
7
+ // src/keyboard/index.ts
8
+
9
+ // src/screen/money.ts
10
+ function formatCents(cents, currency = "BRL", locale = "pt-BR") {
11
+ return new Intl.NumberFormat(locale, { style: "currency", currency }).format(cents / 100);
12
+ }
13
+ function splitEvenly(totalCents, ways) {
14
+ if (ways <= 0) throw new Error("splitEvenly: ways must be positive");
15
+ const base = Math.floor(totalCents / ways);
16
+ const remainder = totalCents - base * ways;
17
+ return Array.from({ length: ways }, (_, i) => base + (i < remainder ? 1 : 0));
18
+ }
19
+ function serviceChargeCents(subtotalCents, percent) {
20
+ return Math.round(subtotalCents * percent / 100);
21
+ }
22
+
23
+ // src/screen/session.ts
24
+ function ticketReducer(ticket, action) {
25
+ switch (action.type) {
26
+ case "add_item": {
27
+ const modifiers = action.modifiers ?? [];
28
+ const unitPriceCents = action.item.priceCents + modifiers.reduce((sum, m) => sum + m.priceDeltaCents, 0);
29
+ const plain = !action.note && modifiers.length === 0;
30
+ const last = plain ? ticket.lines.find(
31
+ (l) => l.itemId === action.item.id && !l.note && !l.modifiers?.length && // A fired line is already in the kitchen's hands; adding to it
32
+ // would raise a quantity nobody downstream heard about.
33
+ (l.state ?? "pending") === "pending"
34
+ ) : void 0;
35
+ const quantity = action.quantity ?? 1;
36
+ if (last) {
37
+ return {
38
+ ...ticket,
39
+ lines: ticket.lines.map(
40
+ (l) => l.id === last.id ? { ...l, quantity: l.quantity + quantity } : l
41
+ )
42
+ };
43
+ }
44
+ return {
45
+ ...ticket,
46
+ lines: [
47
+ ...ticket.lines,
48
+ {
49
+ id: action.lineId,
50
+ itemId: action.item.id,
51
+ name: action.item.name,
52
+ unitPriceCents,
53
+ basePriceCents: action.item.priceCents,
54
+ quantity,
55
+ ...action.note ? { note: action.note } : {},
56
+ ...modifiers.length ? { modifiers } : {}
57
+ }
58
+ ]
59
+ };
60
+ }
61
+ case "change_quantity": {
62
+ const lines = ticket.lines.map((l) => l.id === action.lineId ? { ...l, quantity: l.quantity + action.delta } : l).filter((l) => l.quantity > 0);
63
+ return { ...ticket, lines };
64
+ }
65
+ case "remove_one_of": {
66
+ const target = [...ticket.lines].reverse().find((l) => l.itemId === action.itemId);
67
+ if (!target) return ticket;
68
+ return ticketReducer(ticket, { type: "change_quantity", lineId: target.id, delta: -1 });
69
+ }
70
+ case "remove_line":
71
+ return { ...ticket, lines: ticket.lines.filter((l) => l.id !== action.lineId) };
72
+ case "edit_line": {
73
+ const unitPriceCents = action.basePriceCents + action.modifiers.reduce((sum, m) => sum + m.priceDeltaCents, 0);
74
+ return {
75
+ ...ticket,
76
+ lines: ticket.lines.map(
77
+ (l) => l.id === action.lineId ? {
78
+ ...l,
79
+ unitPriceCents,
80
+ basePriceCents: action.basePriceCents,
81
+ modifiers: action.modifiers.length ? action.modifiers : void 0,
82
+ note: action.note || void 0
83
+ } : l
84
+ )
85
+ };
86
+ }
87
+ case "set_note":
88
+ return {
89
+ ...ticket,
90
+ lines: ticket.lines.map((l) => l.id === action.lineId ? { ...l, note: action.note } : l)
91
+ };
92
+ case "fire": {
93
+ if (!ticket.lines.some((l) => (l.state ?? "pending") === "pending")) return ticket;
94
+ return {
95
+ ...ticket,
96
+ lines: ticket.lines.map(
97
+ (l) => (l.state ?? "pending") === "pending" ? { ...l, state: "fired", firedAt: action.at } : l
98
+ )
99
+ };
100
+ }
101
+ case "set_line_state":
102
+ return {
103
+ ...ticket,
104
+ lines: ticket.lines.map((l) => l.id === action.lineId ? { ...l, state: action.state } : l)
105
+ };
106
+ case "clear":
107
+ return { ...ticket, lines: [] };
108
+ }
109
+ }
110
+ function totalsOf(ticket, options = {}) {
111
+ const subtotalCents = ticket.lines.reduce((sum, l) => sum + l.unitPriceCents * l.quantity, 0);
112
+ const serviceCents = options.serviceChargePercent ? serviceChargeCents(subtotalCents, options.serviceChargePercent) : 0;
113
+ const discountCents = options.discountCents ?? 0;
114
+ return {
115
+ subtotalCents,
116
+ serviceCents,
117
+ discountCents,
118
+ totalCents: subtotalCents + serviceCents - discountCents
119
+ };
120
+ }
121
+ function emptyTicket(id, openedAt) {
122
+ return { id, lines: [], openedAt };
123
+ }
124
+ var COUNTER_KEY = "__counter";
125
+ function bookReducer(book, action) {
126
+ switch (action.type) {
127
+ case "at": {
128
+ const current = book[action.positionId] ?? emptyTicket(`t-${action.positionId}`, "");
129
+ const next = ticketReducer(current, action.action);
130
+ if (next === current) return book;
131
+ return { ...book, [action.positionId]: next };
132
+ }
133
+ case "drop": {
134
+ if (!(action.positionId in book)) return book;
135
+ const { [action.positionId]: _dropped, ...rest } = book;
136
+ return rest;
137
+ }
138
+ }
139
+ }
140
+ function ticketAt(book, positionId) {
141
+ const key = positionId ?? COUNTER_KEY;
142
+ return book[key] ?? emptyTicket(`t-${key}`, "");
143
+ }
144
+ function countsOf(book) {
145
+ const out = {};
146
+ for (const [key, ticket] of Object.entries(book)) {
147
+ out[key] = ticket.lines.reduce((n, l) => n + l.quantity, 0);
148
+ }
149
+ return out;
150
+ }
151
+ var REVEAL_PX = 88;
152
+ var COMMIT_PX = 188;
153
+ function useSwipeAway(onRemove) {
154
+ const [offset, setOffset] = useState(0);
155
+ const [open, setOpen] = useState(false);
156
+ const [removing, setRemoving] = useState(false);
157
+ const start = useRef();
158
+ const base = useRef(0);
159
+ const passed = useRef({ reveal: false, commit: false });
160
+ const commit = useCallback(() => {
161
+ setRemoving(true);
162
+ haptic("commit");
163
+ window.setTimeout(onRemove, 180);
164
+ }, [onRemove]);
165
+ const onPointerDown = useCallback((event) => {
166
+ if (event.pointerType === "mouse") return;
167
+ start.current = event.clientX;
168
+ base.current = offset;
169
+ passed.current = { reveal: false, commit: false };
170
+ }, [offset]);
171
+ const onPointerMove = useCallback((event) => {
172
+ if (start.current == null) return;
173
+ const dx = Math.min(0, base.current + (event.clientX - start.current));
174
+ setOffset(dx);
175
+ const travelled = -dx;
176
+ if (travelled >= REVEAL_PX && !passed.current.reveal) {
177
+ passed.current.reveal = true;
178
+ haptic("tap");
179
+ }
180
+ if (travelled >= COMMIT_PX && !passed.current.commit) {
181
+ passed.current.commit = true;
182
+ haptic("warn");
183
+ }
184
+ }, []);
185
+ const onPointerUp = useCallback(() => {
186
+ if (start.current == null) return;
187
+ const travelled = -offset;
188
+ start.current = void 0;
189
+ if (travelled >= COMMIT_PX) {
190
+ commit();
191
+ return;
192
+ }
193
+ if (travelled >= REVEAL_PX / 2) {
194
+ setOffset(-REVEAL_PX);
195
+ setOpen(true);
196
+ return;
197
+ }
198
+ setOffset(0);
199
+ setOpen(false);
200
+ }, [offset, commit]);
201
+ const close = useCallback(() => {
202
+ setOffset(0);
203
+ setOpen(false);
204
+ }, []);
205
+ return {
206
+ offset,
207
+ /** The row is parked showing its trash. */
208
+ open,
209
+ removing,
210
+ close,
211
+ /** Tapping the revealed trash is the deliberate path to the same place. */
212
+ remove: commit,
213
+ handlers: { onPointerDown, onPointerMove, onPointerUp, onPointerCancel: onPointerUp }
214
+ };
215
+ }
216
+ var STATUS = {
217
+ free: { label: "Livre", Icon: CheckCircle2 },
218
+ occupied: { label: "Ocupada", Icon: Utensils },
219
+ bill_requested: { label: "Conta pedida", Icon: Receipt },
220
+ attention: { label: "Aten\xE7\xE3o", Icon: AlertTriangle }
221
+ };
222
+ function StatusBadge({ status }) {
223
+ const { label, Icon } = STATUS[status];
224
+ return /* @__PURE__ */ jsxs("span", { className: "pos-badge", "data-status": status, children: [
225
+ /* @__PURE__ */ jsx(Icon, { size: 12, strokeWidth: 2.4, "aria-hidden": true }),
226
+ label
227
+ ] });
228
+ }
229
+ function elapsed(openedAt, now = Date.now()) {
230
+ if (!openedAt) return void 0;
231
+ const minutes = Math.floor((now - new Date(openedAt).getTime()) / 6e4);
232
+ if (minutes < 0 || !Number.isFinite(minutes)) return void 0;
233
+ return minutes < 60 ? `${minutes}min` : `${Math.floor(minutes / 60)}h${String(minutes % 60).padStart(2, "0")}`;
234
+ }
235
+ function EmptyState({ title, hint, Icon = Search, action }) {
236
+ return /* @__PURE__ */ jsxs("div", { className: "pos-empty", children: [
237
+ /* @__PURE__ */ jsx("span", { className: "pos-empty__icon", children: /* @__PURE__ */ jsx(Icon, { size: 20, strokeWidth: 2, "aria-hidden": true }) }),
238
+ /* @__PURE__ */ jsx("span", { className: "pos-empty__t", children: title }),
239
+ hint && /* @__PURE__ */ jsx("span", { className: "pos-empty__s", children: hint }),
240
+ action && /* @__PURE__ */ jsx("button", { className: "pos-empty__b", onClick: () => {
241
+ haptic("tap");
242
+ action.onClick();
243
+ }, children: action.label })
244
+ ] });
245
+ }
246
+ function FloorView({ positions, config, activeId, onSelect, now, picking }) {
247
+ if (positions.length === 0) {
248
+ return /* @__PURE__ */ jsx(EmptyState, { title: "Nenhuma mesa nesta \xE1rea", hint: "Escolha outra \xE1rea acima.", Icon: Utensils });
249
+ }
250
+ return config.layout === "plan" ? /* @__PURE__ */ jsx(PlanFloor, { positions, config, activeId, onSelect, picking }) : /* @__PURE__ */ jsx(GridFloor, { positions, activeId, onSelect, now, picking });
251
+ }
252
+ function GridFloor({ positions, activeId, onSelect, now, picking }) {
253
+ return /* @__PURE__ */ jsx("div", { className: "pos-floor pos-scroll", "data-picking": picking, children: positions.map((p, index) => {
254
+ const since = elapsed(p.openedAt, now);
255
+ return /* @__PURE__ */ jsxs(
256
+ "button",
257
+ {
258
+ className: "pos-tbl",
259
+ "data-status": p.status,
260
+ "data-target": picking ? p.status === "free" : void 0,
261
+ "aria-current": p.id === activeId,
262
+ "aria-disabled": picking && p.status !== "free",
263
+ style: { ["--pos-stagger"]: `${Math.min(index, 11) * 20}ms` },
264
+ onClick: () => {
265
+ haptic("tap");
266
+ onSelect(p);
267
+ },
268
+ children: [
269
+ /* @__PURE__ */ jsxs("span", { className: "pos-tbl__top", children: [
270
+ /* @__PURE__ */ jsx("span", { className: "pos-tbl__no", children: p.label }),
271
+ p.seats != null && /* @__PURE__ */ jsxs("span", { className: "pos-tbl__seats", children: [
272
+ /* @__PURE__ */ jsx(Users, { size: 12, strokeWidth: 2.2, "aria-hidden": true }),
273
+ p.seats
274
+ ] })
275
+ ] }),
276
+ /* @__PURE__ */ jsx(StatusBadge, { status: p.status }),
277
+ p.attendantName && /* @__PURE__ */ jsx("span", { className: "pos-tbl__who", children: p.attendantName }),
278
+ /* @__PURE__ */ jsxs("span", { className: "pos-tbl__foot", children: [
279
+ /* @__PURE__ */ jsx("span", { className: "pos-tbl__time pos-num", children: since ? /* @__PURE__ */ jsxs(Fragment, { children: [
280
+ /* @__PURE__ */ jsx(Clock, { size: 12, strokeWidth: 2.2, "aria-hidden": true }),
281
+ since
282
+ ] }) : "\u2014" }),
283
+ p.totalCents != null && /* @__PURE__ */ jsx("span", { className: "pos-tbl__total pos-num", children: formatCents(p.totalCents) })
284
+ ] })
285
+ ]
286
+ },
287
+ p.id
288
+ );
289
+ }) });
290
+ }
291
+ var CELL = 44;
292
+ function PlanFloor({ positions, config, activeId, onSelect, picking }) {
293
+ const cols = Math.max(...positions.map((p) => (p.x ?? 1) + (p.w ?? 2) - 1), 4) + 1;
294
+ const rows = Math.max(...positions.map((p) => (p.y ?? 1) + (p.h ?? 2) - 1), 4) + 1;
295
+ return /* @__PURE__ */ jsxs(
296
+ "div",
297
+ {
298
+ className: "pos-plan pos-scroll",
299
+ "data-picking": picking,
300
+ style: { ["--pos-plan-cell"]: `${CELL}px` },
301
+ children: [
302
+ /* @__PURE__ */ jsx("div", { className: "pos-plan__canvas", style: { width: cols * CELL, height: rows * CELL }, children: positions.map((p) => {
303
+ const w = p.w ?? 2;
304
+ const h = p.h ?? 2;
305
+ const left = ((p.x ?? 1) - 1) * CELL;
306
+ const top = ((p.y ?? 1) - 1) * CELL;
307
+ return /* @__PURE__ */ jsx(
308
+ PlanTable,
309
+ {
310
+ position: p,
311
+ left,
312
+ top,
313
+ width: w * CELL - 8,
314
+ height: h * CELL - 8,
315
+ active: p.id === activeId,
316
+ onSelect,
317
+ picking
318
+ },
319
+ p.id
320
+ );
321
+ }) }),
322
+ /* @__PURE__ */ jsx("div", { className: "pos-plan__legend", children: Object.keys(STATUS).map((s) => /* @__PURE__ */ jsxs("span", { className: "pos-legend__k", children: [
323
+ /* @__PURE__ */ jsx("span", { className: "pos-legend__sw", style: { background: `var(--pos-${{ free: "line-strong", occupied: "warn", bill_requested: "info", attention: "danger" }[s]})` } }),
324
+ STATUS[s].label
325
+ ] }, s)) })
326
+ ]
327
+ }
328
+ );
329
+ }
330
+ function PlanTable({ position, left, top, width, height, active, onSelect, picking }) {
331
+ const seats = position.seats ?? 0;
332
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
333
+ seatOffsets(seats, width, height).map((s, i) => /* @__PURE__ */ jsx(
334
+ "span",
335
+ {
336
+ className: "pos-seat",
337
+ style: { left: left + 4 + s.x, top: top + 4 + s.y },
338
+ "aria-hidden": true
339
+ },
340
+ i
341
+ )),
342
+ /* @__PURE__ */ jsxs(
343
+ "button",
344
+ {
345
+ className: "pos-pt",
346
+ "data-status": position.status,
347
+ "data-shape": position.shape ?? "square",
348
+ "data-target": picking ? position.status === "free" : void 0,
349
+ "aria-disabled": picking && position.status !== "free",
350
+ "aria-current": active,
351
+ onClick: () => onSelect(position),
352
+ style: { left: left + 4, top: top + 4, width, height },
353
+ title: `${position.label} \xB7 ${STATUS[position.status].label}`,
354
+ children: [
355
+ position.itemCount != null && position.itemCount > 0 && /* @__PURE__ */ jsxs("span", { className: "pos-pt__badge pos-num", children: [
356
+ "+",
357
+ position.itemCount
358
+ ] }),
359
+ /* @__PURE__ */ jsx("span", { className: "pos-pt__no", children: position.label.replace(/^Mesa\s*/i, "") }),
360
+ position.attendantName && /* @__PURE__ */ jsx(
361
+ "span",
362
+ {
363
+ className: "pos-pt__chip",
364
+ style: { background: `var(--pos-tone-${position.attendantTone ?? "mint"})` },
365
+ children: initialsOf(position.attendantName)
366
+ }
367
+ )
368
+ ]
369
+ }
370
+ )
371
+ ] });
372
+ }
373
+ function initialsOf(name) {
374
+ const parts = name.trim().split(/\s+/);
375
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
376
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
377
+ }
378
+ function seatOffsets(seats, w, h) {
379
+ if (seats <= 0) return [];
380
+ const out = [];
381
+ const perSide = Math.ceil(seats / 4);
382
+ const place = (n, fn) => {
383
+ for (let i = 0; i < n && out.length < seats; i++) out.push(fn(i));
384
+ };
385
+ const step = (count, span, i) => (i + 1) * span / (count + 1) - 6;
386
+ place(perSide, (i) => ({ x: step(perSide, w, i), y: -17 }));
387
+ place(perSide, (i) => ({ x: step(perSide, w, i), y: h + 4 }));
388
+ place(perSide, (i) => ({ x: -17, y: step(perSide, h, i) }));
389
+ place(perSide, (i) => ({ x: w + 4, y: step(perSide, h, i) }));
390
+ return out;
391
+ }
392
+ function CatalogView({
393
+ categories,
394
+ items,
395
+ activeCategoryId,
396
+ onCategoryChange,
397
+ quantities,
398
+ onAdd,
399
+ onRemove,
400
+ query
401
+ }) {
402
+ const normalised = query.trim().toLowerCase();
403
+ const searching = normalised.length > 0;
404
+ const shown = items.filter(
405
+ (i) => searching ? i.name.toLowerCase().includes(normalised) || i.code?.toLowerCase() === normalised : !activeCategoryId || i.categoryId === activeCategoryId
406
+ );
407
+ return /* @__PURE__ */ jsxs("div", { className: "pos-scroll", style: { flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }, children: [
408
+ !searching && /* @__PURE__ */ jsx("div", { className: "pos-cats", children: categories.map((c, index) => /* @__PURE__ */ jsxs(
409
+ "button",
410
+ {
411
+ className: "pos-cat",
412
+ style: {
413
+ background: `var(--pos-tone-${c.tone})`,
414
+ ["--pos-stagger"]: `${Math.min(index, 11) * 20}ms`
415
+ },
416
+ "aria-pressed": c.id === activeCategoryId,
417
+ onClick: () => {
418
+ haptic("tap");
419
+ onCategoryChange(c.id);
420
+ },
421
+ children: [
422
+ /* @__PURE__ */ jsxs("span", { children: [
423
+ /* @__PURE__ */ jsx("span", { className: "pos-cat__name", children: c.label }),
424
+ c.itemCount != null && /* @__PURE__ */ jsxs("span", { className: "pos-cat__n pos-num", style: { display: "block" }, children: [
425
+ c.itemCount,
426
+ " itens"
427
+ ] })
428
+ ] }),
429
+ c.icon && /* @__PURE__ */ jsx("span", { className: "pos-cat__icon", children: c.icon })
430
+ ]
431
+ },
432
+ c.id
433
+ )) }),
434
+ !searching && /* @__PURE__ */ jsx("hr", { className: "pos-cats-rule" }),
435
+ shown.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { title: `Nada encontrado para \u201C${query}\u201D`, hint: "Tente o c\xF3digo do item ou parte do nome." }) : /* @__PURE__ */ jsx("div", { className: "pos-items", children: shown.map((item, index) => {
436
+ const quantity = quantities[item.id] ?? 0;
437
+ const category = categories.find((c) => c.id === item.categoryId);
438
+ const filled = quantity > 0 && category != null;
439
+ return /* @__PURE__ */ jsxs(
440
+ ItemCard,
441
+ {
442
+ quantity,
443
+ filled,
444
+ onQuickAdd: () => {
445
+ haptic("tap");
446
+ onAdd(item);
447
+ },
448
+ label: item.name,
449
+ delayMs: Math.min(index, 11) * 24,
450
+ tone: category?.tone,
451
+ children: [
452
+ item.route && /* @__PURE__ */ jsx("span", { className: "pos-item__route", children: item.route }),
453
+ /* @__PURE__ */ jsx("span", { className: "pos-item__name", children: item.name }),
454
+ /* @__PURE__ */ jsxs("span", { className: "pos-item__price pos-num", children: [
455
+ formatCents(item.priceCents),
456
+ item.code && ` \xB7 ${item.code}`
457
+ ] }),
458
+ /* @__PURE__ */ jsx("span", { className: "pos-item__foot", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs("span", { className: "pos-step", children: [
459
+ /* @__PURE__ */ jsx(
460
+ "button",
461
+ {
462
+ className: "pos-step__b",
463
+ onClick: () => {
464
+ haptic("tap");
465
+ onRemove(item);
466
+ },
467
+ disabled: quantity === 0,
468
+ "aria-label": `Tirar um ${item.name}`,
469
+ children: /* @__PURE__ */ jsx(Minus, { size: 16, strokeWidth: 2.6, "aria-hidden": true })
470
+ }
471
+ ),
472
+ /* @__PURE__ */ jsx("span", { className: "pos-step__n", children: quantity }),
473
+ /* @__PURE__ */ jsx(
474
+ "button",
475
+ {
476
+ className: "pos-step__b",
477
+ onClick: () => {
478
+ haptic("tap");
479
+ onAdd(item);
480
+ },
481
+ "aria-label": `Mais um ${item.name}`,
482
+ children: /* @__PURE__ */ jsx(Plus, { size: 16, strokeWidth: 2.6, "aria-hidden": true })
483
+ }
484
+ )
485
+ ] }) })
486
+ ]
487
+ },
488
+ item.id
489
+ );
490
+ }) })
491
+ ] });
492
+ }
493
+ function ShiftView({ positions, staff }) {
494
+ const open = positions.filter((p) => p.status !== "free");
495
+ const onFloor = open.reduce((sum, p) => sum + (p.totalCents ?? 0), 0);
496
+ return /* @__PURE__ */ jsxs("div", { className: "pos-scroll", style: { flex: 1, padding: "var(--pos-4x)", display: "grid", gap: "var(--pos-3x)", alignContent: "start" }, children: [
497
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: "var(--pos-3x)" }, children: [
498
+ /* @__PURE__ */ jsx(Stat, { label: "Em aberto no sal\xE3o", value: formatCents(onFloor) }),
499
+ /* @__PURE__ */ jsx(Stat, { label: "Comandas abertas", value: String(open.length) }),
500
+ /* @__PURE__ */ jsx(Stat, { label: "Pessoas no turno", value: String(staff.length) })
501
+ ] }),
502
+ /* @__PURE__ */ jsx("p", { className: "pos-t-sm", style: { margin: 0 }, children: "Abertura de caixa, sangria e fechamento chegam no M5 \u2014 este painel \xE9 o lugar deles." })
503
+ ] });
504
+ }
505
+ function Stat({ label, value }) {
506
+ return /* @__PURE__ */ jsxs("div", { style: {
507
+ border: "1px solid var(--pos-line)",
508
+ borderRadius: "var(--pos-r)",
509
+ background: "var(--pos-2)",
510
+ padding: "var(--pos-4x)"
511
+ }, children: [
512
+ /* @__PURE__ */ jsx("div", { className: "pos-t-xs", style: { marginBottom: 4 }, children: label }),
513
+ /* @__PURE__ */ jsx("div", { className: "pos-t-display pos-num", children: value })
514
+ ] });
515
+ }
516
+ function ItemCard({ quantity, filled, delayMs, tone, onQuickAdd, label, children }) {
517
+ return /* @__PURE__ */ jsx(
518
+ "div",
519
+ {
520
+ className: "pos-item",
521
+ role: "button",
522
+ tabIndex: 0,
523
+ "aria-label": `${label}, adicionar \xE0 comanda`,
524
+ "data-filled": filled,
525
+ onClick: onQuickAdd,
526
+ onKeyDown: (e) => {
527
+ if (e.key === "Enter" || e.key === " ") {
528
+ e.preventDefault();
529
+ onQuickAdd();
530
+ }
531
+ },
532
+ style: {
533
+ ["--pos-stagger"]: `${delayMs}ms`,
534
+ ...tone ? { ["--pos-item-tone"]: `var(--pos-tone-${tone})` } : {},
535
+ ...filled && tone ? { background: `var(--pos-tone-${tone})` } : {}
536
+ },
537
+ children
538
+ }
539
+ );
540
+ }
541
+ var PAY_ICON = {
542
+ cash: Banknote,
543
+ dinheiro: Banknote,
544
+ card: CreditCard,
545
+ cartao: CreditCard,
546
+ "cart\xE3o": CreditCard,
547
+ pix: QrCode
548
+ };
549
+ function TicketRail({
550
+ ticket,
551
+ totals,
552
+ vocabulary,
553
+ paymentMethods,
554
+ selectedPaymentId,
555
+ onSelectPayment,
556
+ onChangeQuantity,
557
+ selectedLineId,
558
+ onSelectLine,
559
+ onClose,
560
+ closeShortcut,
561
+ guests,
562
+ status,
563
+ open,
564
+ zoneLabel,
565
+ request,
566
+ now = Date.now(),
567
+ containerRef,
568
+ onBrowseCatalog,
569
+ onEditLine,
570
+ onEditIdentity,
571
+ alias,
572
+ customerPhone,
573
+ onFire,
574
+ fireShortcut
575
+ }) {
576
+ const [tab, setTab] = useState("order");
577
+ const [paid, setPaid] = useState(false);
578
+ const [stage, setStage] = useState("order");
579
+ const empty = ticket.lines.length === 0;
580
+ const count = ticket.lines.reduce((n, l) => n + l.quantity, 0);
581
+ const pendingCount = ticket.lines.filter((l) => (l.state ?? "pending") === "pending").reduce((n, l) => n + l.quantity, 0);
582
+ ticket.id;
583
+ if (stage === "checkout" && empty) setStage("order");
584
+ const firedAt = ticket.lines.map((l) => l.firedAt).filter((v) => Boolean(v)).sort()[0];
585
+ return /* @__PURE__ */ jsxs(
586
+ "aside",
587
+ {
588
+ className: "pos-aside",
589
+ ref: containerRef,
590
+ "data-open": open ?? true,
591
+ "aria-label": vocabulary.ticket,
592
+ children: [
593
+ /* @__PURE__ */ jsxs("header", { className: "pos-tkt", children: [
594
+ /* @__PURE__ */ jsxs("div", { className: "pos-tkt__row", children: [
595
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xl", children: ticket.positionLabel ?? "Balc\xE3o" }),
596
+ status && /* @__PURE__ */ jsx(StatusBadge, { status }),
597
+ onEditIdentity && /* @__PURE__ */ jsx(
598
+ "button",
599
+ {
600
+ className: "pos-tkt__edit",
601
+ onClick: onEditIdentity,
602
+ "aria-label": "Nomear a comanda ou anotar o celular",
603
+ title: "Nomear a comanda ou anotar o celular",
604
+ children: /* @__PURE__ */ jsx(Pencil, { size: 14, strokeWidth: 2.2 })
605
+ }
606
+ )
607
+ ] }),
608
+ /* @__PURE__ */ jsxs("div", { className: "pos-tkt__sub", children: [
609
+ alias && /* @__PURE__ */ jsx("strong", { className: "pos-tkt__alias", children: alias }),
610
+ /* @__PURE__ */ jsx("span", { className: "pos-tkt__meta", children: [
611
+ ticket.attendantName,
612
+ guests != null && `${guests} pessoas`,
613
+ sinceLabel(ticket.seatedAt, now),
614
+ customerPhone
615
+ ].filter(Boolean).join(" \xB7 ") || `Sem ${vocabulary.position} selecionada` })
616
+ ] })
617
+ ] }),
618
+ /* @__PURE__ */ jsxs("div", { className: "pos-tabs", role: "tablist", children: [
619
+ /* @__PURE__ */ jsxs(
620
+ "button",
621
+ {
622
+ className: "pos-tab",
623
+ role: "tab",
624
+ "aria-selected": tab === "order",
625
+ onClick: () => setTab("order"),
626
+ children: [
627
+ "Pedido",
628
+ count > 0 && /* @__PURE__ */ jsx("span", { className: "pos-tab__n pos-num", children: count })
629
+ ]
630
+ }
631
+ ),
632
+ /* @__PURE__ */ jsxs(
633
+ "button",
634
+ {
635
+ className: "pos-tab",
636
+ role: "tab",
637
+ "aria-selected": tab === "activity",
638
+ onClick: () => setTab("activity"),
639
+ children: [
640
+ "Atividade",
641
+ pendingCount > 0 && /* @__PURE__ */ jsx("span", { className: "pos-tab__dot", "aria-label": `${pendingCount} n\xE3o lan\xE7ados` })
642
+ ]
643
+ }
644
+ )
645
+ ] }),
646
+ /* @__PURE__ */ jsx("div", { className: "pos-scroll", style: { flex: 1, minHeight: 0 }, role: "tabpanel", children: tab === "order" ? empty ? /* @__PURE__ */ jsx(
647
+ EmptyState,
648
+ {
649
+ title: `${vocabulary.ticket} vazia`,
650
+ hint: "Digite o c\xF3digo do item e Enter, ou escolha no card\xE1pio.",
651
+ Icon: Receipt,
652
+ action: onBrowseCatalog && { label: "Ver card\xE1pio", onClick: onBrowseCatalog }
653
+ }
654
+ ) : ticket.lines.map((line) => /* @__PURE__ */ jsx(
655
+ TicketLineRow,
656
+ {
657
+ line,
658
+ selected: line.id === selectedLineId,
659
+ onSelect: () => {
660
+ onSelectLine(line.id);
661
+ onEditLine?.(line.id);
662
+ },
663
+ onChangeQuantity: (delta) => onChangeQuantity(line.id, delta),
664
+ onRemove: () => onChangeQuantity(line.id, -line.quantity)
665
+ },
666
+ line.id
667
+ )) : /* @__PURE__ */ jsx(
668
+ ActivityPanel,
669
+ {
670
+ ticket,
671
+ firedAt,
672
+ pendingCount,
673
+ guests,
674
+ zoneLabel,
675
+ request,
676
+ totals,
677
+ count,
678
+ now
679
+ }
680
+ ) }),
681
+ /* @__PURE__ */ jsxs("div", { className: "pos-tot", children: [
682
+ /* @__PURE__ */ jsxs("div", { className: "pos-tot__r", children: [
683
+ /* @__PURE__ */ jsx("span", { children: "Subtotal" }),
684
+ /* @__PURE__ */ jsx("span", { className: "pos-num", children: formatCents(totals.subtotalCents) })
685
+ ] }),
686
+ totals.serviceCents > 0 && /* @__PURE__ */ jsxs("div", { className: "pos-tot__r", children: [
687
+ /* @__PURE__ */ jsx("span", { children: "Servi\xE7o 10%" }),
688
+ /* @__PURE__ */ jsx("span", { className: "pos-num", children: formatCents(totals.serviceCents) })
689
+ ] }),
690
+ totals.discountCents > 0 && /* @__PURE__ */ jsxs("div", { className: "pos-tot__r", children: [
691
+ /* @__PURE__ */ jsx("span", { children: "Desconto" }),
692
+ /* @__PURE__ */ jsxs("span", { className: "pos-num", children: [
693
+ "\u2212 ",
694
+ formatCents(totals.discountCents)
695
+ ] })
696
+ ] }),
697
+ /* @__PURE__ */ jsxs("div", { className: "pos-tot__g", children: [
698
+ /* @__PURE__ */ jsx("span", { className: "pos-t-md", children: "Total" }),
699
+ /* @__PURE__ */ jsx("strong", { className: "pos-t-display pos-num", children: formatCents(totals.totalCents) })
700
+ ] })
701
+ ] }),
702
+ stage === "order" ? (
703
+ /* The primary action is whatever comes NEXT, not always the last step.
704
+ With items still on the counter, "fechar conta" is the wrong verb —
705
+ and offering it as the big button is how a bill gets paid for food
706
+ the kitchen never heard about. */
707
+ /* @__PURE__ */ jsx("div", { className: "pos-cta pos-cta--stack", children: pendingCount > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
708
+ /* @__PURE__ */ jsxs("p", { className: "pos-hint", children: [
709
+ /* @__PURE__ */ jsx(AlertTriangle, { size: 14, strokeWidth: 2.4, "aria-hidden": true }),
710
+ pendingCount === 1 ? "1 item marcado ainda n\xE3o foi para a cozinha" : `${pendingCount} itens marcados ainda n\xE3o foram para a cozinha`
711
+ ] }),
712
+ /* @__PURE__ */ jsxs(
713
+ "button",
714
+ {
715
+ className: "pos-btn",
716
+ onClick: () => {
717
+ haptic("commit");
718
+ onFire?.();
719
+ },
720
+ children: [
721
+ /* @__PURE__ */ jsx(ChefHat, { size: 19, strokeWidth: 2.2, "aria-hidden": true }),
722
+ "Enviar \xE0 cozinha",
723
+ /* @__PURE__ */ jsx("span", { className: "pos-num", children: pendingCount }),
724
+ fireShortcut && /* @__PURE__ */ jsx("kbd", { className: "pos-kbd", children: fireShortcut })
725
+ ]
726
+ }
727
+ ),
728
+ /* @__PURE__ */ jsxs(
729
+ "button",
730
+ {
731
+ className: "pos-btn pos-btn--ghost pos-btn--wide",
732
+ onClick: () => setStage("checkout"),
733
+ children: [
734
+ "Fechar mesmo assim \xB7 ",
735
+ formatCents(totals.totalCents)
736
+ ]
737
+ }
738
+ )
739
+ ] }) : /* @__PURE__ */ jsxs(
740
+ "button",
741
+ {
742
+ className: "pos-btn",
743
+ disabled: empty,
744
+ onClick: () => {
745
+ haptic("commit");
746
+ setStage("checkout");
747
+ },
748
+ children: [
749
+ "Fechar conta",
750
+ !empty && /* @__PURE__ */ jsx("span", { className: "pos-num", children: formatCents(totals.totalCents) }),
751
+ closeShortcut && /* @__PURE__ */ jsx("kbd", { className: "pos-kbd", children: closeShortcut })
752
+ ]
753
+ }
754
+ ) })
755
+ ) : /* @__PURE__ */ jsxs("div", { className: "pos-checkout", children: [
756
+ /* @__PURE__ */ jsxs(
757
+ "button",
758
+ {
759
+ className: "pos-back",
760
+ onClick: () => {
761
+ haptic("tap");
762
+ setStage("order");
763
+ },
764
+ children: [
765
+ /* @__PURE__ */ jsx(ChevronLeft, { size: 20, strokeWidth: 2.4, "aria-hidden": true }),
766
+ "Voltar \xE0 comanda"
767
+ ]
768
+ }
769
+ ),
770
+ /* @__PURE__ */ jsx("div", { className: "pos-checkout__head", children: /* @__PURE__ */ jsx("span", { className: "pos-t-xs", children: "Como vai pagar" }) }),
771
+ /* @__PURE__ */ jsx("div", { className: "pos-pay", role: "radiogroup", "aria-label": "Forma de pagamento", children: paymentMethods.map((m) => {
772
+ const Icon = PAY_ICON[m.id.toLowerCase()] ?? Banknote;
773
+ return /* @__PURE__ */ jsxs(
774
+ "button",
775
+ {
776
+ className: "pos-pay__b",
777
+ role: "radio",
778
+ "aria-checked": m.id === selectedPaymentId,
779
+ onClick: () => {
780
+ haptic("tap");
781
+ onSelectPayment(m.id);
782
+ },
783
+ children: [
784
+ /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(Icon, { size: 18, strokeWidth: 2, "aria-hidden": true }) }),
785
+ m.label
786
+ ]
787
+ },
788
+ m.id
789
+ );
790
+ }) }),
791
+ /* @__PURE__ */ jsx("div", { className: "pos-cta", children: /* @__PURE__ */ jsx(
792
+ "button",
793
+ {
794
+ className: "pos-btn",
795
+ "data-paid": paid,
796
+ disabled: !selectedPaymentId,
797
+ onClick: () => {
798
+ haptic("commit");
799
+ setPaid(true);
800
+ window.setTimeout(() => {
801
+ setPaid(false);
802
+ setStage("order");
803
+ }, 900);
804
+ onClose();
805
+ },
806
+ children: selectedPaymentId ? /* @__PURE__ */ jsxs(Fragment, { children: [
807
+ "Confirmar ",
808
+ /* @__PURE__ */ jsx("span", { className: "pos-num", children: formatCents(totals.totalCents) })
809
+ ] }) : "Escolha a forma de pagamento"
810
+ }
811
+ ) })
812
+ ] })
813
+ ]
814
+ }
815
+ );
816
+ }
817
+ function ActivityPanel({
818
+ ticket,
819
+ firedAt,
820
+ pendingCount,
821
+ guests,
822
+ zoneLabel,
823
+ request,
824
+ totals,
825
+ count,
826
+ now
827
+ }) {
828
+ const fires = /* @__PURE__ */ new Map();
829
+ for (const line of ticket.lines) {
830
+ if (!line.firedAt) continue;
831
+ fires.set(line.firedAt, [...fires.get(line.firedAt) ?? [], line.name]);
832
+ }
833
+ const events = [];
834
+ if (ticket.seatedAt) {
835
+ events.push({
836
+ at: ticket.seatedAt,
837
+ title: "Sentou",
838
+ detail: guests ? `${guests} pessoas` : void 0,
839
+ done: true
840
+ });
841
+ }
842
+ for (const [at, names] of [...fires.entries()].sort()) {
843
+ events.push({
844
+ at,
845
+ title: "Enviado \xE0 cozinha",
846
+ detail: names.join(", "),
847
+ done: true
848
+ });
849
+ }
850
+ if (pendingCount > 0) {
851
+ events.push({
852
+ title: "Aguardando envio",
853
+ detail: `${pendingCount} ${pendingCount === 1 ? "item" : "itens"} ainda no balc\xE3o`,
854
+ done: false
855
+ });
856
+ }
857
+ return /* @__PURE__ */ jsxs("div", { className: "pos-activity", children: [
858
+ /* @__PURE__ */ jsxs("ol", { className: "pos-tl", children: [
859
+ events.map((e, i) => /* @__PURE__ */ jsxs("li", { className: "pos-ev", "data-done": e.done, children: [
860
+ /* @__PURE__ */ jsx("span", { className: "pos-ev__mark", "aria-hidden": true }),
861
+ /* @__PURE__ */ jsxs("span", { className: "pos-ev__body", children: [
862
+ /* @__PURE__ */ jsxs("span", { className: "pos-ev__row", children: [
863
+ /* @__PURE__ */ jsx("span", { className: "pos-ev__t", children: e.title }),
864
+ e.at && /* @__PURE__ */ jsx("span", { className: "pos-ev__at pos-num", children: clock(e.at) })
865
+ ] }),
866
+ e.detail && /* @__PURE__ */ jsx("span", { className: "pos-ev__d", children: e.detail })
867
+ ] })
868
+ ] }, i)),
869
+ events.length === 0 && /* @__PURE__ */ jsx("li", { className: "pos-ev__none", children: "Nada aconteceu ainda." })
870
+ ] }),
871
+ request && /* @__PURE__ */ jsxs("div", { className: "pos-note-block", children: [
872
+ /* @__PURE__ */ jsx("div", { className: "pos-t-xs", children: "Observa\xE7\xE3o da mesa" }),
873
+ /* @__PURE__ */ jsxs("p", { className: "pos-note-block__t", children: [
874
+ "\u201C",
875
+ request,
876
+ "\u201D"
877
+ ] })
878
+ ] }),
879
+ /* @__PURE__ */ jsxs("dl", { className: "pos-dl", children: [
880
+ /* @__PURE__ */ jsxs("div", { children: [
881
+ /* @__PURE__ */ jsx("dt", { className: "pos-t-xs", children: "\xC1rea" }),
882
+ /* @__PURE__ */ jsx("dd", { children: zoneLabel ?? "\u2014" })
883
+ ] }),
884
+ /* @__PURE__ */ jsxs("div", { children: [
885
+ /* @__PURE__ */ jsx("dt", { className: "pos-t-xs", children: "Por pessoa" }),
886
+ /* @__PURE__ */ jsx("dd", { className: "pos-num", children: guests && guests > 0 ? formatCents(splitEvenly(totals.totalCents, guests)[0] ?? 0) : "\u2014" })
887
+ ] })
888
+ ] })
889
+ ] });
890
+ }
891
+ var STATE_LABEL = {
892
+ pending: "Ainda n\xE3o lan\xE7ado",
893
+ fired: "Na cozinha",
894
+ delivered: "Entregue"
895
+ };
896
+ function StateIcon({ state }) {
897
+ if (state === "fired") return /* @__PURE__ */ jsx(CookingPot, { size: 15, strokeWidth: 2.2, "aria-hidden": true });
898
+ if (state === "delivered") return /* @__PURE__ */ jsx(UtensilsCrossed, { size: 15, strokeWidth: 2.2, "aria-hidden": true });
899
+ return /* @__PURE__ */ jsx(CircleDashed, { size: 15, strokeWidth: 2.2, "aria-hidden": true });
900
+ }
901
+ function clock(iso) {
902
+ return new Date(iso).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
903
+ }
904
+ function sinceLabel(iso, now) {
905
+ if (!iso) return "\u2014";
906
+ const minutes = Math.floor((now - new Date(iso).getTime()) / 6e4);
907
+ if (!Number.isFinite(minutes) || minutes < 0) return "\u2014";
908
+ return minutes < 60 ? `${minutes}min` : `${Math.floor(minutes / 60)}h${String(minutes % 60).padStart(2, "0")}`;
909
+ }
910
+ function TicketLineRow({ line, selected, onSelect, onChangeQuantity, onRemove }) {
911
+ const swipe = useSwipeAway(onRemove);
912
+ const state = line.state ?? "pending";
913
+ return /* @__PURE__ */ jsxs("div", { className: "pos-ln-wrap", "data-open": swipe.open, "data-swiping": swipe.offset !== 0, children: [
914
+ /* @__PURE__ */ jsxs(
915
+ "button",
916
+ {
917
+ className: "pos-swipe-bg",
918
+ tabIndex: swipe.open ? 0 : -1,
919
+ "aria-hidden": !swipe.open,
920
+ onClick: swipe.remove,
921
+ children: [
922
+ /* @__PURE__ */ jsx(Trash2, { size: 18, strokeWidth: 2.3, "aria-hidden": true }),
923
+ "Remover"
924
+ ]
925
+ }
926
+ ),
927
+ /* @__PURE__ */ jsxs(
928
+ "div",
929
+ {
930
+ className: "pos-ln",
931
+ "data-state": state,
932
+ "data-removing": swipe.removing,
933
+ "data-swiping": swipe.offset !== 0,
934
+ style: { transform: swipe.offset ? `translateX(${swipe.offset}px)` : void 0 },
935
+ role: "button",
936
+ tabIndex: 0,
937
+ "aria-current": selected,
938
+ onClick: () => swipe.open ? swipe.close() : onSelect(),
939
+ onKeyDown: (e) => {
940
+ if (e.key === "Enter" || e.key === " ") onSelect();
941
+ },
942
+ ...swipe.handlers,
943
+ children: [
944
+ /* @__PURE__ */ jsx("span", { className: "pos-ln__state", title: STATE_LABEL[state], children: /* @__PURE__ */ jsx(StateIcon, { state }) }),
945
+ /* @__PURE__ */ jsxs("span", { children: [
946
+ /* @__PURE__ */ jsx("span", { className: "pos-ln__n", children: line.name }),
947
+ line.modifiers && line.modifiers.length > 0 && /* @__PURE__ */ jsx("span", { className: "pos-ln__mods", children: line.modifiers.map((m) => /* @__PURE__ */ jsxs("span", { className: "pos-ln__mod", children: [
948
+ m.label,
949
+ m.priceDeltaCents !== 0 && /* @__PURE__ */ jsxs("span", { className: "pos-num", children: [
950
+ " ",
951
+ m.priceDeltaCents > 0 ? "+" : "\u2212",
952
+ formatCents(Math.abs(m.priceDeltaCents))
953
+ ] })
954
+ ] }, `${m.groupId}:${m.optionId}`)) }),
955
+ line.note && /* @__PURE__ */ jsxs("span", { className: "pos-ln__note", children: [
956
+ "\u201C",
957
+ line.note,
958
+ "\u201D"
959
+ ] })
960
+ ] }),
961
+ /* @__PURE__ */ jsxs("span", { className: "pos-step", onClick: (e) => e.stopPropagation(), children: [
962
+ /* @__PURE__ */ jsx(
963
+ "button",
964
+ {
965
+ className: "pos-step__b",
966
+ onClick: () => {
967
+ haptic("tap");
968
+ onChangeQuantity(-1);
969
+ },
970
+ "aria-label": `Tirar um ${line.name}`,
971
+ children: /* @__PURE__ */ jsx(Minus, { size: 14, strokeWidth: 2.6, "aria-hidden": true })
972
+ }
973
+ ),
974
+ /* @__PURE__ */ jsx("span", { className: "pos-step__n", children: line.quantity }),
975
+ /* @__PURE__ */ jsx(
976
+ "button",
977
+ {
978
+ className: "pos-step__b",
979
+ onClick: () => {
980
+ haptic("tap");
981
+ onChangeQuantity(1);
982
+ },
983
+ "aria-label": `Adicionar ${line.name}`,
984
+ children: /* @__PURE__ */ jsx(Plus, { size: 14, strokeWidth: 2.6, "aria-hidden": true })
985
+ }
986
+ )
987
+ ] }),
988
+ /* @__PURE__ */ jsx("span", { className: "pos-ln__p pos-num", children: formatCents(line.unitPriceCents * line.quantity) })
989
+ ]
990
+ }
991
+ )
992
+ ] });
993
+ }
994
+ var COLUMNS = [
995
+ { id: "received", label: "Recebido", Icon: Package },
996
+ { id: "preparing", label: "Em produ\xE7\xE3o", Icon: ChefHat },
997
+ { id: "ready", label: "Pronto", Icon: CheckCheck },
998
+ { id: "out", label: "Saiu", Icon: Bike }
999
+ ];
1000
+ function lateness(p, now) {
1001
+ if (!p.dueAt) return void 0;
1002
+ return Math.floor((now - new Date(p.dueAt).getTime()) / 6e4);
1003
+ }
1004
+ var GHOST_MS = 300;
1005
+ function DeliveryBoard({ orders, activeId, onSelect, now }) {
1006
+ const seen = useRef({});
1007
+ const [ghosts, setGhosts] = useState([]);
1008
+ useEffect(() => {
1009
+ const moved = [];
1010
+ for (const o of orders) {
1011
+ const at = o.dispatch ?? "received";
1012
+ const was = seen.current[o.id];
1013
+ if (was && was !== at) moved.push({ id: o.id, from: was, label: o.label });
1014
+ seen.current[o.id] = at;
1015
+ }
1016
+ if (moved.length === 0) return;
1017
+ setGhosts((g) => [...g, ...moved]);
1018
+ const t = window.setTimeout(
1019
+ () => setGhosts((g) => g.filter((x) => !moved.some((m) => m.id === x.id))),
1020
+ GHOST_MS
1021
+ );
1022
+ return () => window.clearTimeout(t);
1023
+ }, [orders]);
1024
+ if (orders.length === 0) {
1025
+ return /* @__PURE__ */ jsx(EmptyState, { title: "Nenhum pedido de delivery agora", hint: "Pedidos dos aplicativos aparecem aqui assim que entram.", Icon: Bike });
1026
+ }
1027
+ return /* @__PURE__ */ jsx("div", { className: "pos-board", children: COLUMNS.map(({ id, label, Icon }) => {
1028
+ const column = orders.filter((o) => (o.dispatch ?? "received") === id);
1029
+ return /* @__PURE__ */ jsxs("section", { className: "pos-col", "aria-label": label, children: [
1030
+ /* @__PURE__ */ jsxs("header", { className: "pos-col__head", children: [
1031
+ /* @__PURE__ */ jsx(Icon, { size: 15, strokeWidth: 2.2, "aria-hidden": true }),
1032
+ /* @__PURE__ */ jsx("span", { className: "pos-col__t", children: label }),
1033
+ /* @__PURE__ */ jsx("span", { className: "pos-col__n pos-num", children: column.length })
1034
+ ] }),
1035
+ /* @__PURE__ */ jsxs("div", { className: "pos-col__body", children: [
1036
+ column.map((o) => {
1037
+ const late = lateness(o, now);
1038
+ const isLate = late != null && late > 0;
1039
+ const awaiting = id === "received" && !isLate;
1040
+ return /* @__PURE__ */ jsxs(
1041
+ "button",
1042
+ {
1043
+ className: "pos-card",
1044
+ "data-late": isLate,
1045
+ "data-await": awaiting,
1046
+ "aria-current": o.id === activeId,
1047
+ style: o.channelTone && !isLate && !awaiting ? { ["--pos-edge"]: `var(--pos-tone-${o.channelTone})` } : void 0,
1048
+ onClick: () => {
1049
+ haptic("tap");
1050
+ onSelect(o);
1051
+ },
1052
+ children: [
1053
+ /* @__PURE__ */ jsxs("span", { className: "pos-card__top", children: [
1054
+ /* @__PURE__ */ jsxs("span", { className: "pos-card__id pos-num", children: [
1055
+ "#",
1056
+ o.label.replace(/\D/g, "") || o.label
1057
+ ] }),
1058
+ o.channel && /* @__PURE__ */ jsx("span", { className: "pos-card__ch", children: o.channel })
1059
+ ] }),
1060
+ /* @__PURE__ */ jsx("span", { className: "pos-card__who", children: o.customerName ?? "\u2014" }),
1061
+ o.address && /* @__PURE__ */ jsx("span", { className: "pos-card__addr", children: o.address }),
1062
+ /* @__PURE__ */ jsxs("span", { className: "pos-card__foot", children: [
1063
+ /* @__PURE__ */ jsx("span", { className: isLate ? "pos-card__late" : "pos-card__due", children: isLate ? /* @__PURE__ */ jsxs(Fragment, { children: [
1064
+ /* @__PURE__ */ jsx(AlertTriangle, { size: 12, strokeWidth: 2.4, "aria-hidden": true }),
1065
+ late,
1066
+ "min atrasado"
1067
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1068
+ /* @__PURE__ */ jsx(Clock, { size: 12, strokeWidth: 2.2, "aria-hidden": true }),
1069
+ dueLabel(o, now)
1070
+ ] }) }),
1071
+ o.totalCents != null && /* @__PURE__ */ jsx("span", { className: "pos-card__v pos-num", children: formatCents(o.totalCents) })
1072
+ ] }),
1073
+ o.courierName && /* @__PURE__ */ jsxs("span", { className: "pos-card__courier", children: [
1074
+ /* @__PURE__ */ jsx(Bike, { size: 12, strokeWidth: 2.2, "aria-hidden": true }),
1075
+ o.courierName
1076
+ ] })
1077
+ ]
1078
+ },
1079
+ o.id
1080
+ );
1081
+ }),
1082
+ ghosts.filter((g) => g.from === id).map((g) => /* @__PURE__ */ jsx("span", { className: "pos-card pos-card--ghost", "aria-hidden": true }, `ghost-${g.id}`)),
1083
+ column.length === 0 && ghosts.every((g) => g.from !== id) && /* @__PURE__ */ jsx("p", { className: "pos-col__empty", children: "Nenhum" })
1084
+ ] })
1085
+ ] }, id);
1086
+ }) });
1087
+ }
1088
+ function dueLabel(p, now) {
1089
+ if (!p.dueAt) return "\u2014";
1090
+ const minutes = Math.ceil((new Date(p.dueAt).getTime() - now) / 6e4);
1091
+ return minutes <= 0 ? "agora" : `em ${minutes}min`;
1092
+ }
1093
+ var DISPATCH_LABEL = {
1094
+ received: "Recebido",
1095
+ preparing: "Em produ\xE7\xE3o",
1096
+ ready: "Pronto",
1097
+ out: "Saiu para entrega",
1098
+ delivered: "Entregue",
1099
+ cancelled: "Cancelado"
1100
+ };
1101
+ function nextDispatch(current) {
1102
+ const order = ["received", "preparing", "ready", "out", "delivered"];
1103
+ const i = order.indexOf(current);
1104
+ return i >= 0 && i < order.length - 1 ? order[i + 1] : void 0;
1105
+ }
1106
+ function DispatchRail({
1107
+ order,
1108
+ ticket,
1109
+ totals,
1110
+ onAdvance,
1111
+ onCancel,
1112
+ now,
1113
+ open,
1114
+ containerRef
1115
+ }) {
1116
+ const status = order.dispatch ?? "received";
1117
+ const next = nextDispatch(status);
1118
+ const late = order.dueAt ? Math.floor((now - new Date(order.dueAt).getTime()) / 6e4) : void 0;
1119
+ return /* @__PURE__ */ jsxs(
1120
+ "aside",
1121
+ {
1122
+ className: "pos-aside",
1123
+ ref: containerRef,
1124
+ "data-open": open ?? true,
1125
+ "aria-label": `Pedido ${order.label}`,
1126
+ children: [
1127
+ /* @__PURE__ */ jsxs("header", { className: "pos-tkt", children: [
1128
+ /* @__PURE__ */ jsxs("div", { className: "pos-tkt__row", children: [
1129
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xl", children: order.label }),
1130
+ /* @__PURE__ */ jsx("span", { className: "pos-badge", "data-status": status === "out" ? "bill_requested" : "occupied", children: DISPATCH_LABEL[status] })
1131
+ ] }),
1132
+ /* @__PURE__ */ jsx("div", { className: "pos-tkt__sub", children: [order.channel, order.customerName].filter(Boolean).join(" \xB7 ") })
1133
+ ] }),
1134
+ /* @__PURE__ */ jsxs("div", { className: "pos-scroll", style: { flex: 1, minHeight: 0 }, children: [
1135
+ /* @__PURE__ */ jsxs("div", { className: "pos-dispatch", children: [
1136
+ late != null && late > 0 && /* @__PURE__ */ jsxs("div", { className: "pos-alert", children: [
1137
+ /* @__PURE__ */ jsx(AlertTriangle, { size: 16, strokeWidth: 2.3, "aria-hidden": true }),
1138
+ late,
1139
+ "min al\xE9m do prometido"
1140
+ ] }),
1141
+ order.address && /* @__PURE__ */ jsxs("div", { className: "pos-fact", children: [
1142
+ /* @__PURE__ */ jsx(MapPin, { size: 15, strokeWidth: 2.2, "aria-hidden": true }),
1143
+ /* @__PURE__ */ jsx("span", { children: order.address })
1144
+ ] }),
1145
+ order.courierName && /* @__PURE__ */ jsxs("div", { className: "pos-fact", children: [
1146
+ /* @__PURE__ */ jsx(Bike, { size: 15, strokeWidth: 2.2, "aria-hidden": true }),
1147
+ /* @__PURE__ */ jsx("span", { children: order.courierName })
1148
+ ] }),
1149
+ order.dueAt && /* @__PURE__ */ jsxs("div", { className: "pos-fact", children: [
1150
+ /* @__PURE__ */ jsx(Clock, { size: 15, strokeWidth: 2.2, "aria-hidden": true }),
1151
+ /* @__PURE__ */ jsxs("span", { children: [
1152
+ "Prometido para ",
1153
+ clock2(order.dueAt)
1154
+ ] })
1155
+ ] })
1156
+ ] }),
1157
+ /* @__PURE__ */ jsx("div", { className: "pos-t-xs", style: { padding: "0 var(--pos-4x) var(--pos-2x)" }, children: "Itens" }),
1158
+ ticket.lines.map((line) => /* @__PURE__ */ jsxs("div", { className: "pos-ln", "data-state": line.state ?? "pending", "data-readonly": "true", children: [
1159
+ /* @__PURE__ */ jsxs("span", { className: "pos-ln__qty pos-num", children: [
1160
+ line.quantity,
1161
+ "\xD7"
1162
+ ] }),
1163
+ /* @__PURE__ */ jsxs("span", { children: [
1164
+ /* @__PURE__ */ jsx("span", { className: "pos-ln__n", children: line.name }),
1165
+ line.note && /* @__PURE__ */ jsxs("span", { className: "pos-ln__note", children: [
1166
+ "\u201C",
1167
+ line.note,
1168
+ "\u201D"
1169
+ ] })
1170
+ ] }),
1171
+ /* @__PURE__ */ jsx("span", { className: "pos-ln__p pos-num", children: formatCents(line.unitPriceCents * line.quantity) })
1172
+ ] }, line.id))
1173
+ ] }),
1174
+ /* @__PURE__ */ jsxs("div", { className: "pos-tot", children: [
1175
+ /* @__PURE__ */ jsxs("div", { className: "pos-tot__g", children: [
1176
+ /* @__PURE__ */ jsx("span", { className: "pos-t-md", children: "Total" }),
1177
+ /* @__PURE__ */ jsx("strong", { className: "pos-t-display pos-num", children: formatCents(totals.totalCents) })
1178
+ ] }),
1179
+ order.prepaid !== false && /* @__PURE__ */ jsxs("p", { className: "pos-paid", children: [
1180
+ /* @__PURE__ */ jsx(ShieldCheck, { size: 14, strokeWidth: 2.3, "aria-hidden": true }),
1181
+ "J\xE1 pago no ",
1182
+ order.channel ?? "aplicativo",
1183
+ " \u2014 n\xE3o cobrar"
1184
+ ] })
1185
+ ] }),
1186
+ /* @__PURE__ */ jsxs("div", { className: "pos-cta", style: { display: "grid", gap: "var(--pos-2x)" }, children: [
1187
+ next ? /* @__PURE__ */ jsx("button", { className: "pos-btn", onClick: () => onAdvance(next), children: ADVANCE_LABEL[next] }) : /* @__PURE__ */ jsx("button", { className: "pos-btn", disabled: true, children: "Entregue" }),
1188
+ /* @__PURE__ */ jsx("button", { className: "pos-btn pos-btn--ghost", style: { width: "100%" }, onClick: onCancel, children: "Cancelar pedido" })
1189
+ ] })
1190
+ ]
1191
+ }
1192
+ );
1193
+ }
1194
+ var ADVANCE_LABEL = {
1195
+ preparing: "Aceitar e produzir",
1196
+ ready: "Marcar pronto",
1197
+ out: "Despachar",
1198
+ delivered: "Confirmar entrega"
1199
+ };
1200
+ function clock2(iso) {
1201
+ return new Date(iso).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
1202
+ }
1203
+ var BUILT_IN = [
1204
+ { combo: "c\xF3digo + Enter", label: "Lan\xE7ar item pelo c\xF3digo" },
1205
+ { combo: "\u2191 \u2193 \u2190 \u2192", label: "Andar pela tela" },
1206
+ { combo: "Enter", label: "Acionar o que est\xE1 em foco" },
1207
+ { combo: "Esc", label: "Voltar o cursor ao campo de comando" },
1208
+ { combo: "F1", label: "Esta lista" }
1209
+ ];
1210
+ function ShortcutsOverlay({ actions, onClose }) {
1211
+ useEffect(() => {
1212
+ function onKey(event) {
1213
+ if (event.key === "Escape" || event.key === "F1") {
1214
+ event.preventDefault();
1215
+ onClose();
1216
+ }
1217
+ }
1218
+ window.addEventListener("keydown", onKey, true);
1219
+ return () => window.removeEventListener("keydown", onKey, true);
1220
+ }, [onClose]);
1221
+ const bound = actions.filter((a) => a.shortcut);
1222
+ return /* @__PURE__ */ jsxs("div", { className: "pos-sheet", role: "dialog", "aria-modal": "true", "aria-label": "Atalhos", children: [
1223
+ /* @__PURE__ */ jsx("div", { className: "pos-sheet__scrim", onClick: onClose }),
1224
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__panel", style: { width: "min(620px, 94vw)" }, children: [
1225
+ /* @__PURE__ */ jsxs("header", { className: "pos-sheet__head", children: [
1226
+ /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
1227
+ /* @__PURE__ */ jsx(Keyboard, { size: 20, strokeWidth: 2.1, "aria-hidden": true }),
1228
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xl", children: "Atalhos" })
1229
+ ] }),
1230
+ /* @__PURE__ */ jsx("button", { className: "pos-icon-btn", onClick: onClose, "aria-label": "Fechar", children: /* @__PURE__ */ jsx(X, { size: 18, strokeWidth: 2.2 }) })
1231
+ ] }),
1232
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__body pos-scroll", children: [
1233
+ /* @__PURE__ */ jsxs("section", { children: [
1234
+ /* @__PURE__ */ jsx("div", { className: "pos-t-xs", style: { marginBottom: 8 }, children: "Opera\xE7\xE3o" }),
1235
+ /* @__PURE__ */ jsx("div", { className: "pos-keys", children: bound.map((a) => /* @__PURE__ */ jsxs("div", { className: "pos-key", children: [
1236
+ /* @__PURE__ */ jsx("kbd", { className: "pos-kbd", children: a.shortcut }),
1237
+ /* @__PURE__ */ jsx("span", { children: a.label }),
1238
+ a.requiresPin && /* @__PURE__ */ jsx("span", { className: "pos-key__pin", children: "PIN" })
1239
+ ] }, a.id)) })
1240
+ ] }),
1241
+ /* @__PURE__ */ jsxs("section", { children: [
1242
+ /* @__PURE__ */ jsx("div", { className: "pos-t-xs", style: { marginBottom: 8 }, children: "Sempre" }),
1243
+ /* @__PURE__ */ jsx("div", { className: "pos-keys", children: BUILT_IN.map((k) => /* @__PURE__ */ jsxs("div", { className: "pos-key", children: [
1244
+ /* @__PURE__ */ jsx("kbd", { className: "pos-kbd", children: k.combo }),
1245
+ /* @__PURE__ */ jsx("span", { children: k.label })
1246
+ ] }, k.combo)) })
1247
+ ] })
1248
+ ] })
1249
+ ] })
1250
+ ] });
1251
+ }
1252
+ var STATUS2 = {
1253
+ booked: { label: "Reservado", tone: "occupied" },
1254
+ confirmed: { label: "Confirmado", tone: "bill_requested" },
1255
+ arrived: { label: "Chegou", tone: "free" },
1256
+ seated: { label: "Sentado", tone: "free" },
1257
+ no_show: { label: "N\xE3o veio", tone: "attention" }
1258
+ };
1259
+ function ReservationsView({
1260
+ reservations,
1261
+ positions,
1262
+ now,
1263
+ onSeat,
1264
+ onMarkArrived,
1265
+ onOpenPosition
1266
+ }) {
1267
+ if (reservations.length === 0) {
1268
+ return /* @__PURE__ */ jsx(EmptyState, { title: "Nenhuma reserva hoje", hint: "Reservas feitas no site ou por telefone aparecem aqui.", Icon: CalendarClock });
1269
+ }
1270
+ const sorted = [...reservations].sort((a, b) => a.at.localeCompare(b.at));
1271
+ const upcoming = sorted.filter((r) => new Date(r.at).getTime() >= now - 15 * 6e4);
1272
+ const past = sorted.filter((r) => !upcoming.includes(r));
1273
+ return /* @__PURE__ */ jsxs("div", { className: "pos-res pos-scroll", children: [
1274
+ upcoming.length > 0 && /* @__PURE__ */ jsx(
1275
+ Group,
1276
+ {
1277
+ title: "A seguir",
1278
+ rows: upcoming,
1279
+ positions,
1280
+ now,
1281
+ onSeat,
1282
+ onMarkArrived,
1283
+ onOpenPosition
1284
+ }
1285
+ ),
1286
+ past.length > 0 && /* @__PURE__ */ jsx(
1287
+ Group,
1288
+ {
1289
+ title: "J\xE1 passou",
1290
+ rows: past,
1291
+ positions,
1292
+ now,
1293
+ onSeat,
1294
+ onMarkArrived,
1295
+ onOpenPosition,
1296
+ muted: true
1297
+ }
1298
+ )
1299
+ ] });
1300
+ }
1301
+ function Group({ title, rows, positions, now, onSeat, onMarkArrived, onOpenPosition, muted }) {
1302
+ return /* @__PURE__ */ jsxs("section", { className: "pos-res__group", "data-muted": muted, children: [
1303
+ /* @__PURE__ */ jsx("h3", { className: "pos-t-xs", children: title }),
1304
+ rows.map((r) => {
1305
+ const late = Math.floor((now - new Date(r.at).getTime()) / 6e4);
1306
+ const isLate = late > 10 && (r.status === "booked" || r.status === "confirmed");
1307
+ return /* @__PURE__ */ jsxs("article", { className: "pos-res__row", "data-late": isLate, children: [
1308
+ /* @__PURE__ */ jsx("span", { className: "pos-res__time pos-num", children: clock3(r.at) }),
1309
+ /* @__PURE__ */ jsxs("span", { className: "pos-res__who", children: [
1310
+ /* @__PURE__ */ jsx("span", { className: "pos-res__name", children: r.name }),
1311
+ /* @__PURE__ */ jsxs("span", { className: "pos-res__meta", children: [
1312
+ /* @__PURE__ */ jsxs("span", { className: "pos-res__chip", children: [
1313
+ /* @__PURE__ */ jsx(Users, { size: 12, strokeWidth: 2.2, "aria-hidden": true }),
1314
+ r.party
1315
+ ] }),
1316
+ r.phone && /* @__PURE__ */ jsxs("span", { className: "pos-res__chip", children: [
1317
+ /* @__PURE__ */ jsx(Phone, { size: 12, strokeWidth: 2.2, "aria-hidden": true }),
1318
+ r.phone
1319
+ ] }),
1320
+ r.channel && /* @__PURE__ */ jsx("span", { className: "pos-res__chip", children: r.channel })
1321
+ ] }),
1322
+ r.note && /* @__PURE__ */ jsxs("span", { className: "pos-res__note", children: [
1323
+ "\u201C",
1324
+ r.note,
1325
+ "\u201D"
1326
+ ] })
1327
+ ] }),
1328
+ /* @__PURE__ */ jsxs("span", { className: "pos-res__end", children: [
1329
+ /* @__PURE__ */ jsx("span", { className: "pos-badge", "data-status": STATUS2[r.status].tone, children: STATUS2[r.status].label }),
1330
+ isLate && /* @__PURE__ */ jsxs("span", { className: "pos-res__late pos-num", children: [
1331
+ late,
1332
+ "min de atraso"
1333
+ ] })
1334
+ ] }),
1335
+ /* @__PURE__ */ jsx("span", { className: "pos-res__do", children: r.status === "seated" ? (
1336
+ /* Naming the table, and going there. "Na mesa" without saying
1337
+ which one makes the host walk the floor asking. */
1338
+ /* @__PURE__ */ jsxs(
1339
+ "button",
1340
+ {
1341
+ className: "pos-res__b pos-res__at",
1342
+ onClick: () => r.positionId && onOpenPosition(r.positionId),
1343
+ disabled: !r.positionId,
1344
+ children: [
1345
+ /* @__PURE__ */ jsx(Check, { size: 15, strokeWidth: 2.6, "aria-hidden": true }),
1346
+ positions.find((p) => p.id === r.positionId)?.label ?? "Na mesa"
1347
+ ]
1348
+ }
1349
+ )
1350
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1351
+ r.status !== "arrived" && /* @__PURE__ */ jsxs(
1352
+ "button",
1353
+ {
1354
+ className: "pos-res__b",
1355
+ onClick: () => {
1356
+ haptic("tap");
1357
+ onMarkArrived(r.id);
1358
+ },
1359
+ children: [
1360
+ /* @__PURE__ */ jsx(UserRound, { size: 14, strokeWidth: 2.2, "aria-hidden": true }),
1361
+ "Chegou"
1362
+ ]
1363
+ }
1364
+ ),
1365
+ /* @__PURE__ */ jsx(
1366
+ "button",
1367
+ {
1368
+ className: "pos-res__b pos-res__b--primary",
1369
+ onClick: () => {
1370
+ haptic("tap");
1371
+ onSeat(r);
1372
+ },
1373
+ children: "Sentar"
1374
+ }
1375
+ )
1376
+ ] }) })
1377
+ ] }, r.id);
1378
+ })
1379
+ ] });
1380
+ }
1381
+ function clock3(iso) {
1382
+ return new Date(iso).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
1383
+ }
1384
+ function ModifierSheet({ item, onCancel, onConfirm, initial, mode = "add" }) {
1385
+ const groups = item.modifiers ?? [];
1386
+ const [picked, setPicked] = useState(() => {
1387
+ const seed = {};
1388
+ for (const g of groups) seed[g.id] = [];
1389
+ for (const m of initial?.modifiers ?? []) {
1390
+ seed[m.groupId] = [...seed[m.groupId] ?? [], m.optionId];
1391
+ }
1392
+ return seed;
1393
+ });
1394
+ const [note, setNote] = useState(initial?.note ?? "");
1395
+ const noteRef = useRef(null);
1396
+ const chosen = useMemo(
1397
+ () => groups.flatMap(
1398
+ (g) => (picked[g.id] ?? []).flatMap((optionId) => {
1399
+ const option = g.options.find((o) => o.id === optionId);
1400
+ return option ? [{ groupId: g.id, optionId, label: option.label, priceDeltaCents: option.priceDeltaCents ?? 0 }] : [];
1401
+ })
1402
+ ),
1403
+ [groups, picked]
1404
+ );
1405
+ const missing = groups.filter((g) => (g.min ?? 0) > (picked[g.id]?.length ?? 0));
1406
+ const total = item.priceCents + chosen.reduce((s, m) => s + m.priceDeltaCents, 0);
1407
+ function toggle(groupId, optionId, max) {
1408
+ setPicked((prev) => {
1409
+ const current = prev[groupId] ?? [];
1410
+ if (current.includes(optionId)) return { ...prev, [groupId]: current.filter((id) => id !== optionId) };
1411
+ const next = max === 1 ? [optionId] : [...current, optionId].slice(-max);
1412
+ return { ...prev, [groupId]: next };
1413
+ });
1414
+ }
1415
+ useEffect(() => {
1416
+ function onKey(event) {
1417
+ if (event.key === "Escape") {
1418
+ event.preventDefault();
1419
+ onCancel();
1420
+ return;
1421
+ }
1422
+ if (event.key === "Enter" && document.activeElement !== noteRef.current) {
1423
+ if (missing.length === 0) {
1424
+ event.preventDefault();
1425
+ onConfirm(chosen, note.trim());
1426
+ }
1427
+ }
1428
+ }
1429
+ window.addEventListener("keydown", onKey, true);
1430
+ return () => window.removeEventListener("keydown", onKey, true);
1431
+ }, [chosen, note, missing.length, onCancel, onConfirm]);
1432
+ return /* @__PURE__ */ jsxs("div", { className: "pos-sheet", role: "dialog", "aria-modal": "true", "aria-label": `Op\xE7\xF5es de ${item.name}`, children: [
1433
+ /* @__PURE__ */ jsx("div", { className: "pos-sheet__scrim", onClick: onCancel }),
1434
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__panel", children: [
1435
+ /* @__PURE__ */ jsxs("header", { className: "pos-sheet__head", children: [
1436
+ /* @__PURE__ */ jsxs("span", { children: [
1437
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xl", children: item.name }),
1438
+ /* @__PURE__ */ jsx("span", { className: "pos-t-sm pos-num", style: { display: "block" }, children: mode === "edit" ? "Editando o item na comanda" : formatCents(item.priceCents) })
1439
+ ] }),
1440
+ /* @__PURE__ */ jsx("button", { className: "pos-icon-btn", onClick: onCancel, "aria-label": "Cancelar", children: /* @__PURE__ */ jsx(X, { size: 18, strokeWidth: 2.2 }) })
1441
+ ] }),
1442
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__body pos-scroll", children: [
1443
+ groups.length === 0 && mode === "edit" && /* @__PURE__ */ jsx("p", { className: "pos-t-sm", style: { margin: 0 }, children: "Este item n\xE3o tem op\xE7\xF5es \u2014 s\xF3 observa\xE7\xE3o." }),
1444
+ groups.map((g) => {
1445
+ const max = g.max ?? 1;
1446
+ const need = (g.min ?? 0) > (picked[g.id]?.length ?? 0);
1447
+ return /* @__PURE__ */ jsxs("section", { className: "pos-mgroup", children: [
1448
+ /* @__PURE__ */ jsxs("div", { className: "pos-mgroup__head", children: [
1449
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xs", children: g.label }),
1450
+ need && /* @__PURE__ */ jsx("span", { className: "pos-mgroup__req", children: "obrigat\xF3rio" })
1451
+ ] }),
1452
+ /* @__PURE__ */ jsx("div", { className: "pos-mgroup__opts", children: g.options.map((o) => {
1453
+ const on = (picked[g.id] ?? []).includes(o.id);
1454
+ return /* @__PURE__ */ jsxs(
1455
+ "button",
1456
+ {
1457
+ className: "pos-mopt",
1458
+ "aria-pressed": on,
1459
+ onClick: () => toggle(g.id, o.id, max),
1460
+ children: [
1461
+ /* @__PURE__ */ jsx("span", { className: "pos-mopt__check", children: on && /* @__PURE__ */ jsx(Check, { size: 14, strokeWidth: 3 }) }),
1462
+ /* @__PURE__ */ jsx("span", { className: "pos-mopt__label", children: o.label }),
1463
+ o.priceDeltaCents ? /* @__PURE__ */ jsxs("span", { className: "pos-mopt__delta pos-num", children: [
1464
+ o.priceDeltaCents > 0 ? "+" : "\u2212",
1465
+ " ",
1466
+ formatCents(Math.abs(o.priceDeltaCents))
1467
+ ] }) : null
1468
+ ]
1469
+ },
1470
+ o.id
1471
+ );
1472
+ }) })
1473
+ ] }, g.id);
1474
+ }),
1475
+ /* @__PURE__ */ jsxs("section", { className: "pos-mgroup", children: [
1476
+ /* @__PURE__ */ jsx("div", { className: "pos-mgroup__head", children: /* @__PURE__ */ jsx("span", { className: "pos-t-xs", children: "Observa\xE7\xE3o" }) }),
1477
+ /* @__PURE__ */ jsx(
1478
+ "input",
1479
+ {
1480
+ ref: noteRef,
1481
+ className: "pos-note",
1482
+ value: note,
1483
+ onChange: (e) => setNote(e.target.value),
1484
+ placeholder: "Ex.: sem cebola, ponto para a mesa toda",
1485
+ "aria-label": "Observa\xE7\xE3o para a cozinha"
1486
+ }
1487
+ )
1488
+ ] })
1489
+ ] }),
1490
+ /* @__PURE__ */ jsxs("footer", { className: "pos-sheet__foot", children: [
1491
+ /* @__PURE__ */ jsx("button", { className: "pos-btn pos-btn--ghost", onClick: onCancel, children: "Cancelar" }),
1492
+ /* @__PURE__ */ jsx(
1493
+ "button",
1494
+ {
1495
+ className: "pos-btn",
1496
+ disabled: missing.length > 0,
1497
+ onClick: () => onConfirm(chosen, note.trim()),
1498
+ children: missing.length > 0 ? `Falta: ${missing[0].label}` : mode === "edit" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1499
+ "Salvar ",
1500
+ /* @__PURE__ */ jsx("span", { className: "pos-num", children: formatCents(total) })
1501
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1502
+ "Adicionar ",
1503
+ /* @__PURE__ */ jsx("span", { className: "pos-num", children: formatCents(total) })
1504
+ ] })
1505
+ }
1506
+ )
1507
+ ] })
1508
+ ] })
1509
+ ] });
1510
+ }
1511
+ function OpenTableSheet({
1512
+ position,
1513
+ vocabulary,
1514
+ staff,
1515
+ defaultSeats,
1516
+ seatingName,
1517
+ onCancel,
1518
+ onConfirm
1519
+ }) {
1520
+ const [guests, setGuests] = useState(defaultSeats ?? position.seats ?? 2);
1521
+ const [attendantId, setAttendantId] = useState(staff[0]?.id);
1522
+ useEffect(() => {
1523
+ function onKey(event) {
1524
+ if (event.key === "Escape") {
1525
+ event.preventDefault();
1526
+ onCancel();
1527
+ return;
1528
+ }
1529
+ if (event.key === "Enter") {
1530
+ event.preventDefault();
1531
+ onConfirm(guests, attendantId);
1532
+ return;
1533
+ }
1534
+ if (/^[1-9]$/.test(event.key)) {
1535
+ event.preventDefault();
1536
+ setGuests(Number(event.key));
1537
+ }
1538
+ }
1539
+ window.addEventListener("keydown", onKey, true);
1540
+ return () => window.removeEventListener("keydown", onKey, true);
1541
+ }, [guests, attendantId, onCancel, onConfirm]);
1542
+ return /* @__PURE__ */ jsxs("div", { className: "pos-sheet", role: "dialog", "aria-modal": "true", "aria-label": `Abrir ${position.label}`, children: [
1543
+ /* @__PURE__ */ jsx("div", { className: "pos-sheet__scrim", onClick: onCancel }),
1544
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__panel", style: { width: "min(460px, 92vw)" }, children: [
1545
+ /* @__PURE__ */ jsxs("header", { className: "pos-sheet__head", children: [
1546
+ /* @__PURE__ */ jsxs("span", { children: [
1547
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xl", children: seatingName ? `Sentar na ${position.label}` : `Abrir ${position.label}` }),
1548
+ /* @__PURE__ */ jsx("span", { className: "pos-t-sm", style: { display: "block" }, children: seatingName ? `${seatingName} \xB7 ${position.seats ?? "\u2014"} lugares` : position.seats ? `${position.seats} lugares` : vocabulary.position })
1549
+ ] }),
1550
+ /* @__PURE__ */ jsx("button", { className: "pos-icon-btn", onClick: onCancel, "aria-label": "Cancelar", children: /* @__PURE__ */ jsx(X, { size: 18, strokeWidth: 2.2 }) })
1551
+ ] }),
1552
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__body pos-scroll", children: [
1553
+ /* @__PURE__ */ jsxs("section", { className: "pos-mgroup", children: [
1554
+ /* @__PURE__ */ jsx("div", { className: "pos-mgroup__head", children: /* @__PURE__ */ jsx("span", { className: "pos-t-xs", children: "Pessoas" }) }),
1555
+ /* @__PURE__ */ jsxs("div", { className: "pos-guests", children: [
1556
+ /* @__PURE__ */ jsx(
1557
+ "button",
1558
+ {
1559
+ className: "pos-step__b pos-guests__pm",
1560
+ onClick: () => setGuests((g) => Math.max(1, g - 1)),
1561
+ disabled: guests <= 1,
1562
+ "aria-label": "Menos uma pessoa",
1563
+ children: /* @__PURE__ */ jsx(Minus, { size: 18, strokeWidth: 2.6 })
1564
+ }
1565
+ ),
1566
+ /* @__PURE__ */ jsx("span", { className: "pos-guests__n pos-num", "aria-live": "polite", children: guests }),
1567
+ /* @__PURE__ */ jsx(
1568
+ "button",
1569
+ {
1570
+ className: "pos-step__b pos-guests__pm",
1571
+ onClick: () => setGuests((g) => g + 1),
1572
+ "aria-label": "Mais uma pessoa",
1573
+ children: /* @__PURE__ */ jsx(Plus, { size: 18, strokeWidth: 2.6 })
1574
+ }
1575
+ )
1576
+ ] }),
1577
+ /* @__PURE__ */ jsx("div", { className: "pos-quick", children: [1, 2, 3, 4, 5, 6, 8, 10].map((n) => /* @__PURE__ */ jsx(
1578
+ "button",
1579
+ {
1580
+ className: "pos-quick__b pos-num",
1581
+ "aria-pressed": guests === n,
1582
+ onClick: () => setGuests(n),
1583
+ children: n
1584
+ },
1585
+ n
1586
+ )) })
1587
+ ] }),
1588
+ seatingName && position.seats != null && guests > position.seats && /* Said, not blocked: pushing two tables together is a normal thing
1589
+ a host does, and a POS that refuses it is a POS they work around. */
1590
+ /* @__PURE__ */ jsxs("p", { className: "pos-warnline", children: [
1591
+ position.label,
1592
+ " tem ",
1593
+ position.seats,
1594
+ " lugares para ",
1595
+ guests,
1596
+ " pessoas."
1597
+ ] }),
1598
+ staff.length > 0 && /* @__PURE__ */ jsxs("section", { className: "pos-mgroup", children: [
1599
+ /* @__PURE__ */ jsx("div", { className: "pos-mgroup__head", children: /* @__PURE__ */ jsx("span", { className: "pos-t-xs", children: "Atendente" }) }),
1600
+ /* @__PURE__ */ jsx("div", { className: "pos-mgroup__opts", children: staff.map((s) => /* @__PURE__ */ jsxs(
1601
+ "button",
1602
+ {
1603
+ className: "pos-mopt",
1604
+ "aria-pressed": s.id === attendantId,
1605
+ onClick: () => setAttendantId(s.id),
1606
+ children: [
1607
+ /* @__PURE__ */ jsx("span", { className: "pos-person__av", style: { background: `var(--pos-tone-${s.tone})` }, children: s.name.slice(0, 1) }),
1608
+ /* @__PURE__ */ jsx("span", { className: "pos-mopt__label", children: s.name })
1609
+ ]
1610
+ },
1611
+ s.id
1612
+ )) })
1613
+ ] })
1614
+ ] }),
1615
+ /* @__PURE__ */ jsxs("footer", { className: "pos-sheet__foot", children: [
1616
+ /* @__PURE__ */ jsx("button", { className: "pos-btn pos-btn--ghost", onClick: onCancel, children: "Cancelar" }),
1617
+ /* @__PURE__ */ jsxs("button", { className: "pos-btn", onClick: () => onConfirm(guests, attendantId), children: [
1618
+ /* @__PURE__ */ jsx(Users, { size: 18, strokeWidth: 2.2, "aria-hidden": true }),
1619
+ seatingName ? `Sentar ${guests}` : `Abrir com ${guests}`
1620
+ ] })
1621
+ ] })
1622
+ ] })
1623
+ ] });
1624
+ }
1625
+ function IdentitySheet({ position, onCancel, onSave }) {
1626
+ const [alias, setAlias] = useState(position.alias ?? "");
1627
+ const [phone, setPhone] = useState(position.customerPhone ?? "");
1628
+ const first = useRef(null);
1629
+ useEffect(() => {
1630
+ first.current?.focus();
1631
+ }, []);
1632
+ useEffect(() => {
1633
+ function onKey(event) {
1634
+ if (event.key === "Escape") {
1635
+ event.preventDefault();
1636
+ onCancel();
1637
+ }
1638
+ if (event.key === "Enter") {
1639
+ event.preventDefault();
1640
+ onSave(alias.trim(), phone.trim());
1641
+ }
1642
+ }
1643
+ window.addEventListener("keydown", onKey, true);
1644
+ return () => window.removeEventListener("keydown", onKey, true);
1645
+ }, [alias, phone, onCancel, onSave]);
1646
+ return /* @__PURE__ */ jsxs("div", { className: "pos-sheet", role: "dialog", "aria-modal": "true", "aria-label": `Identificar ${position.label}`, children: [
1647
+ /* @__PURE__ */ jsx("div", { className: "pos-sheet__scrim", onClick: onCancel }),
1648
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__panel", style: { width: "min(440px, 92vw)" }, children: [
1649
+ /* @__PURE__ */ jsxs("header", { className: "pos-sheet__head", children: [
1650
+ /* @__PURE__ */ jsxs("span", { children: [
1651
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xl", children: "Identificar" }),
1652
+ /* @__PURE__ */ jsx("span", { className: "pos-t-sm", style: { display: "block" }, children: position.label })
1653
+ ] }),
1654
+ /* @__PURE__ */ jsx("button", { className: "pos-icon-btn", onClick: onCancel, "aria-label": "Cancelar", children: /* @__PURE__ */ jsx(X, { size: 18, strokeWidth: 2.2 }) })
1655
+ ] }),
1656
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__body", children: [
1657
+ /* @__PURE__ */ jsxs("label", { className: "pos-labelled", children: [
1658
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xs", children: "Nome na comanda" }),
1659
+ /* @__PURE__ */ jsxs("span", { className: "pos-input", children: [
1660
+ /* @__PURE__ */ jsx(User, { size: 16, strokeWidth: 2.1, "aria-hidden": true }),
1661
+ /* @__PURE__ */ jsx(
1662
+ "input",
1663
+ {
1664
+ ref: first,
1665
+ value: alias,
1666
+ onChange: (e) => setAlias(e.target.value),
1667
+ placeholder: `Ex.: Jo\xE3o do balc\xE3o`,
1668
+ autoComplete: "off"
1669
+ }
1670
+ )
1671
+ ] })
1672
+ ] }),
1673
+ /* @__PURE__ */ jsxs("label", { className: "pos-labelled", children: [
1674
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xs", children: "Celular do cliente" }),
1675
+ /* @__PURE__ */ jsxs("span", { className: "pos-input", children: [
1676
+ /* @__PURE__ */ jsx(Phone, { size: 16, strokeWidth: 2.1, "aria-hidden": true }),
1677
+ /* @__PURE__ */ jsx(
1678
+ "input",
1679
+ {
1680
+ value: phone,
1681
+ onChange: (e) => setPhone(e.target.value),
1682
+ placeholder: "(11) 9 9999-9999",
1683
+ type: "tel",
1684
+ inputMode: "tel",
1685
+ autoComplete: "tel"
1686
+ }
1687
+ )
1688
+ ] })
1689
+ ] }),
1690
+ /* @__PURE__ */ jsx("p", { className: "pos-t-sm", style: { margin: 0 }, children: "Serve para achar a conta depois \u2014 o cliente costuma lembrar do n\xFAmero, n\xE3o do n\xFAmero da mesa." })
1691
+ ] }),
1692
+ /* @__PURE__ */ jsxs("footer", { className: "pos-sheet__foot", children: [
1693
+ /* @__PURE__ */ jsx("button", { className: "pos-btn pos-btn--ghost", onClick: onCancel, children: "Cancelar" }),
1694
+ /* @__PURE__ */ jsx("button", { className: "pos-btn", onClick: () => onSave(alias.trim(), phone.trim()), children: "Salvar" })
1695
+ ] })
1696
+ ] })
1697
+ ] });
1698
+ }
1699
+ var PIN_LENGTH = 4;
1700
+ function PinSheet({ actionLabel, onCancel, onVerify, onGranted }) {
1701
+ const [pin, setPin] = useState("");
1702
+ const [error, setError] = useState();
1703
+ const [checking, setChecking] = useState(false);
1704
+ async function submit(value) {
1705
+ setChecking(true);
1706
+ try {
1707
+ const person = await onVerify(value);
1708
+ if (person) {
1709
+ onGranted(person);
1710
+ return;
1711
+ }
1712
+ setError("PIN n\xE3o confere");
1713
+ setPin("");
1714
+ } finally {
1715
+ setChecking(false);
1716
+ }
1717
+ }
1718
+ function push(digit) {
1719
+ setError(void 0);
1720
+ const next = (pin + digit).slice(0, PIN_LENGTH);
1721
+ setPin(next);
1722
+ if (next.length === PIN_LENGTH) void submit(next);
1723
+ }
1724
+ useEffect(() => {
1725
+ function onKey(event) {
1726
+ if (event.key === "Escape") {
1727
+ event.preventDefault();
1728
+ onCancel();
1729
+ return;
1730
+ }
1731
+ if (event.key === "Backspace") {
1732
+ event.preventDefault();
1733
+ setPin((p) => p.slice(0, -1));
1734
+ return;
1735
+ }
1736
+ if (/^[0-9]$/.test(event.key)) {
1737
+ event.preventDefault();
1738
+ push(event.key);
1739
+ }
1740
+ }
1741
+ window.addEventListener("keydown", onKey, true);
1742
+ return () => window.removeEventListener("keydown", onKey, true);
1743
+ });
1744
+ return /* @__PURE__ */ jsxs("div", { className: "pos-sheet", role: "dialog", "aria-modal": "true", "aria-label": `PIN para ${actionLabel}`, children: [
1745
+ /* @__PURE__ */ jsx("div", { className: "pos-sheet__scrim", onClick: onCancel }),
1746
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__panel", style: { width: "min(400px, 92vw)" }, children: [
1747
+ /* @__PURE__ */ jsxs("header", { className: "pos-sheet__head", children: [
1748
+ /* @__PURE__ */ jsxs("span", { children: [
1749
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xl", children: actionLabel }),
1750
+ /* @__PURE__ */ jsx("span", { className: "pos-t-sm", style: { display: "block" }, children: "Precisa de autoriza\xE7\xE3o" })
1751
+ ] }),
1752
+ /* @__PURE__ */ jsx("button", { className: "pos-icon-btn", onClick: onCancel, "aria-label": "Cancelar", children: /* @__PURE__ */ jsx(X, { size: 18, strokeWidth: 2.2 }) })
1753
+ ] }),
1754
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__body", children: [
1755
+ /* @__PURE__ */ jsx("div", { className: "pos-pin__dots", "aria-live": "polite", "aria-label": `${pin.length} de ${PIN_LENGTH} d\xEDgitos`, children: Array.from({ length: PIN_LENGTH }, (_, i) => /* @__PURE__ */ jsx("span", { className: "pos-pin__dot", "data-on": i < pin.length }, i)) }),
1756
+ /* @__PURE__ */ jsx("p", { className: "pos-pin__err", role: "alert", children: error ?? "\xA0" }),
1757
+ /* @__PURE__ */ jsxs("div", { className: "pos-pad", children: [
1758
+ ["1", "2", "3", "4", "5", "6", "7", "8", "9"].map((d) => /* @__PURE__ */ jsx("button", { className: "pos-pad__b pos-num", onClick: () => push(d), disabled: checking, children: d }, d)),
1759
+ /* @__PURE__ */ jsx("button", { className: "pos-pad__b", onClick: onCancel, "aria-label": "Cancelar", children: /* @__PURE__ */ jsx(X, { size: 20, strokeWidth: 2.4 }) }),
1760
+ /* @__PURE__ */ jsx("button", { className: "pos-pad__b pos-num", onClick: () => push("0"), disabled: checking, children: "0" }),
1761
+ /* @__PURE__ */ jsx(
1762
+ "button",
1763
+ {
1764
+ className: "pos-pad__b",
1765
+ onClick: () => setPin((p) => p.slice(0, -1)),
1766
+ "aria-label": "Apagar \xFAltimo d\xEDgito",
1767
+ children: /* @__PURE__ */ jsx(Delete, { size: 20, strokeWidth: 2.2 })
1768
+ }
1769
+ )
1770
+ ] })
1771
+ ] }),
1772
+ /* @__PURE__ */ jsx("footer", { className: "pos-sheet__foot", style: { gridTemplateColumns: "1fr" }, children: /* @__PURE__ */ jsxs("span", { className: "pos-pin__hint", children: [
1773
+ /* @__PURE__ */ jsx(ShieldCheck, { size: 15, strokeWidth: 2.2, "aria-hidden": true }),
1774
+ "Fica registrado quem autorizou"
1775
+ ] }) })
1776
+ ] })
1777
+ ] });
1778
+ }
1779
+ function SplitSheet({ ticket, totals, guests, onCancel, onConfirm }) {
1780
+ const [mode, setMode] = useState("even");
1781
+ const [ways, setWays] = useState(Math.max(2, guests ?? 2));
1782
+ const [assigned, setAssigned] = useState({});
1783
+ const evenParts = useMemo(() => splitEvenly(totals.totalCents, ways), [totals.totalCents, ways]);
1784
+ const itemParts = useMemo(() => {
1785
+ const parts2 = Array.from({ length: ways }, () => 0);
1786
+ let unassigned = 0;
1787
+ for (const line of ticket.lines) {
1788
+ const value = line.unitPriceCents * line.quantity;
1789
+ const share = assigned[line.id];
1790
+ if (share == null) {
1791
+ unassigned += value;
1792
+ continue;
1793
+ }
1794
+ parts2[share] = (parts2[share] ?? 0) + value;
1795
+ }
1796
+ const shared = totals.totalCents - totals.subtotalCents + unassigned;
1797
+ const spread = splitEvenly(Math.max(0, shared), ways);
1798
+ return parts2.map((p, i) => p + (spread[i] ?? 0));
1799
+ }, [ticket.lines, assigned, ways, totals]);
1800
+ const parts = mode === "even" ? evenParts : itemParts;
1801
+ const sum = parts.reduce((a, b) => a + b, 0);
1802
+ const unassignedCount = ticket.lines.filter((l) => assigned[l.id] == null).length;
1803
+ return /* @__PURE__ */ jsxs("div", { className: "pos-sheet", role: "dialog", "aria-modal": "true", "aria-label": "Dividir conta", children: [
1804
+ /* @__PURE__ */ jsx("div", { className: "pos-sheet__scrim", onClick: onCancel }),
1805
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__panel", style: { width: "min(680px, 94vw)" }, children: [
1806
+ /* @__PURE__ */ jsxs("header", { className: "pos-sheet__head", children: [
1807
+ /* @__PURE__ */ jsxs("span", { children: [
1808
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xl", children: "Dividir conta" }),
1809
+ /* @__PURE__ */ jsxs("span", { className: "pos-t-sm pos-num", style: { display: "block" }, children: [
1810
+ formatCents(totals.totalCents),
1811
+ " no total"
1812
+ ] })
1813
+ ] }),
1814
+ /* @__PURE__ */ jsx("button", { className: "pos-icon-btn", onClick: onCancel, "aria-label": "Cancelar", children: /* @__PURE__ */ jsx(X, { size: 18, strokeWidth: 2.2 }) })
1815
+ ] }),
1816
+ /* @__PURE__ */ jsxs("div", { className: "pos-sheet__body pos-scroll", children: [
1817
+ /* @__PURE__ */ jsxs("div", { className: "pos-seg", style: { alignSelf: "start" }, children: [
1818
+ /* @__PURE__ */ jsxs("button", { className: "pos-seg__btn", "aria-pressed": mode === "even", onClick: () => setMode("even"), children: [
1819
+ /* @__PURE__ */ jsx(Users, { size: 15, strokeWidth: 2.2, "aria-hidden": true }),
1820
+ " Igual"
1821
+ ] }),
1822
+ /* @__PURE__ */ jsxs("button", { className: "pos-seg__btn", "aria-pressed": mode === "by_item", onClick: () => setMode("by_item"), children: [
1823
+ /* @__PURE__ */ jsx(UtensilsCrossed, { size: 15, strokeWidth: 2.2, "aria-hidden": true }),
1824
+ " Por item"
1825
+ ] })
1826
+ ] }),
1827
+ /* @__PURE__ */ jsxs("section", { className: "pos-mgroup", children: [
1828
+ /* @__PURE__ */ jsx("div", { className: "pos-mgroup__head", children: /* @__PURE__ */ jsx("span", { className: "pos-t-xs", children: "Em quantas partes" }) }),
1829
+ /* @__PURE__ */ jsxs("div", { className: "pos-guests", children: [
1830
+ /* @__PURE__ */ jsx(
1831
+ "button",
1832
+ {
1833
+ className: "pos-step__b pos-guests__pm",
1834
+ onClick: () => setWays((w) => Math.max(2, w - 1)),
1835
+ disabled: ways <= 2,
1836
+ "aria-label": "Menos uma parte",
1837
+ children: /* @__PURE__ */ jsx(Minus, { size: 18, strokeWidth: 2.6 })
1838
+ }
1839
+ ),
1840
+ /* @__PURE__ */ jsx("span", { className: "pos-guests__n pos-num", children: ways }),
1841
+ /* @__PURE__ */ jsx(
1842
+ "button",
1843
+ {
1844
+ className: "pos-step__b pos-guests__pm",
1845
+ onClick: () => setWays((w) => w + 1),
1846
+ "aria-label": "Mais uma parte",
1847
+ children: /* @__PURE__ */ jsx(Plus, { size: 18, strokeWidth: 2.6 })
1848
+ }
1849
+ )
1850
+ ] })
1851
+ ] }),
1852
+ mode === "by_item" && /* @__PURE__ */ jsxs("section", { className: "pos-mgroup", children: [
1853
+ /* @__PURE__ */ jsxs("div", { className: "pos-mgroup__head", children: [
1854
+ /* @__PURE__ */ jsx("span", { className: "pos-t-xs", children: "Quem pediu o qu\xEA" }),
1855
+ unassignedCount > 0 && /* @__PURE__ */ jsxs("span", { className: "pos-mgroup__req", children: [
1856
+ unassignedCount,
1857
+ " sem dono \u2014 rateado"
1858
+ ] })
1859
+ ] }),
1860
+ ticket.lines.map((line) => /* @__PURE__ */ jsxs("div", { className: "pos-split__line", children: [
1861
+ /* @__PURE__ */ jsxs("span", { className: "pos-split__name", children: [
1862
+ line.quantity,
1863
+ "\xD7 ",
1864
+ line.name
1865
+ ] }),
1866
+ /* @__PURE__ */ jsx("span", { className: "pos-split__pick", children: Array.from({ length: ways }, (_, i) => /* @__PURE__ */ jsx(
1867
+ "button",
1868
+ {
1869
+ className: "pos-split__b pos-num",
1870
+ "aria-pressed": assigned[line.id] === i,
1871
+ onClick: () => setAssigned(
1872
+ (prev) => prev[line.id] === i ? Object.fromEntries(Object.entries(prev).filter(([k]) => k !== line.id)) : { ...prev, [line.id]: i }
1873
+ ),
1874
+ children: i + 1
1875
+ },
1876
+ i
1877
+ )) }),
1878
+ /* @__PURE__ */ jsx("span", { className: "pos-split__v pos-num", children: formatCents(line.unitPriceCents * line.quantity) })
1879
+ ] }, line.id))
1880
+ ] }),
1881
+ /* @__PURE__ */ jsxs("section", { className: "pos-mgroup", children: [
1882
+ /* @__PURE__ */ jsx("div", { className: "pos-mgroup__head", children: /* @__PURE__ */ jsx("span", { className: "pos-t-xs", children: "Cada parte paga" }) }),
1883
+ /* @__PURE__ */ jsx("div", { className: "pos-parts", children: parts.map((value, i) => /* @__PURE__ */ jsxs("div", { className: "pos-part", children: [
1884
+ /* @__PURE__ */ jsxs("span", { className: "pos-part__k pos-num", children: [
1885
+ "Parte ",
1886
+ i + 1
1887
+ ] }),
1888
+ /* @__PURE__ */ jsx("span", { className: "pos-part__v pos-num", children: formatCents(value) })
1889
+ ] }, i)) }),
1890
+ /* @__PURE__ */ jsxs("p", { className: "pos-split__sum pos-num", children: [
1891
+ "Soma das partes: ",
1892
+ formatCents(sum),
1893
+ sum === totals.totalCents ? " \u2014 confere" : " \u2014 N\xC3O confere"
1894
+ ] })
1895
+ ] })
1896
+ ] }),
1897
+ /* @__PURE__ */ jsxs("footer", { className: "pos-sheet__foot", children: [
1898
+ /* @__PURE__ */ jsx("button", { className: "pos-btn pos-btn--ghost", onClick: onCancel, children: "Cancelar" }),
1899
+ /* @__PURE__ */ jsxs("button", { className: "pos-btn", onClick: () => onConfirm(parts), children: [
1900
+ "Dividir em ",
1901
+ ways
1902
+ ] })
1903
+ ] })
1904
+ ] })
1905
+ ] });
1906
+ }
1907
+ var ICON = { info: Info, ok: Check, warn: AlertTriangle };
1908
+ function Notice({ notice, onDone }) {
1909
+ const [leaving, setLeaving] = useState(false);
1910
+ useEffect(() => {
1911
+ if (!notice) return;
1912
+ setLeaving(false);
1913
+ const out = window.setTimeout(() => setLeaving(true), 1900);
1914
+ const gone = window.setTimeout(onDone, 2200);
1915
+ return () => {
1916
+ window.clearTimeout(out);
1917
+ window.clearTimeout(gone);
1918
+ };
1919
+ }, [notice, onDone]);
1920
+ if (!notice) return null;
1921
+ const tone = notice.tone ?? "info";
1922
+ const Icon = ICON[tone];
1923
+ return /* @__PURE__ */ jsxs("div", { className: "pos-notice", "data-tone": tone, "data-leaving": leaving, role: "status", "aria-live": "polite", children: [
1924
+ /* @__PURE__ */ jsx("span", { className: "pos-notice__i", "aria-hidden": true, children: /* @__PURE__ */ jsx(Icon, { size: 17, strokeWidth: 2.6 }) }),
1925
+ notice.text
1926
+ ] });
1927
+ }
1928
+ var MENU_ICON = {
1929
+ // `list` é o quadro de despacho — a moto, a mesma que o back office usa para
1930
+ // Delivery. Um caminhão fala de carga, e o que sai daqui vai numa garupa.
1931
+ floor: LayoutGrid,
1932
+ catalog: UtensilsCrossed,
1933
+ list: Bike,
1934
+ shift: Banknote,
1935
+ reservations: CalendarClock
1936
+ };
1937
+ var lineSeq = 0;
1938
+ var nextLineId = () => `line-${++lineSeq}`;
1939
+ function PosScreen({
1940
+ config,
1941
+ categories,
1942
+ items,
1943
+ positions,
1944
+ staff = [],
1945
+ reservations = [],
1946
+ terminalName,
1947
+ operatorName,
1948
+ connection = "online",
1949
+ onAction,
1950
+ onOpenPosition,
1951
+ onFire,
1952
+ onCloseBill,
1953
+ onDispatch,
1954
+ verifyPin,
1955
+ onAuthorised,
1956
+ initialTickets,
1957
+ notice: appNotice,
1958
+ brandMark,
1959
+ brandName,
1960
+ className,
1961
+ railTop,
1962
+ now = Date.now()
1963
+ }) {
1964
+ const [book, dispatchBook] = useReducer(bookReducer, void 0, () => {
1965
+ const seeded = {};
1966
+ const openedAt = new Date(now).toISOString();
1967
+ for (const [positionId, lines] of Object.entries(initialTickets ?? {})) {
1968
+ const seatedAt2 = positions.find((p) => p.id === positionId)?.openedAt ?? openedAt;
1969
+ seeded[positionId] = { id: `t-${positionId}`, lines, openedAt, seatedAt: seatedAt2 };
1970
+ }
1971
+ return seeded;
1972
+ });
1973
+ const [menuId, setMenuId] = useState(config.menus[0]?.id);
1974
+ const [activePositionId, setActivePositionId] = useState();
1975
+ const [activeZoneId, setActiveZoneId] = useState(config.positions.zones?.[0]?.id);
1976
+ const [activeCategoryId, setActiveCategoryId] = useState(categories[0]?.id);
1977
+ const [selectedLineId, setSelectedLineId] = useState();
1978
+ const [paymentId, setPaymentId] = useState();
1979
+ const [query, setQuery] = useState("");
1980
+ const [railOpen, setRailOpen] = useState(false);
1981
+ const [pendingItem, setPendingItem] = useState();
1982
+ const [openingPosition, setOpeningPosition] = useState();
1983
+ const [editing, setEditing] = useState();
1984
+ const [naming, setNaming] = useState();
1985
+ const [named, setNamed] = useState({});
1986
+ const [bookings, setBookings] = useState({});
1987
+ const [seatedAt, setSeatedAt] = useState({});
1988
+ const [seating, setSeating] = useState();
1989
+ const [pinFor, setPinFor] = useState();
1990
+ const [splitting, setSplitting] = useState(false);
1991
+ const [showKeys, setShowKeys] = useState(false);
1992
+ const [notice, setNotice] = useState();
1993
+ const [dispatched, setDispatched] = useState({});
1994
+ const [opened, setOpened] = useState({});
1995
+ const [settled, setSettled] = useState({});
1996
+ const [settling, setSettling] = useState(false);
1997
+ const commandRef = useRef(null);
1998
+ useEffect(() => {
1999
+ if (appNotice) setNotice(appNotice);
2000
+ }, [appNotice]);
2001
+ const menu = config.menus.find((m) => m.id === menuId) ?? config.menus[0];
2002
+ const ticketKey = activePositionId ?? COUNTER_KEY;
2003
+ const ticket = ticketAt(book, ticketKey);
2004
+ const dispatch = useCallback(
2005
+ (action) => dispatchBook({ type: "at", positionId: ticketKey, action }),
2006
+ [ticketKey]
2007
+ );
2008
+ const counts = useMemo(() => countsOf(book), [book]);
2009
+ const totals = useMemo(
2010
+ () => totalsOf(ticket, { serviceChargePercent: config.serviceChargePercent }),
2011
+ [ticket, config.serviceChargePercent]
2012
+ );
2013
+ const quantities = useMemo(() => {
2014
+ const map = {};
2015
+ for (const line of ticket.lines) map[line.itemId] = (map[line.itemId] ?? 0) + line.quantity;
2016
+ return map;
2017
+ }, [ticket.lines]);
2018
+ const ring = useCallback((item, modifiers, note) => {
2019
+ dispatch({ type: "add_item", item, lineId: nextLineId(), modifiers, note });
2020
+ }, [dispatch]);
2021
+ const addItem = useCallback((item) => {
2022
+ if (item.modifiers && item.modifiers.length > 0) {
2023
+ setPendingItem(item);
2024
+ return;
2025
+ }
2026
+ ring(item);
2027
+ }, [ring]);
2028
+ const removeItem = useCallback((item) => {
2029
+ dispatch({ type: "remove_one_of", itemId: item.id });
2030
+ }, [dispatch]);
2031
+ const submitCode = useCallback(() => {
2032
+ const code = query.trim().toLowerCase();
2033
+ if (!code) return;
2034
+ const hit = items.find((i) => i.code?.toLowerCase() === code);
2035
+ if (hit) {
2036
+ addItem(hit);
2037
+ setQuery("");
2038
+ }
2039
+ }, [query, items, addItem]);
2040
+ const keymap = useMemo(() => createKeymap(), []);
2041
+ const handleActionRef = useRef((_id) => {
2042
+ });
2043
+ const settleBillRef = useRef(async () => {
2044
+ });
2045
+ const mainRef = useRef(null);
2046
+ const asideRef = useRef(null);
2047
+ const zonePositionsRef = useRef([]);
2048
+ const ticketRef = useRef(ticket);
2049
+ const seatingRef = useRef(void 0);
2050
+ useEffect(() => {
2051
+ const disposers = config.actions.filter((a) => a.shortcut).map((a) => keymap.register({ id: a.id, combo: a.shortcut, handler: () => handleActionRef.current(a.id) }));
2052
+ return () => disposers.forEach((d) => d());
2053
+ }, [config.actions, keymap]);
2054
+ useEffect(() => {
2055
+ function onKeyDown(event) {
2056
+ if (document.querySelector(".pos-sheet")) return;
2057
+ if (event.key === "Enter" && document.activeElement === commandRef.current) {
2058
+ submitCode();
2059
+ return;
2060
+ }
2061
+ if (event.key === "F1") {
2062
+ event.preventDefault();
2063
+ setShowKeys(true);
2064
+ return;
2065
+ }
2066
+ if (event.key === "Escape") {
2067
+ commandRef.current?.focus();
2068
+ return;
2069
+ }
2070
+ const direction = directionOf(event.key);
2071
+ if (direction && document.activeElement !== commandRef.current) {
2072
+ const active = document.activeElement;
2073
+ const region = asideRef.current?.contains(active) ? asideRef.current : mainRef.current;
2074
+ if (region && moveFocus(region, direction)) event.preventDefault();
2075
+ return;
2076
+ }
2077
+ if (keymap.dispatch(event)) event.preventDefault();
2078
+ }
2079
+ window.addEventListener("keydown", onKeyDown);
2080
+ return () => window.removeEventListener("keydown", onKeyDown);
2081
+ }, [keymap, submitCode]);
2082
+ const livePositions = useMemo(() => positions.map((raw) => {
2083
+ if (settled[raw.id]) {
2084
+ const {
2085
+ ticketId: _t,
2086
+ openedAt: _o,
2087
+ attendantName: _a,
2088
+ attendantTone: _n,
2089
+ totalCents: _c,
2090
+ itemCount: _i,
2091
+ request: _r,
2092
+ ...rest
2093
+ } = raw;
2094
+ return { ...rest, status: "free" };
2095
+ }
2096
+ const just = opened[raw.id];
2097
+ let p = just ? {
2098
+ ...raw,
2099
+ status: "occupied",
2100
+ seats: just.seats,
2101
+ attendantName: just.attendantName ?? raw.attendantName,
2102
+ attendantTone: just.attendantTone ?? raw.attendantTone,
2103
+ openedAt: just.openedAt
2104
+ } : raw;
2105
+ const moved = dispatched[raw.id];
2106
+ if (moved) p = { ...p, dispatch: moved };
2107
+ const t = book[p.id];
2108
+ if (!t) return p;
2109
+ const totalCents = totalsOf(t, { serviceChargePercent: config.serviceChargePercent }).totalCents;
2110
+ return { ...p, itemCount: counts[p.id] ?? p.itemCount, totalCents: totalCents || p.totalCents };
2111
+ }), [positions, opened, dispatched, settled, book, counts, config.serviceChargePercent]);
2112
+ const activePosition = livePositions.find((p) => p.id === activePositionId);
2113
+ const zonePositions = activeZoneId ? livePositions.filter((p) => p.zoneId === activeZoneId) : livePositions;
2114
+ const selectPosition = useCallback((p) => {
2115
+ if (p.status === "free") {
2116
+ setOpeningPosition(p);
2117
+ return;
2118
+ }
2119
+ if (seatingRef.current) return;
2120
+ setActivePositionId(p.id);
2121
+ setRailOpen(true);
2122
+ }, []);
2123
+ const confirmOpen = useCallback((guests, attendantId) => {
2124
+ const p = openingPosition;
2125
+ if (!p) return;
2126
+ const person = staff.find((s) => s.id === attendantId);
2127
+ setOpened((prev) => ({
2128
+ ...prev,
2129
+ [p.id]: {
2130
+ seats: guests,
2131
+ attendantName: person?.name,
2132
+ attendantTone: person?.tone,
2133
+ openedAt: new Date(now).toISOString()
2134
+ }
2135
+ }));
2136
+ setActivePositionId(p.id);
2137
+ setRailOpen(true);
2138
+ setOpeningPosition(void 0);
2139
+ if (seating) {
2140
+ setNamed((prev) => ({ ...prev, [p.id]: { alias: seating.name, phone: seating.phone ?? "" } }));
2141
+ setBookings((prev) => ({ ...prev, [seating.id]: "seated" }));
2142
+ setSeatedAt((prev) => ({ ...prev, [seating.id]: p.id }));
2143
+ setSeating(void 0);
2144
+ }
2145
+ setNotice({
2146
+ text: seating ? `${seating.name} na ${config.vocabulary.position} ${p.label}` : `${config.vocabulary.position} ${p.label} aberta`,
2147
+ tone: "ok"
2148
+ });
2149
+ onOpenPosition?.(p.id, guests, attendantId);
2150
+ }, [openingPosition, staff, now, seating, onOpenPosition, config.vocabulary.position]);
2151
+ const runAction = useCallback((actionId) => {
2152
+ if (actionId === "fire") {
2153
+ const at = new Date(now).toISOString();
2154
+ const sent = ticketRef.current.lines.filter((l) => (l.state ?? "pending") === "pending");
2155
+ dispatch({ type: "fire", at });
2156
+ setNotice(sent.length > 0 ? { text: sent.length === 1 ? "1 item enviado \xE0 cozinha" : `${sent.length} itens enviados \xE0 cozinha`, tone: "ok" } : { text: "Nada novo para enviar", tone: "warn" });
2157
+ if (sent.length > 0) {
2158
+ const stamp = (l) => ({ ...l, state: "fired", firedAt: at });
2159
+ const whole = ticketRef.current;
2160
+ void onFire?.(ticketKey, sent.map(stamp), {
2161
+ ...whole,
2162
+ lines: whole.lines.map((l) => (l.state ?? "pending") === "pending" ? stamp(l) : l)
2163
+ });
2164
+ }
2165
+ }
2166
+ if (actionId === "close_bill") {
2167
+ void settleBillRef.current();
2168
+ }
2169
+ if (actionId === "split") setSplitting(true);
2170
+ if (actionId === "open_table") {
2171
+ const free = zonePositionsRef.current.find((p) => p.status === "free");
2172
+ if (free) setOpeningPosition(free);
2173
+ }
2174
+ onAction?.(actionId);
2175
+ }, [dispatch, now, onAction, onFire, ticketKey]);
2176
+ const settleBill = useCallback(async () => {
2177
+ if (settling) return;
2178
+ const positionId = ticketKey;
2179
+ const current = ticketRef.current;
2180
+ if (current.lines.length === 0) {
2181
+ setNotice({ text: `${config.vocabulary.ticket} vazia`, tone: "warn" });
2182
+ return;
2183
+ }
2184
+ if (!paymentId) {
2185
+ setNotice({ text: "Escolha a forma de pagamento", tone: "warn" });
2186
+ return;
2187
+ }
2188
+ const bill = { ticket: current, totals, paymentMethodId: paymentId };
2189
+ setSettling(true);
2190
+ try {
2191
+ const outcome = await onCloseBill?.(positionId, bill);
2192
+ if (outcome === false) {
2193
+ setNotice({ text: "A conta n\xE3o foi fechada", tone: "warn" });
2194
+ return;
2195
+ }
2196
+ dispatchBook({ type: "drop", positionId });
2197
+ setSettled((prev) => ({ ...prev, [positionId]: true }));
2198
+ setOpened((prev) => {
2199
+ const { [positionId]: _closed, ...rest } = prev;
2200
+ return rest;
2201
+ });
2202
+ setPaymentId(void 0);
2203
+ setSelectedLineId(void 0);
2204
+ setActivePositionId(void 0);
2205
+ setRailOpen(false);
2206
+ setNotice({
2207
+ text: `${activePosition?.label ?? config.vocabulary.ticket} fechada \xB7 ${formatCents(bill.totals.totalCents)}`,
2208
+ tone: "ok"
2209
+ });
2210
+ } catch (error) {
2211
+ setNotice({
2212
+ text: error instanceof Error ? error.message : "A conta n\xE3o foi fechada",
2213
+ tone: "warn"
2214
+ });
2215
+ } finally {
2216
+ setSettling(false);
2217
+ }
2218
+ }, [settling, ticketKey, paymentId, totals, onCloseBill, config.vocabulary.ticket, activePosition?.label]);
2219
+ const handleAction = useCallback((actionId) => {
2220
+ const action = config.actions.find((a) => a.id === actionId);
2221
+ if (action?.requiresPin && verifyPin) {
2222
+ haptic("warn");
2223
+ setPinFor(action);
2224
+ return;
2225
+ }
2226
+ haptic("tap");
2227
+ runAction(actionId);
2228
+ }, [config.actions, verifyPin, runAction]);
2229
+ config.actions.filter(
2230
+ (a) => !a.cycles || a.cycles.includes(config.saleCycle)
2231
+ );
2232
+ useEffect(() => {
2233
+ handleActionRef.current = handleAction;
2234
+ }, [handleAction]);
2235
+ useEffect(() => {
2236
+ settleBillRef.current = settleBill;
2237
+ }, [settleBill]);
2238
+ useEffect(() => {
2239
+ zonePositionsRef.current = zonePositions;
2240
+ }, [zonePositions]);
2241
+ useEffect(() => {
2242
+ ticketRef.current = ticket;
2243
+ }, [ticket]);
2244
+ useEffect(() => {
2245
+ seatingRef.current = seating;
2246
+ }, [seating]);
2247
+ const openCount = livePositions.filter((p) => p.status !== "free").length;
2248
+ return /* @__PURE__ */ jsxs("div", { className: className ? `pos ${className}` : "pos", children: [
2249
+ /* @__PURE__ */ jsxs("div", { className: "pos-body", children: [
2250
+ /* @__PURE__ */ jsxs("nav", { className: "pos-rail", "aria-label": "Navega\xE7\xE3o principal", children: [
2251
+ /* @__PURE__ */ jsxs("div", { className: "pos-brand", children: [
2252
+ /* @__PURE__ */ jsx("span", { className: "pos-brand__mark", children: brandMark ?? config.name.slice(0, 1) }),
2253
+ /* @__PURE__ */ jsx("span", { className: "pos-brand__name", children: brandName ?? config.name })
2254
+ ] }),
2255
+ /* @__PURE__ */ jsxs("label", { className: "pos-field", children: [
2256
+ /* @__PURE__ */ jsx("span", { className: "pos-field__icon", children: /* @__PURE__ */ jsx(Search, { size: 17, strokeWidth: 2.2, "aria-hidden": true }) }),
2257
+ /* @__PURE__ */ jsx(
2258
+ "input",
2259
+ {
2260
+ ref: commandRef,
2261
+ value: query,
2262
+ onChange: (e) => {
2263
+ setQuery(e.target.value);
2264
+ const cat = config.menus.find((m) => m.view === "catalog");
2265
+ if (e.target.value && cat && menu?.view !== "catalog") setMenuId(cat.id);
2266
+ },
2267
+ placeholder: "Buscar",
2268
+ "aria-label": "Buscar item por c\xF3digo ou nome"
2269
+ }
2270
+ ),
2271
+ query ? /* @__PURE__ */ jsx(
2272
+ "button",
2273
+ {
2274
+ type: "button",
2275
+ className: "pos-field__clear",
2276
+ onClick: () => {
2277
+ setQuery("");
2278
+ commandRef.current?.focus();
2279
+ },
2280
+ "aria-label": "Limpar busca",
2281
+ children: /* @__PURE__ */ jsx(X, { size: 14, strokeWidth: 2.6 })
2282
+ }
2283
+ ) : (
2284
+ // Esc is the way back to this field from anywhere; saying so
2285
+ // costs one chip and saves the operator from hunting for it.
2286
+ /* @__PURE__ */ jsx("kbd", { className: "pos-field__hint", "aria-hidden": true, children: "Esc" })
2287
+ )
2288
+ ] }),
2289
+ railTop && /* @__PURE__ */ jsx("div", { className: "pos-rail__top", children: railTop }),
2290
+ /* @__PURE__ */ jsx("div", { className: "pos-rail__label pos-t-xs", children: "Opera\xE7\xE3o" }),
2291
+ /* @__PURE__ */ jsx("div", { className: "pos-nav", children: config.menus.map((m) => {
2292
+ const Icon = MENU_ICON[m.view] ?? ClipboardList;
2293
+ return /* @__PURE__ */ jsxs(
2294
+ "button",
2295
+ {
2296
+ className: "pos-nav__item",
2297
+ "aria-current": m.id === menu?.id ? "page" : void 0,
2298
+ onClick: () => setMenuId(m.id),
2299
+ title: m.label,
2300
+ children: [
2301
+ /* @__PURE__ */ jsx("span", { className: "pos-nav__icon", children: /* @__PURE__ */ jsx(Icon, { size: 19, strokeWidth: 2.1, "aria-hidden": true }) }),
2302
+ /* @__PURE__ */ jsx("span", { className: "pos-nav__text", children: m.label }),
2303
+ m.badge != null && m.badge > 0 && /* @__PURE__ */ jsx("span", { className: "pos-nav__count pos-num", children: m.badge })
2304
+ ]
2305
+ },
2306
+ m.id
2307
+ );
2308
+ }) }),
2309
+ /* @__PURE__ */ jsxs("div", { className: "pos-user", "data-state": connection, children: [
2310
+ /* @__PURE__ */ jsx("span", { className: "pos-user__av", children: (operatorName ?? config.name).slice(0, 1) }),
2311
+ /* @__PURE__ */ jsxs("span", { className: "pos-user__txt", children: [
2312
+ /* @__PURE__ */ jsxs("span", { className: terminalName ? "pos-user__name" : "pos-user__name pos-user__name--dotted", children: [
2313
+ !terminalName && /* @__PURE__ */ jsx("span", { className: "pos-user__dot", "aria-hidden": true }),
2314
+ operatorName ?? config.name
2315
+ ] }),
2316
+ terminalName && /* @__PURE__ */ jsxs("span", { className: "pos-user__meta", children: [
2317
+ /* @__PURE__ */ jsx("span", { className: "pos-user__dot", "aria-hidden": true }),
2318
+ terminalName
2319
+ ] })
2320
+ ] }),
2321
+ /* @__PURE__ */ jsx(
2322
+ "button",
2323
+ {
2324
+ className: "pos-user__act",
2325
+ onClick: () => setShowKeys(true),
2326
+ "aria-label": "Atalhos",
2327
+ title: "Atalhos (F1)",
2328
+ children: /* @__PURE__ */ jsx(Keyboard, { size: 15, strokeWidth: 2.2, "aria-hidden": true })
2329
+ }
2330
+ )
2331
+ ] })
2332
+ ] }),
2333
+ /* @__PURE__ */ jsxs("main", { className: "pos-main", ref: mainRef, children: [
2334
+ /* @__PURE__ */ jsxs("div", { className: "pos-head", children: [
2335
+ /* @__PURE__ */ jsx("span", { className: "pos-head__title", children: menu?.label }),
2336
+ menu?.view === "floor" && (config.positions.zones ?? []).length > 0 && /* @__PURE__ */ jsx("span", { className: "pos-seg", role: "group", "aria-label": "\xC1reas", children: config.positions.zones.map((z) => /* @__PURE__ */ jsx(
2337
+ "button",
2338
+ {
2339
+ className: "pos-seg__btn",
2340
+ "aria-pressed": z.id === activeZoneId,
2341
+ onClick: () => setActiveZoneId(z.id),
2342
+ children: z.label
2343
+ },
2344
+ z.id
2345
+ )) })
2346
+ ] }),
2347
+ menu?.view === "floor" && /* @__PURE__ */ jsx(
2348
+ FloorView,
2349
+ {
2350
+ positions: zonePositions,
2351
+ config: config.positions,
2352
+ activeId: activePositionId,
2353
+ onSelect: selectPosition,
2354
+ now,
2355
+ picking: seating != null
2356
+ }
2357
+ ),
2358
+ menu?.view === "catalog" && /* @__PURE__ */ jsx(
2359
+ CatalogView,
2360
+ {
2361
+ categories,
2362
+ items,
2363
+ activeCategoryId,
2364
+ onCategoryChange: (id) => {
2365
+ setActiveCategoryId(id);
2366
+ setQuery("");
2367
+ },
2368
+ quantities,
2369
+ onAdd: addItem,
2370
+ onRemove: removeItem,
2371
+ query
2372
+ }
2373
+ ),
2374
+ menu?.view === "list" && /* @__PURE__ */ jsx(
2375
+ DeliveryBoard,
2376
+ {
2377
+ orders: livePositions.filter((p) => p.kind === "delivery"),
2378
+ activeId: activePositionId,
2379
+ onSelect: selectPosition,
2380
+ now
2381
+ }
2382
+ ),
2383
+ menu?.view === "reservations" && /* @__PURE__ */ jsx(
2384
+ ReservationsView,
2385
+ {
2386
+ reservations: reservations.map((r) => ({
2387
+ ...r,
2388
+ ...bookings[r.id] ? { status: bookings[r.id] } : {},
2389
+ ...seatedAt[r.id] ? { positionId: seatedAt[r.id] } : {}
2390
+ })),
2391
+ positions: livePositions,
2392
+ now,
2393
+ onOpenPosition: (id) => {
2394
+ const p = livePositions.find((x) => x.id === id);
2395
+ if (p) selectPosition(p);
2396
+ },
2397
+ onMarkArrived: (id) => setBookings((prev) => ({ ...prev, [id]: "arrived" })),
2398
+ onSeat: (reservation) => {
2399
+ setSeating(reservation);
2400
+ const floor = config.menus.find((m) => m.view === "floor");
2401
+ if (floor) setMenuId(floor.id);
2402
+ }
2403
+ }
2404
+ ),
2405
+ menu?.view === "shift" && /* @__PURE__ */ jsx(ShiftView, { positions: livePositions, staff }),
2406
+ /* @__PURE__ */ jsx(
2407
+ Dock,
2408
+ {
2409
+ positions: livePositions.filter((p) => p.status !== "free" && p.kind !== "delivery"),
2410
+ activeId: activePositionId,
2411
+ onSelect: selectPosition,
2412
+ now,
2413
+ openCount,
2414
+ seating,
2415
+ onCancelSeating: () => setSeating(void 0)
2416
+ }
2417
+ ),
2418
+ /* @__PURE__ */ jsx(Notice, { notice, onDone: () => setNotice(void 0) })
2419
+ ] }),
2420
+ activePosition?.kind === "delivery" ? /* @__PURE__ */ jsx(
2421
+ DispatchRail,
2422
+ {
2423
+ containerRef: asideRef,
2424
+ order: activePosition,
2425
+ ticket,
2426
+ totals,
2427
+ now,
2428
+ open: railOpen,
2429
+ onAdvance: (to) => {
2430
+ setDispatched((prev) => ({ ...prev, [activePosition.id]: to }));
2431
+ void onDispatch?.(activePosition.id, to);
2432
+ },
2433
+ onCancel: () => handleAction("cancel_order")
2434
+ }
2435
+ ) : /* @__PURE__ */ jsx(
2436
+ TicketRail,
2437
+ {
2438
+ containerRef: asideRef,
2439
+ ticket: {
2440
+ ...ticket,
2441
+ positionLabel: activePosition?.label,
2442
+ attendantName: activePosition?.attendantName,
2443
+ seatedAt: ticket.seatedAt ?? activePosition?.openedAt
2444
+ },
2445
+ totals,
2446
+ vocabulary: config.vocabulary,
2447
+ paymentMethods: config.paymentMethods,
2448
+ selectedPaymentId: paymentId,
2449
+ onSelectPayment: setPaymentId,
2450
+ onChangeQuantity: (lineId, delta) => dispatch({ type: "change_quantity", lineId, delta }),
2451
+ selectedLineId,
2452
+ onSelectLine: setSelectedLineId,
2453
+ onClose: () => handleAction("close_bill"),
2454
+ onFire: () => handleAction("fire"),
2455
+ fireShortcut: config.actions.find((a) => a.id === "fire")?.shortcut,
2456
+ closeShortcut: config.actions.find((a) => a.id === "close_bill")?.shortcut,
2457
+ onBrowseCatalog: () => {
2458
+ const cat = config.menus.find((m) => m.view === "catalog");
2459
+ if (cat) setMenuId(cat.id);
2460
+ commandRef.current?.focus();
2461
+ },
2462
+ alias: activePositionId ? named[activePositionId]?.alias : void 0,
2463
+ customerPhone: activePositionId ? named[activePositionId]?.phone : void 0,
2464
+ onEditIdentity: activePosition ? () => setNaming(activePosition) : void 0,
2465
+ onEditLine: (lineId) => {
2466
+ const line = ticket.lines.find((l) => l.id === lineId);
2467
+ const item = line && items.find((i) => i.id === line.itemId);
2468
+ if (line && item) setEditing({ item, line });
2469
+ },
2470
+ guests: activePosition?.seats,
2471
+ status: activePosition?.status,
2472
+ zoneLabel: config.positions.zones?.find((z) => z.id === activePosition?.zoneId)?.label,
2473
+ request: activePosition?.request,
2474
+ now,
2475
+ open: railOpen
2476
+ }
2477
+ )
2478
+ ] }),
2479
+ showKeys && /* @__PURE__ */ jsx(ShortcutsOverlay, { actions: config.actions, onClose: () => setShowKeys(false) }),
2480
+ pinFor && /* @__PURE__ */ jsx(
2481
+ PinSheet,
2482
+ {
2483
+ actionLabel: pinFor.label,
2484
+ onCancel: () => setPinFor(void 0),
2485
+ onVerify: (pin) => verifyPin(pin, pinFor.id),
2486
+ onGranted: (by) => {
2487
+ const id = pinFor.id;
2488
+ setPinFor(void 0);
2489
+ onAuthorised?.(id, by);
2490
+ runAction(id);
2491
+ }
2492
+ }
2493
+ ),
2494
+ splitting && /* @__PURE__ */ jsx(
2495
+ SplitSheet,
2496
+ {
2497
+ ticket,
2498
+ totals,
2499
+ guests: activePosition?.seats,
2500
+ onCancel: () => setSplitting(false),
2501
+ onConfirm: () => setSplitting(false)
2502
+ }
2503
+ ),
2504
+ openingPosition && /* @__PURE__ */ jsx(
2505
+ OpenTableSheet,
2506
+ {
2507
+ position: openingPosition,
2508
+ vocabulary: config.vocabulary,
2509
+ staff,
2510
+ defaultSeats: seating?.party,
2511
+ seatingName: seating?.name,
2512
+ onCancel: () => {
2513
+ setOpeningPosition(void 0);
2514
+ setSeating(void 0);
2515
+ },
2516
+ onConfirm: confirmOpen
2517
+ }
2518
+ ),
2519
+ naming && /* @__PURE__ */ jsx(
2520
+ IdentitySheet,
2521
+ {
2522
+ position: {
2523
+ ...naming,
2524
+ alias: named[naming.id]?.alias ?? naming.alias,
2525
+ customerPhone: named[naming.id]?.phone ?? naming.customerPhone
2526
+ },
2527
+ onCancel: () => setNaming(void 0),
2528
+ onSave: (alias, phone) => {
2529
+ setNamed((prev) => ({ ...prev, [naming.id]: { alias, phone } }));
2530
+ setNaming(void 0);
2531
+ }
2532
+ }
2533
+ ),
2534
+ editing && /* @__PURE__ */ jsx(
2535
+ ModifierSheet,
2536
+ {
2537
+ item: editing.item,
2538
+ mode: "edit",
2539
+ initial: { modifiers: editing.line.modifiers, note: editing.line.note },
2540
+ onCancel: () => setEditing(void 0),
2541
+ onConfirm: (modifiers, note) => {
2542
+ dispatch({
2543
+ type: "edit_line",
2544
+ lineId: editing.line.id,
2545
+ modifiers,
2546
+ note,
2547
+ basePriceCents: editing.line.basePriceCents ?? editing.item.priceCents
2548
+ });
2549
+ setEditing(void 0);
2550
+ }
2551
+ }
2552
+ ),
2553
+ pendingItem && /* @__PURE__ */ jsx(
2554
+ ModifierSheet,
2555
+ {
2556
+ item: pendingItem,
2557
+ onCancel: () => setPendingItem(void 0),
2558
+ onConfirm: (modifiers, note) => {
2559
+ ring(pendingItem, modifiers, note || void 0);
2560
+ setPendingItem(void 0);
2561
+ }
2562
+ }
2563
+ )
2564
+ ] });
2565
+ }
2566
+ function Dock({ positions, activeId, onSelect, now, openCount, seating, onCancelSeating }) {
2567
+ const activeRef = useRef(null);
2568
+ useEffect(() => {
2569
+ const el = activeRef.current;
2570
+ if (!el) return;
2571
+ const still = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
2572
+ el.scrollIntoView?.({
2573
+ behavior: still ? "auto" : "smooth",
2574
+ block: "nearest",
2575
+ inline: "nearest"
2576
+ });
2577
+ }, [activeId]);
2578
+ if (positions.length === 0 && !seating) return null;
2579
+ return /* @__PURE__ */ jsxs("footer", { className: "pos-dock pos-scroll-x", "aria-label": `${openCount} comandas abertas`, children: [
2580
+ seating && /* @__PURE__ */ jsxs("div", { className: "pos-seating", role: "status", children: [
2581
+ /* @__PURE__ */ jsx("span", { className: "pos-seating__pulse", "aria-hidden": true }),
2582
+ /* @__PURE__ */ jsxs("span", { children: [
2583
+ /* @__PURE__ */ jsx("span", { className: "pos-seating__t", children: seating.name }),
2584
+ /* @__PURE__ */ jsxs("span", { className: "pos-seating__s pos-num", children: [
2585
+ seating.party,
2586
+ " pessoas \xB7 escolha a mesa"
2587
+ ] })
2588
+ ] }),
2589
+ /* @__PURE__ */ jsx(
2590
+ "button",
2591
+ {
2592
+ className: "pos-seating__x",
2593
+ onClick: onCancelSeating,
2594
+ "aria-label": "Cancelar a acomoda\xE7\xE3o",
2595
+ children: /* @__PURE__ */ jsx(X, { size: 15, strokeWidth: 2.4 })
2596
+ }
2597
+ )
2598
+ ] }),
2599
+ positions.map((p) => /* @__PURE__ */ jsxs(
2600
+ "button",
2601
+ {
2602
+ ref: p.id === activeId ? activeRef : void 0,
2603
+ className: "pos-dchip",
2604
+ "data-status": p.status,
2605
+ "aria-current": p.id === activeId,
2606
+ onClick: () => onSelect(p),
2607
+ children: [
2608
+ /* @__PURE__ */ jsx("span", { className: "pos-dchip__tag pos-num", children: p.label.replace(/\D/g, "").slice(-3) || p.label.slice(0, 2) }),
2609
+ /* @__PURE__ */ jsxs("span", { children: [
2610
+ /* @__PURE__ */ jsx("span", { className: "pos-dchip__t", children: p.attendantName ?? p.label }),
2611
+ /* @__PURE__ */ jsxs("span", { className: "pos-dchip__s pos-num", children: [
2612
+ p.totalCents != null && formatCents(p.totalCents),
2613
+ elapsed(p.openedAt, now) && ` \xB7 ${elapsed(p.openedAt, now)}`
2614
+ ] })
2615
+ ] }),
2616
+ p.status !== "occupied" && /* A glyph, not a word: on a strip of five the word is the widest
2617
+ thing in the row and it repeats what the colour already said.
2618
+ `title` keeps it readable for anyone who needs the word. */
2619
+ /* @__PURE__ */ jsx(
2620
+ "span",
2621
+ {
2622
+ className: "pos-dchip__st",
2623
+ "data-status": p.status,
2624
+ title: p.status === "bill_requested" ? "Conta pedida" : "Aten\xE7\xE3o",
2625
+ "aria-label": p.status === "bill_requested" ? "Conta pedida" : "Aten\xE7\xE3o",
2626
+ children: p.status === "bill_requested" ? /* @__PURE__ */ jsx(Receipt, { size: 14, strokeWidth: 2.3, "aria-hidden": true }) : /* @__PURE__ */ jsx(AlertTriangle, { size: 14, strokeWidth: 2.3, "aria-hidden": true })
2627
+ }
2628
+ )
2629
+ ]
2630
+ },
2631
+ p.id
2632
+ ))
2633
+ ] });
2634
+ }
2635
+
2636
+ export { COUNTER_KEY, DISPATCH_LABEL, DeliveryBoard, DispatchRail, IdentitySheet, ModifierSheet, Notice, OpenTableSheet, PinSheet, PosScreen, ReservationsView, ShortcutsOverlay, SplitSheet, TicketRail, bookReducer, countsOf, emptyTicket, formatCents, nextDispatch, serviceChargeCents, splitEvenly, ticketAt, ticketReducer, totalsOf, useSwipeAway };
2637
+ //# sourceMappingURL=index.js.map
2638
+ //# sourceMappingURL=index.js.map