// ====== Orders page (kanban + drawer + new-order modal) ======

// Build a mods string from whichever fields are available.
// Visibility is controlled by the showCardMods toggle — no filtering here.
const getItemMods = (it) => {
  if (it.mods) return it.mods;
  const size = it.size_label || it.size;
  const sweet = it.sweetness_label || it.sweetness;
  const ice = it.ice_label || it.ice;
  const parts = [size, sweet ? `${sweet} sugar` : null, ice].filter(Boolean);
  return parts.length ? parts.join(" · ") : null;
};

const channelIcon = (c) => c === "delivery" ? "pickup" : "store";
const channelLabel = (c) => c === "delivery" ? "Delivery" : "Pickup";
const formatScheduledTime = (iso) => {
  if (!iso) return null;
  return new Date(iso).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
};
const isDateOnlyScheduledTime = (date) =>
  (date.getHours() === 0 && date.getMinutes() === 0) ||
  (date.getHours() === 23 && date.getMinutes() === 59);
const formatScheduledDateTime = (iso) => {
  if (!iso) return null;
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return null;
  const now = new Date();
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  const tomorrow = new Date(today.getTime() + 864e5);
  const dDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
  const timeSet = !isDateOnlyScheduledTime(d);
  const time = timeSet ? formatScheduledTime(iso) : null;
  const isToday = dDay.getTime() === today.getTime();
  const isTomorrow = dDay.getTime() === tomorrow.getTime();
  const dateLabel = isToday ? "Today" : isTomorrow ? "Tomorrow" : d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
  if (!dateLabel && !time) return null;
  if (!dateLabel) return time;
  if (!time) return dateLabel;
  return `${dateLabel}, ${time}`;
};
const formatOrderDateTime = (value) => {
  if (!value) return null;
  const d = new Date(value);
  if (Number.isNaN(d.getTime())) return null;
  return d.toLocaleString("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
    hour: "numeric",
    minute: "2-digit",
  });
};
const money = (value) => Number(value || 0).toFixed(2);
const floorDiscount = (value) => Math.floor(Math.max(0, Number(value || 0)));
const FALLBACK_ORDER_ITEM_TYPES = [
  { id: "regular-sale", name: "Regular Sale", pricing_mode: "normal", adjustment_value: 0, stock_effect: "deduct", is_default: true, is_active: true },
];
const activeFlag = (value) => value !== false && value !== "false" && value !== 0;
const orderSubtotal = (order) => Number(order?.subtotal ?? (order?.items || []).reduce((sum, item) => (
  sum + Number((item.line_total ?? (Number(item.price || 0) * Number(item.qty || 1))) || 0)
), 0));
const isRegularOrderItem = (item) => (
  (item.pricing_mode || item.pricingMode || "normal") === "normal" &&
  (item.line_type_name || item.lineTypeName || "Regular Sale") === "Regular Sale"
);
const isIncludedDefaultAddOn = (item) => item?.is_included_addon === true || item?.isIncludedAddon === true;
const visibleOrderItems = (items) => (items || []).filter(item => !isIncludedDefaultAddOn(item));
const orderItemLineTotal = (item) => Number(
  item.line_total ?? item.lineTotal ?? (Number(item.price || 0) * Number(item.qty || 1))
);
const orderDiscount = (order) => Math.max(0, Number(order?.discount_amount ?? order?.discount ?? 0));
const orderDiscountLabel = (order) => {
  const type = order?.discount_type || "fixed";
  const value = Number(order?.discount_value ?? orderDiscount(order));
  if (type === "percent" && value > 0) return `$${money(orderDiscount(order))} (${money(value).replace(/\.00$/, "")}%)`;
  return `$${money(orderDiscount(order))}`;
};
const dateInputValue = (value) => {
  if (!value) return "";
  const d = new Date(value);
  if (Number.isNaN(d.getTime())) return "";
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
};
const todayInputValue = () => dateInputValue(new Date());

const PAYMENT_LABELS = {
  card: "Card",
  cash: "Cash",
  apple_pay: "Apple Pay",
  google_pay: "Google Pay",
  zelle: "Zelle",
};
const PaymentBadge = ({ payment, compact }) => {
  if (!payment) return null;
  const label = PAYMENT_LABELS[payment.method] || payment.method;
  const detail = payment.brand && payment.last4 ? `${payment.brand} ····${payment.last4}` : null;
  return (
    <span className="pay-badge" title={detail || label}>
      <Ic name={payment.method === "cash" ? "money" : "money"} size={12} />
      {compact ? label : (detail || label)}
    </span>
  );
};

const SourceBadge = ({ source }) => {
  if (!source) return null;
  const dotColor = {
    "Counter":   "var(--green-500)",
    "In-app":    "var(--gold-500)",
    "Phone-in":  "var(--green-300)",
    "Uber Eats": "#1f8a5b",
    "DoorDash":  "#b35a4a",
  }[source] || "var(--gold-500)";
  return (
    <span className="src-badge">
      <span className="src-dot" style={{ background: dotColor }} />
      {source}
    </span>
  );
};


// ============================================================
// New Order modal
// ============================================================
const glyphForName = (name, menuItems = []) => menuItems.find(m => m.name === name)?.glyph || "茶";

// Tier color helpers (also used in customers.jsx)
const orderTierColor = (t) => t === "VIP" ? "var(--gold-600)" : t === "Regular" ? "var(--green-700)" : "var(--ink-500)";
const orderTierBg = (t) => t === "VIP" ? "rgba(200,162,92,0.18)" : t === "Regular" ? "rgba(53,97,72,0.10)" : "rgba(125,132,114,0.10)";

// Customer picker — search existing or enter new
const CustomerPicker = ({ value, customers, onChange }) => {
  const [mode, setMode] = React.useState("existing");
  const [query, setQuery] = React.useState("");
  const [open, setOpen] = React.useState(false);
  const [matches, setMatches] = React.useState(() => customers.slice(0, 6));
  const [loading, setLoading] = React.useState(false);
  const searchSeq = React.useRef(0);

  React.useEffect(() => {
    if (!query.trim()) setMatches(customers.slice(0, 6));
  }, [customers, query]);

  React.useEffect(() => {
    const backend = window.TeaGangBackend;
    const term = query.trim();
    const seq = ++searchSeq.current;

    if (!backend?.enabled || !backend.searchCustomers) {
      const q = normalizeVi(term);
      const localMatches = !q
        ? customers.slice(0, 6)
        : customers.filter(c =>
            normalizeVi(c.name).includes(q) ||
            c.phone.includes(term) ||
            normalizeVi(c.email || "").includes(q)
          ).slice(0, 8);
      setMatches(localMatches);
      return;
    }

    setLoading(Boolean(term));
    const timer = window.setTimeout(async () => {
      try {
        const rows = await backend.searchCustomers({ query: term, limit: term ? 8 : 6 });
        if (seq !== searchSeq.current) return;
        setMatches(rows || []);
      } catch (error) {
        if (seq !== searchSeq.current) return;
        console.error("Could not search order customers.", error);
        setMatches([]);
      } finally {
        if (seq === searchSeq.current) setLoading(false);
      }
    }, 250);

    return () => window.clearTimeout(timer);
  }, [query, customers]);

  const pick = (c) => {
    onChange({ id: c.id, name: c.name, phone: c.phone, email: c.email, tier: c.tier, _existing: true });
    setOpen(false);
    setQuery("");
  };
  const clearExisting = () => {
    onChange({ id: null, name: "", phone: "", email: "", _existing: false });
    setQuery("");
  };

  return (
    <div className="cust-picker">
      <div className="seg" style={{ width: "100%" }}>
        <button className={mode === "existing" ? "on" : ""} onClick={() => { setMode("existing"); setOpen(true); }}>
          <Ic name="customers" size={13} /> Existing guest
        </button>
        <button className={mode === "new" ? "on" : ""} onClick={() => { setMode("new"); clearExisting(); setOpen(false); }}>
          <Ic name="plus" size={13} /> New guest
        </button>
      </div>

      {mode === "existing" ? (
        <div style={{ position: "relative" }}>
          {value._existing ? (
            <div className="picked-customer">
              <div className="avatar" style={{ width: 36, height: 36, fontSize: 12, background: value.tier === "VIP" ? "var(--gold-500)" : "var(--cream-200)", color: value.tier === "VIP" ? "var(--green-900)" : "var(--green-700)" }}>
                {value.name.split(" ").map(n => n[0]).join("")}
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="row" style={{ gap: 6, minWidth: 0 }}>
                  <span style={{ fontFamily: "var(--font-display)", fontSize: 15, color: "var(--green-800)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1 }}>{value.name}</span>
                  {value.tier && <span className="tier-pill" style={{ background: orderTierBg(value.tier), color: orderTierColor(value.tier) }}>{value.tier}</span>}
                </div>
                <div className="muted mono" style={{ fontSize: 11 }}>{value.phone}</div>
              </div>
              <button className="icon-btn" style={{ width: 30, height: 30 }} onClick={clearExisting} title="Change">
                <Ic name="x" size={12} />
              </button>
            </div>
          ) : (
            <React.Fragment>
              <div className="search" style={{ width: "100%", margin: 0 }}>
                <Ic name="search" size={14} />
                <input
                  placeholder="Search guests by name, phone, email…"
                  value={query}
                  onChange={(e) => { setQuery(e.target.value); setOpen(true); }}
                  onFocus={() => setOpen(true)}
                />
                {loading && <span className="search-spin" />}
              </div>
              {open && matches.length > 0 && (
                <div className="picker-list">
                  {matches.map(c => (
                    <div key={c.id} className="picker-row" onClick={() => pick(c)}>
                      <div className="avatar" style={{ width: 30, height: 30, fontSize: 11, background: c.tier === "VIP" ? "var(--gold-500)" : "var(--cream-200)", color: c.tier === "VIP" ? "var(--green-900)" : "var(--green-700)" }}>
                        {c.name.split(" ").map(n => n[0]).join("")}
                      </div>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{ fontSize: 13, color: "var(--green-800)" }}>{c.name}</div>
                        <div className="muted mono" style={{ fontSize: 10.5 }}>{c.phone} · {c.visits} visits</div>
                      </div>
                      {c.tier === "VIP" && <Ic name="star" size={12} />}
                    </div>
                  ))}
                </div>
              )}
              {open && !loading && query.trim() && matches.length === 0 && (
                <div className="picker-list">
                  <div className="picker-row muted" style={{ cursor: "default" }}>
                    No matching guest found
                  </div>
                </div>
              )}
            </React.Fragment>
          )}
        </div>
      ) : (
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          <input
            placeholder="Full name"
            value={value.name}
            onChange={(e) => onChange({ ...value, name: e.target.value, _existing: false, id: null })}
            style={{ padding: "8px 12px", background: "var(--surface-2)", border: "1px solid var(--line)", borderRadius: "var(--r-md)", fontSize: 13, outline: "none" }}
          />
          <input
            placeholder="Phone"
            value={value.phone}
            onChange={(e) => onChange({ ...value, phone: formatPhone(e.target.value), _existing: false, id: null })}
            style={{ padding: "8px 12px", background: "var(--surface-2)", border: "1px solid var(--line)", borderRadius: "var(--r-md)", fontSize: 13, outline: "none" }}
          />
        </div>
      )}
    </div>
  );
};

const ClockTimePicker = ({ value, onChange }) => {
  const parse = (v) => {
    if (!v) return { h: null, m: null, ampm: "AM" };
    const [h24, m] = v.split(":").map(Number);
    return { h: h24 % 12 || 12, m, ampm: h24 >= 12 ? "PM" : "AM" };
  };
  const [t, setT] = React.useState(() => parse(value));
  const [face, setFace] = React.useState("hour");
  React.useEffect(() => { setT(parse(value)); setFace("hour"); }, [value]);

  const emit = (next) => {
    if (next.h == null || next.m == null) return;
    let h24 = next.h % 12;
    if (next.ampm === "PM") h24 += 12;
    onChange(`${String(h24).padStart(2,"0")}:${String(next.m).padStart(2,"0")}`);
  };
  const pickH = (v) => { const n = { ...t, h: v }; setT(n); emit(n); setFace("minute"); };
  const pickM = (v) => { const n = { ...t, m: v }; setT(n); emit(n); };
  const pickAP = (v) => { const n = { ...t, ampm: v }; setT(n); emit(n); };

  const ITEMS = face === "hour"
    ? [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    : [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
  const sel = face === "hour" ? t.h : t.m;
  const selIdx = ITEMS.indexOf(sel);
  const CX = 110, CY = 110, R = 82, BR = 17;
  const ang = (i) => (i / 12) * 2 * Math.PI - Math.PI / 2;
  const hx = selIdx >= 0 ? CX + R * Math.cos(ang(selIdx)) : CX;
  const hy = selIdx >= 0 ? CY + R * Math.sin(ang(selIdx)) : CY;

  return (
    <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 10, padding: 14, background: "var(--cream-50)", border: "1px solid var(--line-gold)", borderRadius: "var(--r-lg)", marginTop: 6 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 4 }}>
        <span onClick={() => setFace("hour")} style={{ fontFamily: "var(--font-mono)", fontSize: 30, fontWeight: 600, cursor: "pointer", padding: "2px 8px", borderRadius: 8, background: face === "hour" ? "rgba(53,97,72,0.12)" : "transparent", color: face === "hour" ? "var(--green-700)" : "var(--ink-600)" }}>
          {t.h != null ? String(t.h).padStart(2,"0") : "--"}
        </span>
        <span style={{ fontFamily: "var(--font-mono)", fontSize: 28, color: "var(--ink-300)" }}>:</span>
        <span onClick={() => setFace("minute")} style={{ fontFamily: "var(--font-mono)", fontSize: 30, fontWeight: 600, cursor: "pointer", padding: "2px 8px", borderRadius: 8, background: face === "minute" ? "rgba(53,97,72,0.12)" : "transparent", color: face === "minute" ? "var(--green-700)" : "var(--ink-600)" }}>
          {t.m != null ? String(t.m).padStart(2,"0") : "--"}
        </span>
        <div style={{ display: "flex", flexDirection: "column", gap: 3, marginLeft: 10 }}>
          {["AM", "PM"].map(ap => (
            <button key={ap} onClick={() => pickAP(ap)} style={{ padding: "3px 9px", fontSize: 11, fontWeight: 700, letterSpacing: ".05em", background: t.ampm === ap ? "var(--green-700)" : "var(--cream-200)", color: t.ampm === ap ? "white" : "var(--ink-600)", border: "none", borderRadius: 5, cursor: "pointer" }}>
              {ap}
            </button>
          ))}
        </div>
      </div>

      <svg viewBox="0 0 220 220" style={{ width: 220, height: 220 }}>
        <circle cx={CX} cy={CY} r={104} fill="var(--cream-100)" stroke="var(--line-soft)" strokeWidth={1} />
        {selIdx >= 0 && <>
          <line x1={CX} y1={CY} x2={hx} y2={hy} stroke="var(--green-700)" strokeWidth={2} strokeLinecap="round" opacity={0.85} />
          <circle cx={hx} cy={hy} r={BR} fill="var(--green-700)" />
        </>}
        <circle cx={CX} cy={CY} r={5} fill="var(--green-700)" />
        {ITEMS.map((val, i) => {
          const a = ang(i), x = CX + R * Math.cos(a), y = CY + R * Math.sin(a);
          const isSel = selIdx === i;
          return (
            <g key={val} style={{ cursor: "pointer" }} onClick={() => face === "hour" ? pickH(val) : pickM(val)}>
              {!isSel && <circle cx={x} cy={y} r={BR} fill="transparent" />}
              <text x={x} y={y} textAnchor="middle" dominantBaseline="central" fontSize={13} fontWeight={isSel ? 700 : 500} fill={isSel ? "white" : "var(--green-800)"} style={{ userSelect: "none", pointerEvents: "none" }}>
                {face === "minute" ? String(val).padStart(2,"0") : val}
              </text>
            </g>
          );
        })}
      </svg>
    </div>
  );
};

const DateFilterDialog = ({ config, initialValue, onApply, onCancel }) => {
  const [value, setValue] = React.useState(initialValue || "");
  const [rangeValue, setRangeValue] = React.useState(initialValue || { from: "", to: "" });
  const pickerRef = React.useRef(null);
  const endPickerRef = React.useRef(null);
  React.useEffect(() => {
    if (config?.kind === "range") {
      setRangeValue(initialValue || { from: "", to: "" });
    } else {
      setValue(initialValue || "");
    }
  }, [initialValue, config?.id]);
  if (!config) return null;

  const openPicker = (ref = pickerRef) => {
    const input = ref.current;
    if (!input) return;
    try { input.showPicker(); } catch { input.focus(); }
  };

  const openFirstPicker = () => {
    const input = pickerRef.current;
    if (!input) return;
    try { input.showPicker(); } catch { input.focus(); }
  };

  React.useEffect(() => {
    const timer = window.setTimeout(openFirstPicker, 80);
    return () => window.clearTimeout(timer);
  }, [config.id]);

  const canApply = config.kind === "range" ? Boolean(rangeValue.from && rangeValue.to) : Boolean(value);
  const apply = () => {
    if (!canApply) return;
    onApply(config.id, config.kind === "range" ? rangeValue : value);
  };

  return ReactDOM.createPortal(
    <div
      className="date-dialog-scrim"
      style={{
        position: "fixed",
        inset: 0,
        zIndex: 2000,
        display: "grid",
        placeItems: "center",
        padding: 22,
        background: "rgba(17, 38, 27, 0.42)",
        backdropFilter: "blur(3px)",
      }}
      onClick={onCancel}
    >
      <div
        className="date-dialog"
        style={{
          width: "min(390px, 100%)",
          padding: 22,
          border: "1px solid var(--line-gold)",
          borderRadius: "var(--r-lg)",
          background: "var(--cream-50)",
          boxShadow: "var(--shadow-lg)",
        }}
        onClick={e => e.stopPropagation()}
      >
        <div className="date-dialog-head">
          <div>
            <div className="page-eyebrow">{config.eyebrow}</div>
            <h2>{config.title}</h2>
          </div>
        </div>
        <div className="field">
          {config.kind === "range" ? (
            <div style={{ display: "grid", gap: 12 }}>
              <div>
                <label>Start day</label>
                <div className="date-dialog-input-row">
                  <input
                    ref={pickerRef}
                    autoFocus
                    type="date"
                    value={rangeValue.from}
                    onChange={e => setRangeValue(v => ({ ...v, from: e.target.value }))}
                  />
                  <button className="icon-btn" onClick={() => openPicker(pickerRef)} title="Open picker">
                    <Ic name="calendar" size={15} />
                  </button>
                </div>
              </div>
              <div>
                <label>End day</label>
                <div className="date-dialog-input-row">
                  <input
                    ref={endPickerRef}
                    type="date"
                    value={rangeValue.to}
                    onChange={e => setRangeValue(v => ({ ...v, to: e.target.value }))}
                  />
                  <button className="icon-btn" onClick={() => openPicker(endPickerRef)} title="Open picker">
                    <Ic name="calendar" size={15} />
                  </button>
                </div>
              </div>
            </div>
          ) : (
            <React.Fragment>
              <label>{config.inputLabel}</label>
              <div className="date-dialog-input-row">
                <input
                  ref={pickerRef}
                  autoFocus
                  type={config.type}
                  value={value}
                  onChange={e => setValue(e.target.value)}
                />
                <button className="icon-btn" onClick={() => openPicker(pickerRef)} title="Open picker">
                  <Ic name="calendar" size={15} />
                </button>
              </div>
            </React.Fragment>
          )}
          {config.kind === "range" && rangeValue.from && rangeValue.to && rangeValue.from > rangeValue.to && (
            <div style={{ marginTop: 8, color: "var(--rose-500)", fontSize: 12 }}>
              End day should be after start day.
            </div>
          )}
          </div>
        <div className="date-dialog-foot">
          <button className="btn btn-ghost" onClick={onCancel}>Cancel</button>
          <button className="btn btn-primary" disabled={!canApply || (config.kind === "range" && rangeValue.from > rangeValue.to)} onClick={apply}>
            Apply
          </button>
        </div>
      </div>
    </div>,
    document.body
  );
};

const NewOrderModal = ({
  open,
  onClose,
  onCreate,
  onUpdate,
  nextOrderNumber,
  editingOrder,
  menuItems,
  categories,
  customers,
  orderSources,
  drinkOptions,
  orderItemTypes = [],
}) => {
  const [tab, setTab] = React.useState("all");
  const [cart, setCart] = React.useState([]);
  const [placing, setPlacing] = React.useState(false);
  const [showTimePicker, setShowTimePicker] = React.useState(false);
  const [editingIdx, setEditingIdx] = React.useState(null); // index into cart
  const [itemQuery, setItemQuery] = React.useState("");
  const [modalItemTypes, setModalItemTypes] = React.useState(null);
  const defaultSource = orderSources[0] || { name: "Counter", id: null };
  const [customer, setCustomer] = React.useState({ id: null, name: "", phone: "", email: "", tier: null, channel: "pickup", source: defaultSource.name || "Counter", source_id: defaultSource.id || null, note: "", scheduledDate: "", scheduledTime: "", _existing: false });

  const isEdit = !!editingOrder;
  React.useEffect(() => {
    if (!open) return;
    let disposed = false;
    const backend = window.TeaGangBackend;
    setModalItemTypes(null);
    if (!backend?.enabled || !backend.fetchOrderItemTypes) {
      setModalItemTypes(FALLBACK_ORDER_ITEM_TYPES);
      return;
    }
    backend.fetchOrderItemTypes()
      .then(rows => {
        if (!disposed) setModalItemTypes(rows || []);
      })
      .catch(error => {
        console.warn("Could not refresh order item types for modal.", error);
        if (!disposed) setModalItemTypes(orderItemTypes.length ? orderItemTypes : null);
      });
    return () => { disposed = true; };
  }, [open, orderItemTypes]);

  const normalizedItemTypes = React.useMemo(() => {
    const backend = window.TeaGangBackend;
    let rows = [];
    if (modalItemTypes !== null) {
      rows = modalItemTypes;
    } else if (orderItemTypes.length) {
      rows = orderItemTypes;
    } else if (!backend?.enabled || !backend.fetchOrderItemTypes) {
      rows = FALLBACK_ORDER_ITEM_TYPES;
    }
    return rows.map(type => ({
      ...type,
      id: type.id || type.name,
      name: type.name || "Regular Sale",
      pricing_mode: type.pricing_mode || type.pricingMode || "normal",
      adjustment_value: Number(type.adjustment_value ?? type.adjustmentValue ?? 0),
      stock_effect: type.stock_effect || type.stockEffect || "deduct",
      is_default: type.is_default === true || type.isDefault === true,
      is_active: activeFlag(type.is_active) && activeFlag(type.isActive),
    }));
  }, [modalItemTypes, open, orderItemTypes]);
  const defaultItemType = React.useMemo(
    () => normalizedItemTypes.find(t => t.is_default) || normalizedItemTypes[0] || null,
    [normalizedItemTypes]
  );
  const activeItemTypes = React.useMemo(
    () => normalizedItemTypes.filter(t => t.is_active !== false),
    [normalizedItemTypes]
  );
  const adjustedUnitPriceForType = (type, original) => {
    const base = Math.max(0, Number(original || 0));
    const value = Number(type?.adjustment_value || 0);
    const mode = type?.pricing_mode || "normal";
    const adjusted = mode === "free"
      ? 0
      : mode === "fixed_price"
        ? value
        : mode === "fixed_discount"
          ? base - value
          : mode === "percent_discount"
            ? base * (1 - Math.min(Math.max(value, 0), 100) / 100)
            : base;
    return +Math.max(0, adjusted).toFixed(2);
  };
  const stockDeltaForType = (type, qty) => {
    const quantity = Number(qty || 1);
    const effect = type?.stock_effect || "deduct";
    if (effect === "none") return 0;
    if (effect === "restock") return -quantity;
    return quantity;
  };
  const applyItemType = (line, type = defaultItemType) => {
    if (!type) return line;
    const original = Number(line.originalUnitPrice ?? line.original_unit_price ?? line.price ?? (line.basePrice + (line.sizeDelta || 0)) ?? 0);
    const adjusted = adjustedUnitPriceForType(type, original);
    const qty = Number(line.qty || 1);
    return {
      ...line,
      lineTypeId: type.id,
      lineTypeName: type.name || "Regular Sale",
      pricingMode: type.pricing_mode || "normal",
      adjustmentValue: Number(type.adjustment_value || 0),
      stockEffect: type.stock_effect || "deduct",
      originalUnitPrice: original,
      adjustedUnitPrice: adjusted,
      lineDiscountAmount: Math.max(0, original - adjusted) * qty,
      stockQuantityDelta: stockDeltaForType(type, qty),
    };
  };

  // Reset on open
  React.useEffect(() => {
    if (!open) { setPlacing(false); setShowTimePicker(false); return; }
    setEditingIdx(null);
    setTab("all");
    setItemQuery("");
      if (editingOrder) {
        // Pre-populate from existing order — items are "locked" (qty/remove only)
        const lines = visibleOrderItems(editingOrder.items).map(it => ({
          id: menuItems.find(m => m.name === it.name)?.id || it.menuItemId || "x",
          menuItemId: it.menuItemId,
          name: it.name,
          glyph: it.glyph || glyphForName(it.name, menuItems),
          basePrice: Number(it.original_unit_price ?? it.price ?? 0),
          line_total: it.line_total,
          sizeDelta: 0,
          size: "",
          sweetness: it.sweetness_label || null,
          ice: it.ice_label || null,
          addons: [],
          qty: it.qty,
          note: it.note || "",
          mods: it.mods,
          size_label: it.size_label || null,
          sweetness_level: it.sweetness_level || null,
          sweetness_label: it.sweetness_label || null,
          ice_level: it.ice_level || null,
          ice_label: it.ice_label || null,
          add_ons: it.add_ons || [],
          lineTypeId: it.line_type_id || null,
          lineTypeName: it.line_type_name || "Regular Sale",
          pricingMode: it.pricing_mode || "normal",
          adjustmentValue: Number(it.adjustment_value || 0),
          stockEffect: it.stock_effect || "deduct",
          originalUnitPrice: Number(it.original_unit_price ?? it.price ?? 0),
          adjustedUnitPrice: Number(it.adjusted_unit_price ?? it.price ?? 0),
          lineDiscountAmount: Number(it.line_discount_amount || 0),
          stockQuantityDelta: Number(it.stock_quantity_delta ?? it.qty ?? 1),
          locked: true,
        }));
        setCart(lines);
        const existingDiscount = Number(editingOrder.discount_amount ?? editingOrder.discount ?? 0);
        const safeDiscount = Number.isFinite(existingDiscount) ? Math.max(0, +existingDiscount.toFixed(2)) : 0;
        const existingDiscountType = editingOrder.discount_type === "percent" ? "percent" : "fixed";
        const existingDiscountValue = Number(
          editingOrder.discount_value ?? (existingDiscountType === "percent" ? 0 : safeDiscount)
        );
        const safeDiscountValue = Number.isFinite(existingDiscountValue) ? Math.max(0, +existingDiscountValue.toFixed(2)) : 0;
        const calculatedTotal = Math.max(0, +(lines.reduce((s, l) => s + Number(l.line_total ?? (l.basePrice * l.qty)), 0) - safeDiscount).toFixed(2));
        setDiscountType(existingDiscountType);
        setDiscountValue(safeDiscountValue);
        setPriceOverride(
          editingOrder.total != null && Math.abs(Number(editingOrder.total || 0) - calculatedTotal) > 0.001
            ? Number(editingOrder.total || 0)
            : null
        );
        // Try to match an existing customer row by name
        const existing = customers.find(c => c.name === editingOrder.customer);
        const sourceName = editingOrder.source || "Counter";
        const sourceId = editingOrder.source_id || orderSources.find(s => s.name === sourceName)?.id || null;
        setCustomer({
          id: editingOrder.customer_id || existing?.id || null,
          name: editingOrder.customer === "Walk-in guest" ? "" : editingOrder.customer,
          phone: editingOrder.phone && editingOrder.phone !== "—" ? editingOrder.phone : "",
          email: existing?.email || "",
          tier: existing?.tier || null,
          channel: editingOrder.channel,
          source: sourceName,
          source_id: sourceId,
          note: editingOrder.note || "",
          scheduledDate: dateInputValue(editingOrder.scheduled_at),
          scheduledTime: editingOrder.scheduled_at ? (() => {
            const d = new Date(editingOrder.scheduled_at);
            return isDateOnlyScheduledTime(d) ? "" : d.toTimeString().slice(0, 5);
          })() : "",
          _existing: Boolean(editingOrder.customer_id || existing),
        });
      } else {
        setCart([]);
        setDiscountType("fixed");
        setDiscountValue(0);
        setPriceOverride(null);
        const source = orderSources[0] || { name: "Counter", id: null };
        setCustomer({ id: null, name: "", phone: "", email: "", tier: null, channel: "pickup", source: source.name || "Counter", source_id: source.id || null, note: "", scheduledDate: "", scheduledTime: "", _existing: false });
      }
  }, [open, editingOrder, orderSources]);

  const categoryItems = tab === "all" ? menuItems : menuItems.filter(m => m.cat === tab);
  const normalizedItemQuery = normalizeVi(itemQuery.trim());
  const visibleItems = normalizedItemQuery
    ? categoryItems.filter(m => {
        const haystack = [
          m.name,
          m.description,
          m.cat,
          m.category,
          m.label,
        ].map(normalizeVi).join(" ");
        return haystack.includes(normalizedItemQuery);
      })
    : categoryItems;
  const sweetnessLevels = drinkOptions.filter(o => o.option_type === "sweetness");
  const iceLevels = drinkOptions.filter(o => o.option_type === "ice");
  const allowsSweetness = (item) => item?.item_type !== "addon" && item?.sweetness_enabled !== false;
  const allowsIce = (item) => item?.item_type !== "addon" && item?.ice_enabled !== false;
  const compatibleAddOnsFor = (item) => {
    const addonIds = Array.isArray(item?.addon_item_ids)
      ? item.addon_item_ids
      : Array.isArray(item?.addons)
        ? item.addons
        : [];
    if (!item || item.item_type === "addon" || !addonIds.length) return [];
    const compatibleIds = new Set(addonIds);
    return menuItems.filter(addOn =>
      addOn.item_type === "addon" &&
      addOn.available !== false &&
      compatibleIds.has(addOn.id)
    );
  };
  const includedAddOnsFor = (item) => {
    const addonIds = Array.isArray(item?.included_addon_item_ids)
      ? item.included_addon_item_ids
      : [];
    if (!item || item.item_type === "addon" || !addonIds.length) return [];
    const includedIds = new Set(addonIds);
    return menuItems.filter(addOn =>
      addOn.item_type === "addon" &&
      addOn.available !== false &&
      includedIds.has(addOn.id)
    );
  };
  const defaultSizeForItem = (item) => {
    const sizes = Array.isArray(item?.sizes) ? item.sizes : [];
    return sizes.find(size => String(size.label || "").toLowerCase() === "no size") || sizes[0] || null;
  };
  const addOnUnitPrice = (addon) => Number(addon.price || addon.unit_price || addon.sizes?.[0]?.price || 0);
  const addOnQuantity = (addon) => Number(addon.quantity || 1);
  const addOnLabel = (addon) => addon.name || addon.menu_item_name || "Add-on";
  const addOnPayload = (addon) => ({
    menu_item_id: addon.menu_item_id || addon.id || null,
    menu_item_name: addOnLabel(addon),
    size_id: addon.size_id || addon.sizes?.[0]?.id || null,
    size_label: addon.size_label || addon.sizes?.[0]?.label || null,
    unit_price: addOnUnitPrice(addon),
    quantity: addOnQuantity(addon),
  });
  const includedDefaultRowsForLine = (line) => {
    const menuItem = menuItems.find(item => item.id === (line.menuItemId || line.id));
    if (!menuItem) return [];
    return includedAddOnsFor(menuItem).map(addOn => {
      const size = defaultSizeForItem(addOn);
      return {
        name: addOn.name,
        menuItemId: addOn.id,
        qty: Number(line.qty || 1),
        price: 0,
        mods: "",
        glyph: addOn.glyph,
        size: size?.label || null,
        size_label: size?.label || null,
        sweetness: null,
        sweetness_label: null,
        sweetness_level: null,
        ice: null,
        ice_label: null,
        ice_level: null,
        addons: [],
        add_ons: [],
        note: "",
        lineTypeId: null,
        lineTypeName: "Regular Sale",
        pricingMode: "normal",
        adjustmentValue: 0,
        stockEffect: "deduct",
        originalUnitPrice: 0,
        adjustedUnitPrice: 0,
        lineDiscountAmount: 0,
        stockQuantityDelta: Number(line.qty || 1),
        line_total: 0,
        is_included_addon: true,
        isIncludedAddon: true,
      };
    });
  };

  const addToCart = (item) => {
    const sizeChoices = (item.sizes?.length ? item.sizes : [{ label: "No size", price: item.price }])
      .map(s => ({ label: s.label, delta: +(Number(s.price || item.price) - item.price).toFixed(2) }));
    const def = sizeChoices[0];
    const sweet = sweetnessLevels.find(s => s.is_default) || sweetnessLevels[0];
    const ice = iceLevels.find(s => s.is_default) || iceLevels[0];
    const entry = {
      id: item.id, menuItemId: item.id, name: item.name, glyph: item.glyph, basePrice: item.price,
      size: def.label, sizeDelta: def.delta,
      sweetness: allowsSweetness(item) ? sweet?.label : null,
      ice: allowsIce(item) ? ice?.label : null,
      addons: [], qty: 1, note: "",
    };
    const typedEntry = applyItemType(entry);
    setCart(prev => {
      const next = [...prev, typedEntry];
      setEditingIdx(next.length - 1);
      return next;
    });
  };

  const lineTotal = (l) => {
    const addOns = Array.isArray(l.addons) && l.addons.length
      ? l.addons
      : (Array.isArray(l.add_ons) ? l.add_ons : []);
    const unitPrice = Number(l.adjustedUnitPrice ?? l.basePrice + l.sizeDelta);
    const addonSum = addOns.reduce((s, a) => s + addOnUnitPrice(a) * addOnQuantity(a), 0);
    return (unitPrice + addonSum) * l.qty;
  };
  const updateLine = (idx, patch) => {
    setCart(prev => prev.map((l, i) => {
      if (i !== idx) return l;
      const next = { ...l, ...patch };
      if (patch.qty != null || patch.sizeDelta != null || patch.lineTypeId != null) {
        const type = normalizedItemTypes.find(t => t.id === next.lineTypeId) || defaultItemType;
        const original = patch.sizeDelta != null
          ? Number(next.basePrice || 0) + Number(next.sizeDelta || 0)
          : Number(next.originalUnitPrice ?? next.original_unit_price ?? next.basePrice ?? 0);
        return applyItemType({ ...next, originalUnitPrice: original }, type);
      }
      return next;
    }));
  };
  const removeLine = (idx) => {
    setCart(prev => prev.filter((_, i) => i !== idx));
    if (editingIdx === idx) setEditingIdx(null);
    else if (editingIdx > idx) setEditingIdx(editingIdx - 1);
  };
  const setAddonQty = (idx, addon, qty) => {
    setCart(prev => prev.map((l, i) => {
      if (i !== idx) return l;
      if (qty <= 0) return { ...l, addons: l.addons.filter(a => a.id !== addon.id) };
      const has = l.addons.some(a => a.id === addon.id);
      const normalized = { ...addon, price: addOnUnitPrice(addon), quantity: qty };
      return {
        ...l,
        addons: has
          ? l.addons.map(a => a.id === addon.id ? { ...a, quantity: qty } : a)
          : [...l.addons, normalized],
      };
    }));
  };

  const subtotal = cart.reduce((s, l) => s + lineTotal(l), 0);
  const tax = 0;
  const grand = +(subtotal + tax).toFixed(2);
  const [priceOverride, setPriceOverride] = React.useState(null);
  const [discountType, setDiscountType] = React.useState("fixed");
  const [discountValue, setDiscountValue] = React.useState(0);
  const [editingDiscount, setEditingDiscount] = React.useState(false);
  const [discountInput, setDiscountInput] = React.useState("");
  const [editingTotal, setEditingTotal] = React.useState(false);
  const [totalInput, setTotalInput] = React.useState("");
  const rawDiscountValue = Math.max(0, Number(discountValue || 0));
  const effectiveDiscountValue = discountType === "percent"
    ? Math.min(rawDiscountValue, 100)
    : Math.min(rawDiscountValue, grand);
  const appliedDiscount = Math.min(
    floorDiscount(discountType === "percent" ? grand * effectiveDiscountValue / 100 : effectiveDiscountValue),
    grand
  );
  const discountedGrand = Math.max(0, +(grand - appliedDiscount).toFixed(2));
  const finalTotal = priceOverride != null ? priceOverride : discountedGrand;
  const priceDelta = priceOverride != null ? +(finalTotal - discountedGrand).toFixed(2) : 0;
  const fixedDiscountSuggestions = [1, 2, 3, 4, 5, 6, 8, 10, 15, 20].filter(v => v <= Math.max(1, grand));
  const percentDiscountSuggestions = [5, 10, 15, 20, 25, 50];
  const applyDiscountValue = (value) => {
    const max = discountType === "percent" ? 100 : grand;
    const next = Number(value);
    setDiscountValue(Number.isFinite(next) && next >= 0 ? +Math.min(next, max).toFixed(2) : 0);
  };
  const scheduledAt = () => {
    if (!customer.scheduledDate && !customer.scheduledTime) return null;
    const date = customer.scheduledDate || todayInputValue();
    const time = customer.scheduledTime || "23:59";
    const [h, m] = time.split(":").map(Number);
    const d = new Date(`${date}T00:00:00`);
    d.setHours(Number.isFinite(h) ? h : 0, Number.isFinite(m) ? m : 0, 0, 0);
    return d.toISOString();
  };
  const editing = editingIdx != null && !cart[editingIdx]?.locked ? cart[editingIdx] : null;
  const editingMenuItem = editing ? menuItems.find(m => m.id === editing.id) : null;
  const editingSizeChoices = editingMenuItem
    ? (editingMenuItem.sizes?.length ? editingMenuItem.sizes : [{ label: "No size", price: editingMenuItem.price }])
        .map(s => ({ label: s.label, delta: +(Number(s.price || editingMenuItem.price) - editingMenuItem.price).toFixed(2) }))
    : [];
  const editingAddOns = compatibleAddOnsFor(editingMenuItem);

  const placeOrder = async () => {
    if (!cart.length) return;

    // Auto-save new guest to customers table
    let resolvedCustomerId = customer.id || null;
    if (!customer._existing && customer.name && customer.name.trim()) {
      try {
        const saved = await window.TeaGangBackend?.saveCustomer?.({
          name: customer.name,
          phone: customer.phone || "",
          email: customer.email || "",
        });
        if (saved?.id) resolvedCustomerId = saved.id;
      } catch (e) {
        console.warn("Could not auto-save new customer:", e);
      }
    }

    const items = cart.flatMap(l => {
      if (l.locked) {
        const baseLine = {
          name: l.name,
          menuItemId: l.menuItemId || l.id,
          qty: l.qty,
          price: l.adjustedUnitPrice ?? l.basePrice,
          mods: l.mods || "",
          glyph: l.glyph,
          size: l.size_label,
          size_label: l.size_label,
          sweetness: l.sweetness_label,
          sweetness_label: l.sweetness_label,
          sweetness_level: l.sweetness_level,
          ice: l.ice_label,
          ice_label: l.ice_label,
          ice_level: l.ice_level,
          addons: l.add_ons || [],
          add_ons: l.add_ons || [],
          note: l.note || "",
          lineTypeId: l.lineTypeId,
          lineTypeName: l.lineTypeName,
          pricingMode: l.pricingMode,
          adjustmentValue: l.adjustmentValue,
          stockEffect: l.stockEffect,
          originalUnitPrice: l.originalUnitPrice,
          adjustedUnitPrice: l.adjustedUnitPrice,
          lineDiscountAmount: l.lineDiscountAmount,
          stockQuantityDelta: l.stockQuantityDelta,
        };
        return [baseLine, ...includedDefaultRowsForLine(l)];
      }
      const mods = [
        l.size && l.size !== "No size" ? l.size : null,
        l.sweetness ? `${l.sweetness} sugar` : null,
        l.ice,
        ...l.addons.map(a => `+${addOnLabel(a)}${addOnQuantity(a) > 1 ? ` x${addOnQuantity(a)}` : ""}`),
      ].filter(Boolean).join(" · ");
      const addOns = l.addons.map(addOnPayload);
      const baseLine = {
        name: l.name,
        menuItemId: l.menuItemId || l.id,
        qty: l.qty,
        price: l.adjustedUnitPrice ?? (l.basePrice + l.sizeDelta),
        mods,
        glyph: l.glyph,
        size: l.size,
        sweetness: l.sweetness,
        ice: l.ice,
        addons: addOns,
        add_ons: addOns,
        lineTypeId: l.lineTypeId,
        lineTypeName: l.lineTypeName,
        pricingMode: l.pricingMode,
        adjustmentValue: l.adjustmentValue,
        stockEffect: l.stockEffect,
        originalUnitPrice: l.originalUnitPrice,
        adjustedUnitPrice: l.adjustedUnitPrice,
        lineDiscountAmount: l.lineDiscountAmount,
        stockQuantityDelta: l.stockQuantityDelta,
      };
      return [baseLine, ...includedDefaultRowsForLine(l)];
    });
    if (isEdit) {
      onUpdate({
        ...editingOrder,
        customer_id: resolvedCustomerId,
        customer: customer.name || "Walk-in guest",
	        phone: customer.phone || "—",
	        channel: customer.channel,
	        source: customer.source,
	        source_id: customer.source_id,
        note: customer.note,
        scheduled_at: scheduledAt(),
        items,
        discount_amount: appliedDiscount,
        discount_type: discountType,
        discount_value: effectiveDiscountValue,
        total: finalTotal,
      });
    } else {
      onCreate({
        id: `TG-${nextOrderNumber}`,
        order_number: nextOrderNumber,
        customer_id: resolvedCustomerId,
        customer: customer.name || "Walk-in guest",
        phone: customer.phone || "—",
	        time: "Just now", waited: 0,
	        channel: customer.channel,
	        source: customer.source,
	        source_id: customer.source_id,
	        discount_amount: appliedDiscount,
	        discount_type: discountType,
	        discount_value: effectiveDiscountValue,
	        total: finalTotal,
        status: "pending",
        note: customer.note,
        scheduled_at: scheduledAt(),
        items,
      });
    }
  };

  return (
    <div className={`modal-scrim ${open ? "open" : ""}`} onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        {/* LEFT — menu picker */}
        <div className="modal-left">
          <div className="modal-head">
            <div>
              <h2>{isEdit ? `Edit Order` : "New Order"}</h2>
              <div className="sub">
                {isEdit ? <>Order <span className="mono">{editingOrder.id}</span> · {editingOrder.time}</>
                        : <>Build a cup — order #<span className="mono">{nextOrderNumber}</span></>}
              </div>
            </div>
            <button className="icon-btn" onClick={onClose}><Ic name="x" /></button>
          </div>

          <div className="modal-tabs">
            {categories.map(c => (
              <span key={c.id} className={`modal-tab ${tab === c.id ? "active" : ""}`} onClick={() => setTab(c.id)}>
                {c.label} <span className="count">{c.count}</span>
              </span>
            ))}
          </div>

          <div className="menu-pick-search search">
            <Ic name="search" size={14} />
            <input
              placeholder="Search drinks..."
              value={itemQuery}
              onChange={(e) => setItemQuery(e.target.value)}
            />
            {itemQuery && (
              <button className="icon-btn" style={{ width: 24, height: 24 }} onClick={() => setItemQuery("")} title="Clear search">
                <Ic name="x" size={11} />
              </button>
            )}
          </div>

          <div className="modal-body">
            {visibleItems.length === 0 ? (
              <div className="menu-pick-empty">
                No menu items found
              </div>
            ) : (
              <div className="menu-pick-grid">
                {visibleItems.map(m => (
                <button key={m.id} className="menu-pick" disabled={!m.available} onClick={() => addToCart(m)}>
                  <div className={`pick-img ${m.image_url ? "has-image" : ""}`}>
                    {m.image_url
                      ? <MenuCachedImage itemId={m.id} imageUrl={m.image_url} alt={m.name} style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "contain", padding: 10, transform: `scale(${Number(m.image_zoom || 1)})`, transformOrigin: "center" }} />
                      : <div className="pick-img-emblem">{m.glyph}</div>
                    }
                    {!m.available && <span className="sold-pill">Sold out</span>}
                  </div>
                  <div className="pick-footer">
                    <div className="pick-name">{m.name}</div>
                    <div className="pick-price">${m.price.toFixed(2)}</div>
                  </div>
                </button>
                ))}
              </div>
            )}
          </div>

          {editing && (
            <div className="customizer">
              <div className="spread" style={{ marginBottom: 6 }}>
                <span style={{ fontFamily: "var(--font-display)", fontSize: 18, color: "var(--green-800)" }}>
                  Customize · {editing.name}
                </span>
                <button className="icon-btn" style={{ width: 28, height: 28 }} onClick={() => setEditingIdx(null)}><Ic name="check" size={14} /></button>
              </div>

              <div className="cust-row">
                <label>Size</label>
                <div className="opt-group">
                  {editingSizeChoices.map(s => (
                    <button key={s.label}
                      className={`opt ${editing.size === s.label ? "active" : ""}`}
                      onClick={() => updateLine(editingIdx, { size: s.label, sizeDelta: s.delta })}
                    >
                      {s.label}{s.delta > 0 && <span className="opt-price">+${s.delta.toFixed(2)}</span>}
                    </button>
                  ))}
                </div>
              </div>

              {allowsSweetness(editingMenuItem) && (
                <div className="cust-row">
                  <label>Sugar</label>
                  <div className="opt-group">
                    {sweetnessLevels.map(s => (
                      <button key={s.id || s.label}
                        className={`opt ${editing.sweetness === s.label ? "active" : ""}`}
                        onClick={() => updateLine(editingIdx, { sweetness: s.label })}
                      >{s.label}</button>
                    ))}
                  </div>
                </div>
              )}

              {allowsIce(editingMenuItem) && (
                <div className="cust-row">
                  <label>Ice</label>
                  <div className="opt-group">
                    {iceLevels.map(s => (
                      <button key={s.id || s.label}
                        className={`opt ${editing.ice === s.label ? "active" : ""}`}
                        onClick={() => updateLine(editingIdx, { ice: s.label })}
                      >{s.label}</button>
                    ))}
                  </div>
                </div>
              )}

              {editingAddOns.length > 0 && (
                <div className="cust-row">
	                  <label>Add-ons</label>
	                  <div className="opt-group">
	                    {editingAddOns.map(a => {
	                      const selectedAddOn = editing.addons.find(x => x.id === a.id);
	                      const qty = selectedAddOn?.quantity || 0;
	                      const on = qty > 0;
	                      const addOnPrice = addOnUnitPrice(a);
	                      return (
	                        <div key={a.id} className={`opt addon-qty-opt ${on ? "active" : ""}`}>
	                          <div className="addon-qty-name">
	                            {on && <Ic name="check" size={11} />} {a.name}
	                            <span className="opt-price">+${addOnPrice.toFixed(2)}</span>
	                          </div>
	                          <div className="addon-qty-stepper">
	                            <button type="button" disabled={!on} onClick={() => setAddonQty(editingIdx, a, Math.max(0, qty - 1))}>−</button>
	                            <span>{qty}</span>
	                            <button type="button" onClick={() => setAddonQty(editingIdx, a, qty + 1)}>+</button>
	                          </div>
	                        </div>
	                      );
	                    })}
	                  </div>
                </div>
              )}

              <div className="cust-row">
                <label>Note</label>
                <input
                  type="text"
                  placeholder="e.g. extra hot, light cream"
                  value={editing.note}
                  onChange={(e) => updateLine(editingIdx, { note: e.target.value })}
                  style={{ flex: 1, padding: "7px 10px", background: "var(--surface-2)", border: "1px solid var(--line)", borderRadius: "var(--r-md)", fontSize: 12.5, outline: "none" }}
                />
              </div>
            </div>
          )}
        </div>

        {/* RIGHT — cart + customer */}
        <div className="modal-right">
          <div className="cart-customer">
            <div style={{ fontSize: 10.5, letterSpacing: ".18em", textTransform: "uppercase", color: "var(--ink-500)" }}>
              Customer
            </div>
            <CustomerPicker
              value={customer}
              customers={customers}
              onChange={(v) => setCustomer({ ...customer, ...v })}
            />
            <div className="row-2">
              <div className="seg">
                {[
                  { id: "pickup", label: "Pickup", icon: "store" },
                  { id: "delivery", label: "Delivery", icon: "pickup" },
                ].map(o => (
                  <button key={o.id} className={customer.channel === o.id ? "on" : ""} onClick={() => setCustomer({ ...customer, channel: o.id })}>
                    <Ic name={o.icon} size={13} /> {o.label}
                  </button>
                ))}
              </div>
              <div className="field">
                <select value={customer.source_id || customer.source} onChange={(e) => {
                  const selected = orderSources.find(s => (s.id || s.name || s) === e.target.value);
                  setCustomer({
                    ...customer,
                    source: selected?.name || e.target.value,
                    source_id: selected?.id || null,
                  });
                }}>
                  {orderSources.map(s => <option key={s.id || s.name || s} value={s.id || s.name || s}>{s.name || s}</option>)}
                </select>
              </div>
            </div>
            <div>
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <span style={{ fontSize: 12, color: "var(--ink-500)", whiteSpace: "nowrap", minWidth: 88 }}>
                  {customer.channel === "delivery" ? "Delivery date" : "Pickup date"}
                </span>
                <div className="date-dialog-input-row" style={{ flex: 1, margin: 0, minWidth: 0 }}>
                  <input
                    type="date"
                    value={customer.scheduledDate}
                    onChange={e => setCustomer({ ...customer, scheduledDate: e.target.value })}
                    style={{ minWidth: 0 }}
                  />
                </div>
                {customer.scheduledDate && (
                  <button className="icon-btn" style={{ width: 26, height: 26, color: "var(--ink-400)" }} onClick={() => setCustomer({ ...customer, scheduledDate: "" })}>
                    <Ic name="x" size={12} />
                  </button>
                )}
              </div>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 8 }}>
                <span style={{ fontSize: 12, color: "var(--ink-500)", whiteSpace: "nowrap", minWidth: 88 }}>
                  {customer.channel === "delivery" ? "Delivery time" : "Pickup time"}
                </span>
                <button
                  className="btn btn-ghost"
                  style={{ flex: 1, justifyContent: "center", fontFamily: "var(--font-mono)", fontSize: 13, background: showTimePicker ? "rgba(53,97,72,0.08)" : undefined }}
                  onClick={() => setShowTimePicker(v => !v)}
                >
                  {customer.scheduledTime ? (() => { const [h,m] = customer.scheduledTime.split(":").map(Number); const ap = h >= 12 ? "PM" : "AM"; return `${h%12||12}:${String(m).padStart(2,"0")} ${ap}`; })() : (customer.scheduledDate ? "Date only" : "ASAP — tap to set")}
                </button>
                {customer.scheduledTime && (
                  <button className="icon-btn" style={{ width: 26, height: 26, color: "var(--ink-400)" }} onClick={() => { setCustomer({ ...customer, scheduledTime: "" }); setShowTimePicker(false); }}>
                    <Ic name="x" size={12} />
                  </button>
                )}
              </div>
              {showTimePicker && (
                <ClockTimePicker
                  value={customer.scheduledTime}
                  onChange={v => setCustomer({ ...customer, scheduledTime: v })}
                />
              )}
            </div>
            <input
              placeholder="Order note (optional)"
              value={customer.note}
              onChange={(e) => setCustomer({ ...customer, note: e.target.value })}
              style={{ padding: "8px 12px", background: "var(--surface-2)", border: "1px solid var(--line)", borderRadius: "var(--r-md)", fontSize: 13, outline: "none" }}
            />
          </div>

          <div className="cart-list">
            {cart.length === 0 ? (
              <div className="cart-empty">
                <div className="icon">茶</div>
                <div className="msg">an empty cup awaits</div>
                <div style={{ fontSize: 12, marginTop: 6 }}>Tap drinks on the left to add them.</div>
              </div>
            ) : cart.map((l, i) => {
              const baseMods = l.locked ? [] : [
                l.size,
                l.sweetness ? `${l.sweetness} sugar` : null,
                l.ice,
              ].filter(Boolean);
              const addOnMods = l.locked ? [] : l.addons.map(a => `+${addOnLabel(a)}${addOnQuantity(a) > 1 ? ` x${addOnQuantity(a)}` : ""}`);
              const noteMod = !l.locked && l.note ? `“${l.note}”` : null;
              const mods = l.locked ? l.mods : [...baseMods, noteMod].filter(Boolean).join(" · ");
              const isActive = !l.locked && editingIdx === i;
              const currentLineType = normalizedItemTypes.find(t => t.id === l.lineTypeId);
              const lineItemTypes = currentLineType && !activeItemTypes.some(t => t.id === currentLineType.id)
                ? [...activeItemTypes, currentLineType]
                : activeItemTypes;
              return (
                <div key={i}
                  className="cart-item"
                  style={{ background: isActive ? "rgba(200,162,92,0.08)" : "transparent", borderRadius: 6, padding: "12px 8px" }}
                >
                  <div className="cart-thumb">{l.glyph}</div>
                  <div style={{ minWidth: 0 }} onClick={() => !l.locked && setEditingIdx(i)}>
	                    <div className="cart-item-name" style={{ cursor: l.locked ? "default" : "pointer" }}>{l.name}</div>
	                    <div className="cart-item-mods">
	                      {mods || (!addOnMods.length && <span style={{ fontStyle: "italic" }}>Tap to customize</span>)}
	                      {l.locked && <span style={{ marginLeft: 6, color: "var(--gold-600)", fontSize: 10, letterSpacing: ".1em", textTransform: "uppercase" }}>· existing</span>}
	                      {addOnMods.map(addOn => (
	                        <div className="cart-addon-line" key={addOn}>{addOn}</div>
	                      ))}
	                    </div>
                    {lineItemTypes.length > 0 && (
                      <label style={{ display: "grid", gap: 3, marginTop: 6, maxWidth: 190 }}>
                        <span style={{ color: "var(--ink-500)", fontSize: 10, fontWeight: 800, letterSpacing: ".08em", textTransform: "uppercase" }}>Type</span>
                        <select
                          value={lineItemTypes.some(type => type.id === l.lineTypeId) ? l.lineTypeId : defaultItemType?.id || ""}
                          onClick={e => e.stopPropagation()}
                          onChange={e => {
                            const type = normalizedItemTypes.find(t => t.id === e.target.value);
                            if (type) updateLine(i, { lineTypeId: type.id });
                          }}
                          style={{ width: "100%", border: "1px solid var(--line)", borderRadius: 8, background: "var(--cream-50)", color: "var(--green-800)", fontSize: 12, fontWeight: 700, padding: "4px 8px" }}
                        >
                          {lineItemTypes.map(type => (
                            <option key={type.id} value={type.id}>{type.name}</option>
                          ))}
                        </select>
                      </label>
                    )}
                    {lineItemTypes.length === 0 && l.locked && l.lineTypeName && l.lineTypeName !== "Regular Sale" && (
                      <div style={{ marginTop: 4, color: "var(--rose-500)", fontSize: 11, fontWeight: 700 }}>{l.lineTypeName}</div>
                    )}
                    <div className="qty-stepper">
                      <button onClick={(e) => { e.stopPropagation(); updateLine(i, { qty: Math.max(1, l.qty - 1) }); }}>−</button>
                      <span className="q">{l.qty}</span>
                      <button onClick={(e) => { e.stopPropagation(); updateLine(i, { qty: l.qty + 1 }); }}>+</button>
                    </div>
                  </div>
                  <div className="cart-item-right">
                    <span className="cart-item-price">${lineTotal(l).toFixed(2)}</span>
                    <span className="cart-item-remove" onClick={() => removeLine(i)}>Remove</span>
                  </div>
                </div>
              );
            })}
          </div>

          <div className="cart-totals">
            <div className="line"><span>Subtotal</span><span className="mono">${money(subtotal)}</span></div>
            <div className="line"><span>Tax</span><span className="mono">${money(tax)}</span></div>
            <div className="line discount-line">
              <span className="discount-label">
                Discount
                <span className="discount-mode">
                  <button
                    className={discountType === "fixed" ? "on" : ""}
                    onClick={() => {
                      setDiscountType("fixed");
                      setDiscountInput(money(discountType === "fixed" ? effectiveDiscountValue : 0));
                      setEditingDiscount(true);
                    }}
                  >$</button>
                  <button
                    className={discountType === "percent" ? "on" : ""}
                    onClick={() => {
                      setDiscountType("percent");
                      setDiscountInput(money(discountType === "percent" ? effectiveDiscountValue : 0));
                      setEditingDiscount(true);
                    }}
                  >%</button>
                </span>
              </span>
              <span className="total-display">
                <span className="mono discount-value">
                  {appliedDiscount > 0
                    ? `−$${money(appliedDiscount)}${discountType === "percent" ? ` (${money(effectiveDiscountValue).replace(/\.00$/, "")}%)` : ""}`
                    : "$0.00"}
                </span>
                <button
                  className="total-edit-btn"
                  title="Choose discount"
                  onClick={() => { setDiscountInput(money(effectiveDiscountValue)); setEditingDiscount(v => !v); }}
                ><Ic name="edit" size={13} /></button>
                {appliedDiscount > 0 && (
                  <button
                    className="total-reset-btn"
                    title="Remove discount"
                    onClick={() => setDiscountValue(0)}
                  >↺</button>
                )}
              </span>
            </div>
            {editingDiscount && (
              <div className="discount-picker">
                <div className="discount-picker-head">
                  <span>{discountType === "fixed" ? "Fixed amount" : "Percent discount"}</span>
                  <button className="icon-btn" onClick={() => setEditingDiscount(false)} title="Close discount picker">
                    <Ic name="x" size={11} />
                  </button>
                </div>
                <div className="discount-suggestions">
                  {(discountType === "fixed" ? fixedDiscountSuggestions : percentDiscountSuggestions).map(value => (
                    <button
                      key={value}
                      className={Math.abs(effectiveDiscountValue - value) < 0.001 ? "on" : ""}
                      onClick={() => {
                        applyDiscountValue(value);
                        setDiscountInput(money(value));
                      }}
                    >
                      {discountType === "fixed" ? `$${value}` : `${value}%`}
                    </button>
                  ))}
                </div>
                <div className="discount-custom">
                  <span>{discountType === "fixed" ? "$" : "%"}</span>
                  <input
                    className="discount-input"
                    type="number"
                    step="0.01"
                    min="0"
                    max={discountType === "percent" ? "100" : undefined}
                    placeholder={discountType === "fixed" ? "Amount" : "Percent"}
                    value={discountInput}
                    onFocus={() => setDiscountInput("")}
                    onChange={e => setDiscountInput(e.target.value)}
                    onKeyDown={e => {
                      if (e.key === "Enter") {
                        applyDiscountValue(parseFloat(discountInput));
                        setEditingDiscount(false);
                      }
                      if (e.key === "Escape") setEditingDiscount(false);
                    }}
                  />
                  <button
                    className="btn btn-ghost"
                    onClick={() => {
                      applyDiscountValue(parseFloat(discountInput));
                      setEditingDiscount(false);
                    }}
                  >
                    Apply
                  </button>
                </div>
              </div>
            )}
            {priceDelta !== 0 && (
              <div className="line" style={{ color: priceDelta < 0 ? "var(--rose-500)" : "var(--green-500)" }}>
                <span>{priceDelta < 0 ? "Custom total adjustment" : "Custom total surcharge"}</span>
                <span className="mono">{priceDelta < 0 ? `−$${Math.abs(priceDelta).toFixed(2)}` : `+$${priceDelta.toFixed(2)}`}</span>
              </div>
            )}
            <div className="line grand">
              <span>Total</span>
              {editingTotal ? (
                <span className="total-edit-wrap">
                  <span className="total-currency">$</span>
                  <input
                    className="total-input"
                    type="number"
                    step="0.01"
                    min="0"
                    autoFocus
                    value={totalInput}
                    onChange={e => setTotalInput(e.target.value)}
                    onBlur={() => {
                      const val = parseFloat(totalInput);
                      if (!isNaN(val) && val >= 0) setPriceOverride(+val.toFixed(2));
                      else setPriceOverride(null);
                      setEditingTotal(false);
                    }}
                    onKeyDown={e => {
                      if (e.key === "Enter") e.target.blur();
                      if (e.key === "Escape") { setPriceOverride(null); setEditingTotal(false); }
                    }}
                  />
                </span>
              ) : (
                <span className="total-display">
                  <span className="v" style={priceOverride != null ? { color: "var(--gold-600)" } : {}}>${money(finalTotal)}</span>
                  <button
                    className="total-edit-btn"
                    title="Override total"
                    onClick={() => { setTotalInput(money(finalTotal)); setEditingTotal(true); }}
                  ><Ic name="edit" size={13} /></button>
                  {priceOverride != null && (
                    <button
                      className="total-reset-btn"
                      title="Reset to calculated"
                      onClick={() => setPriceOverride(null)}
                    >↺</button>
                  )}
                </span>
              )}
            </div>
          </div>
          <div className="modal-foot">
            <button className="btn btn-ghost" onClick={onClose} style={{ flex: 1 }} disabled={placing}>Cancel</button>
            <button className="btn btn-primary" disabled={!cart.length || placing} style={{ flex: 2, opacity: cart.length && !placing ? 1 : 0.4 }}
              onClick={async () => { if (!placing) { setPlacing(true); await placeOrder(); } }}>
              {placing ? <><span className="btn-spinner" /> Saving…</> : <>{isEdit ? "Save changes" : "Place order"} <Ic name="arrow-right" size={14} /></>}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ============================================================
// Main Orders page
// ============================================================
const ORDERS_FILTER_STORAGE_KEY = "teaGangOrdersFiltersV1";
const ORDERS_FILTER_CHANGED_EVENT = "teaGangOrdersFiltersChanged";
const ORDERS_FILTER_PREF_KEY = "ordersFiltersV1";
const ORDERS_SORT_STORAGE_KEY = "teaGangOrdersSortV1";
const ORDERS_RECEIPT_SETTINGS_STORAGE_KEY = "teaGangOrdersReceiptSettingsV1";
const PRINT_BRIDGE_URL = "http://127.0.0.1:8766/print";
const DASHBOARD_FILTER_STORAGE_KEY_FOR_ORDER_CARDS = "teaGangDashboardFiltersV1";
const DEFAULT_ORDERS_SORT = {
  ordersSortDir: "desc",
  chipsSort: "count", chipsSortDir: "desc",
  detailSort: "count", detailSortDir: "desc",
  showCardMods: true,
  cardCategoryStyle: "lines",
};
const readOrdersSort = () => {
  try {
    const saved = JSON.parse(window.localStorage.getItem(ORDERS_SORT_STORAGE_KEY) || "{}");
    return {
      ordersSortDir: saved.ordersSortDir === "asc" ? "asc" : "desc",
      chipsSort: saved.chipsSort === "alpha" ? "alpha" : "count",
      chipsSortDir: saved.chipsSortDir === "asc" ? "asc" : "desc",
      detailSort: saved.detailSort === "alpha" ? "alpha" : "count",
      detailSortDir: saved.detailSortDir === "asc" ? "asc" : "desc",
      showCardMods: saved.showCardMods !== false,
      cardCategoryStyle: saved.cardCategoryStyle === "labels" ? "labels" : "lines",
    };
  } catch { return { ...DEFAULT_ORDERS_SORT }; }
};
const DEFAULT_RECEIPT_SETTINGS = {
  paperWidth: "80",
  showOrderNumber: true,
  showOrderType: true,
  showScheduledTime: true,
  showCustomer: true,
  showNote: true,
  showItemDetails: false,
  showThankYou: true,
  footer: "Thank you!",
};
const readReceiptSettings = () => {
  try {
    const saved = JSON.parse(window.localStorage.getItem(ORDERS_RECEIPT_SETTINGS_STORAGE_KEY) || "{}");
    return { ...DEFAULT_RECEIPT_SETTINGS, ...saved };
  } catch {
    return { ...DEFAULT_RECEIPT_SETTINGS };
  }
};
// Maps store-wide receipt settings (store_settings table) <-> local receiptSettings keys
const RECEIPT_SETTING_DB_COLUMNS = {
  paperWidth: "receipt_paper_width",
  showOrderNumber: "receipt_show_order_number",
  showOrderType: "receipt_show_order_type",
  showScheduledTime: "receipt_show_scheduled_time",
  showCustomer: "receipt_show_customer",
  showNote: "receipt_show_note",
  showItemDetails: "receipt_show_item_details",
  showThankYou: "receipt_show_thank_you",
  footer: "receipt_footer_text",
};
const receiptSettingsFromStoreSettings = (settings) => {
  const next = {};
  for (const [key, column] of Object.entries(RECEIPT_SETTING_DB_COLUMNS)) {
    if (settings && Object.prototype.hasOwnProperty.call(settings, column)) {
      next[key] = settings[column];
    }
  }
  return next;
};
const readDashboardCategoryOrderForOrderCards = () => {
  try {
    const saved = JSON.parse(window.localStorage.getItem(DASHBOARD_FILTER_STORAGE_KEY_FOR_ORDER_CARDS) || "{}");
    return Array.isArray(saved.reportCategoryOrder) ? saved.reportCategoryOrder.filter(Boolean) : [];
  } catch { return []; }
};
const ORDERS_DATE_FILTERS = new Set(["all", "today", "week", "month", "custom-day", "custom-week", "custom-month", "custom-range"]);
const ORDERS_CHANNEL_FILTERS = new Set(["all", "pickup", "delivery"]);
const DEFAULT_ORDERS_FILTERS = {
  dateFilter: "today",
  dateBasis: "scheduled",
  customDate: "",
  customRange: { from: "", to: "" },
  channelFilter: "all",
  sourceFilter: "all",
  query: "",
  collapseAllByDefault: true,
};

const normalizeOrdersFilters = (saved = {}) => ({
  dateFilter: ORDERS_DATE_FILTERS.has(saved.dateFilter) || String(saved.dateFilter || "").startsWith("custom-range:")
    ? saved.dateFilter
    : "today",
  customDate: typeof saved.customDate === "string" ? saved.customDate : "",
  customRange: saved.customRange && typeof saved.customRange === "object"
    ? { from: saved.customRange.from || "", to: saved.customRange.to || "" }
    : { from: "", to: "" },
  dateBasis: saved.dateBasis === "created" ? "created" : "scheduled",
  channelFilter: ORDERS_CHANNEL_FILTERS.has(saved.channelFilter) ? saved.channelFilter : "all",
  sourceFilter: typeof saved.sourceFilter === "string" ? saved.sourceFilter : "all",
  query: typeof saved.query === "string" ? saved.query : "",
  collapseAllByDefault: saved.collapseAllByDefault !== false,
});

const readOrdersFilters = () => {
  try {
    const raw = window.localStorage.getItem(ORDERS_FILTER_STORAGE_KEY);
    if (!raw) return { ...DEFAULT_ORDERS_FILTERS, customRange: { ...DEFAULT_ORDERS_FILTERS.customRange } };
    const saved = raw ? JSON.parse(raw) : {};
    return normalizeOrdersFilters(saved);
  } catch {
    return { ...DEFAULT_ORDERS_FILTERS, customRange: { ...DEFAULT_ORDERS_FILTERS.customRange } };
  }
};

const buildOrdersFilters = ({
  dateFilter,
  dateBasis,
  customDate,
  customRange,
  channelFilter,
  sourceFilter,
  query,
  collapseAllByDefault,
}) => ({
  dateFilter,
  dateBasis,
  customDate,
  customRange,
  channelFilter,
  sourceFilter,
  query,
  collapseAllByDefault,
});

const Orders = () => {
  const initialFilters = React.useMemo(readOrdersFilters, []);
  const [orders, setOrders] = React.useState([]);
  const [menuItems, setMenuItems] = React.useState([]);
  const [categories, setCategories] = React.useState([{ id: "all", label: "All", count: 0 }]);
  const [customers, setCustomers] = React.useState([]);
  const [orderSources, setOrderSources] = React.useState([]);
  const [drinkOptions, setDrinkOptions] = React.useState([]);
  const [orderItemTypes, setOrderItemTypes] = React.useState([]);
  const [nextNumber, setNextNumber] = React.useState(1);
  const [selectedId, setSelectedId] = React.useState(null);
  const [modalOpen, setModalOpen] = React.useState(false);
  const [editingOrder, setEditingOrder] = React.useState(null);
  const [dateFilter, setDateFilter] = React.useState(initialFilters.dateFilter);
  const [dateBasis, setDateBasis] = React.useState(initialFilters.dateBasis);
  const [customDate, setCustomDate] = React.useState(initialFilters.customDate);
  const [customRange, setCustomRange] = React.useState(initialFilters.customRange);
  const [customDateRanges, setCustomDateRanges] = React.useState(() => window.TeaGangCustomDateRanges?.readLocal?.() || []);
  const [dateDialog, setDateDialog] = React.useState(null);
  const [moving, setMoving] = React.useState(false);
  const [groupMenu, setGroupMenu] = React.useState(null); // { top, right, colId, list }
  const [groupSummary, setGroupSummary] = React.useState(null); // { customer, list }
  const [collapsedGroups, setCollapsedGroups] = React.useState(new Set()); // keys: `${colId}__${groupKey}`
  const [collapseAllByDefault, setCollapseAllByDefault] = React.useState(initialFilters.collapseAllByDefault);
  const [channelFilter, setChannelFilter] = React.useState(initialFilters.channelFilter);
  const [sourceFilter, setSourceFilter] = React.useState(initialFilters.sourceFilter);
  const dashboardCategoryOrder = React.useMemo(readDashboardCategoryOrderForOrderCards, []);

  const initialSort = React.useMemo(readOrdersSort, []);
  const [ordersSortDir, setOrdersSortDir] = React.useState(initialSort.ordersSortDir);
  const [chipsSort, setChipsSort] = React.useState(initialSort.chipsSort);
  const [chipsSortDir, setChipsSortDir] = React.useState(initialSort.chipsSortDir);
  const [detailSort, setDetailSort] = React.useState(initialSort.detailSort);
  const [detailSortDir, setDetailSortDir] = React.useState(initialSort.detailSortDir);
  const [showCardMods, setShowCardMods] = React.useState(initialSort.showCardMods);
  const [cardCategoryStyle, setCardCategoryStyle] = React.useState(initialSort.cardCategoryStyle);
  const [receiptSettings, setReceiptSettings] = React.useState(readReceiptSettings);
  const receiptSettingsLoadedRef = React.useRef(false);
  const [printingOrderId, setPrintingOrderId] = React.useState(null);
  const [printerIp, setPrinterIp] = React.useState("");
  const [printerIpSaving, setPrinterIpSaving] = React.useState(false);
  const [printerIpError, setPrinterIpError] = React.useState("");
  const [printNotice, setPrintNotice] = React.useState(null);
  const [printPreviewOrder, setPrintPreviewOrder] = React.useState(null);
  const previewCanvasRef = React.useRef(null);
  const previewFrameRef = React.useRef(null);
  const [previewScale, setPreviewScale] = React.useState(0.72);
  const [previewContentHeight, setPreviewContentHeight] = React.useState(760);
  const printNoticeTimerRef = React.useRef(null);
  const [sortPanelOpen, setSortPanelOpen] = React.useState(false);
  const sortPanelRef = React.useRef(null);
  const sortPrefsLoadedRef = React.useRef(false);
  const filtersPrefsLoadedRef = React.useRef(false);
  const ordersLoadSeqRef = React.useRef(0);

  const showPrintNotice = React.useCallback((message, title = "Print warning") => {
    if (printNoticeTimerRef.current) window.clearTimeout(printNoticeTimerRef.current);
    setPrintNotice({ id: Date.now(), title, message });
    printNoticeTimerRef.current = window.setTimeout(() => setPrintNotice(null), 5000);
  }, []);

  React.useEffect(() => {
    if (!sortPanelOpen) return;
    const handler = (e) => { if (!sortPanelRef.current?.contains(e.target)) setSortPanelOpen(false); };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, [sortPanelOpen]);

  React.useEffect(() => () => {
    if (printNoticeTimerRef.current) window.clearTimeout(printNoticeTimerRef.current);
  }, []);

  // Load sort prefs from Supabase on mount (remote wins over localStorage)
  React.useEffect(() => {
    const backend = window.TeaGangBackend;
    if (!backend?.enabled || !backend.fetchUserPreferences) {
      sortPrefsLoadedRef.current = true;
      return;
    }
    let disposed = false;
    backend.fetchUserPreferences("web").then(remote => {
      if (disposed) return;
      const saved = remote?.ordersSortV1;
      if (!saved) return;
      if (saved.ordersSortDir === "asc" || saved.ordersSortDir === "desc") setOrdersSortDir(saved.ordersSortDir);
      if (saved.chipsSort === "alpha" || saved.chipsSort === "count") setChipsSort(saved.chipsSort);
      if (saved.chipsSortDir === "asc" || saved.chipsSortDir === "desc") setChipsSortDir(saved.chipsSortDir);
      if (saved.detailSort === "alpha" || saved.detailSort === "count") setDetailSort(saved.detailSort);
      if (saved.detailSortDir === "asc" || saved.detailSortDir === "desc") setDetailSortDir(saved.detailSortDir);
      if (typeof saved.showCardMods === "boolean") setShowCardMods(saved.showCardMods);
      if (saved.cardCategoryStyle === "labels" || saved.cardCategoryStyle === "lines") setCardCategoryStyle(saved.cardCategoryStyle);
    }).catch(err => {
      console.warn("Could not load sort prefs from Supabase.", err);
    }).finally(() => {
      if (!disposed) sortPrefsLoadedRef.current = true;
    });
    return () => { disposed = true; };
  }, []); // eslint-disable-line

  // Save sort prefs to localStorage immediately and Supabase with debounce
  React.useEffect(() => {
    const sortPrefs = { ordersSortDir, chipsSort, chipsSortDir, detailSort, detailSortDir, showCardMods, cardCategoryStyle };
    window.localStorage.setItem(ORDERS_SORT_STORAGE_KEY, JSON.stringify(sortPrefs));

    const backend = window.TeaGangBackend;
    if (!backend?.enabled || !backend.fetchUserPreferences || !backend.upsertUserPreferences) return;
    if (!sortPrefsLoadedRef.current) return;
    const timer = window.setTimeout(async () => {
      try {
        const remote = await backend.fetchUserPreferences("web") || {};
        await backend.upsertUserPreferences({ ...remote, ordersSortV1: sortPrefs }, "web");
      } catch (err) {
        console.warn("Could not save sort prefs to Supabase.", err);
      }
    }, 1000);
    return () => window.clearTimeout(timer);
  }, [ordersSortDir, chipsSort, chipsSortDir, detailSort, detailSortDir, showCardMods, cardCategoryStyle]);

  // Save receipt settings to localStorage as an offline cache.
  React.useEffect(() => {
    window.localStorage.setItem(ORDERS_RECEIPT_SETTINGS_STORAGE_KEY, JSON.stringify(receiptSettings));
  }, [receiptSettings]);

  // Load printer IP and receipt settings from the store-wide settings on mount
  // so all devices/users for this store share the same printing configuration.
  React.useEffect(() => {
    let disposed = false;
    const loadStoreSettings = async () => {
      const backend = window.TeaGangBackend;
      const localIp = window.localStorage.getItem("teaGangPrinterIp") || "";
      if (!backend?.enabled || !backend.fetchStoreSettings) {
        if (!disposed) setPrinterIp(localIp);
        receiptSettingsLoadedRef.current = true;
        return;
      }
      try {
        const settings = await backend.fetchStoreSettings();
        if (disposed) return;
        const nextIp = settings?.printer_ip || "";
        setPrinterIp(nextIp);
        if (nextIp) window.localStorage.setItem("teaGangPrinterIp", nextIp);
        else window.localStorage.removeItem("teaGangPrinterIp");
        setPrinterIpError("");
        setReceiptSettings(prev => ({ ...prev, ...receiptSettingsFromStoreSettings(settings) }));
      } catch (err) {
        console.warn("Could not load printer IP.", err);
        if (!disposed) {
          setPrinterIp(localIp);
          setPrinterIpError(err.message || "Could not load printer IP.");
        }
      } finally {
        if (!disposed) receiptSettingsLoadedRef.current = true;
      }
    };
    loadStoreSettings();
    return () => { disposed = true; };
  }, []);

  // Push receipt setting changes to the store-wide settings, debounced.
  React.useEffect(() => {
    if (!receiptSettingsLoadedRef.current) return;
    const backend = window.TeaGangBackend;
    if (!backend?.enabled || !backend.updateStoreSettings) return;
    const payload = {};
    for (const [key, column] of Object.entries(RECEIPT_SETTING_DB_COLUMNS)) {
      payload[column] = receiptSettings[key];
    }
    const timer = window.setTimeout(() => {
      backend.updateStoreSettings(payload).catch(err => console.warn("Could not save receipt settings.", err));
    }, 600);
    return () => window.clearTimeout(timer);
  }, [receiptSettings]);

  const orderNumberValue = (order) => Number(order.order_number || String(order.id || "").replace(/\D/g, "") || 0);
  const addedDateValue = (order) => {
    const ts = order.created_at ? new Date(order.created_at).getTime() : NaN;
    return Number.isFinite(ts) ? ts : orderNumberValue(order);
  };
  const completedDateValue = (order) => {
    const ts = order.completed_at ? new Date(order.completed_at).getTime() : NaN;
    return Number.isFinite(ts) ? ts : addedDateValue(order);
  };
  const scheduledSortParts = (order) => {
    if (!order.scheduled_at) return { scheduled: false, day: 0, hasTime: false, time: 0 };
    const d = new Date(order.scheduled_at);
    if (Number.isNaN(d.getTime())) return { scheduled: false, day: 0, hasTime: false, time: 0 };
    const day = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
    const hasTime = !isDateOnlyScheduledTime(d);
    return {
      scheduled: true,
      day,
      hasTime,
      time: hasTime ? d.getHours() * 60 + d.getMinutes() : Infinity,
    };
  };
  const compareScheduledDate = (a, b, asc) => {
    const sa = scheduledSortParts(a);
    const sb = scheduledSortParts(b);
    if (sa.scheduled !== sb.scheduled) return sa.scheduled ? -1 : 1;
    const dir = asc ? 1 : -1;
    if (!sa.scheduled && !sb.scheduled) return (addedDateValue(a) - addedDateValue(b)) * dir;
    if (sa.day !== sb.day) return (sa.day - sb.day) * dir;
    if (sa.hasTime !== sb.hasTime) return sa.hasTime ? -1 : 1;
    if (sa.time !== sb.time) return (sa.time - sb.time) * dir;
    return (addedDateValue(a) - addedDateValue(b)) * dir;
  };

  const applyOrdersSort = (list, status) => {
    const asc = ordersSortDir === "asc";
    return [...list].sort((a, b) => {
      if (status === "completed") {
        const cmp = completedDateValue(a) - completedDateValue(b);
        return asc ? cmp : -cmp;
      }
      if (dateBasis === "scheduled") return compareScheduledDate(a, b, asc);
      const cmp = addedDateValue(a) - addedDateValue(b);
      return asc ? cmp : -cmp;
    });
  };

  const applyChipsSort = (entries) => {
    const asc = chipsSortDir === "asc";
    return [...entries].sort(([nA, qA], [nB, qB]) =>
      chipsSort === "alpha"
        ? (asc ? nA.localeCompare(nB) : nB.localeCompare(nA))
        : (asc ? qA - qB : qB - qA)
    );
  };

  const applyDetailSort = (items) => {
    const asc = detailSortDir === "asc";
    return [...items].sort((a, b) =>
      detailSort === "alpha"
        ? (asc ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name))
        : (asc ? (a.qty || 1) - (b.qty || 1) : (b.qty || 1) - (a.qty || 1))
    );
  };

  const menuItemForOrderItem = (item) => menuItems.find(m =>
    m.id === item.menuItemId ||
    m.id === item.menu_item_id ||
    m.name === item.name
  );

  const categoryForOrderItem = (item) => {
    const menuItem = menuItemForOrderItem(item);
    const categoryId = menuItem?.cat || menuItem?.category_id || menuItem?.category || item.cat || item.category_id || item.category || "uncategorized";
    return categories.find(c => c.id === categoryId) || { id: categoryId, label: "Uncategorized", sort_order: 999999 };
  };

  const categoryRankForOrderItem = (item) => {
    const cat = categoryForOrderItem(item);
    const savedIndex = dashboardCategoryOrder.indexOf(cat.id);
    if (savedIndex !== -1) return savedIndex;
    const sortOrder = Number(cat.sort_order);
    return 100000 + (Number.isFinite(sortOrder) ? sortOrder : 999999);
  };

  const applyCardItemSort = (items) => {
    const base = applyDetailSort(items);
    const baseIndex = new Map(base.map((item, index) => [item, index]));
    return [...base].sort((a, b) => {
      const catCmp = categoryRankForOrderItem(a) - categoryRankForOrderItem(b);
      if (catCmp !== 0) return catCmp;
      return baseIndex.get(a) - baseIndex.get(b);
    });
  };

  const groupCardItemsByCategory = (items) => {
    const groups = [];
    const groupIndex = {};
    applyCardItemSort(items).forEach(item => {
      const cat = categoryForOrderItem(item);
      if (!(cat.id in groupIndex)) {
        groupIndex[cat.id] = groups.length;
        groups.push({ id: cat.id, label: cat.label || "Uncategorized", items: [] });
      }
      groups[groupIndex[cat.id]].items.push(item);
    });
    return groups;
  };

  const groupSummaryItemsByCategory = (groupOrders, predicate = () => true) => {
    const byName = {};
    groupOrders.forEach(o => visibleOrderItems(o.items).filter(predicate).forEach(item => {
      if (!byName[item.name]) byName[item.name] = { ...item, qty: 0, line_total: undefined, lineTotal: undefined };
      byName[item.name].qty += item.qty || 1;
    }));
    return groupCardItemsByCategory(Object.values(byName));
  };

  const toggleGroup = (key) => {
    setCollapseAllByDefault(false);
    setCollapsedGroups(prev => {
      const next = new Set(prev);
      next.has(key) ? next.delete(key) : next.add(key);
      return next;
    });
  };

  const toggleColAll = (colId, groups) => {
    const keys = groups.map(g => `${colId}__${g.key}`);
    const allCollapsed = keys.every(k => collapsedGroups.has(k));
    setCollapseAllByDefault(false);
    setCollapsedGroups(prev => {
      const next = new Set(prev);
      if (allCollapsed) { keys.forEach(k => next.delete(k)); } else { keys.forEach(k => next.add(k)); }
      return next;
    });
  };

  const groupedKeysForColumn = (colId, source) => {
    const groups = [];
    const seen = new Set();
    source
      .filter(o => o.status === colId)
      .forEach(o => {
        const name = o.customer || "Walk-in guest";
        const isAnon = !o.customer || o.customer === "Walk-in guest" || o.customer === "Walk-in";
        const key = isAnon ? `anon-${o.dbId || o.id}` : name;
        if (!seen.has(key)) {
          seen.add(key);
          groups.push(`${colId}__${key}`);
        }
      });
    return groups;
  };

  const groupedKeysForAllColumns = (source = filtered) =>
    cols.flatMap(col => groupedKeysForColumn(col.id, source));

  const toggleAllColumns = () => {
    const keys = groupedKeysForAllColumns();
    if (!keys.length) return;
    const allCollapsed = keys.every(k => collapsedGroups.has(k));
    setCollapseAllByDefault(!allCollapsed);
    setCollapsedGroups(prev => {
      const next = new Set(prev);
      if (allCollapsed) keys.forEach(k => next.delete(k));
      else keys.forEach(k => next.add(k));
      return next;
    });
  };
  const [dialog, setDialog] = React.useState(null); // { title, message, onConfirm }

  const showConfirm = (title, message, onConfirm, confirmLabel, cancelLabel) => setDialog({ title, message, onConfirm, confirmLabel, cancelLabel });
  const [query, setQuery] = React.useState(initialFilters.query);

  React.useEffect(() => {
    let disposed = false;
    const backend = window.TeaGangBackend;
    if (!backend?.enabled || !backend.fetchUserPreferences) {
      filtersPrefsLoadedRef.current = true;
      return;
    }
    backend.fetchUserPreferences("web").then(remote => {
      if (disposed) return;
      const saved = remote?.[ORDERS_FILTER_PREF_KEY];
      if (saved && typeof saved === "object") {
        const next = normalizeOrdersFilters(saved);
        setDateFilter(next.dateFilter);
        setDateBasis(next.dateBasis);
        setCustomDate(next.customDate);
        setCustomRange(next.customRange);
        setChannelFilter(next.channelFilter);
        setSourceFilter(next.sourceFilter);
        setQuery(next.query);
        setCollapseAllByDefault(next.collapseAllByDefault);
      }
    }).catch(err => {
      console.warn("Could not load order filter prefs from Supabase.", err);
    }).finally(() => {
      if (!disposed) filtersPrefsLoadedRef.current = true;
    });
    return () => { disposed = true; };
  }, []); // eslint-disable-line

  const notifyOrder = React.useCallback((title, message, tone = "success") => {
    window.TeaGangNotify?.(title, message, tone);
  }, []);
  const orderNoticeSubject = (order) => `${order?.id || "Order"} of ${order?.customer || "Walk-in guest"}`;
  const orderGroupNoticeSubject = (groupOrders) => {
    const name = groupOrders[0]?.customer || "Walk-in guest";
    const codes = groupOrders.map(o => o.id).filter(Boolean).join(", ");
    return `${codes || `${groupOrders.length} orders`} of ${name}`;
  };
  const stockAffectingStatuses = new Set(["ready", "completed"]);
  const orderAffectsStock = (order) => stockAffectingStatuses.has(order?.status);
  const confirmStockRollback = (count = 1) => {
    const subject = count === 1 ? "this order" : `${count} orders`;
    return window.confirm(
      `Rollback stock count for ${subject}?\n\nThis adds the order item quantities back to menu stock count.`
    );
  };
  const statusNoticeVerb = (status) => ({
    ready: "ready",
    completed: "completed",
    pending: "pending",
  }[status] || "updated");
  const beginLocalOrderMutation = React.useCallback(() => {
    window.TeaGangSuppressOrderRealtime?.();
  }, []);

  const rangeRef = React.useRef({});

  const getRange = (filter, date) => {
    const namedRange = window.TeaGangCustomDateRanges?.findByFilter?.(customDateRanges, filter);
    if (namedRange) return window.TeaGangCustomDateRanges.range(namedRange);
    const now = new Date();
    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    const day = 864e5;
    switch (filter) {
      case "all":    return {};
      case "today":  return { dateFrom: today, dateTo: new Date(today.getTime() + day) };
      case "week":   { const s = new Date(today); s.setDate(today.getDate() - ((today.getDay() || 7) - 1)); return { dateFrom: s, dateTo: new Date(today.getTime() + day) }; }
      case "month":  return { dateFrom: new Date(now.getFullYear(), now.getMonth()), dateTo: new Date(today.getTime() + day) };
      case "custom-day":   if (!date) return {}; { const d = new Date(date + "T00:00:00"); return { dateFrom: d, dateTo: new Date(d.getTime() + day) }; }
      case "custom-week":  if (!date) return {}; { const d = new Date(date + "T00:00:00"); const s = new Date(d); s.setDate(d.getDate() - ((d.getDay() || 7) - 1)); return { dateFrom: s, dateTo: new Date(s.getTime() + 7 * day) }; }
      case "custom-month": if (!date) return {}; { const [y, m] = date.split("-").map(Number); return { dateFrom: new Date(y, m - 1), dateTo: new Date(y, m) }; }
      case "custom-range": {
        if (!customRange.from || !customRange.to || customRange.from > customRange.to) return {};
        const from = new Date(customRange.from + "T00:00:00");
        const to = new Date(customRange.to + "T00:00:00");
        return { dateFrom: from, dateTo: new Date(to.getTime() + day) };
      }
      default: return {};
    }
  };

  React.useEffect(() => {
    let disposed = false;
    const loadRanges = async () => {
      const ranges = await window.TeaGangCustomDateRanges?.load?.();
      if (!disposed) setCustomDateRanges(ranges || []);
    };
    loadRanges();
    const onChanged = () => loadRanges();
    window.addEventListener(window.TeaGangCustomDateRanges?.event || "teaGangCustomDateRangesChanged", onChanged);
    return () => {
      disposed = true;
      window.removeEventListener(window.TeaGangCustomDateRanges?.event || "teaGangCustomDateRangesChanged", onChanged);
    };
  }, []);

  React.useEffect(() => {
    rangeRef.current = getRange(dateFilter, customDate);
    loadOrders();
  }, [dateFilter, dateBasis, customDate, customRange, customDateRanges]); // eslint-disable-line

  React.useEffect(() => {
    const filterPrefs = buildOrdersFilters({
      dateFilter,
      dateBasis,
      customDate,
      customRange,
      channelFilter,
      sourceFilter,
      query,
      collapseAllByDefault,
    });
    window.localStorage.setItem(ORDERS_FILTER_STORAGE_KEY, JSON.stringify(filterPrefs));
    window.dispatchEvent(new CustomEvent(ORDERS_FILTER_CHANGED_EVENT, { detail: filterPrefs }));

    const backend = window.TeaGangBackend;
    if (!backend?.enabled || !backend.fetchUserPreferences || !backend.upsertUserPreferences) return;
    if (!filtersPrefsLoadedRef.current) return;
    const timer = window.setTimeout(async () => {
      try {
        const remote = await backend.fetchUserPreferences("web") || {};
        await backend.upsertUserPreferences({ ...remote, [ORDERS_FILTER_PREF_KEY]: filterPrefs }, "web");
      } catch (err) {
        console.warn("Could not save order filter prefs to Supabase.", err);
      }
    }, 1000);
    return () => window.clearTimeout(timer);
  }, [dateFilter, dateBasis, customDate, customRange, channelFilter, sourceFilter, query, collapseAllByDefault]);

  const loadOrderItemTypes = React.useCallback(async () => {
    const backend = window.TeaGangBackend;
    if (!backend?.enabled || !backend.fetchOrderItemTypes) {
      setOrderItemTypes(prev => prev.length ? prev : FALLBACK_ORDER_ITEM_TYPES);
      return FALLBACK_ORDER_ITEM_TYPES;
    }
    try {
      const rows = await backend.fetchOrderItemTypes();
      const nextRows = rows || [];
      setOrderItemTypes(nextRows);
      return nextRows;
    } catch (error) {
      console.warn("Order item types unavailable.", error);
      setOrderItemTypes(prev => prev);
      return [];
    }
  }, []);

  const loadOrders = React.useCallback(async () => {
    const backend = window.TeaGangBackend;
    if (!backend?.enabled) return;
    const loadSeq = ordersLoadSeqRef.current + 1;
    ordersLoadSeqRef.current = loadSeq;
    try {
      const [orderRows, menu, customerRows, sourceRows, optionRows, itemTypeRows, orderNumber] = await Promise.all([
        backend.fetchOrders({ ...rangeRef.current, dateBasis }),
        backend.fetchMenu(),
        backend.searchCustomers?.({ query: "", limit: 50 }) || backend.fetchCustomers(),
        backend.fetchOrderSources(),
        backend.fetchDrinkOptions(),
        loadOrderItemTypes(),
        backend.nextOrderNumber(),
      ]);
      if (loadSeq !== ordersLoadSeqRef.current) return;
      setOrders(orderRows);
      setMenuItems(menu.items || []);
      setCategories(menu.cats || [{ id: "all", label: "All", count: 0 }]);
      setCustomers(customerRows || []);
      setOrderSources(sourceRows || []);
      setDrinkOptions(optionRows || []);
      setOrderItemTypes(itemTypeRows || []);
      setNextNumber(orderNumber || 1);
      if (collapseAllByDefault) {
        setCollapsedGroups(new Set(groupedKeysForAllColumns(orderRows || [])));
      }
    } catch (error) {
      if (loadSeq !== ordersLoadSeqRef.current) return;
      console.error("Orders backend unavailable.", error);
      setOrders([]);
    }
  }, [collapseAllByDefault, dateBasis, loadOrderItemTypes]);

  React.useEffect(() => { loadOrders(); }, [loadOrders]);

  React.useEffect(() => {
    const backend = window.TeaGangBackend;
    if (!backend?.subscribeToStoreData) return undefined;
    let disposed = false;
    let subscription = null;

    const refreshSoon = () => {
      if (!disposed) {
        loadOrders();
      }
    };

    backend
      .subscribeToStoreData(refreshSoon, ["orders", "order_items", "store_order_item_types"])
      .then((nextSubscription) => {
        if (disposed) {
          nextSubscription?.unsubscribe?.();
          return;
        }
        subscription = nextSubscription;
      })
      .catch((error) => {
        console.warn("Orders realtime unavailable.", error);
      });

    return () => {
      disposed = true;
      subscription?.unsubscribe?.();
    };
  }, [loadOrders]);

  const selected = orders.find(o => (o.dbId || o.id) === selectedId);
  const selectedItems = React.useMemo(() => {
    if (!selected?.items?.length) return selected?.items || [];
    return applyDetailSort(visibleOrderItems(selected.items));
  }, [selected, detailSort, detailSortDir]); // eslint-disable-line
  const selectedRegularItems = React.useMemo(
    () => selectedItems.filter(isRegularOrderItem),
    [selectedItems]
  );
  const selectedPromotionItems = React.useMemo(
    () => selectedItems.filter(item => !isRegularOrderItem(item)),
    [selectedItems]
  );
  const selectedRegularGroups = React.useMemo(
    () => groupCardItemsByCategory(selectedRegularItems),
    [selectedRegularItems, cardCategoryStyle, dashboardCategoryOrder, menuItems, categories, detailSort, detailSortDir] // eslint-disable-line
  );
  const selectedPromotionGroups = React.useMemo(
    () => groupCardItemsByCategory(selectedPromotionItems),
    [selectedPromotionItems, cardCategoryStyle, dashboardCategoryOrder, menuItems, categories, detailSort, detailSortDir] // eslint-disable-line
  );
  const renderDrawerItem = (it, index, keyPrefix, promotion = false) => {
    const menuItem = menuItemForOrderItem(it);
    const imageUrl = it.image_url || menuItem?.image_url || null;
    const imageZoom = Number(it.image_zoom ?? menuItem?.image_zoom ?? 1);
    const itemId = it.menuItemId || it.menu_item_id || menuItem?.id || it.id || it.name;
    return (
      <div key={`${keyPrefix}-${it.id || it.name || index}`} className={`detail-item ${promotion ? "promotion" : ""}`}>
        <div className={`detail-thumb ${imageUrl ? "has-image" : ""}`}>
          {imageUrl
            ? <MenuCachedImage itemId={itemId} imageUrl={imageUrl} alt={it.name} style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "contain", padding: 4, transform: `scale(${imageZoom})`, transformOrigin: "center" }} />
            : (it.glyph || glyphForName(it.name, menuItems))}
        </div>
        <div className="detail-main">
          <div className="detail-name">{it.name}</div>
          {showCardMods && getItemMods(it) && <div className="detail-mods">{getItemMods(it)}</div>}
        </div>
        <div className="detail-qty">×{it.qty}</div>
        <div className="detail-price">${orderItemLineTotal(it).toFixed(2)}</div>
      </div>
    );
  };
  const updateReceiptSetting = (key, value) => {
    setReceiptSettings(prev => ({ ...prev, [key]: value }));
  };
  const updatePrinterIp = (value) => {
    setPrinterIp(value);
    setPrinterIpError("");
  };
  const savePrinterIp = async () => {
    const backend = window.TeaGangBackend;
    const nextIp = String(printerIp || "").trim();
    setPrinterIpSaving(true);
    setPrinterIpError("");
    try {
      if (backend?.enabled && backend.updateStoreSettings) {
        const saved = await backend.updateStoreSettings({ printer_ip: nextIp });
        const savedIp = saved?.printer_ip || "";
        setPrinterIp(savedIp);
        if (savedIp) window.localStorage.setItem("teaGangPrinterIp", savedIp);
        else window.localStorage.removeItem("teaGangPrinterIp");
      } else {
        setPrinterIp(nextIp);
        if (nextIp) window.localStorage.setItem("teaGangPrinterIp", nextIp);
        else window.localStorage.removeItem("teaGangPrinterIp");
      }
    } catch (err) {
      console.warn("Could not save printer IP.", err);
      setPrinterIpError(err.message || "Could not save printer IP.");
    } finally {
      setPrinterIpSaving(false);
    }
  };
  const loadLatestPrinterIp = async () => {
    const backend = window.TeaGangBackend;
    const fallbackIp = String(printerIp || window.localStorage.getItem("teaGangPrinterIp") || "").trim();
    if (!backend?.enabled || !backend.fetchStoreSettings) return fallbackIp;
    try {
      const settings = await backend.fetchStoreSettings();
      const latestIp = String(settings?.printer_ip || "").trim();
      setPrinterIp(latestIp);
      if (latestIp) window.localStorage.setItem("teaGangPrinterIp", latestIp);
      else window.localStorage.removeItem("teaGangPrinterIp");
      setPrinterIpError("");
      return latestIp || fallbackIp;
    } catch (err) {
      console.warn("Could not refresh printer IP before printing.", err);
      setPrinterIpError(err.message || "Could not load printer IP.");
      return fallbackIp;
    }
  };
  const receiptEscape = (value) => String(value ?? "").replace(/[&<>"']/g, ch => ({
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': "&quot;",
    "'": "&#39;",
  }[ch]));
  const receiptItemLines = (items, showDetails) => items.map(item => {
    const mods = showDetails ? getItemMods(item) : null;
    return `
      <div class="receipt-item">
        <div class="receipt-item-main">
          <span class="receipt-qty">${receiptEscape(item.qty || 1)}</span>
          <span class="receipt-times">×</span>
          <span class="receipt-name">${receiptEscape(item.name)}</span>
          <span class="receipt-price">$${money(orderItemLineTotal(item))}</span>
        </div>
        ${mods ? `<div class="receipt-mods">${receiptEscape(mods)}</div>` : ""}
      </div>
    `;
  }).join("");
  const receiptItemsWithCategorySeparators = (items, showDetails) => {
    const groups = groupCardItemsByCategory(items);
    return groups.map((group, index) => `
      ${index > 0 ? `<div class="category-gap"></div><div class="category-rule"></div>` : ""}
      ${receiptItemLines(group.items, showDetails)}
    `).join("");
  };
  const foldReceiptText = (value) => String(value ?? "")
    .replace(/đ/g, "d")
    .replace(/Đ/g, "D")
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^\x20-\x7E\n]/g, "");
  const escposText = (value) => Array.from(String(value ?? ""), ch => ch.charCodeAt(0) & 0xFF);
  const escposLine = (left, right = "", width = 42) => {
    const l = foldReceiptText(left);
    const r = foldReceiptText(right);
    if (!r) return `${l}\n`;
    const gap = Math.max(1, width - l.length - r.length);
    return `${l}${" ".repeat(gap)}${r}\n`;
  };
  const pushEscposWrappedItem = (lines, qty, name, price, width = 42) => {
    const prefix = `${qty}x `;
    const right = `$${money(price)}`;
    const available = Math.max(8, width - prefix.length - right.length - 1);
    const cleanName = foldReceiptText(name);
    const words = cleanName.split(/\s+/).filter(Boolean);
    let current = "";
    words.forEach(word => {
      const next = current ? `${current} ${word}` : word;
      if (next.length > available && current) {
        lines.push(`${prefix}${current}\n`);
        current = word;
      } else {
        current = next;
      }
    });
    if (!current) current = cleanName.slice(0, available);
    lines.push(escposLine(`${prefix}${current}`, right, width));
  };
  const buildDirectReceiptBytes = (order) => {
    const settings = receiptSettings;
    const sortedItems = applyDetailSort(visibleOrderItems(order.items));
    const regular = sortedItems.filter(isRegularOrderItem);
    const promotion = sortedItems.filter(item => !isRegularOrderItem(item));
    const scheduled = formatScheduledDateTime(order.scheduled_at);
    const orderNumber = order.order_number || String(order.id || "").replace(/\D/g, "") || order.id;
    const promoLoss = promotion.reduce((sum, item) => {
      const saved = Number(item.line_discount_amount || item.lineDiscountAmount || 0);
      if (saved > 0) return sum + saved;
      const qty = Number(item.qty || 1);
      const original = Number(item.original_unit_price ?? item.originalUnitPrice ?? item.price ?? 0);
      const adjusted = Number(item.adjusted_unit_price ?? item.adjustedUnitPrice ?? item.price ?? 0);
      return sum + Math.max(0, original - adjusted) * qty;
    }, 0);
    const discount = orderDiscount(order);
    const displaySubtotal = Number(order.total || 0) + promoLoss + discount;
    const storeName = window.localStorage.getItem("teaGangStoreName") || "The Tea Gang";
    const lines = [];
    const separator = "-".repeat(42);
    const sectionSeparator = "- ".repeat(21).trim();
    const addItems = (items, showDetails) => {
      const groups = groupCardItemsByCategory(items);
      groups.forEach((group, index) => {
        if (index > 0) lines.push(`${separator}\n`);
        group.items.forEach(item => {
          pushEscposWrappedItem(lines, item.qty || 1, item.name, orderItemLineTotal(item));
          const mods = showDetails ? getItemMods(item) : null;
          if (mods) lines.push(`     ${foldReceiptText(mods)}\n`);
        });
      });
    };

    lines.push("\x1B\x40");
    lines.push("\x1B\x61\x01");
    lines.push("\x1B\x45\x01");
    lines.push(`${foldReceiptText(storeName)}\n`);
    lines.push("\x1B\x45\x00");
    lines.push("Customer Receipt\n\n");
    lines.push("\x1B\x61\x00");
    if (settings.showOrderNumber) lines.push(escposLine("Order #", orderNumber, 42));
    if (settings.showOrderType) lines.push(escposLine("Type", order.channel === "delivery" ? "Delivery" : "Pick Up", 42));
    if (settings.showScheduledTime && scheduled) lines.push(escposLine("Scheduled", scheduled, 42));
    if (settings.showCustomer) lines.push(escposLine("Customer", order.customer || "Walk-in guest", 42));
    if (settings.showNote && order.note) lines.push(escposLine("Note", order.note, 42));
    lines.push(`${sectionSeparator}\n`);
    addItems(regular, settings.showItemDetails);
    if (promotion.length) {
      lines.push(`${sectionSeparator}\n`);
      lines.push("PROMOTIONS & COMPS\n");
      addItems(promotion, false);
    }
    lines.push(`${sectionSeparator}\n`);
    if (promoLoss > 0 || discount > 0) lines.push(escposLine("SUBTOTAL", `$${money(displaySubtotal)}`, 42));
    if (promoLoss > 0) lines.push(escposLine("PROMOS / COMPS", `-$${money(promoLoss)}`, 42));
    if (discount > 0) lines.push(escposLine("DISCOUNT", `-${orderDiscountLabel(order)}`, 42));
    lines.push(escposLine("TOTAL", `$${money(order.total || 0)}`, 42));
    if (settings.showThankYou) {
      lines.push("\n\x1B\x61\x01");
      lines.push(`${foldReceiptText(settings.footer || "Thank you!")}\n`);
    }
    lines.push("\n\n\n\x1D\x56\x42\x00");
    return lines.flatMap(escposText);
  };
  const buildReceiptHtml = (order, activePrinterIp = "", autoPrint = true) => {
    const settings = receiptSettings;
    const sortedItems = applyDetailSort(visibleOrderItems(order.items));
    const regular = sortedItems.filter(isRegularOrderItem);
    const promotion = sortedItems.filter(item => !isRegularOrderItem(item));
    const width = settings.paperWidth === "58" ? "58mm" : "80mm";
    const previewWidth = "560px";
    const scheduled = formatScheduledDateTime(order.scheduled_at);
    const orderNumber = order.order_number || String(order.id || "").replace(/\D/g, "") || order.id;
    const promoLoss = promotion.reduce((sum, item) => {
      const saved = Number(item.line_discount_amount || item.lineDiscountAmount || 0);
      if (saved > 0) return sum + saved;
      const qty = Number(item.qty || 1);
      const original = Number(item.original_unit_price ?? item.originalUnitPrice ?? item.price ?? 0);
      const adjusted = Number(item.adjusted_unit_price ?? item.adjustedUnitPrice ?? item.price ?? 0);
      return sum + Math.max(0, original - adjusted) * qty;
    }, 0);
    const discount = orderDiscount(order);
    const displaySubtotal = Number(order.total || 0) + promoLoss + discount;
    const storeName = window.localStorage.getItem("teaGangStoreName") || "The Tea Gang";
    const orderTypeIcon = order.channel === "delivery"
      ? `<svg class="type-icon" viewBox="0 0 24 24"><path d="M3 7h13l1 4h4v6h-2"/><circle cx="7" cy="18" r="2"/><circle cx="17" cy="18" r="2"/><path d="M3 7v10h2"/></svg>`
      : `<svg class="type-icon" viewBox="0 0 24 24"><path d="M3 9 5 4h14l2 5"/><path d="M3 9h18v3a3 3 0 0 1-6 0 3 3 0 0 1-6 0 3 3 0 0 1-6 0V9Z"/><path d="M5 13v8h14v-8"/></svg>`;
    const cupIcon = `<svg class="cup-icon" viewBox="0 0 24 24"><path d="M6 8h12l-1 11a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L6 8Z"/><path d="M18 11h2a2 2 0 0 1 0 4h-2"/></svg>`;
    return `<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>${receiptEscape(activePrinterIp ? `Printer ${activePrinterIp}` : "Printer not set")} · ${receiptEscape(order.id)} Receipt</title>
<style>
  @page { size: ${width} auto; margin: 0; }
  * { box-sizing: border-box; }
  body { margin: 0; background: #fff; color: #111; font: 18.9px/1.12 Arial, Helvetica, sans-serif; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .receipt { width: ${previewWidth}; padding: 28px; }
  .center { text-align: center; }
  .shop-spacer { height: 14px; }
  .shop { font-size: 28.35px; font-weight: 700; line-height: 1; letter-spacing: 0; }
  .title { margin-top: 3.5px; font-size: 18.9px; font-weight: 400; }
  .meta { margin-top: 28px; }
  .meta-row { display: grid; grid-template-columns: 112px 1fr; gap: 14px; margin: 0 0 3.5px; align-items: start; }
  .type-icon, .cup-icon { width: 21px; height: 21px; fill: none; stroke: #111; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; vertical-align: -4px; }
  .type-icon { margin-right: 7px; }
  .rule-wrap { padding: 14px 0; }
  .receipt-rule, .category-rule { height: 1.225px; width: 100%; }
  .receipt-rule { background: repeating-linear-gradient(to right, #111 0 5.25px, transparent 5.25px 15.75px); }
  .category-gap { height: 3.5px; }
  .category-rule { margin: 1.75px 0 5.25px; background: repeating-linear-gradient(to right, #111 0 5.25px, transparent 5.25px 7.7px); }
  .receipt-item { margin: 0 0 8.75px; }
  .receipt-item-main { display: grid; grid-template-columns: 35px auto minmax(0, 1fr) auto; align-items: start; }
  .receipt-qty { text-align: right; }
  .receipt-times { padding-left: 1.75px; padding-right: 7px; }
  .receipt-price { white-space: nowrap; }
  .receipt-name { min-width: 0; }
  .receipt-mods { padding-left: 50.75px; font-size: 17.3px; color: #111; }
  .section { margin: 0 0 7px; font-weight: 700; text-transform: uppercase; font-size: 17.15px; }
  .row { display: flex; justify-content: space-between; gap: 14px; font-size: 20.5px; line-height: 1.12; margin-bottom: 3.5px; }
  .total { text-transform: uppercase; }
  .totals { padding-top: 10.5px; }
  .footer { margin-top: 24.5px; text-align: center; font-size: 18.9px; }
  .cup-icon { margin-right: 7px; }
  .receipt-bottom-spacer { height: 14px; }
  @media print { body { width: ${width}; } }
</style>
</head>
<body>
  <div class="receipt">
    <div class="center">
      <div class="shop-spacer"></div>
      <div class="shop">${receiptEscape(storeName)}</div>
      <div class="title">Customer Receipt</div>
    </div>
    <div class="meta">
      ${settings.showOrderNumber ? `<div class="meta-row"><span>Order #</span><span>${receiptEscape(orderNumber)}</span></div>` : ""}
      ${settings.showOrderType ? `<div class="meta-row"><span>Type</span><span>${orderTypeIcon}${receiptEscape(order.channel === "delivery" ? "Delivery" : "Pick Up")}</span></div>` : ""}
      ${settings.showScheduledTime && scheduled ? `<div class="meta-row"><span>Scheduled</span><span>${receiptEscape(scheduled)}</span></div>` : ""}
      ${settings.showCustomer ? `<div class="meta-row"><span>Customer</span><span>${receiptEscape(order.customer || "Walk-in guest")}</span></div>` : ""}
      ${settings.showNote && order.note ? `<div class="meta-row"><span>Note</span><span>${receiptEscape(order.note)}</span></div>` : ""}
    </div>
    <div class="rule-wrap"><div class="receipt-rule"></div></div>
    ${receiptItemsWithCategorySeparators(regular, settings.showItemDetails)}
    ${promotion.length ? `<div style="height:7px"></div><div class="receipt-rule"></div><div style="height:10.5px"></div><div class="section">PROMOTIONS & COMPS</div>${receiptItemsWithCategorySeparators(promotion, false)}` : ""}
    <div class="receipt-rule"></div>
    <div class="totals">
      ${(promoLoss > 0 || discount > 0) ? `<div class="row"><span>SUBTOTAL</span><span>$${money(displaySubtotal)}</span></div>` : ""}
      ${promoLoss > 0 ? `<div class="row"><span>PROMOS / COMPS</span><span>-$${money(promoLoss)}</span></div>` : ""}
      ${discount > 0 ? `<div class="row"><span>DISCOUNT</span><span>-${orderDiscountLabel(order)}</span></div>` : ""}
      <div class="row total"><span>TOTAL</span><span>$${money(order.total || 0)}</span></div>
    </div>
    ${settings.showThankYou ? `<div class="footer">${cupIcon}${receiptEscape(settings.footer || "Thank you!")}</div>` : ""}
    <div class="receipt-bottom-spacer"></div>
  </div>
  ${autoPrint ? `<script>
    window.addEventListener("load", () => setTimeout(() => window.print(), 150));
  </script>` : ""}
</body>
</html>`;
  };
  const printReceipt = async (order) => {
    if (!order || printingOrderId) return false;
    setPrintingOrderId(order.dbId || order.id);
    try {
      const activePrinterIp = await loadLatestPrinterIp();
      if (!activePrinterIp) {
        showPrintNotice("No printer IP set. Add it in Order settings.");
        return false;
      }
      const response = await fetch(PRINT_BRIDGE_URL, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          ip: activePrinterIp,
          port: 9100,
          bytes: buildDirectReceiptBytes(order),
        }),
      });
      const result = await response.json().catch(() => ({}));
      if (!response.ok || result.ok === false) {
        throw new Error(result.error || "Print bridge failed.");
      }
      return true;
    } catch (err) {
      const message = err instanceof TypeError
        ? "Print bridge is not running."
        : (err.message || "Could not print directly.");
      showPrintNotice(message);
      return false;
    } finally {
      setTimeout(() => setPrintingOrderId(null), 500);
    }
  };
  const openReceiptPrintPreview = async (order) => {
    if (!order) return;
    setPrintPreviewOrder(order);
    loadLatestPrinterIp();
  };
  // Scale the receipt preview to fill its canvas width without horizontal scrolling.
  React.useEffect(() => {
    if (!printPreviewOrder) return;
    setPreviewContentHeight(760);
    const el = previewCanvasRef.current;
    if (!el) return;
    const update = () => {
      const available = el.clientWidth - 24; // account for canvas padding
      setPreviewScale(Math.min(1, Math.max(0.4, available / 560)));
    };
    update();
    const ro = new ResizeObserver(update);
    ro.observe(el);
    return () => ro.disconnect();
  }, [printPreviewOrder]);
  const handlePreviewLoad = () => {
    const doc = previewFrameRef.current?.contentDocument;
    const height = doc?.documentElement?.scrollHeight || doc?.body?.scrollHeight;
    if (height) setPreviewContentHeight(height);
  };
  const openBrowserPrintPreview = async (order) => {
    if (!order) return;
    const activePrinterIp = await loadLatestPrinterIp();
    const printWindow = window.open("", "_blank", "width=640,height=720");
    if (!printWindow) {
      showPrintNotice("Could not open receipt preview. Allow popups for this site.");
      return;
    }
    printWindow.document.open();
    printWindow.document.write(buildReceiptHtml(order, activePrinterIp));
    printWindow.document.close();
  };
  const printTestReceipt = () => {
    const sample = {
      id: "TEST",
      customer: "Walk-in guest",
      channel: "pickup",
      source: "Counter",
      scheduled_at: new Date().toISOString(),
      total: 7,
      items: [{ id: "test", name: "Test Milk Tea", qty: 1, price: 7, line_total: 7, pricing_mode: "normal", line_type_name: "Regular Sale" }],
    };
    printReceipt(sample);
  };
  const cols = [
    { id: "pending",   title: "Pending",   icon: "bell",  hint: "Awaiting brew" },
    { id: "ready",     title: "Ready",     icon: "check", hint: "Awaiting pickup" },
    { id: "completed", title: "Completed", icon: "clock", hint: "Handed off" },
  ];

  // Search filter — matches customer name OR any order-item name (Vietnamese/accent insensitive)
  const q = normalizeVi(query.trim());
  const searchFiltered = q
    ? orders.filter(o =>
        normalizeVi(o.customer || "").includes(q) ||
        normalizeVi(o.id || "").includes(q) ||
        visibleOrderItems(o.items).some(it => normalizeVi(it.name || "").includes(q))
      )
    : orders;
  const filtered = searchFiltered.filter(o => {
    const channelOk = channelFilter === "all" || o.channel === channelFilter;
    const sourceOk = sourceFilter === "all" || o.source_id === sourceFilter || o.source === sourceFilter;
    return channelOk && sourceOk;
  });

  const removeGroup = (groupOrders) => {
    if (moving) return;
    setGroupMenu(null);
    const name = groupOrders[0]?.customer || "Walk-in guest";
    showConfirm(
      "Remove all orders?",
      `Remove all ${groupOrders.length} order${groupOrders.length > 1 ? "s" : ""} for ${name}? This cannot be undone.`,
      async () => {
        const stockOrders = groupOrders.filter(orderAffectsStock);
        const rollbackStock = stockOrders.length ? confirmStockRollback(stockOrders.length) : false;
        setMoving(true);
        beginLocalOrderMutation();
        try {
          for (const o of groupOrders) {
            await window.TeaGangBackend.deleteOrder(o, {
              rollbackStock: rollbackStock && orderAffectsStock(o),
            });
          }
          await loadOrders();
          notifyOrder("Orders removed", `${orderGroupNoticeSubject(groupOrders)} are removed.`);
        } catch (e) {
          console.error("Could not remove group.", e);
        } finally {
          setMoving(false);
          setSelectedId(null);
        }
      },
      "Remove all",
      "Keep"
    );
  };

  const moveGroup = async (groupOrders, status, payment) => {
    if (moving) return;
    setGroupMenu(null);
    setMoving(true);
    beginLocalOrderMutation();
    try {
      for (const o of groupOrders) {
        await window.TeaGangBackend.updateOrderStatus(o, status, payment);
      }
      await loadOrders();
      notifyOrder(
        "Orders updated",
        `${orderGroupNoticeSubject(groupOrders)} are ${statusNoticeVerb(status)}.`
      );
    } catch (e) {
      console.error("Could not update group.", e);
    } finally {
      setMoving(false);
    }
  };

  const move = async (id, next, payment) => {
    const order = orders.find(o => (o.dbId || o.id) === id);
    if (!order || moving) return;
    setMoving(true);
    beginLocalOrderMutation();
    try {
      await window.TeaGangBackend.updateOrderStatus(order, next, payment);
      await loadOrders();
      notifyOrder("Order updated", `${orderNoticeSubject(order)} is ${statusNoticeVerb(next)}.`);
    } catch (error) {
      console.error("Could not update order status.", error);
    } finally {
      setMoving(false);
    }
  };
  const removeOrder = async (id) => {
    const order = orders.find(o => (o.dbId || o.id) === id);
    if (!order || moving) return;
    showConfirm(
      "Remove order?",
      `Remove ${order.id} of ${order.customer}? This cannot be undone.`,
      async () => {
        const rollbackStock = orderAffectsStock(order) ? confirmStockRollback(1) : false;
        setMoving(true);
        beginLocalOrderMutation();
        try {
          await window.TeaGangBackend.deleteOrder(order, { rollbackStock });
          await loadOrders();
          notifyOrder("Order removed", `${orderNoticeSubject(order)} is removed.`);
        } catch (error) {
          console.error("Could not delete order.", error);
        } finally {
          setMoving(false);
          setSelectedId(null);
        }
      },
      "Remove",
      "Keep"
    );
  };
  const nextStatus = (cur) => cur === "pending" ? "ready" : cur === "ready" ? "completed" : "completed";
  const nextLabel  = (cur) => cur === "pending" ? "Mark ready" : cur === "ready" ? "Hand off & complete" : "Completed";

  const totalToday = orders.length;
  const inFlight = orders.filter(o => o.status !== "completed").length;
  const nextOrderNumber = nextNumber;

  const createOrder = async (newOrder) => {
    try {
      beginLocalOrderMutation();
      const saved = await window.TeaGangBackend.saveOrder(newOrder);
      await loadOrders();
      setSelectedId(saved.dbId || saved.id);
      notifyOrder("Order added", `${orderNoticeSubject(saved || newOrder)} is added.`);
    } catch (error) {
      console.error("Could not create order.", error);
    }
    setModalOpen(false);
  };
  const updateOrder = async (updated) => {
    try {
      beginLocalOrderMutation();
      await window.TeaGangBackend.saveOrder(updated);
      await loadOrders();
      notifyOrder("Order edited", `${orderNoticeSubject(updated)} is edited.`);
    } catch (error) {
      console.error("Could not update order.", error);
    }
    setEditingOrder(null);
    setModalOpen(false);
  };
  const startNewOrder = async () => {
    setEditingOrder(null);
    await loadOrderItemTypes();
    setModalOpen(true);
  };
  const startEdit = async (order) => {
    await loadOrderItemTypes();
    setEditingOrder(order);
    setModalOpen(true);
  };
  const closeModal = () => {
    setModalOpen(false);
    setEditingOrder(null);
  };

  return (
    <div>
      <div className="page-head">
        <div>
          <div className="page-eyebrow">Order queue</div>
          <h1 className="page-title">Today's Orders</h1>
          <div className="page-meta" style={{ marginTop: 8 }}>
            <span className="ornament"></span>
            {q ? (
              <span>
                <b style={{ color: "var(--gold-700)" }}>{filtered.length}</b> match{filtered.length !== 1 ? "es" : ""} for "<i style={{ color: "var(--green-700)" }}>{query}</i>" · <b>{orders.length}</b> total today
              </span>
            ) : (
              <span><b style={{ color: "var(--green-700)" }}>{inFlight}</b> in flight · <b>{totalToday}</b> total today · avg fulfillment <b>4m 18s</b></span>
            )}
          </div>
        </div>
        <div className="row" style={{ gap: 10 }}>
          <div className="search" style={{ width: 240, margin: 0 }}>
            <Ic name="search" size={14} />
            <input
              placeholder="Customer or drink…"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
            />
            {query && (
              <button
                className="icon-btn"
                style={{ width: 22, height: 22, border: "none", background: "transparent", padding: 0 }}
                onClick={() => setQuery("")}
                title="Clear"
              ><Ic name="x" size={12} /></button>
            )}
          </div>
          <button className="btn btn-gold" onClick={startNewOrder}><Ic name="plus" /> New order</button>
        </div>
      </div>

      {/* Date filter bar */}
      {(() => {
        const chips = [
          { id: "all", label: "All" },
          { id: "today", label: "Today" },
          { id: "week",  label: "This week" },
          { id: "month", label: "This month" },
        ];
        const fmtDay   = v => new Date(v + "T00:00:00").toLocaleDateString("en-US", { month: "short", day: "numeric" });
        const fmtWeek  = v => { const d = new Date(v + "T00:00:00"); const s = new Date(d); s.setDate(d.getDate() - d.getDay()); return "Wk of " + s.toLocaleDateString("en-US", { month: "short", day: "numeric" }); };
        const fmtMonth = v => { const [y, m] = v.split("-"); return new Date(+y, +m - 1).toLocaleDateString("en-US", { month: "short", year: "numeric" }); };
        const fmtRange = r => r.from && r.to ? `${fmtDay(r.from)} - ${fmtDay(r.to)}` : "Date range";
        const chipStyle = (id) => ({ display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 13px", borderRadius: 999, fontSize: 12.5, fontWeight: 600, cursor: "pointer", border: `1px solid ${dateFilter === id ? "var(--green-700)" : "var(--line)"}`, background: dateFilter === id ? "var(--green-700)" : "var(--cream-50)", color: dateFilter === id ? "white" : "var(--ink-700)" });
        const basisBtn = (id) => ({ display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 11px", borderRadius: 999, fontSize: 12.5, fontWeight: 700, cursor: "pointer", border: `1px solid ${dateBasis === id ? "var(--gold-600)" : "var(--line)"}`, background: dateBasis === id ? "rgba(200,162,92,0.18)" : "var(--cream-50)", color: dateBasis === id ? "var(--green-800)" : "var(--ink-600)" });
        const customPickers = [
          { id: "custom-day", label: dateFilter === "custom-day" && customDate ? fmtDay(customDate) : "Day", icon: "calendar", type: "date", eyebrow: "Date filter", title: "Choose a day", inputLabel: "Day" },
          { id: "custom-week", label: dateFilter === "custom-week" && customDate ? fmtWeek(customDate) : "Week", icon: "calendar", type: "date", eyebrow: "Date filter", title: "Choose a week", inputLabel: "Any day in the week" },
          { id: "custom-month", label: dateFilter === "custom-month" && customDate ? fmtMonth(customDate) : "Month", icon: "calendar", type: "month", eyebrow: "Date filter", title: "Choose a month", inputLabel: "Month" },
          { id: "custom-range", label: dateFilter === "custom-range" ? fmtRange(customRange) : "Date range", icon: "calendar", type: "date", eyebrow: "Date filter", title: "Choose a date range", inputLabel: "Range", kind: "range" },
        ];
        const namedRanges = customDateRanges.map(range => ({
          id: window.TeaGangCustomDateRanges.filterId(range),
          label: range.name,
        }));
        const selectCustomDate = (id, value) => {
          if (!value) return;
          setDateFilter(id);
          if (id === "custom-range") {
            setCustomRange(value);
            setCustomDate("");
          } else {
            setCustomDate(value);
          }
          setDateDialog(null);
        };
        return (
          <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginBottom: 16 }}>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 6, color: "var(--ink-500)", fontSize: 12, fontWeight: 800, textTransform: "uppercase", letterSpacing: 0.4 }}>Organize by</span>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 4, padding: 3, border: "1px solid var(--line)", borderRadius: 999, background: "var(--cream-100)" }}>
              <button style={basisBtn("created")} onClick={() => setDateBasis("created")}>Added date</button>
              <button style={basisBtn("scheduled")} onClick={() => setDateBasis("scheduled")}>Pickup/delivery date</button>
            </span>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 6, color: "var(--ink-500)", fontSize: 12, fontWeight: 800, textTransform: "uppercase", letterSpacing: 0.4 }}>Added date filter</span>
            {chips.map(c => (
              <button key={c.id} style={chipStyle(c.id)} onClick={() => { setDateFilter(c.id); setCustomDate(""); setDateDialog(null); }}>{c.label}</button>
            ))}
            {namedRanges.map(c => (
              <button key={c.id} style={chipStyle(c.id)} onClick={() => { setDateFilter(c.id); setCustomDate(""); setDateDialog(null); }}>
                <Ic name="calendar" size={12} /> {c.label}
              </button>
            ))}
            {customPickers.map(p => (
              <span key={p.id} style={{ display: "inline-flex" }}>
                <button style={chipStyle(p.id)} onClick={() => setDateDialog(p)}>
                  <Ic name={p.icon} size={12} /> {p.label}
                </button>
              </span>
            ))}
            {dateDialog && (
              <DateFilterDialog
                config={dateDialog}
                initialValue={dateDialog.kind === "range" ? customRange : (dateFilter === dateDialog.id ? customDate : "")}
                onApply={selectCustomDate}
                onCancel={() => setDateDialog(null)}
              />
            )}
          </div>
        );
      })()}

      {(() => {
        const allGroupKeys = groupedKeysForAllColumns();
        const allCollapsed = allGroupKeys.length > 0 && allGroupKeys.every(k => collapsedGroups.has(k));
        const sourceOptions = Array.from(new Map([
          ...orderSources.map(s => [s.id || s.name || s, { id: s.id || s.name || s, label: s.name || s }]),
          ...orders.filter(o => o.source).map(o => [o.source_id || o.source, { id: o.source_id || o.source, label: o.source }]),
        ]).values());
        return (
          <div className="order-board-tools">
            <button className="btn btn-ghost" disabled={!allGroupKeys.length} onClick={toggleAllColumns} style={{ padding: "7px 12px" }}>
              {allCollapsed ? "Expand all columns" : "Collapse all columns"}
            </button>
            <div className="seg order-filter-seg">
              {[
                { id: "all", label: "All" },
                { id: "pickup", label: "Pickup" },
                { id: "delivery", label: "Delivery" },
              ].map(f => (
                <button key={f.id} className={channelFilter === f.id ? "on" : ""} onClick={() => setChannelFilter(f.id)}>
                  {f.label}
                </button>
              ))}
            </div>
            <div className="order-source-filter">
              <Ic name="filter" size={13} />
              <select value={sourceFilter} onChange={(e) => setSourceFilter(e.target.value)}>
                <option value="all">All sources</option>
                {sourceOptions.map(s => (
                  <option key={s.id} value={s.id}>{s.label}</option>
                ))}
              </select>
            </div>
            <div ref={sortPanelRef} style={{ position: "relative" }}>
              <button
                className="btn btn-ghost"
                style={{ padding: "7px 12px", background: sortPanelOpen ? "rgba(53,97,72,0.08)" : undefined }}
                onClick={() => setSortPanelOpen(v => !v)}
              >
                <Ic name="settings" size={13} /> Order settings
              </button>
              {sortPanelOpen && (() => {
                const chip = (active) => ({
                  padding: "4px 11px", borderRadius: 999, fontSize: 12, fontWeight: 600, cursor: "pointer",
                  border: `1px solid ${active ? "var(--green-700)" : "var(--line)"}`,
                  background: active ? "var(--green-700)" : "var(--cream-50)",
                  color: active ? "white" : "var(--ink-700)",
                });
                const dirBtn = (active) => ({
                  display: "inline-flex", alignItems: "center", gap: 4,
                  padding: "4px 11px", borderRadius: 999, fontSize: 12, fontWeight: 600, cursor: "pointer",
                  border: `1px solid ${active ? "var(--green-700)" : "var(--line)"}`,
                  background: active ? "var(--green-700)" : "var(--cream-50)",
                  color: active ? "white" : "var(--ink-700)",
                });
                const label = { fontSize: 10.5, letterSpacing: ".18em", textTransform: "uppercase", color: "var(--ink-500)", marginBottom: 6, display: "block" };
                const row = { display: "flex", flexWrap: "wrap", gap: 5, marginBottom: 6 };
                return (
                  <div style={{ position: "absolute", left: "50%", top: "calc(100% + 8px)", transform: "translateX(-50%)", zIndex: 200, background: "var(--cream-50)", border: "1px solid var(--line-gold)", borderRadius: "var(--r-lg)", boxShadow: "0 18px 70px rgba(0,0,0,0.24)", padding: 18, width: 700, maxWidth: "calc(100vw - 32px)", maxHeight: "calc(100vh - 260px)", overflowY: "auto" }}>
                    <div style={{ fontFamily: "var(--font-display)", fontSize: 16, color: "var(--green-800)", marginBottom: 14 }}>Order settings</div>

                    <div style={{ display: "grid", gridTemplateColumns: "1fr 280px", gap: 18, alignItems: "start" }}>
                      <div>
                        <div style={{ marginBottom: 14 }}>
                          <span style={label}>Orders</span>
                          <div style={row}>
                            <button style={dirBtn(ordersSortDir === "asc")} onClick={() => setOrdersSortDir("asc")}>↑ Ascending</button>
                            <button style={dirBtn(ordersSortDir === "desc")} onClick={() => setOrdersSortDir("desc")}>↓ Descending</button>
                          </div>
                        </div>

                        <div style={{ borderTop: "1px solid var(--line-soft)", paddingTop: 12, marginBottom: 14 }}>
                          <span style={label}>Group summary chips</span>
                          <div style={row}>
                            {[["count","By count"],["alpha","A – Z"]].map(([id,lbl]) => (
                              <button key={id} style={chip(chipsSort === id)} onClick={() => setChipsSort(id)}>{lbl}</button>
                            ))}
                          </div>
                          <div style={row}>
                            <button style={dirBtn(chipsSortDir === "asc")} onClick={() => setChipsSortDir("asc")}>↑ Ascending</button>
                            <button style={dirBtn(chipsSortDir === "desc")} onClick={() => setChipsSortDir("desc")}>↓ Descending</button>
                          </div>
                        </div>

                        <div style={{ borderTop: "1px solid var(--line-soft)", paddingTop: 12, marginBottom: 14 }}>
                          <span style={label}>Order detail items</span>
                          <div style={row}>
                            {[["count","By count"],["alpha","A – Z"]].map(([id,lbl]) => (
                              <button key={id} style={chip(detailSort === id)} onClick={() => setDetailSort(id)}>{lbl}</button>
                            ))}
                          </div>
                          <div style={row}>
                            <button style={dirBtn(detailSortDir === "asc")} onClick={() => setDetailSortDir("asc")}>↑ Ascending</button>
                            <button style={dirBtn(detailSortDir === "desc")} onClick={() => setDetailSortDir("desc")}>↓ Descending</button>
                          </div>
                        </div>

                        <div style={{ borderTop: "1px solid var(--line-soft)", paddingTop: 12 }}>
                          <span style={label}>Card details</span>
                          <div style={{ ...row, marginBottom: 8 }}>
                            {[["labels","Category labels"],["lines","Thin lines"]].map(([id,lbl]) => (
                              <button key={id} style={chip(cardCategoryStyle === id)} onClick={() => setCardCategoryStyle(id)}>{lbl}</button>
                            ))}
                          </div>
                          <button
                            style={{ display: "flex", alignItems: "center", gap: 8, width: "100%", padding: "7px 10px", borderRadius: "var(--r-md)", border: `1px solid ${showCardMods ? "var(--green-600)" : "var(--line)"}`, background: showCardMods ? "rgba(53,97,72,0.13)" : "var(--cream-100)", cursor: "pointer", fontSize: 12.5, color: "var(--ink-700)" }}
                            onClick={() => setShowCardMods(v => !v)}
                          >
                            <span style={{ width: 32, height: 18, borderRadius: 999, background: showCardMods ? "var(--green-700)" : "var(--ink-300)", position: "relative", flexShrink: 0, transition: "background .15s" }}>
                              <span style={{ position: "absolute", top: 2, left: showCardMods ? 16 : 2, width: 14, height: 14, borderRadius: 999, background: "white", transition: "left .15s" }} />
                            </span>
                            Show size &amp; mods on cards
                          </button>
                        </div>
                      </div>

                      <div style={{ borderLeft: "1px solid var(--line-soft)", paddingLeft: 18 }}>
                        <span style={label}>Receipt printing</span>
                        <div style={{ display: "grid", gap: 6, marginBottom: 10 }}>
                        <input
                          value={printerIp}
                          onChange={e => updatePrinterIp(e.target.value)}
                          onKeyDown={e => { if (e.key === "Enter") savePrinterIp(); }}
                          inputMode="text"
                          autoCorrect="off"
                          spellCheck="false"
                          placeholder="Printer IP address"
                          className="mono"
                          style={{ width: "100%", border: "1px solid var(--line)", borderRadius: "var(--r-md)", background: "var(--cream-50)", color: "var(--ink-800)", padding: "7px 10px", fontSize: 12.5 }}
                        />
                        <button
                          className="btn btn-ghost"
                          style={{ width: "100%", justifyContent: "center", padding: "7px 10px", fontSize: 12.5 }}
                          onClick={savePrinterIp}
                          disabled={printerIpSaving}
                        >
                          {printerIpSaving ? <span className="btn-spinner" /> : <Ic name="check" size={13} />} Save printer IP
                        </button>
                        {printerIpError && <div style={{ color: "var(--rose-500)", fontSize: 11.5 }}>{printerIpError}</div>}
                      </div>
                      <div style={{ ...row, marginBottom: 8 }}>
                        {[["80","80mm"],["58","58mm"]].map(([id,lbl]) => (
                          <button key={id} style={chip(receiptSettings.paperWidth === id)} onClick={() => updateReceiptSetting("paperWidth", id)}>{lbl}</button>
                        ))}
                      </div>
                      {[
                        ["showOrderNumber", "Show order number"],
                        ["showOrderType", "Show pickup/delivery"],
                        ["showScheduledTime", "Show scheduled time"],
                        ["showCustomer", "Show customer"],
                        ["showNote", "Show note"],
                        ["showItemDetails", "Show item details"],
                        ["showThankYou", "Show thank you"],
                      ].map(([key, text]) => (
                        <button
                          key={key}
                          style={{ display: "flex", alignItems: "center", gap: 8, width: "100%", padding: "6px 10px", marginBottom: 5, borderRadius: "var(--r-md)", border: `1px solid ${receiptSettings[key] ? "var(--green-600)" : "var(--line)"}`, background: receiptSettings[key] ? "rgba(53,97,72,0.13)" : "var(--cream-100)", cursor: "pointer", fontSize: 12.5, color: "var(--ink-700)" }}
                          onClick={() => updateReceiptSetting(key, !receiptSettings[key])}
                        >
                          <span style={{ width: 32, height: 18, borderRadius: 999, background: receiptSettings[key] ? "var(--green-700)" : "var(--ink-300)", position: "relative", flexShrink: 0, transition: "background .15s" }}>
                            <span style={{ position: "absolute", top: 2, left: receiptSettings[key] ? 16 : 2, width: 14, height: 14, borderRadius: 999, background: "white", transition: "left .15s" }} />
                          </span>
                          {text}
                        </button>
                      ))}
                      <textarea
                        value={receiptSettings.footer}
                        onChange={e => updateReceiptSetting("footer", e.target.value)}
                        rows={2}
                        placeholder="Receipt footer"
                        style={{ width: "100%", resize: "vertical", border: "1px solid var(--line)", borderRadius: "var(--r-md)", background: "var(--cream-50)", color: "var(--ink-800)", padding: "8px 10px", fontSize: 12.5, marginTop: 4 }}
                      />
                      <button className="btn btn-ghost" style={{ width: "100%", justifyContent: "center", marginTop: 8, padding: "7px 10px", fontSize: 12.5 }} onClick={printTestReceipt}>
                        <Ic name="print" size={13} /> Test receipt
                      </button>
                      </div>
                    </div>
                  </div>
                );
              })()}
            </div>
          </div>
        );
      })()}

      <div className="kanban" style={{ gridTemplateColumns: "repeat(3, 1fr)" }}>
        {cols.map(col => {
          const list = applyOrdersSort(filtered.filter(o => o.status === col.id), col.id);
          const stageTotal = list.reduce((sum, order) => (
            sum + Number(order.total || 0) + (order.status === "completed" ? Number(order.payment?.tip || 0) : 0)
          ), 0);
          // Group by customer name within this column, preserving first-occurrence order
          const groups = [];
          const groupIdx = {};
          list.forEach(o => {
            const name = o.customer || "Walk-in guest";
            const isAnon = !o.customer || o.customer === "Walk-in guest" || o.customer === "Walk-in";
            const k = isAnon ? `anon-${o.dbId || o.id}` : name;
            if (!(k in groupIdx)) {
              groupIdx[k] = groups.length;
              groups.push({ key: k, name, list: [] });
            }
            groups[groupIdx[k]].list.push(o);
          });
          return (
            <div key={col.id} className={`kanban-col stage-${col.id}`}>
              <div className="kanban-head">
                <span className="kanban-title">
                  <span style={{ color: "var(--gold-500)" }}><Ic name={col.icon} size={15} /></span>
                  {col.title}
                  <span style={{ fontSize: 11, color: "var(--ink-400)", fontFamily: "var(--font-body)", fontWeight: 400, marginLeft: 4 }}>· {col.hint}</span>
                </span>
                <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
                  <span className="mono" style={{ fontSize: 12, fontWeight: 800, color: "var(--green-800)", background: "var(--cream-100)", border: "1px solid var(--line)", borderRadius: 999, padding: "3px 8px" }}>${stageTotal.toFixed(2)}</span>
                  <span className="kanban-count">{list.length}</span>
                  {groups.length > 0 && (() => {
                    const allCollapsed = groups.every(g => collapsedGroups.has(`${col.id}__${g.key}`));
                    return (
                      <button className="icon-btn" style={{ width: 22, height: 22, fontSize: 13, color: "var(--ink-400)" }}
                        title={allCollapsed ? "Expand all" : "Collapse all"}
                        onClick={() => toggleColAll(col.id, groups)}>
                        {allCollapsed ? "▶" : "▼"}
                      </button>
                    );
                  })()}
                </span>
              </div>
              {list.length === 0 && (
                <div style={{ textAlign: "center", color: "var(--ink-400)", fontSize: 12, padding: "32px 8px", fontStyle: "italic" }}>
                  Nothing here yet.
                </div>
              )}
              {groups.map(g => {
                const profile = customers.find(c => c.name === g.name);
                const initials = g.name.split(" ").map(n => (n.match(/[a-zA-ZÀ-ÿĀ-ɏ]/g) || [])[0]).filter(Boolean).slice(0, 2).join("") || "?";
                const groupTotal = g.list.reduce((s, o) => s + o.total, 0);
                const groupKey = `${col.id}__${g.key}`;
                const isCollapsed = collapsedGroups.has(groupKey);
                return (
                  <div key={g.key} className="cust-bucket">
                    <div className="cust-bucket-head" style={{ cursor: "pointer" }} onClick={() => toggleGroup(groupKey)}>
                      <div className="avatar" style={{ width: 26, height: 26, fontSize: 10, background: profile?.tier === "VIP" ? "var(--gold-500)" : "var(--cream-200)", color: profile?.tier === "VIP" ? "var(--green-900)" : "var(--green-700)" }}>
                        {initials}
                      </div>
                      <span className="cust-bucket-name" title={g.name}>{g.name}</span>
                      {profile?.tier === "VIP" && (
                        <span className="tier-pill" style={{ background: orderTierBg("VIP"), color: orderTierColor("VIP"), padding: "1px 6px", fontSize: 9 }}>VIP</span>
                      )}
                      {g.list.length > 1 && <span className="cust-bucket-count">×{g.list.length}</span>}
                      <span className="cust-bucket-total">${groupTotal.toFixed(2)}</span>
                      <span style={{ fontSize: 11, color: "var(--ink-400)", marginLeft: 2 }}>{isCollapsed ? "▶" : "▼"}</span>
	                      <button className="icon-btn" style={{ width: 22, height: 22, fontSize: 14, color: "var(--ink-400)" }}
	                        onClick={e => { e.stopPropagation(); const r = e.currentTarget.getBoundingClientRect(); setGroupMenu({ top: r.bottom + 4, right: window.innerWidth - r.right, colId: col.id, customer: g.name, list: g.list }); }}>⋯</button>
                    </div>
                    {!isCollapsed && g.list.map(o => (
                      <div
                        key={o.dbId || o.id}
                        className={`order-card grouped status-${o.status} ${selectedId === (o.dbId || o.id) ? "selected" : ""}`}
                        onClick={() => setSelectedId(o.dbId || o.id)}
                      >
                        <div className="order-card-head">
                          <span className="order-id">{o.id}</span>
                          <span className="order-total-wrap">
                            {orderDiscount(o) > 0 && <span className="order-discount-badge">−{orderDiscountLabel(o)}</span>}
                            <span className="order-total">${money(o.total)}</span>
                          </span>
                        </div>
                        <ul className="order-items-list">
                          {(() => {
                            const cardItems = visibleOrderItems(o.items);
                            const regularItems = cardItems.filter(isRegularOrderItem);
                            const nonRegularItems = applyCardItemSort(cardItems.filter(item => !isRegularOrderItem(item)));
                            return (
                              <React.Fragment>
                                {groupCardItemsByCategory(regularItems).map(group => (
                                  <React.Fragment key={group.id}>
                                    <li className={cardCategoryStyle === "lines" ? "order-item-category-line" : "order-item-category"}>
                                      {cardCategoryStyle === "labels" ? group.label : null}
                                    </li>
                                    {group.items.map((it, idx) => (
                                      <li key={`${group.id}-${idx}`}>
                                        <span className="qty mono">{it.qty}×</span>
                                        <span className="name">{it.name}</span>
                                        <span style={{ fontFamily: "var(--font-mono)", fontSize: 11.5, color: "var(--gold-700)", textAlign: "right", alignSelf: "center" }}>
                                          ${orderItemLineTotal(it).toFixed(2)}
                                        </span>
                                        {showCardMods && getItemMods(it) && <div className="mods" style={{ gridColumn: "2 / 3" }}>{getItemMods(it)}</div>}
                                      </li>
                                    ))}
                                  </React.Fragment>
                                ))}
                                {nonRegularItems.length > 0 && (
                                  <li className="order-card-promo-block">
                                    <div className="order-card-promo-head">
                                      <span>Promotion items</span>
                                      <span>{nonRegularItems.length}</span>
                                    </div>
                                    <div className="order-card-promo-list">
                                      {nonRegularItems.map((it, idx) => (
                                        <div key={`${it.id || it.name}-${idx}`} className="order-card-promo-row">
                                          <span className="qty mono">{it.qty}×</span>
                                          <span className="name">{it.name}</span>
                                          <span className="order-card-promo-total">${orderItemLineTotal(it).toFixed(2)}</span>
                                          {showCardMods && getItemMods(it) && <div className="mods">{getItemMods(it)}</div>}
                                        </div>
                                      ))}
                                    </div>
                                  </li>
                                )}
                              </React.Fragment>
                            );
                          })()}
                        </ul>
	                        <div className="order-meta-row">
	                          <SourceBadge source={o.source} />
	                          {o.status === "completed" && <PaymentBadge payment={o.payment} compact />}
	                          <span style={{ marginLeft: "auto", fontSize: 11.5, color: "var(--ink-500)", display: "inline-flex", alignItems: "center", gap: 4 }}>
                            <Ic name={channelIcon(o.channel)} size={12} />
                            {channelLabel(o.channel)}
                            {formatScheduledDateTime(o.scheduled_at) && (
                              <span style={{ color: "var(--gold-700)", fontWeight: 600 }}>· {formatScheduledDateTime(o.scheduled_at)}</span>
                            )}
                          </span>
                        </div>
                        {o.note && (
                          <div className="order-card-note">
                            <span className="order-card-note-mark">✤</span>
                            <span>{o.note}</span>
                          </div>
                        )}
                      </div>
                    ))}
                  </div>
                );
              })}
            </div>
          );
        })}
      </div>

      {/* Drawer */}
      <div className={`drawer-scrim ${selected ? "open" : ""}`} onClick={() => setSelectedId(null)} />
      <div className={`drawer ${selected ? "open" : ""}`}>
        {selected && (
          <React.Fragment>
            <div className="drawer-head">
              <div>
                <div style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--green-700)" }}>{selected.id}</div>
                <div style={{ fontFamily: "var(--font-display)", fontSize: 26, color: "var(--green-800)", lineHeight: 1.1, marginTop: 2 }}>
                  {selected.customer}
                </div>
                <div className="row" style={{ gap: 8, marginTop: 8, fontSize: 12, color: "var(--ink-500)", flexWrap: "wrap" }}>
                  <Ic name={channelIcon(selected.channel)} size={13} /> {channelLabel(selected.channel)}
                  <span style={{ color: "var(--gold-500)" }}>·</span>
                  <Ic name="clock" size={13} /> Added {formatOrderDateTime(selected.created_at) || selected.time}
                  {selected.status === "completed" && selected.completed_at && (
                    <React.Fragment>
                      <span style={{ color: "var(--gold-500)" }}>·</span>
                      <Ic name="check" size={13} /> Completed {formatOrderDateTime(selected.completed_at)}
                    </React.Fragment>
                  )}
                </div>
                <div className="row" style={{ gap: 6, marginTop: 8, flexWrap: "wrap" }}>
                  <SourceBadge source={selected.source} />
                  {selected.status === "completed" && <PaymentBadge payment={selected.payment} />}
                </div>
              </div>
              <div className="row" style={{ gap: 6 }}>
                <button className="icon-btn" onClick={() => { startEdit(selected); setSelectedId(null); }} title="Edit order"><Ic name="edit" size={14} /></button>
                <button className="icon-btn" onClick={() => setSelectedId(null)}><Ic name="x" /></button>
              </div>
            </div>

            <div className="drawer-body">
              <section className="order-info-section">
                <div className="order-info-title">Items</div>
                <div className="order-info-list">
                  {selectedRegularGroups.map(group => (
                    <React.Fragment key={group.id}>
                      <div className={cardCategoryStyle === "lines" ? "detail-category-line" : "detail-category"}>
                        {cardCategoryStyle === "labels" ? group.label : null}
                      </div>
                      {group.items.map((it, index) => renderDrawerItem(it, index, group.id))}
                    </React.Fragment>
                  ))}
                  {selectedPromotionItems.length > 0 && (
                    <React.Fragment>
                      {selectedRegularItems.length > 0 && <div className="detail-section-divider" />}
                      <div className="detail-section-label">Promotion items</div>
                      {selectedPromotionGroups.map(group => (
                        <React.Fragment key={`promo-${group.id}`}>
                          <div className={cardCategoryStyle === "lines" ? "detail-category-line promo" : "detail-category promo"}>
                            {cardCategoryStyle === "labels" ? group.label : null}
                          </div>
                          {group.items.map((it, index) => renderDrawerItem(it, index, `promo-${group.id}`, true))}
                        </React.Fragment>
                      ))}
                    </React.Fragment>
                  )}
                </div>
              </section>

              {selected.note && (
                <section className="order-info-section compact">
                  <div className="order-info-title">Note</div>
                  <div className="order-info-note">{selected.note}</div>
                </section>
              )}

              <section className="order-total-card">
                <div className="spread" style={{ fontSize: 13, marginBottom: 7 }}>
                  <span className="muted">Subtotal</span>
                  <span className="mono">${money(orderSubtotal(selected))}</span>
                </div>
                <div className="spread" style={{ fontSize: 13, marginBottom: 7 }}>
                  <span className="muted">Tax</span>
                  <span className="mono">$0.00</span>
                </div>
                {orderDiscount(selected) > 0 && (
                  <div className="spread" style={{ fontSize: 13, marginBottom: 7, color: "var(--rose-500)" }}>
                    <span>Discount</span>
                    <span className="mono">−{orderDiscountLabel(selected)}</span>
                  </div>
                )}
                {selected.status === "completed" && selected.payment?.tip > 0 && (
                  <div className="spread" style={{ fontSize: 13, marginBottom: 7 }}>
                    <span className="muted">Tip</span>
                    <span className="mono">${selected.payment.tip.toFixed(2)}</span>
                  </div>
                )}
                <div className="spread order-total-row">
                  <span>Total</span>
                  <span className="mono">
                    ${money(selected.total + (selected.status === "completed" ? (selected.payment?.tip || 0) : 0))}
                  </span>
                </div>
              </section>

              {selected.status === "completed" && selected.payment && (
                <section className="order-info-section compact">
                  <div className="order-info-title">Payment</div>
                  <div className="order-info-person">
                    <div className="order-info-avatar icon">
                      <Ic name="money" size={18} />
                    </div>
                    <div className="order-info-person-main">
                      <div className="order-info-person-name">
                        {PAYMENT_LABELS[selected.payment.method] || selected.payment.method}
                        {selected.payment.brand && <span style={{ color: "var(--ink-500)", fontWeight: 400, marginLeft: 6 }}>· {selected.payment.brand} ····{selected.payment.last4}</span>}
                      </div>
                      <div className="order-info-person-sub">
                        Paid {formatOrderDateTime(selected.completed_at) || formatOrderDateTime(selected.created_at) || selected.time}{selected.payment.tip > 0 ? ` · $${selected.payment.tip.toFixed(2)} tip` : ""}
                      </div>
                    </div>
                    <span className="order-info-paid">
                      <Ic name="check" size={11} /> Paid
                    </span>
                  </div>
                </section>
              )}

              <section className="order-info-section compact">
                <div className="order-info-title">Customer</div>
                <div className="order-info-person">
                  <div className="order-info-avatar">
                    {selected.customer.split(" ").map(n => n[0]).join("")}
                  </div>
                  <div className="order-info-person-main">
                    <div className="order-info-person-name">{selected.customer}</div>
                    <div className="order-info-person-sub">{selected.phone || "Walk-in"}</div>
                  </div>
                  <button className="icon-btn" style={{ marginLeft: "auto" }}><Ic name="phone" size={14} /></button>
                </div>
              </section>
            </div>

            {selected.status === "ready" ? (
              <div className="drawer-foot" style={{ flexDirection: "column", gap: 10, alignItems: "stretch" }}>
                <div className="row" style={{ gap: 10 }}>
                  <button className="btn btn-ghost" style={{ flex: 1 }} onClick={() => removeOrder(selected.dbId || selected.id)}>
                    <Ic name="trash" size={14} /> Remove
                  </button>
                  <button className="btn btn-ghost" style={{ flex: 1 }} disabled={printingOrderId === (selected.dbId || selected.id)} onClick={() => openReceiptPrintPreview(selected)}>
                    <Ic name="print" size={14} /> Print
                  </button>
                  <div style={{
                    flex: 2,
                    fontFamily: "var(--font-display)",
                    fontSize: 16,
                    color: "var(--green-800)",
                    letterSpacing: 0.2,
                    display: "flex", alignItems: "center", justifyContent: "center",
                  }}>
                    Complete with payment
                  </div>
                </div>
                <div className="row" style={{ gap: 10 }}>
                  <button
                    className="btn btn-primary"
                    style={{ flex: 1, justifyContent: "center" }}
                    disabled={moving}
                    onClick={() => move(selected.dbId || selected.id, "completed", { method: "zelle", tip: 0 })}
                  >
                    {moving ? <span className="btn-spinner" /> : <Ic name="money" size={14} />} Zelle
                  </button>
                  <button
                    className="btn btn-gold"
                    style={{ flex: 1, justifyContent: "center" }}
                    disabled={moving}
                    onClick={() => move(selected.dbId || selected.id, "completed", { method: "cash", tip: 0 })}
                  >
                    {moving ? <span className="btn-spinner" /> : <Ic name="money" size={14} />} Cash
                  </button>
                </div>
              </div>
            ) : (
              <div className="drawer-foot">
                <button className="btn btn-ghost" style={{ flex: 1 }} disabled={moving} onClick={() => removeOrder(selected.dbId || selected.id)}>
                  <Ic name="trash" size={14} /> Remove
                </button>
                <button className="btn btn-ghost" style={{ flex: 1 }} disabled={printingOrderId === (selected.dbId || selected.id)} onClick={() => openReceiptPrintPreview(selected)}>
                  <Ic name="print" size={14} /> Print
                </button>
                <button
                  className="btn btn-primary"
                  style={{ flex: 2, opacity: selected.status === "completed" || moving ? 0.5 : 1 }}
                  disabled={selected.status === "completed" || moving}
                  onClick={() => move(selected.dbId || selected.id, nextStatus(selected.status))}
                >
                  {moving ? <><span className="btn-spinner" /> Working…</> : nextLabel(selected.status)}
                  <Ic name="arrow-right" size={14} />
                </button>
              </div>
            )}
          </React.Fragment>
        )}
      </div>

      <NewOrderModal
        open={modalOpen}
        onClose={closeModal}
        onCreate={createOrder}
        onUpdate={updateOrder}
        editingOrder={editingOrder}
        nextOrderNumber={nextOrderNumber}
        menuItems={menuItems}
        categories={categories}
        customers={customers}
        orderSources={orderSources}
        drinkOptions={drinkOptions}
        orderItemTypes={orderItemTypes}
      />

      {groupMenu && ReactDOM.createPortal(
        <>
          <div style={{ position: "fixed", inset: 0, zIndex: 299 }} onClick={() => setGroupMenu(null)} />
          <div style={{ position: "fixed", top: groupMenu.top, right: groupMenu.right, zIndex: 300, background: "var(--cream-50)", border: "1px solid var(--line)", borderRadius: "var(--r-md)", boxShadow: "0 4px 20px rgba(0,0,0,0.18)", minWidth: 180, padding: "4px 0" }}>
              <button className="group-menu-action" onClick={() => { setGroupSummary({ customer: groupMenu.customer, list: groupMenu.list }); setGroupMenu(null); }}>
                <Ic name="orders" size={13} /> View item summary
              </button>
              <div style={{ borderTop: "1px solid var(--line)", margin: "4px 0" }} />
	            {groupMenu.colId === "pending" && (
	              <button className="group-menu-action" disabled={moving} onClick={() => moveGroup(groupMenu.list, "ready", null)}>
	                <Ic name="check" size={13} /> Mark all Ready
	              </button>
	            )}
	            {groupMenu.colId !== "completed" && (
	              <React.Fragment>
	                <button className="group-menu-action" disabled={moving} onClick={() => moveGroup(groupMenu.list, "completed", { method: "zelle" })}>
	                  <Ic name="money" size={13} /> Complete · Zelle
	                </button>
	                <button className="group-menu-action" disabled={moving} onClick={() => moveGroup(groupMenu.list, "completed", { method: "cash" })}>
	                  <Ic name="money" size={13} /> Complete · Cash
	                </button>
	                <div style={{ borderTop: "1px solid var(--line)", margin: "4px 0" }} />
	              </React.Fragment>
	            )}
	            <button className="group-menu-action" disabled={moving} style={{ color: "var(--rose-500)" }} onClick={() => removeGroup(groupMenu.list)}>
	              <Ic name="trash" size={13} /> Remove all
            </button>
          </div>
        </>,
        document.body
      )}

      {groupSummary && (() => {
        const summaryRegularGroups = groupSummaryItemsByCategory(groupSummary.list, isRegularOrderItem);
        const summaryPromotionGroups = groupSummaryItemsByCategory(groupSummary.list, item => !isRegularOrderItem(item));
        const summaryTotal = groupSummary.list.reduce((sum, order) => sum + Number(order.total || 0), 0);
        return ReactDOM.createPortal(
          <div className="date-dialog-scrim" onClick={() => setGroupSummary(null)}>
            <div className="date-dialog" style={{ width: "min(460px, 100%)" }} onClick={e => e.stopPropagation()}>
              <div className="date-dialog-head">
                <div>
                  <div className="date-dialog-eyebrow">Item summary</div>
                  <h3>{groupSummary.customer || "Walk-in guest"}</h3>
                  <p>{groupSummary.list.length} order{groupSummary.list.length !== 1 ? "s" : ""} · ${summaryTotal.toFixed(2)}</p>
                </div>
                <button className="icon-btn" onClick={() => setGroupSummary(null)}><Ic name="x" size={13} /></button>
              </div>
              <section className="order-info-section" style={{ marginTop: 14 }}>
                <div className="order-info-title">Items</div>
                <div className="order-info-list">
                  {summaryRegularGroups.length || summaryPromotionGroups.length ? (
                    <React.Fragment>
                      {summaryRegularGroups.map(group => (
                        <React.Fragment key={group.id}>
                          <div className={cardCategoryStyle === "lines" ? "detail-category-line" : "detail-category"}>
                            {cardCategoryStyle === "labels" ? group.label : null}
                          </div>
                          {group.items.map((it, index) => renderDrawerItem(it, index, group.id))}
                        </React.Fragment>
                      ))}
                      {summaryPromotionGroups.length > 0 && (
                        <React.Fragment>
                          {summaryRegularGroups.length > 0 && <div className="detail-section-divider" />}
                          <div className="detail-section-label">Promotion items</div>
                          {summaryPromotionGroups.map(group => (
                            <React.Fragment key={`promo-${group.id}`}>
                              <div className={cardCategoryStyle === "lines" ? "detail-category-line promo" : "detail-category promo"}>
                                {cardCategoryStyle === "labels" ? group.label : null}
                              </div>
                              {group.items.map((it, index) => renderDrawerItem(it, index, `promo-${group.id}`, true))}
                            </React.Fragment>
                          ))}
                        </React.Fragment>
                      )}
                    </React.Fragment>
                  ) : (
                    <div style={{ padding: 24, textAlign: "center", color: "var(--ink-500)" }}>No items found.</div>
                  )}
                </div>
              </section>
            </div>
          </div>,
          document.body
        );
      })()}

      {printNotice && ReactDOM.createPortal(
        <div className="order-toast-stack">
          <div className="order-toast error" style={{ minWidth: 320 }}>
            <div className="order-toast-icon">
              <Ic name="x" size={14} />
            </div>
            <div className="order-toast-copy">
              <div className="order-toast-title">{printNotice.title}</div>
              <div className="order-toast-message">{printNotice.message}</div>
            </div>
            <button className="order-toast-close" onClick={() => setPrintNotice(null)} title="Dismiss">
              <Ic name="x" size={12} />
            </button>
          </div>
        </div>,
        document.body
      )}

      {printPreviewOrder && ReactDOM.createPortal(
        <div className="date-dialog-scrim" onClick={() => setPrintPreviewOrder(null)}>
          <div className="date-dialog" style={{ width: "min(760px, calc(100vw - 32px))", maxHeight: "calc(100vh - 44px)", display: "grid", gridTemplateRows: "auto minmax(0, 1fr) auto" }} onClick={e => e.stopPropagation()}>
            <div className="date-dialog-head">
              <div>
                <div className="date-dialog-eyebrow">Receipt preview</div>
                <h3>{printPreviewOrder.order_number ? `Order #${printPreviewOrder.order_number}` : printPreviewOrder.id}</h3>
                <p>{printerIp ? `Printer ${printerIp}` : "No printer IP set"}</p>
              </div>
              <button className="icon-btn" onClick={() => setPrintPreviewOrder(null)}><Ic name="x" size={13} /></button>
            </div>
            <div style={{ minHeight: 0, overflow: "hidden", display: "grid", gridTemplateColumns: "minmax(280px, 1fr) 220px", gap: 14 }}>
              <div ref={previewCanvasRef} style={{ background: "#5f646a", border: "1px solid var(--line)", borderRadius: "var(--r-md)", overflowY: "auto", overflowX: "hidden", minHeight: 360, maxHeight: "min(58vh, 520px)", padding: 12 }}>
                <div style={{ width: 560 * previewScale, height: previewContentHeight * previewScale, margin: "0 auto" }}>
                  <iframe
                    ref={previewFrameRef}
                    title="Receipt preview"
                    srcDoc={buildReceiptHtml(printPreviewOrder, printerIp, false)}
                    scrolling="no"
                    onLoad={handlePreviewLoad}
                    style={{ width: 560, height: previewContentHeight, border: 0, background: "white", boxShadow: "0 8px 24px rgba(0,0,0,0.22)", transform: `scale(${previewScale})`, transformOrigin: "top left", display: "block" }}
                  />
                </div>
              </div>
              <div style={{ border: "1px solid var(--line-soft)", borderRadius: "var(--r-md)", background: "var(--cream-100)", padding: 14, alignSelf: "start" }}>
                <div style={{ fontSize: 10.5, letterSpacing: ".18em", textTransform: "uppercase", color: "var(--ink-500)", marginBottom: 8 }}>Print destination</div>
                <div className="mono" style={{ color: "var(--green-800)", fontSize: 14, fontWeight: 800, marginBottom: 8 }}>
                  {printerIp || "Not set"}
                </div>
              </div>
            </div>
            <div className="date-dialog-foot">
              <button className="btn btn-ghost" onClick={() => setPrintPreviewOrder(null)}>Cancel</button>
              <button
                className="btn btn-primary"
                disabled={printingOrderId === (printPreviewOrder.dbId || printPreviewOrder.id)}
                onClick={async () => {
                  const printed = await printReceipt(printPreviewOrder);
                  if (printed) setPrintPreviewOrder(null);
                }}
              >
                {printingOrderId === (printPreviewOrder.dbId || printPreviewOrder.id) ? <span className="btn-spinner" /> : <Ic name="print" size={14} />} Print
              </button>
            </div>
          </div>
        </div>,
        document.body
      )}

      {dialog && <ConfirmDialog title={dialog.title} message={dialog.message} confirmLabel={dialog.confirmLabel || "Remove"} cancelLabel={dialog.cancelLabel} onConfirm={() => { setDialog(null); dialog.onConfirm(); }} onCancel={() => setDialog(null)} />}
    </div>
  );
};

window.Orders = Orders;
