@fayz-ai/pos 0.20.0 → 0.20.1

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.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { haptic, createKeymap, directionOf, moveFocus } from '@fayz-ai/core/interaction';
2
2
  export { comboOf, createKeymap, haptic, setHapticsEnabled } from '@fayz-ai/core/interaction';
3
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';
4
+ import { ChevronDown, Pencil, Receipt, ChefHat, AlertTriangle, ChevronLeft, QrCode, CreditCard, Banknote, Trash2, Minus, Plus, Bike, Package, CheckCheck, Clock, MapPin, ShieldCheck, Keyboard, X, CalendarClock, Users, Phone, Check, UserRound, User, Delete, UtensilsCrossed, Info, LayoutGrid, ClipboardList, ChevronUp, MoreHorizontal, Search, Utensils, CheckCircle2, CookingPot } from 'lucide-react';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
6
 
7
7
  // src/keyboard/index.ts
@@ -232,6 +232,28 @@ function elapsed(openedAt, now = Date.now()) {
232
232
  if (minutes < 0 || !Number.isFinite(minutes)) return void 0;
233
233
  return minutes < 60 ? `${minutes}min` : `${Math.floor(minutes / 60)}h${String(minutes % 60).padStart(2, "0")}`;
234
234
  }
235
+ function FloorSkeleton({ count = 8 }) {
236
+ return /* @__PURE__ */ jsx("div", { className: "pos-floor pos-scroll", "aria-busy": "true", "aria-label": "Carregando as mesas", children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "pos-tbl pos-skel", "aria-hidden": true, children: [
237
+ /* @__PURE__ */ jsx("span", { className: "pos-skel__b", style: { width: "46%", height: 21 } }),
238
+ /* @__PURE__ */ jsx("span", { className: "pos-skel__b", style: { width: "58%", height: 22 } }),
239
+ /* @__PURE__ */ jsx("span", { className: "pos-skel__b", style: { width: "34%", height: 12, marginTop: "auto" } })
240
+ ] }, i)) });
241
+ }
242
+ function CatalogSkeleton({ categories = 6, items = 6 }) {
243
+ return /* @__PURE__ */ jsxs("div", { className: "pos-scroll", style: { flex: 1, minHeight: 0 }, "aria-busy": "true", "aria-label": "Carregando o card\xE1pio", children: [
244
+ /* @__PURE__ */ jsx("div", { className: "pos-cats", children: Array.from({ length: categories }, (_, i) => /* @__PURE__ */ jsx("div", { className: "pos-cat pos-skel", "aria-hidden": true, children: /* @__PURE__ */ jsx("span", { className: "pos-skel__b", style: { width: "70%", height: 13 } }) }, i)) }),
245
+ /* @__PURE__ */ jsx("div", { className: "pos-items", children: Array.from({ length: items }, (_, i) => /* @__PURE__ */ jsxs("div", { className: "pos-item pos-skel", "aria-hidden": true, children: [
246
+ /* @__PURE__ */ jsxs("span", { className: "pos-item__top", children: [
247
+ /* @__PURE__ */ jsx("span", { className: "pos-skel__b", style: { width: 52, height: 52, flex: "none" } }),
248
+ /* @__PURE__ */ jsxs("span", { className: "pos-item__id", children: [
249
+ /* @__PURE__ */ jsx("span", { className: "pos-skel__b", style: { width: "80%", height: 14 } }),
250
+ /* @__PURE__ */ jsx("span", { className: "pos-skel__b", style: { width: "45%", height: 12 } })
251
+ ] })
252
+ ] }),
253
+ /* @__PURE__ */ jsx("span", { className: "pos-skel__b", style: { width: "100%", height: 34, marginTop: "auto" } })
254
+ ] }, i)) })
255
+ ] });
256
+ }
235
257
  function EmptyState({ title, hint, Icon = Search, action }) {
236
258
  return /* @__PURE__ */ jsxs("div", { className: "pos-empty", children: [
237
259
  /* @__PURE__ */ jsx("span", { className: "pos-empty__icon", children: /* @__PURE__ */ jsx(Icon, { size: 20, strokeWidth: 2, "aria-hidden": true }) }),
@@ -243,11 +265,11 @@ function EmptyState({ title, hint, Icon = Search, action }) {
243
265
  }, children: action.label })
244
266
  ] });
245
267
  }
246
- function FloorView({ positions, config, activeId, onSelect, now, picking }) {
268
+ function FloorView({ positions, config, activeId, onSelect, now, picking, layout }) {
247
269
  if (positions.length === 0) {
248
270
  return /* @__PURE__ */ jsx(EmptyState, { title: "Nenhuma mesa nesta \xE1rea", hint: "Escolha outra \xE1rea acima.", Icon: Utensils });
249
271
  }
250
- return config.layout === "plan" ? /* @__PURE__ */ jsx(PlanFloor, { positions, config, activeId, onSelect, picking }) : /* @__PURE__ */ jsx(GridFloor, { positions, activeId, onSelect, now, picking });
272
+ return layout === "plan" ? /* @__PURE__ */ jsx(PlanFloor, { positions, config, activeId, onSelect, picking }) : /* @__PURE__ */ jsx(GridFloor, { positions, activeId, onSelect, now, picking });
251
273
  }
252
274
  function GridFloor({ positions, activeId, onSelect, now, picking }) {
253
275
  return /* @__PURE__ */ jsx("div", { className: "pos-floor pos-scroll", "data-picking": picking, children: positions.map((p, index) => {
@@ -449,11 +471,16 @@ function CatalogView({
449
471
  delayMs: Math.min(index, 11) * 24,
450
472
  tone: category?.tone,
451
473
  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}`
474
+ /* @__PURE__ */ jsxs("span", { className: "pos-item__top", children: [
475
+ item.imageUrl ? /* @__PURE__ */ jsx("span", { className: "pos-item__img", children: /* @__PURE__ */ jsx("img", { src: item.imageUrl, alt: "", loading: "lazy", decoding: "async" }) }) : null,
476
+ /* @__PURE__ */ jsxs("span", { className: "pos-item__id", children: [
477
+ item.route && /* @__PURE__ */ jsx("span", { className: "pos-item__route", children: item.route }),
478
+ /* @__PURE__ */ jsx("span", { className: "pos-item__name", children: item.name }),
479
+ /* @__PURE__ */ jsxs("span", { className: "pos-item__price pos-num", children: [
480
+ formatCents(item.priceCents),
481
+ item.code && ` \xB7 ${item.code}`
482
+ ] })
483
+ ] })
457
484
  ] }),
458
485
  /* @__PURE__ */ jsx("span", { className: "pos-item__foot", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs("span", { className: "pos-step", children: [
459
486
  /* @__PURE__ */ jsx(
@@ -546,7 +573,36 @@ var PAY_ICON = {
546
573
  "cart\xE3o": CreditCard,
547
574
  pix: QrCode
548
575
  };
576
+ function useSheetDrag(onDismiss) {
577
+ const [offset, setOffset] = useState(0);
578
+ const [dragging, setDragging] = useState(false);
579
+ const startRef = useRef(0);
580
+ if (!onDismiss) return { offset: 0, dragging: false, handlers: {} };
581
+ return {
582
+ offset,
583
+ dragging,
584
+ handlers: {
585
+ onPointerDown: (e) => {
586
+ startRef.current = e.clientY;
587
+ setDragging(true);
588
+ e.currentTarget.setPointerCapture(e.pointerId);
589
+ },
590
+ onPointerMove: (e) => {
591
+ if (!dragging) return;
592
+ setOffset(Math.max(0, e.clientY - startRef.current));
593
+ },
594
+ onPointerUp: (e) => {
595
+ setDragging(false);
596
+ const el = e.currentTarget.closest(".pos-aside");
597
+ const height = el?.getBoundingClientRect().height ?? window.innerHeight;
598
+ if (offset > height / 3) onDismiss();
599
+ setOffset(0);
600
+ }
601
+ }
602
+ };
603
+ }
549
604
  function TicketRail({
605
+ onCollapse,
550
606
  ticket,
551
607
  totals,
552
608
  vocabulary,
@@ -573,6 +629,7 @@ function TicketRail({
573
629
  onFire,
574
630
  fireShortcut
575
631
  }) {
632
+ const drag = useSheetDrag(onCollapse);
576
633
  const [tab, setTab] = useState("order");
577
634
  const [paid, setPaid] = useState(false);
578
635
  const [stage, setStage] = useState("order");
@@ -588,12 +645,40 @@ function TicketRail({
588
645
  className: "pos-aside",
589
646
  ref: containerRef,
590
647
  "data-open": open ?? true,
648
+ "data-dragging": drag.dragging || void 0,
649
+ style: drag.offset ? { transform: `translateY(${drag.offset}px)` } : void 0,
591
650
  "aria-label": vocabulary.ticket,
592
651
  children: [
652
+ onCollapse && /* @__PURE__ */ jsx(
653
+ "div",
654
+ {
655
+ className: "pos-aside__grab",
656
+ role: "button",
657
+ tabIndex: 0,
658
+ "aria-label": "Arraste para baixo para minimizar a comanda",
659
+ onKeyDown: (e) => {
660
+ if (e.key === "Enter" || e.key === " ") {
661
+ e.preventDefault();
662
+ onCollapse();
663
+ }
664
+ },
665
+ ...drag.handlers
666
+ }
667
+ ),
593
668
  /* @__PURE__ */ jsxs("header", { className: "pos-tkt", children: [
594
669
  /* @__PURE__ */ jsxs("div", { className: "pos-tkt__row", children: [
595
670
  /* @__PURE__ */ jsx("span", { className: "pos-t-xl", children: ticket.positionLabel ?? "Balc\xE3o" }),
596
671
  status && /* @__PURE__ */ jsx(StatusBadge, { status }),
672
+ onCollapse && /* @__PURE__ */ jsx(
673
+ "button",
674
+ {
675
+ className: "pos-tkt__collapse",
676
+ onClick: onCollapse,
677
+ "aria-label": "Fechar a comanda",
678
+ title: "Fechar a comanda",
679
+ children: /* @__PURE__ */ jsx(ChevronDown, { size: 18, strokeWidth: 2.4 })
680
+ }
681
+ ),
597
682
  onEditIdentity && /* @__PURE__ */ jsx(
598
683
  "button",
599
684
  {
@@ -705,10 +790,6 @@ function TicketRail({
705
790
  and offering it as the big button is how a bill gets paid for food
706
791
  the kitchen never heard about. */
707
792
  /* @__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
793
  /* @__PURE__ */ jsxs(
713
794
  "button",
714
795
  {
@@ -728,11 +809,17 @@ function TicketRail({
728
809
  /* @__PURE__ */ jsxs(
729
810
  "button",
730
811
  {
731
- className: "pos-btn pos-btn--ghost pos-btn--wide",
812
+ className: "pos-btn pos-btn--ghost pos-btn--wide pos-btn--warn",
732
813
  onClick: () => setStage("checkout"),
733
814
  children: [
734
- "Fechar mesmo assim \xB7 ",
735
- formatCents(totals.totalCents)
815
+ /* @__PURE__ */ jsxs("span", { className: "pos-btn__l1", children: [
816
+ "Fechar mesmo assim \xB7 ",
817
+ formatCents(totals.totalCents)
818
+ ] }),
819
+ /* @__PURE__ */ jsxs("span", { className: "pos-btn__l2", children: [
820
+ /* @__PURE__ */ jsx(AlertTriangle, { size: 12, strokeWidth: 2.4, "aria-hidden": true }),
821
+ pendingCount === 1 ? "1 item n\xE3o foi para a cozinha" : `${pendingCount} itens n\xE3o foram para a cozinha`
822
+ ] })
736
823
  ]
737
824
  }
738
825
  )
@@ -896,7 +983,7 @@ var STATE_LABEL = {
896
983
  function StateIcon({ state }) {
897
984
  if (state === "fired") return /* @__PURE__ */ jsx(CookingPot, { size: 15, strokeWidth: 2.2, "aria-hidden": true });
898
985
  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 });
986
+ return null;
900
987
  }
901
988
  function clock(iso) {
902
989
  return new Date(iso).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
@@ -1512,13 +1599,14 @@ function OpenTableSheet({
1512
1599
  position,
1513
1600
  vocabulary,
1514
1601
  staff,
1602
+ operator,
1515
1603
  defaultSeats,
1516
1604
  seatingName,
1517
1605
  onCancel,
1518
1606
  onConfirm
1519
1607
  }) {
1520
1608
  const [guests, setGuests] = useState(defaultSeats ?? position.seats ?? 2);
1521
- const [attendantId, setAttendantId] = useState(staff[0]?.id);
1609
+ const [attendantId, setAttendantId] = useState(operator?.id ?? staff[0]?.id);
1522
1610
  useEffect(() => {
1523
1611
  function onKey(event) {
1524
1612
  if (event.key === "Escape") {
@@ -1595,7 +1683,11 @@ function OpenTableSheet({
1595
1683
  guests,
1596
1684
  " pessoas."
1597
1685
  ] }),
1598
- staff.length > 0 && /* @__PURE__ */ jsxs("section", { className: "pos-mgroup", children: [
1686
+ operator && /* @__PURE__ */ jsxs("p", { className: "pos-sheet__who", children: [
1687
+ "Abrindo no nome de ",
1688
+ /* @__PURE__ */ jsx("strong", { children: operator.name })
1689
+ ] }),
1690
+ !operator && staff.length > 0 && /* @__PURE__ */ jsxs("section", { className: "pos-mgroup", children: [
1599
1691
  /* @__PURE__ */ jsx("div", { className: "pos-mgroup__head", children: /* @__PURE__ */ jsx("span", { className: "pos-t-xs", children: "Atendente" }) }),
1600
1692
  /* @__PURE__ */ jsx("div", { className: "pos-mgroup__opts", children: staff.map((s) => /* @__PURE__ */ jsxs(
1601
1693
  "button",
@@ -1934,6 +2026,26 @@ var MENU_ICON = {
1934
2026
  shift: Banknote,
1935
2027
  reservations: CalendarClock
1936
2028
  };
2029
+ function useMedia(query) {
2030
+ const [matches, setMatches] = useState(
2031
+ () => typeof window !== "undefined" && window.matchMedia(query).matches
2032
+ );
2033
+ useEffect(() => {
2034
+ if (typeof window === "undefined") return;
2035
+ const mq = window.matchMedia(query);
2036
+ const on = () => setMatches(mq.matches);
2037
+ setMatches(mq.matches);
2038
+ mq.addEventListener("change", on);
2039
+ return () => mq.removeEventListener("change", on);
2040
+ }, [query]);
2041
+ return matches;
2042
+ }
2043
+ function usePhone() {
2044
+ return useMedia("(max-width: 640px)");
2045
+ }
2046
+ function useWideRail() {
2047
+ return useMedia("(min-width: 1181px)");
2048
+ }
1937
2049
  var lineSeq = 0;
1938
2050
  var nextLineId = () => `line-${++lineSeq}`;
1939
2051
  function PosScreen({
@@ -1946,6 +2058,7 @@ function PosScreen({
1946
2058
  terminalName,
1947
2059
  operatorName,
1948
2060
  connection = "online",
2061
+ operatorId,
1949
2062
  onAction,
1950
2063
  onOpenPosition,
1951
2064
  onFire,
@@ -1958,7 +2071,10 @@ function PosScreen({
1958
2071
  brandMark,
1959
2072
  brandName,
1960
2073
  className,
2074
+ style,
1961
2075
  railTop,
2076
+ unitSwitcher,
2077
+ loading = false,
1962
2078
  now = Date.now()
1963
2079
  }) {
1964
2080
  const [book, dispatchBook] = useReducer(bookReducer, void 0, () => {
@@ -2146,8 +2262,13 @@ function PosScreen({
2146
2262
  text: seating ? `${seating.name} na ${config.vocabulary.position} ${p.label}` : `${config.vocabulary.position} ${p.label} aberta`,
2147
2263
  tone: "ok"
2148
2264
  });
2265
+ const catalog = config.menus.find((m) => m.view === "catalog");
2266
+ if (catalog) {
2267
+ setMenuId(catalog.id);
2268
+ requestAnimationFrame(() => commandRef.current?.focus());
2269
+ }
2149
2270
  onOpenPosition?.(p.id, guests, attendantId);
2150
- }, [openingPosition, staff, now, seating, onOpenPosition, config.vocabulary.position]);
2271
+ }, [openingPosition, staff, now, seating, onOpenPosition, config.vocabulary.position, config.menus]);
2151
2272
  const runAction = useCallback((actionId) => {
2152
2273
  if (actionId === "fire") {
2153
2274
  const at = new Date(now).toISOString();
@@ -2245,323 +2366,488 @@ function PosScreen({
2245
2366
  seatingRef.current = seating;
2246
2367
  }, [seating]);
2247
2368
  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(
2369
+ const TABS_ON_PHONE = 3;
2370
+ const [moreOpen, setMoreOpen] = useState(false);
2371
+ const pendingCount = ticket.lines.filter((l) => (l.state ?? "pending") === "pending").length;
2372
+ const [floorLayout, setFloorLayout] = useState("grid");
2373
+ const isPhone = usePhone();
2374
+ const wideRail = useWideRail();
2375
+ const phoneTabs = config.menus.slice(0, TABS_ON_PHONE);
2376
+ const overflowTabs = config.menus.slice(TABS_ON_PHONE);
2377
+ const searchField = /* @__PURE__ */ jsxs("label", { className: "pos-field", children: [
2378
+ /* @__PURE__ */ jsx("span", { className: "pos-field__icon", children: /* @__PURE__ */ jsx(Search, { size: 17, strokeWidth: 2.2, "aria-hidden": true }) }),
2379
+ /* @__PURE__ */ jsx(
2380
+ "input",
2381
+ {
2382
+ ref: commandRef,
2383
+ value: query,
2384
+ onChange: (e) => {
2385
+ setQuery(e.target.value);
2386
+ const cat = config.menus.find((m) => m.view === "catalog");
2387
+ if (e.target.value && cat && menu?.view !== "catalog") setMenuId(cat.id);
2388
+ },
2389
+ placeholder: "Buscar",
2390
+ "aria-label": "Buscar item por c\xF3digo ou nome"
2391
+ }
2392
+ ),
2393
+ query ? /* @__PURE__ */ jsx(
2394
+ "button",
2395
+ {
2396
+ type: "button",
2397
+ className: "pos-field__clear",
2398
+ onClick: () => {
2399
+ setQuery("");
2400
+ commandRef.current?.focus();
2401
+ },
2402
+ "aria-label": "Limpar busca",
2403
+ children: /* @__PURE__ */ jsx(X, { size: 14, strokeWidth: 2.6 })
2404
+ }
2405
+ ) : (
2406
+ // Esc is the way back to this field from anywhere; saying so
2407
+ // costs one chip and saves the operator from hunting for it.
2408
+ /* @__PURE__ */ jsx("kbd", { className: "pos-field__hint", "aria-hidden": true, children: "Esc" })
2409
+ )
2410
+ ] });
2411
+ return /* @__PURE__ */ jsxs(
2412
+ "div",
2413
+ {
2414
+ className: className ? `pos ${className}` : "pos",
2415
+ style,
2416
+ "data-cart-open": railOpen || void 0,
2417
+ children: [
2418
+ /* @__PURE__ */ jsxs("div", { className: "pos-body", children: [
2419
+ /* @__PURE__ */ jsxs("nav", { className: "pos-rail", "aria-label": "Navega\xE7\xE3o principal", children: [
2420
+ /* @__PURE__ */ jsxs("div", { className: "pos-brand", children: [
2421
+ /* @__PURE__ */ jsx("span", { className: "pos-brand__mark", children: brandMark ?? config.name.slice(0, 1) }),
2422
+ /* @__PURE__ */ jsx("span", { className: "pos-brand__name", children: brandName ?? config.name })
2423
+ ] }),
2424
+ railTop && /* @__PURE__ */ jsx("div", { className: "pos-rail__top", children: railTop }),
2425
+ wideRail && /* @__PURE__ */ jsx("div", { className: "pos-rail__search", children: searchField }),
2426
+ /* @__PURE__ */ jsx("div", { className: "pos-rail__label pos-t-xs", children: "Opera\xE7\xE3o" }),
2427
+ /* @__PURE__ */ jsx("div", { className: "pos-nav", children: config.menus.map((m) => {
2428
+ const Icon = MENU_ICON[m.view] ?? ClipboardList;
2429
+ return /* @__PURE__ */ jsxs(
2430
+ "button",
2431
+ {
2432
+ className: "pos-nav__item",
2433
+ "aria-current": m.id === menu?.id ? "page" : void 0,
2434
+ onClick: () => setMenuId(m.id),
2435
+ title: m.label,
2436
+ children: [
2437
+ /* @__PURE__ */ jsx("span", { className: "pos-nav__icon", children: /* @__PURE__ */ jsx(Icon, { size: 19, strokeWidth: 2.1, "aria-hidden": true }) }),
2438
+ /* @__PURE__ */ jsx("span", { className: "pos-nav__text", children: m.label }),
2439
+ m.badge != null && m.badge > 0 && /* @__PURE__ */ jsx("span", { className: "pos-nav__count pos-num", children: m.badge })
2440
+ ]
2441
+ },
2442
+ m.id
2443
+ );
2444
+ }) }),
2445
+ /* @__PURE__ */ jsxs("div", { className: "pos-user", "data-state": connection, children: [
2446
+ /* @__PURE__ */ jsx("span", { className: "pos-user__av", children: (operatorName ?? config.name).slice(0, 1) }),
2447
+ /* @__PURE__ */ jsxs("span", { className: "pos-user__txt", children: [
2448
+ /* @__PURE__ */ jsxs("span", { className: terminalName ? "pos-user__name" : "pos-user__name pos-user__name--dotted", children: [
2449
+ !terminalName && /* @__PURE__ */ jsx("span", { className: "pos-user__dot", "aria-hidden": true }),
2450
+ operatorName ?? config.name
2451
+ ] }),
2452
+ terminalName && /* @__PURE__ */ jsxs("span", { className: "pos-user__meta", children: [
2453
+ /* @__PURE__ */ jsx("span", { className: "pos-user__dot", "aria-hidden": true }),
2454
+ terminalName
2455
+ ] })
2456
+ ] }),
2457
+ /* @__PURE__ */ jsx(
2458
+ "button",
2459
+ {
2460
+ className: "pos-user__act",
2461
+ onClick: () => setShowKeys(true),
2462
+ "aria-label": "Atalhos",
2463
+ title: "Atalhos (F1)",
2464
+ children: /* @__PURE__ */ jsx(Keyboard, { size: 15, strokeWidth: 2.2, "aria-hidden": true })
2465
+ }
2466
+ )
2467
+ ] })
2468
+ ] }),
2469
+ /* @__PURE__ */ jsxs("main", { className: "pos-main", ref: mainRef, children: [
2470
+ /* @__PURE__ */ jsxs("div", { className: "pos-head", children: [
2471
+ /* @__PURE__ */ jsxs("div", { className: "pos-head__where", children: [
2472
+ /* @__PURE__ */ jsx("span", { className: "pos-head__title", children: menu?.label }),
2473
+ unitSwitcher
2474
+ ] }),
2475
+ !wideRail && searchField,
2476
+ /* @__PURE__ */ jsxs("span", { className: "pos-head__views", children: [
2477
+ menu?.view === "floor" && !isPhone && /* @__PURE__ */ jsx("span", { className: "pos-seg", role: "group", "aria-label": "Como ver as mesas", children: [["grid", "Grade"], ["plan", "Planta"]].map(([value, label]) => /* @__PURE__ */ jsx(
2478
+ "button",
2479
+ {
2480
+ className: "pos-seg__btn",
2481
+ "aria-pressed": floorLayout === value,
2482
+ onClick: () => {
2483
+ haptic("tap");
2484
+ setFloorLayout(value);
2485
+ },
2486
+ children: label
2487
+ },
2488
+ value
2489
+ )) }),
2490
+ 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(
2491
+ "button",
2492
+ {
2493
+ className: "pos-seg__btn",
2494
+ "aria-pressed": z.id === activeZoneId,
2495
+ onClick: () => setActiveZoneId(z.id),
2496
+ children: z.label
2497
+ },
2498
+ z.id
2499
+ )) })
2500
+ ] })
2501
+ ] }),
2502
+ menu?.view === "floor" && loading && /* @__PURE__ */ jsx(FloorSkeleton, {}),
2503
+ menu?.view === "floor" && !loading && /* @__PURE__ */ jsx(
2504
+ FloorView,
2505
+ {
2506
+ layout: isPhone ? "grid" : floorLayout,
2507
+ positions: zonePositions,
2508
+ config: config.positions,
2509
+ activeId: activePositionId,
2510
+ onSelect: selectPosition,
2511
+ now,
2512
+ picking: seating != null
2513
+ }
2514
+ ),
2515
+ menu?.view === "catalog" && loading && /* @__PURE__ */ jsx(CatalogSkeleton, {}),
2516
+ menu?.view === "catalog" && !loading && /* @__PURE__ */ jsx(
2517
+ CatalogView,
2518
+ {
2519
+ categories,
2520
+ items,
2521
+ activeCategoryId,
2522
+ onCategoryChange: (id) => {
2523
+ setActiveCategoryId(id);
2524
+ setQuery("");
2525
+ },
2526
+ quantities,
2527
+ onAdd: addItem,
2528
+ onRemove: removeItem,
2529
+ query
2530
+ }
2531
+ ),
2532
+ menu?.view === "list" && /* @__PURE__ */ jsx(
2533
+ DeliveryBoard,
2534
+ {
2535
+ orders: livePositions.filter((p) => p.kind === "delivery"),
2536
+ activeId: activePositionId,
2537
+ onSelect: selectPosition,
2538
+ now
2539
+ }
2540
+ ),
2541
+ menu?.view === "reservations" && /* @__PURE__ */ jsx(
2542
+ ReservationsView,
2543
+ {
2544
+ reservations: reservations.map((r) => ({
2545
+ ...r,
2546
+ ...bookings[r.id] ? { status: bookings[r.id] } : {},
2547
+ ...seatedAt[r.id] ? { positionId: seatedAt[r.id] } : {}
2548
+ })),
2549
+ positions: livePositions,
2550
+ now,
2551
+ onOpenPosition: (id) => {
2552
+ const p = livePositions.find((x) => x.id === id);
2553
+ if (p) selectPosition(p);
2554
+ },
2555
+ onMarkArrived: (id) => setBookings((prev) => ({ ...prev, [id]: "arrived" })),
2556
+ onSeat: (reservation) => {
2557
+ setSeating(reservation);
2558
+ const floor = config.menus.find((m) => m.view === "floor");
2559
+ if (floor) setMenuId(floor.id);
2560
+ }
2561
+ }
2562
+ ),
2563
+ menu?.view === "shift" && /* @__PURE__ */ jsx(ShiftView, { positions: livePositions, staff }),
2564
+ /* @__PURE__ */ jsx(
2565
+ Dock,
2566
+ {
2567
+ positions: livePositions.filter((p) => p.status !== "free" && p.kind !== "delivery"),
2568
+ activeId: activePositionId,
2569
+ onSelect: selectPosition,
2570
+ now,
2571
+ openCount,
2572
+ seating,
2573
+ onCancelSeating: () => setSeating(void 0)
2574
+ }
2575
+ ),
2576
+ /* @__PURE__ */ jsx(Notice, { notice, onDone: () => setNotice(void 0) })
2577
+ ] }),
2578
+ ticket.lines.length > 0 && !railOpen && /* @__PURE__ */ jsxs(
2272
2579
  "button",
2273
2580
  {
2274
2581
  type: "button",
2275
- className: "pos-field__clear",
2582
+ className: "pos-cartbar",
2276
2583
  onClick: () => {
2277
- setQuery("");
2278
- commandRef.current?.focus();
2584
+ haptic("tap");
2585
+ setRailOpen(true);
2279
2586
  },
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,
2587
+ "aria-expanded": false,
2300
2588
  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 })
2589
+ /* @__PURE__ */ jsxs("span", { className: "pos-cartbar__id", children: [
2590
+ /* @__PURE__ */ jsx("span", { className: "pos-cartbar__where", children: activePosition?.label ?? config.vocabulary.ticket }),
2591
+ /* @__PURE__ */ jsxs("span", { className: "pos-cartbar__meta", children: [
2592
+ ticket.lines.length,
2593
+ " ",
2594
+ ticket.lines.length === 1 ? "item" : "itens",
2595
+ pendingCount > 0 && ` \xB7 ${pendingCount} a enviar`
2596
+ ] })
2597
+ ] }),
2598
+ /* @__PURE__ */ jsx("span", { className: "pos-cartbar__total pos-num", children: formatCents(totals.totalCents) }),
2599
+ /* @__PURE__ */ jsx(ChevronUp, { size: 18, strokeWidth: 2.4, "aria-hidden": true })
2304
2600
  ]
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
2601
+ }
2602
+ ),
2603
+ /* @__PURE__ */ jsxs("nav", { className: "pos-navbar", "aria-label": "Vis\xF5es do PDV", children: [
2604
+ phoneTabs.map((m) => {
2605
+ const Icon = MENU_ICON[m.view] ?? ClipboardList;
2606
+ return /* @__PURE__ */ jsxs(
2607
+ "button",
2608
+ {
2609
+ type: "button",
2610
+ className: "pos-navbar__item",
2611
+ "aria-current": m.id === menu?.id ? "page" : void 0,
2612
+ onClick: () => {
2613
+ haptic("tap");
2614
+ setMenuId(m.id);
2615
+ },
2616
+ children: [
2617
+ /* @__PURE__ */ jsxs("span", { className: "pos-navbar__icon", children: [
2618
+ /* @__PURE__ */ jsx(Icon, { size: 20, strokeWidth: 2.1, "aria-hidden": true }),
2619
+ m.badge != null && m.badge > 0 && /* @__PURE__ */ jsx("span", { className: "pos-navbar__count pos-num", children: m.badge })
2620
+ ] }),
2621
+ /* @__PURE__ */ jsx("span", { className: "pos-navbar__text", children: m.label })
2622
+ ]
2623
+ },
2624
+ m.id
2625
+ );
2626
+ }),
2627
+ /* @__PURE__ */ jsxs(
2628
+ "button",
2629
+ {
2630
+ type: "button",
2631
+ className: "pos-navbar__item",
2632
+ "aria-expanded": moreOpen,
2633
+ onClick: () => {
2634
+ haptic("tap");
2635
+ setMoreOpen(true);
2636
+ },
2637
+ children: [
2638
+ /* @__PURE__ */ jsx("span", { className: "pos-navbar__icon", children: /* @__PURE__ */ jsx(MoreHorizontal, { size: 20, strokeWidth: 2.1, "aria-hidden": true }) }),
2639
+ /* @__PURE__ */ jsx("span", { className: "pos-navbar__text", children: "Mais" })
2640
+ ]
2641
+ }
2642
+ )
2643
+ ] }),
2644
+ moreOpen && /* @__PURE__ */ jsxs("div", { className: "pos-more", role: "dialog", "aria-label": "Mais op\xE7\xF5es", children: [
2645
+ /* @__PURE__ */ jsx("button", { className: "pos-more__scrim", "aria-label": "Fechar menu", onClick: () => setMoreOpen(false) }),
2646
+ /* @__PURE__ */ jsxs("div", { className: "pos-more__panel", children: [
2647
+ /* @__PURE__ */ jsxs("div", { className: "pos-more__head", children: [
2648
+ /* @__PURE__ */ jsx("span", { className: "pos-brand__mark", children: brandMark ?? config.name.slice(0, 1) }),
2649
+ /* @__PURE__ */ jsx("span", { className: "pos-brand__name", children: brandName ?? config.name }),
2650
+ /* @__PURE__ */ jsx(
2651
+ "button",
2652
+ {
2653
+ type: "button",
2654
+ className: "pos-more__close",
2655
+ "aria-label": "Fechar menu",
2656
+ onClick: () => setMoreOpen(false),
2657
+ children: /* @__PURE__ */ jsx(X, { size: 18, strokeWidth: 2.4 })
2658
+ }
2659
+ )
2660
+ ] }),
2661
+ railTop && /* @__PURE__ */ jsx("div", { className: "pos-more__top", children: railTop }),
2662
+ overflowTabs.length > 0 && /* @__PURE__ */ jsx("div", { className: "pos-more__nav", children: overflowTabs.map((m) => {
2663
+ const Icon = MENU_ICON[m.view] ?? ClipboardList;
2664
+ return /* @__PURE__ */ jsxs(
2665
+ "button",
2666
+ {
2667
+ type: "button",
2668
+ className: "pos-nav__item",
2669
+ "aria-current": m.id === menu?.id ? "page" : void 0,
2670
+ onClick: () => {
2671
+ haptic("tap");
2672
+ setMenuId(m.id);
2673
+ setMoreOpen(false);
2674
+ },
2675
+ children: [
2676
+ /* @__PURE__ */ jsx("span", { className: "pos-nav__icon", children: /* @__PURE__ */ jsx(Icon, { size: 19, strokeWidth: 2.1, "aria-hidden": true }) }),
2677
+ /* @__PURE__ */ jsx("span", { className: "pos-nav__text", children: m.label }),
2678
+ m.badge != null && m.badge > 0 && /* @__PURE__ */ jsx("span", { className: "pos-nav__count pos-num", children: m.badge })
2679
+ ]
2680
+ },
2681
+ m.id
2682
+ );
2683
+ }) }),
2684
+ unitSwitcher && /* @__PURE__ */ jsx("div", { className: "pos-more__unit", children: unitSwitcher }),
2685
+ /* @__PURE__ */ jsxs("div", { className: "pos-more__foot", children: [
2686
+ /* @__PURE__ */ jsx("span", { className: "pos-user__av", children: (operatorName ?? config.name).slice(0, 1) }),
2687
+ /* @__PURE__ */ jsx("span", { className: "pos-user__txt", children: operatorName ?? config.name })
2688
+ ] })
2319
2689
  ] })
2320
2690
  ] }),
2321
- /* @__PURE__ */ jsx(
2322
- "button",
2691
+ activePosition?.kind === "delivery" ? /* @__PURE__ */ jsx(
2692
+ DispatchRail,
2323
2693
  {
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 })
2694
+ containerRef: asideRef,
2695
+ order: activePosition,
2696
+ ticket,
2697
+ totals,
2698
+ now,
2699
+ open: railOpen,
2700
+ onAdvance: (to) => {
2701
+ setDispatched((prev) => ({ ...prev, [activePosition.id]: to }));
2702
+ void onDispatch?.(activePosition.id, to);
2703
+ },
2704
+ onCancel: () => handleAction("cancel_order")
2329
2705
  }
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
- )) })
2706
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
2707
+ railOpen && /* @__PURE__ */ jsx(
2708
+ "button",
2709
+ {
2710
+ className: "pos-sheet-scrim",
2711
+ "aria-label": "Minimizar a comanda",
2712
+ onClick: () => setRailOpen(false)
2713
+ }
2714
+ ),
2715
+ /* @__PURE__ */ jsx(
2716
+ TicketRail,
2717
+ {
2718
+ containerRef: asideRef,
2719
+ onCollapse: () => setRailOpen(false),
2720
+ ticket: {
2721
+ ...ticket,
2722
+ positionLabel: activePosition?.label,
2723
+ attendantName: activePosition?.attendantName,
2724
+ seatedAt: ticket.seatedAt ?? activePosition?.openedAt
2725
+ },
2726
+ totals,
2727
+ vocabulary: config.vocabulary,
2728
+ paymentMethods: config.paymentMethods,
2729
+ selectedPaymentId: paymentId,
2730
+ onSelectPayment: setPaymentId,
2731
+ onChangeQuantity: (lineId, delta) => dispatch({ type: "change_quantity", lineId, delta }),
2732
+ selectedLineId,
2733
+ onSelectLine: setSelectedLineId,
2734
+ onClose: () => handleAction("close_bill"),
2735
+ onFire: () => handleAction("fire"),
2736
+ fireShortcut: config.actions.find((a) => a.id === "fire")?.shortcut,
2737
+ closeShortcut: config.actions.find((a) => a.id === "close_bill")?.shortcut,
2738
+ onBrowseCatalog: () => {
2739
+ const cat = config.menus.find((m) => m.view === "catalog");
2740
+ if (cat) setMenuId(cat.id);
2741
+ setRailOpen(false);
2742
+ commandRef.current?.focus();
2743
+ },
2744
+ alias: activePositionId ? named[activePositionId]?.alias : void 0,
2745
+ customerPhone: activePositionId ? named[activePositionId]?.phone : void 0,
2746
+ onEditIdentity: activePosition ? () => setNaming(activePosition) : void 0,
2747
+ onEditLine: (lineId) => {
2748
+ const line = ticket.lines.find((l) => l.id === lineId);
2749
+ const item = line && items.find((i) => i.id === line.itemId);
2750
+ if (line && item) setEditing({ item, line });
2751
+ },
2752
+ guests: activePosition?.seats,
2753
+ status: activePosition?.status,
2754
+ zoneLabel: config.positions.zones?.find((z) => z.id === activePosition?.zoneId)?.label,
2755
+ request: activePosition?.request,
2756
+ now,
2757
+ open: railOpen
2758
+ }
2759
+ )
2760
+ ] })
2346
2761
  ] }),
2347
- menu?.view === "floor" && /* @__PURE__ */ jsx(
2348
- FloorView,
2762
+ showKeys && /* @__PURE__ */ jsx(ShortcutsOverlay, { actions: config.actions, onClose: () => setShowKeys(false) }),
2763
+ pinFor && /* @__PURE__ */ jsx(
2764
+ PinSheet,
2349
2765
  {
2350
- positions: zonePositions,
2351
- config: config.positions,
2352
- activeId: activePositionId,
2353
- onSelect: selectPosition,
2354
- now,
2355
- picking: seating != null
2766
+ actionLabel: pinFor.label,
2767
+ onCancel: () => setPinFor(void 0),
2768
+ onVerify: (pin) => verifyPin(pin, pinFor.id),
2769
+ onGranted: (by) => {
2770
+ const id = pinFor.id;
2771
+ setPinFor(void 0);
2772
+ onAuthorised?.(id, by);
2773
+ runAction(id);
2774
+ }
2356
2775
  }
2357
2776
  ),
2358
- menu?.view === "catalog" && /* @__PURE__ */ jsx(
2359
- CatalogView,
2777
+ splitting && /* @__PURE__ */ jsx(
2778
+ SplitSheet,
2360
2779
  {
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
2780
+ ticket,
2781
+ totals,
2782
+ guests: activePosition?.seats,
2783
+ onCancel: () => setSplitting(false),
2784
+ onConfirm: () => setSplitting(false)
2372
2785
  }
2373
2786
  ),
2374
- menu?.view === "list" && /* @__PURE__ */ jsx(
2375
- DeliveryBoard,
2787
+ openingPosition && /* @__PURE__ */ jsx(
2788
+ OpenTableSheet,
2376
2789
  {
2377
- orders: livePositions.filter((p) => p.kind === "delivery"),
2378
- activeId: activePositionId,
2379
- onSelect: selectPosition,
2380
- now
2790
+ position: openingPosition,
2791
+ vocabulary: config.vocabulary,
2792
+ staff,
2793
+ operator: operatorId ? { id: operatorId, name: operatorName ?? operatorId } : void 0,
2794
+ defaultSeats: seating?.party,
2795
+ seatingName: seating?.name,
2796
+ onCancel: () => {
2797
+ setOpeningPosition(void 0);
2798
+ setSeating(void 0);
2799
+ },
2800
+ onConfirm: confirmOpen
2381
2801
  }
2382
2802
  ),
2383
- menu?.view === "reservations" && /* @__PURE__ */ jsx(
2384
- ReservationsView,
2803
+ naming && /* @__PURE__ */ jsx(
2804
+ IdentitySheet,
2385
2805
  {
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);
2806
+ position: {
2807
+ ...naming,
2808
+ alias: named[naming.id]?.alias ?? naming.alias,
2809
+ customerPhone: named[naming.id]?.phone ?? naming.customerPhone
2396
2810
  },
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);
2811
+ onCancel: () => setNaming(void 0),
2812
+ onSave: (alias, phone) => {
2813
+ setNamed((prev) => ({ ...prev, [naming.id]: { alias, phone } }));
2814
+ setNaming(void 0);
2402
2815
  }
2403
2816
  }
2404
2817
  ),
2405
- menu?.view === "shift" && /* @__PURE__ */ jsx(ShiftView, { positions: livePositions, staff }),
2406
- /* @__PURE__ */ jsx(
2407
- Dock,
2818
+ editing && /* @__PURE__ */ jsx(
2819
+ ModifierSheet,
2408
2820
  {
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)
2821
+ item: editing.item,
2822
+ mode: "edit",
2823
+ initial: { modifiers: editing.line.modifiers, note: editing.line.note },
2824
+ onCancel: () => setEditing(void 0),
2825
+ onConfirm: (modifiers, note) => {
2826
+ dispatch({
2827
+ type: "edit_line",
2828
+ lineId: editing.line.id,
2829
+ modifiers,
2830
+ note,
2831
+ basePriceCents: editing.line.basePriceCents ?? editing.item.priceCents
2832
+ });
2833
+ setEditing(void 0);
2834
+ }
2416
2835
  }
2417
2836
  ),
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
- ] });
2837
+ pendingItem && /* @__PURE__ */ jsx(
2838
+ ModifierSheet,
2839
+ {
2840
+ item: pendingItem,
2841
+ onCancel: () => setPendingItem(void 0),
2842
+ onConfirm: (modifiers, note) => {
2843
+ ring(pendingItem, modifiers, note || void 0);
2844
+ setPendingItem(void 0);
2845
+ }
2846
+ }
2847
+ )
2848
+ ]
2849
+ }
2850
+ );
2565
2851
  }
2566
2852
  function Dock({ positions, activeId, onSelect, now, openCount, seating, onCancelSeating }) {
2567
2853
  const activeRef = useRef(null);