/* global React, I, UI */
// ═══════════════════════════════════════════════════════════════════
//  ORDER ONLINE · เว็บสั่งงานลูกค้า — หลังบ้าน (หลายเว็บ: LWC · BOTH · …)
//  · 1 เว็บ = 1 ลูกค้าองค์กร มีลิงก์ของตัวเอง triples-creative.com/shop/<slug>
//    มีสี/โลโก้/หมวดหมู่/รายการสินค้า+ราคาของตัวเอง
//  · ในแต่ละเว็บมี "บัญชีลูกค้า" ได้หลายบัญชี (รหัสลูกค้า+รหัสผ่านที่เราออกให้)
//    แต่ละบัญชีมีออเดอร์และบิลของตัวเอง
//  ─ ข้อมูลที่เผยแพร่ลง portal_store (RLS: อ่านได้เฉพาะแถวที่ตรงกับ header x-portal-key) ─
//    site:<slug>            หน้าตาเว็บ (ชื่อ/สี/โลโก้/ติดต่อ) — ไม่มีราคา
//    cat:<slug>:<secret>    หมวด + รายการสินค้า + ราคา — secret อยู่ในแถวลูกค้าเท่านั้น
//    cust:<ID>:<CODE>       ข้อมูลลูกค้า + ออเดอร์ + บิล + catKey
//  ปิด/ลบบัญชีลูกค้า → สุ่ม secret ใหม่ทันที (บัญชีเก่าอ่านราคาต่อไม่ได้)
// ═══════════════════════════════════════════════════════════════════
const { useState: useStateLP, useEffect: useEffectLP, useRef: useRefLP, useMemo: useMemoLP } = React;

const LP_KEY = "sss-lwc-portal";
const LP_STATUSES = ["ส่งคำสั่งซื้อแล้ว", "ยืนยันแล้ว", "กำลังผลิต", "ผลิตเสร็จ", "ส่งของแล้ว"];
const LP_COLORS = ["#0f766e", "#1d4ed8", "#7c3aed", "#be185d", "#dc2626", "#ea580c", "#ca8a04", "#15803d", "#0e7490", "#334155"];
const LP_RESERVED = ["app", "order", "track", "shop", "api", "admin", "assets"];
const LP_R2 = (n) => Math.round(((Number(n) || 0) + Number.EPSILON) * 100) / 100;

function lpClient() {
  try { const c = window.SSSDB && window.SSSDB.client; return typeof c === "function" ? c() : (c || null); } catch (e) { return null; }
}
function lpRand(len, alphabet) {
  const A = alphabet || "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
  const buf = new Uint32Array(len);
  if (window.crypto && window.crypto.getRandomValues) window.crypto.getRandomValues(buf);
  else for (let i = 0; i < len; i++) buf[i] = Math.floor(Math.random() * 4294967295);
  let s = ""; for (let i = 0; i < len; i++) s += A[buf[i] % A.length];
  return s;
}
// รหัสผ่านลูกค้า — ตัดตัวที่อ่านสับสน (0/O, 1/I) ออก
function lpGenCode() { const s = lpRand(8); return s.slice(0, 4) + "-" + s.slice(4); }
function lpGenSecret() { return lpRand(22, "abcdefghijkmnpqrstuvwxyz23456789"); }
const lpUid = (p) => (p || "id") + Date.now().toString(36) + lpRand(4, "abcdefghjkmnpqrstuvwxyz23456789");
const lpToday = () => { const d = new Date(); d.setHours(12, 0, 0, 0); return d.toISOString().slice(0, 10); };
const LP_TH_M = ["ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค."];
const lpThai = (iso) => { if (!iso) return "—"; const p = String(iso).slice(0, 10).split("-"); return p.length === 3 ? `${+p[2]} ${LP_TH_M[+p[1] - 1]} ${+p[0] + 543}` : String(iso); };
const lpTime = (iso) => { try { return new Date(iso).toLocaleTimeString("th-TH", { hour: "2-digit", minute: "2-digit" }); } catch (e) { return ""; } };
const lpToast = (m) => window.toast && window.toast(m);

// ═══ อ่าน/เขียนสถานะ + ย้ายข้อมูลรุ่นเก่าอัตโนมัติ (ห้ามมีข้อมูลหาย) ═══
function lpNewSite(slug, seed) {
  seed = seed || {};
  return {
    slug, name: seed.name || slug.toUpperCase(), title: seed.title || `ORDER งาน ${seed.name || slug.toUpperCase()}`,
    subtitle: seed.subtitle || "ระบบสั่งงานลูกค้า · TRIPLES CREATIVE", color: seed.color || LP_COLORS[0], logo: "",
    phone: "", line: "", active: true, vatPct: seed.vatPct == null ? 7 : Number(seed.vatPct), priceNote: seed.priceNote || "",
    catSecret: lpGenSecret(), categories: [], catalog: [], createdAt: new Date().toISOString(),
  };
}
function lpNormalize(raw) {
  const st = raw && typeof raw === "object" ? raw : {};
  let changed = false;
  const sites = Array.isArray(st.sites) ? st.sites.map(s => ({ ...s })) : (changed = true, []);
  const custs = Array.isArray(st.customers) ? st.customers.map(c => ({ ...c })) : [];
  const ensure = (slug, seed) => {
    let s = sites.find(x => x.slug === slug);
    if (!s) { s = lpNewSite(slug, seed); sites.push(s); changed = true; }
    return s;
  };
  // รุ่นเดิม: แคตตาล็อกอยู่รายลูกค้า + ทุกคนเป็นของ LWC → ย้ายไปรวมที่เว็บ LWC (รวมชื่อซ้ำ ไม่ทิ้งรายการไหน)
  custs.forEach(c => {
    if (!c.site) { c.site = "lwc"; changed = true; }
    const s = ensure(c.site, { name: c.site.toUpperCase(), vatPct: c.vatPct, priceNote: c.priceNote });
    if (Array.isArray(c.catalog)) {
      s.catalog = (s.catalog || []).slice();
      c.catalog.forEach(it => {
        if (!it || !String(it.name || "").trim()) return;
        const nm = String(it.name).trim().toLowerCase();
        const same = s.catalog.find(x => String(x.name || "").trim().toLowerCase() === nm &&
          (!(Number(x.price) > 0) || !(Number(it.price) > 0) || Number(x.price) === Number(it.price)));
        if (same) {
          const i = s.catalog.indexOf(same);
          s.catalog[i] = { ...same, price: Number(same.price) > 0 ? same.price : it.price, photo: same.photo || it.photo || "" };
        } else s.catalog.push({ ...it });
      });
      delete c.catalog; changed = true;
    }
  });
  sites.forEach(s => {
    if (!s.catSecret) { s.catSecret = lpGenSecret(); changed = true; }
    if (!Array.isArray(s.categories)) { s.categories = []; changed = true; }
    if (!Array.isArray(s.catalog)) { s.catalog = []; changed = true; }
    if (s.catalog.some(it => !it._k)) { s.catalog = s.catalog.map(it => (it._k ? it : { ...it, _k: lpUid("it") })); changed = true; }
  });
  return { st: { ...st, v: 2, sites, customers: custs }, changed };
}
function lpLoadRaw() {
  try { const v = window.lsSyncGet ? window.lsSyncGet(LP_KEY) : null; if (v) return v; } catch (e) {}
  try { return window.lsRead(LP_KEY, {}) || {}; } catch (e) { return {}; }
}
function lpSave(v) {
  if (window.lsSyncSet) window.lsSyncSet(LP_KEY, v);
  else { try { localStorage.setItem(LP_KEY, JSON.stringify(v)); } catch (e) {} }
  try { window.dispatchEvent(new Event("sss-lwc-changed")); } catch (e) {}
}
function lpLoad() {
  const { st, changed } = lpNormalize(lpLoadRaw());
  if (changed) lpSave(st);
  return st;
}
// แก้ไขแบบปลอดภัย: อ่านสถานะล่าสุด → คัดลอกลึก → ให้ fn แก้ → บันทึก
function lpMutate(fn) {
  const next = JSON.parse(JSON.stringify(lpLoad()));
  fn(next);
  lpSave(next);
  return next;
}

// ═══ รูป: ย่อเป็นรูปเล็ก (ส่งขึ้นเว็บลูกค้าได้เร็ว + ไม่ถ่วงระบบซิงค์) ═══
const LP_THUMB_CACHE = new Map();
function lpShrink(src, max, q) {
  if (!src || !/^data:image\//.test(src)) return Promise.resolve(src || "");
  const ck = max + "|" + src.length + "|" + src.slice(-40);
  if (LP_THUMB_CACHE.has(ck)) return Promise.resolve(LP_THUMB_CACHE.get(ck));
  return new Promise(res => {
    const img = new Image();
    img.onload = () => {
      const sc = Math.min(1, max / Math.max(img.width || 1, img.height || 1));
      const c = document.createElement("canvas");
      c.width = Math.max(1, Math.round(img.width * sc)); c.height = Math.max(1, Math.round(img.height * sc));
      const g = c.getContext("2d"); g.fillStyle = "#fff"; g.fillRect(0, 0, c.width, c.height);
      g.drawImage(img, 0, 0, c.width, c.height);
      let out = c.toDataURL("image/jpeg", q || 0.75);
      if (out.length >= src.length) out = src;
      LP_THUMB_CACHE.set(ck, out); res(out);
    };
    img.onerror = () => res(src);
    img.src = src;
  });
}
// รูปใหญ่ที่ดึงมาจากคลังชิ้นงานรุ่นก่อน → ย่อเก็บ (ต้นฉบับยังอยู่ในคลังชิ้นงาน)
async function lpCompactPhotos() {
  const st = lpLoad(); const repl = {};
  for (const s of st.sites) for (const it of s.catalog) {
    if (it.photo && /^data:image\//.test(it.photo) && it.photo.length > 120000) {
      const t = await lpShrink(it.photo, 600, 0.8);
      if (t && t.length < it.photo.length) repl[it._k] = { from: it.photo, to: t };
    }
  }
  const keys = Object.keys(repl);
  if (!keys.length) return 0;
  lpMutate(n => n.sites.forEach(s => s.catalog.forEach(it => {
    const r = repl[it._k]; if (r && it.photo === r.from) it.photo = r.to;      // แทนเฉพาะถ้ายังเป็นรูปเดิม
  })));
  return keys.length;
}

// ═══ เผยแพร่ขึ้น portal_store ═══
function lpErrText(e) {
  const m = String((e && (e.message || e.details || e.hint)) || e || "");
  const code = e && e.code;
  if (code === "PGRST205" || code === "42P01" || /portal_store/.test(m) && /(not find|does not exist|schema cache)/i.test(m))
    return "ยังไม่ได้สร้างตาราง portal_store ในฐานข้อมูล — รัน bash migrate/setup-portal-store.sh บนเครื่อง DATA ก่อน";
  if (/Failed to fetch|NetworkError|network/i.test(m)) return "เชื่อมต่อฐานข้อมูลไม่ได้ — ตรวจเน็ต/เครื่อง DATA แล้วลองใหม่";
  return m || "ไม่ทราบสาเหตุ";
}
async function lpUpsert(key, data) {
  const c = lpClient(); if (!c) throw new Error("ยังไม่ได้เชื่อมฐานข้อมูล");
  const res = await c.from("portal_store").upsert([{ key, data, updated_at: new Date().toISOString() }], { onConflict: "key" });
  if (res && res.error) throw res.error;          // supabase-js ไม่ throw เอง — ต้องเช็ค error ทุกครั้ง
}
async function lpDelete(key) {
  const c = lpClient(); if (!c || !key) return;
  try { const res = await c.from("portal_store").delete().eq("key", key); if (res && res.error) console.warn(res.error); } catch (e) {}
}
const lpCatKey = (s) => `cat:${s.slug}:${s.catSecret}`;
const lpCustKey = (c) => `cust:${String(c.id).toUpperCase()}:${String(c.code).toUpperCase()}`;
function lpBrand(s) {
  return { slug: s.slug, name: s.name || s.slug.toUpperCase(), title: s.title || "ORDER ONLINE", subtitle: s.subtitle || "",
    color: s.color || LP_COLORS[0], logo: s.logo || "", phone: s.phone || "", line: s.line || "", active: s.active !== false };
}
async function lpPublishCustomer(s, c, catAt) {
  const C = (window.SSSData && window.SSSData.Company) || {};
  const on = c.active !== false && s.active !== false;
  await lpUpsert(lpCustKey(c), {
    v: 2, site: s.slug, catKey: on ? lpCatKey(s) : "", catAt: catAt || s.catAt || "",
    id: c.id, name: c.name || c.id, contact: c.contact || "", phone: c.phone || "", address: c.address || "", taxId: c.taxId || "",
    vatPct: c.vatPct != null && c.vatPct !== "" ? Number(c.vatPct) : (s.vatPct == null ? 7 : Number(s.vatPct)),
    priceNote: s.priceNote || "", active: c.active !== false,
    shop: { name: C.name || "", address: C.address || "", phone: s.phone || C.phone || "", taxId: C.taxId || "" },
    brand: lpBrand(s),
    pay: s.pay && s.pay.mode && s.pay.mode !== "none" ? { mode: s.pay.mode, promptpayId: s.pay.promptpayId || "", billerId: s.pay.billerId || "",
      accName: s.pay.accName || "", bankText: s.pay.bankText || "" } : null,
    orders: (c.orders || []).map(o => ({
      no: o.no, ref: o.ref || "", at: o.at, due: o.due || "", status: o.status, items: o.items || [],
      note: o.note || "", reply: o.reply || "", total: o.total == null ? null : LP_R2(o.total), billNo: o.billNo || "", woNo: o.woNo || "",
    })),
    bills: c.bills || [],
    updatedAt: new Date().toISOString(),
  });
}
async function lpPublishSite(slug, onProgress) {
  const st = lpLoad();
  const s = st.sites.find(x => x.slug === slug); if (!s) return;
  const catAt = new Date().toISOString();
  await lpUpsert(`site:${s.slug}`, lpBrand(s));
  const catalog = [];
  const list = s.catalog.filter(it => it && String(it.name || "").trim() && it.show !== false);
  for (let i = 0; i < list.length; i++) {
    const it = list[i];
    if (onProgress && i % 20 === 0) onProgress(`เตรียมรูปสินค้า ${i}/${list.length}…`);
    catalog.push({
      id: it._k, code: it.code || "", name: String(it.name).trim(), unit: it.unit || "ชิ้น",
      price: Number(it.price) > 0 ? LP_R2(it.price) : null,       // ยังไม่ตั้งราคา = ลูกค้าเห็น "สอบถามราคา"
      photo: await lpShrink(it.photo, 440, 0.74), note: it.note || "", moq: Number(it.moq) || 1,
      cat: it.cat || "", options: (it.options || []).filter(o => o && o.label && (o.choices || []).some(c => c.name))
        .map(o => ({ label: o.label, choices: o.choices.filter(c => c.name).map(c => ({ name: c.name, add: Number(c.add) || 0 })) })),
    });
  }
  onProgress && onProgress("กำลังส่งขึ้นเว็บ…");
  // บัญชีลูกค้าต้องขึ้นเสมอ แม้ส่งรายการสินค้าไม่ผ่าน (ไม่งั้นลูกค้าล็อกอินไม่ได้เลย)
  let catErr = null;
  try { await lpUpsert(lpCatKey(s), { v: 2, slug: s.slug, categories: s.categories || [], catalog, updatedAt: catAt }); }
  catch (e) { catErr = e; }
  const custs = st.customers.filter(c => c.site === s.slug && c.id && c.code);
  for (const c of custs) await lpPublishCustomer(s, c, catAt);
  if (catErr) { const e = new Error("บัญชีลูกค้าขึ้นแล้ว แต่ส่งรายการสินค้าไม่ผ่าน: " + lpErrText(catErr)); throw e; }
  lpMutate(n => { const t = n.sites.find(x => x.slug === slug); if (t) { t.catAt = catAt; t.publishedAt = catAt; t.dirty = false; } });
  return { items: catalog.length, customers: custs.length };
}
// ปิด/ลบบัญชีลูกค้า → เปลี่ยน secret ของแคตตาล็อก (บัญชีเก่าที่จำ catKey ไว้อ่านราคาต่อไม่ได้)
async function lpRotateSecret(slug) {
  let oldKey = "";
  lpMutate(n => { const s = n.sites.find(x => x.slug === slug); if (s) { oldKey = lpCatKey(s); s.catSecret = lpGenSecret(); } });
  await lpPublishSite(slug);
  if (oldKey) await lpDelete(oldKey);
}

// ═══ ดูดออเดอร์ใหม่จากกล่องรับ (public_orders kind=lwc-order) ═══
async function lpIngest() {
  const c = lpClient(); if (!c) return 0;
  let rows = [];
  try {
    const res = await c.from("public_orders").select("id,order_id,kind,data,created_at").eq("kind", "lwc-order").order("id", { ascending: true }).limit(500);
    rows = (res && res.data) || [];
  } catch (e) { return 0; }
  if (!rows.length) return 0;
  let added = 0;
  const cur = lpLoad();
  const want = rows.filter(r => {
    const d = r.data || {};
    const cust = cur.customers.find(x => String(x.id).toUpperCase() === String(d.custId || "").toUpperCase());
    return cust && !(cust.orders || []).some(o => o.no === d.no || o.ref === d.no);
  });
  if (!want.length) return 0;
  lpMutate(n => want.forEach(r => {
    const d = r.data || {};
    const cust = n.customers.find(x => String(x.id).toUpperCase() === String(d.custId || "").toUpperCase());
    if (!cust || (cust.orders || []).some(o => o.no === d.no || o.ref === d.no)) return;
    cust.orders = cust.orders || [];
    cust.orders.unshift({
      no: d.no, ref: d.no, at: d.at || r.created_at, due: d.due || "",
      status: "ส่งคำสั่งซื้อแล้ว", items: (d.items || []).map(it => ({ ...it })),
      note: d.note || "", reply: "", total: (d.est && d.est.hasQuote) ? null : ((d.est && d.est.total) != null ? d.est.total : null),
      billNo: "", inboxId: r.id, isNew: true,
    });
    added++;
  }));
  return added;
}

function lpOrderTotals(order, vatPct) {
  let sub = 0, pending = false;
  (order.items || []).forEach(it => {
    if (it.price == null || it.price === "") pending = true;
    else sub = LP_R2(sub + LP_R2((Number(it.price) || 0) * (Number(it.qty) || 0)));
  });
  const vat = LP_R2(sub * (Number(vatPct) || 0) / 100);
  return { sub, vat, total: LP_R2(sub + vat), pending };
}
// ═══ 💳 QR รับชำระ — PromptPay (Tag 29) หรือ SCB Bill Payment (Tag 30 · Ref1 = เลขบิล · Ref2 = รหัสลูกค้า) ═══
// ต้องตรงกับ lwc/portal.js (ลูกค้าสแกนจากบิล)
function lpCrc16(t) {
  let crc = 0xFFFF;
  for (let i = 0; i < t.length; i++) { crc ^= t.charCodeAt(i) << 8; for (let b = 0; b < 8; b++) crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) & 0xFFFF : (crc << 1) & 0xFFFF; }
  return crc.toString(16).toUpperCase().padStart(4, "0");
}
const lpTlv = (id, v) => id + String(v.length).padStart(2, "0") + v;
const lpRef = (v) => String(v || "").toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 20);
function lpPayPayload(pay, amount, ref1, ref2) {
  if (!pay || !pay.mode || pay.mode === "none") return "";
  const amt = Number(amount) > 0 ? lpTlv("54", LP_R2(amount).toFixed(2)) : "";
  let merchant = "";
  if (pay.mode === "scb") {
    const biller = String(pay.billerId || "").replace(/\D/g, "");
    if (!biller || !lpRef(ref1)) return "";
    merchant = lpTlv("30", lpTlv("00", "A000000677010112") + lpTlv("01", biller) + lpTlv("02", lpRef(ref1)) + (lpRef(ref2) ? lpTlv("03", lpRef(ref2)) : ""));
  } else {
    const d = String(pay.promptpayId || "").replace(/\D/g, "");
    if (!d) return "";
    const target = d.length >= 13 ? lpTlv("02", d.slice(0, 13)) : lpTlv("01", "0066" + (d.length === 10 ? d.slice(1) : d));
    merchant = lpTlv("29", lpTlv("00", "A000000677010111") + target);
  }
  const body = lpTlv("00", "01") + lpTlv("01", amt ? "12" : "11") + merchant + lpTlv("53", "764") + amt + lpTlv("58", "TH") + "6304";
  return body + lpCrc16(body);
}
function LPPayQR({ payload, size }) {
  if (!payload || !window.qrcode) return null;
  try {
    const q = window.qrcode(0, "M"); q.addData(payload); q.make();
    return <img alt="QR" src={q.createDataURL(5, 2)} style={{width: size || 150, height: size || 150, imageRendering: "pixelated", border: "1px solid var(--line)", borderRadius: 8, background: "#fff"}}/>;
  } catch (e) { return null; }
}

// ═══ 📋 เปิดใบสั่งงานจากออเดอร์ลูกค้า → โปรเจกต์ + ใบสั่งงาน + การ์ดสถานี ═══
function lpOpenWorkOrder({ site, cust, order, projectCode, types, due, items, detail }) {
  const PX = window.ProjX, WC = window.WOCards;
  if (!PX || !WC || !PX.pxNextWoNo) throw new Error("ระบบโปรเจกต์ยังโหลดไม่เสร็จ — ลองรีเฟรชหน้า");
  const users = PX.upLoad() || [];
  let code = projectCode;
  if (!code) {
    code = PX.pxNextJobCode();
    const first = (items[0] && items[0].name) || "งานสั่งออนไลน์";
    PX.upSave([{ code, name: `🛍️ ${cust.name || cust.id} · ${first}${items.length > 1 ? ` +${items.length - 1}` : ""}`,
      customer: cust.name || cust.id, lead: "", status: "กำลังผลิต", progress: 0,
      start: PX.pxTodayISO(), end: due || "", budget: order.total || 0,
      detail: `ORDER ONLINE ${site.name} · ออเดอร์ ${order.no}` }, ...users]);
  }
  const woNo = PX.pxNextWoNo();
  const woItems = items.map(it => ({ name: it.name, qty: it.qty, unit: it.unit || "ชิ้น" }));
  const cards = (types.length ? types : ["cut"]).map(t => WC.normalize({
    type: t, title: `${(WC.TYPES[t] || {}).label || "งาน"} · ${order.no}`, status: "รอทำ", due: due || "",
    detail: `ออเดอร์ ${order.no} จาก ${cust.name || cust.id} (เว็บ ${site.name})`,
    items: items.map(it => ({ name: it.name, qty: it.qty, unit: it.unit || "ชิ้น", material: it.code || "", spec: it.spec || "", photos: [] })),
  }));
  const wo = {
    no: woNo, issueDate: PX.pxToday(), startDate: PX.pxTodayISO(), due: due || "", priority: "ปกติ",
    detail: detail || `🛍️ ORDER ONLINE ${site.name} · ออเดอร์ ${order.no}\nลูกค้า: ${cust.name || cust.id}${cust.phone ? " · โทร " + cust.phone : ""}${order.note ? "\nลูกค้าแจ้ง: " + order.note : ""}`,
    items: woItems, photos: [], assignees: [], assigneeIds: [],
    issuedBy: (window.Session && window.Session.get && window.Session.get() && window.Session.get().name) || "ORDER ONLINE",
    status: "เปิดงาน", cards, lwcOrder: { site: site.slug, custId: cust.id, no: order.no },
  };
  const ex = PX.pxGet(code) || {};
  PX.pxSet(code, { workOrders: [...(ex.workOrders || []), wo], status: ex.status || "กำลังผลิต" });
  try { window.SSSSync && window.SSSSync.queue && window.SSSSync.queue(); } catch (e) {}
  try { window.dispatchEvent(new Event("sss-projects-changed")); } catch (e) {}
  return { projectCode: code, woNo };
}
// สถานะออเดอร์ตามการ์ดในใบสั่งงาน (ขยับไปข้างหน้าเท่านั้น — ไม่ย้อนสิ่งที่แก้มือ)
function lpStatusFromWO(o) {
  const PX = window.ProjX; if (!PX || !o.projectCode || !o.woNo) return null;
  const wo = ((PX.pxGet(o.projectCode) || {}).workOrders || []).find(w => w.no === o.woNo);
  if (!wo) return null;
  const cards = wo.cards || [], prod = cards.filter(c => c.type !== "deliver"), dl = cards.filter(c => c.type === "deliver");
  if (dl.length && dl.every(c => c.status === "เสร็จแล้ว") && prod.every(c => c.status === "เสร็จแล้ว")) return "ส่งของแล้ว";
  if (prod.length && prod.every(c => c.status === "เสร็จแล้ว")) return "ผลิตเสร็จ";
  if (cards.some(c => c.status === "กำลังทำ" || c.status === "เสร็จแล้ว")) return "กำลังผลิต";
  return null;
}
let _lpAutoT = null;
function lpAutoSync() {
  clearTimeout(_lpAutoT);
  _lpAutoT = setTimeout(async () => {
    let st; try { st = lpLoad(); } catch (e) { return; }
    const touched = [];
    (st.customers || []).forEach(c => (c.orders || []).forEach(o => {
      const want = lpStatusFromWO(o);
      if (want && LP_STATUSES.indexOf(want) > LP_STATUSES.indexOf(o.status) && o.status !== "ยกเลิก") touched.push({ id: c.id, no: o.no, want });
    }));
    if (!touched.length) return;
    const next = lpMutate(n => touched.forEach(t => {
      const c = n.customers.find(x => x.id === t.id); const o = c && (c.orders || []).find(x => x.no === t.no);
      if (o) o.status = t.want;
    }));
    for (const id of [...new Set(touched.map(t => t.id))]) {
      const c = next.customers.find(x => x.id === id), s = c && next.sites.find(x => x.slug === c.site);
      if (s && c) { try { await lpPublishCustomer(s, c); } catch (e) {} }
    }
    lpToast(`🛍️ อัปเดตสถานะออเดอร์ลูกค้าตามใบสั่งงาน ${touched.length} ใบ`);
  }, 3000);
}
["sss-projects-changed", "sss-data-synced", "sss-db-loaded"].forEach(ev => window.addEventListener(ev, lpAutoSync));

const lpVat = (s, c) => (c && c.vatPct != null && c.vatPct !== "" ? Number(c.vatPct) : (s && s.vatPct != null ? Number(s.vatPct) : 7));
const lpLink = (s) => `${location.origin}/shop/${s.slug}`;

// ═══════════════════════════════════════════════════════════════════
//  หน้าหลัก
// ═══════════════════════════════════════════════════════════════════
function LWCPortalPage({ navigate }) {
  const { Btn, Card, PageHead, Tabs } = window.UI;
  const [st, setSt] = useStateLP(lpLoad);
  const [slug, setSlug] = useStateLP(() => { try { return localStorage.getItem("sss-lwc-site") || ""; } catch (e) { return ""; } });
  const [tab, setTab] = useStateLP("items");
  const [modal, setModal] = useStateLP(null);        // { kind, ...payload }
  const [pub, setPub] = useStateLP({ state: "idle" });
  const timer = useRefLP(null);

  const sites = st.sites || [];
  const site = sites.find(s => s.slug === slug) || sites[0] || null;
  const custs = (st.customers || []).filter(c => site && c.site === site.slug);
  const newCount = (s) => (st.customers || []).filter(c => c.site === s.slug).reduce((n, c) => n + (c.orders || []).filter(o => o.isNew).length, 0);

  useEffectLP(() => { if (site && site.slug !== slug) setSlug(site.slug); }, [site && site.slug]);
  useEffectLP(() => { try { if (slug) localStorage.setItem("sss-lwc-site", slug); } catch (e) {} }, [slug]);
  useEffectLP(() => {
    const h = () => setSt(lpLoad());
    window.addEventListener("sss-lwc-changed", h);
    window.addEventListener("sss-data-synced", h);
    return () => { window.removeEventListener("sss-lwc-changed", h); window.removeEventListener("sss-data-synced", h); };
  }, []);
  useEffectLP(() => {
    let alive = true;
    lpCompactPhotos().then(n => { if (alive && n) setSt(lpLoad()); }).catch(() => {});
    const run = () => lpIngest().then(n => { if (alive && n) { setSt(lpLoad()); lpToast(`📥 ออเดอร์ใหม่จากลูกค้า ${n} ใบ`); } });
    run();
    const t = setInterval(run, 60000);
    return () => { alive = false; clearInterval(t); if (timer.current) clearTimeout(timer.current); };
  }, []);

  const mutate = (fn) => { const n = lpMutate(fn); setSt(n); return n; };
  const siteOf = (n) => n.sites.find(x => x.slug === site.slug);

  // เผยแพร่ทั้งเว็บ (หน้าตา + สินค้า + บัญชีลูกค้าทุกคน)
  const publishNow = async (targetSlug) => {
    const sl = targetSlug || (site && site.slug); if (!sl) return;
    if (timer.current) { clearTimeout(timer.current); timer.current = null; }
    setPub({ state: "busy", msg: "กำลังเผยแพร่…" });
    try {
      const r = await lpPublishSite(sl, (m) => setPub({ state: "busy", msg: m }));
      setSt(lpLoad());
      setPub({ state: "ok", at: new Date().toISOString(), msg: r ? `${r.items} รายการ · ${r.customers} บัญชี` : "" });
    } catch (e) { setPub({ state: "err", msg: lpErrText(e) }); }
  };
  // แก้สินค้า/ราคา → รอ 2.5 วิแล้วเผยแพร่อัตโนมัติ (แก้ติด ๆ กันหลายช่องก็ส่งรอบเดียว)
  const schedule = () => {
    mutate(n => { const s = siteOf(n); if (s) s.dirty = true; });
    setPub({ state: "wait", msg: "มีการแก้ไข — จะเผยแพร่อัตโนมัติใน 2 วินาที" });
    if (timer.current) clearTimeout(timer.current);
    const sl = site.slug;
    timer.current = setTimeout(() => { timer.current = null; publishNow(sl); }, 2500);
  };
  // ออเดอร์/บิลเปลี่ยน → ส่งเฉพาะแถวของลูกค้าคนนั้น (เร็ว)
  const publishCust = async (custId) => {
    const n = lpLoad(); const s = n.sites.find(x => x.slug === site.slug);
    const c = n.customers.find(x => x.id === custId);
    if (!s || !c) return;
    try { await lpPublishCustomer(s, c); setPub({ state: "ok", at: new Date().toISOString(), msg: `อัปเดตให้ ${c.name || c.id} แล้ว` }); }
    catch (e) { setPub({ state: "err", msg: lpErrText(e) }); }
  };
  const mutateCust = (custId, fn) => mutate(n => { const c = n.customers.find(x => x.id === custId); if (c) fn(c); });

  const copyLink = (text, msg) => {
    try { navigator.clipboard.writeText(text); lpToast(msg || "คัดลอกแล้ว"); }
    catch (e) { window.prompt("คัดลอกข้อความนี้", text); }
  };

  // ── ไม่มีเว็บเลย ──
  if (!site) {
    return (
      <>
        <PageHead title="ORDER ONLINE · เว็บสั่งงานลูกค้า" sub="สร้างเว็บสั่งงานแยกให้ลูกค้าแต่ละราย (LWC · BOTH · …) พร้อมรหัสเข้าใช้ของตัวเอง"/>
        <Card>
          <div style={{padding: 34, textAlign: "center"}}>
            <div style={{fontSize: 40, marginBottom: 8}}>🌐</div>
            <div style={{fontSize: 16, fontWeight: 800, marginBottom: 6}}>ยังไม่มีเว็บสั่งงานลูกค้า</div>
            <div style={{fontSize: 13, color: "var(--ink-3)", marginBottom: 16, lineHeight: 1.8}}>
              สร้างเว็บให้ลูกค้าหนึ่งราย → ได้ลิงก์ <b className="mono">{location.origin}/shop/ชื่อเว็บ</b> ทันที<br/>
              ตั้งสินค้า+ราคา แล้วออกรหัสเข้าใช้ให้ลูกค้า
            </div>
            <Btn kind="primary" icon={I.plus} onClick={() => setModal({ kind: "site", f: { ...lpNewSite(""), slug: "", name: "", title: "", _new: true } })}>สร้างเว็บแรก</Btn>
          </div>
        </Card>
        {modal && modal.kind === "site" && <LPSiteModal f={modal.f} sites={sites} onClose={() => setModal(null)}
          onSave={(f) => { const sl = f.slug; mutate(n => { n.sites.push({ ...lpNewSite(sl), ...f, _new: undefined }); }); setSlug(sl); setModal(null); publishNow(sl); }}/>}
      </>
    );
  }

  const noPrice = site.catalog.filter(it => it.show !== false && !(Number(it.price) > 0)).length;
  const orderCount = custs.reduce((n, c) => n + (c.orders || []).length, 0);
  const billCount = custs.reduce((n, c) => n + (c.bills || []).length, 0);

  return (
    <>
      <PageHead title="ORDER ONLINE · เว็บสั่งงานลูกค้า"
        sub="แต่ละลูกค้ามีเว็บของตัวเอง · ลูกค้าล็อกอินด้วยรหัสที่เราออกให้ แล้วสั่งงาน ดูสถานะ และพิมพ์บิลเองได้"
        right={<>
          <Btn icon={I.download} kind="ghost" onClick={async () => { const n = await lpIngest(); setSt(lpLoad()); lpToast(n ? `รับออเดอร์ใหม่ ${n} ใบ` : "ยังไม่มีออเดอร์ใหม่"); }}>ดึงออเดอร์</Btn>
          <Btn icon={I.plus} kind="primary" onClick={() => setModal({ kind: "site", f: { ...lpNewSite(""), slug: "", name: "", title: "", _new: true } })}>สร้างเว็บใหม่</Btn>
        </>}/>

      {/* ── เลือกเว็บ ── */}
      <div style={{display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 12}}>
        {sites.map(s => {
          const on = s.slug === site.slug, nw = newCount(s);
          return (
            <button key={s.slug} onClick={() => { setSlug(s.slug); setPub({ state: "idle" }); }}
              style={{display: "inline-flex", alignItems: "center", gap: 8, padding: "8px 15px", borderRadius: 99, cursor: "pointer", fontFamily: "inherit",
                fontSize: 13.5, fontWeight: 800, border: `2px solid ${on ? s.color : "var(--line-strong)"}`,
                background: on ? s.color : "var(--surface)", color: on ? "#fff" : "var(--ink-2)", opacity: s.active === false ? 0.55 : 1}}>
              <span style={{width: 10, height: 10, borderRadius: "50%", background: on ? "#fff" : s.color}}/>
              {s.name || s.slug}{s.active === false ? " (ปิด)" : ""}
              {nw > 0 && <span style={{background: "#dc2626", color: "#fff", borderRadius: 99, fontSize: 11, padding: "1px 7px"}}>{nw} ใหม่</span>}
            </button>
          );
        })}
      </div>

      {/* ── การ์ดเว็บ ── */}
      <Card flush>
        <div style={{height: 5, background: site.color, borderRadius: "10px 10px 0 0"}}/>
        <div style={{padding: "13px 15px", display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap"}}>
          <div style={{width: 52, height: 52, borderRadius: 14, background: site.color, color: "#fff", display: "grid", placeItems: "center",
            fontWeight: 800, fontSize: 15, overflow: "hidden", flexShrink: 0}}>
            {site.logo ? <img src={site.logo} alt="" style={{width: "100%", height: "100%", objectFit: "cover"}}/> : (site.name || site.slug).slice(0, 4).toUpperCase()}
          </div>
          <div style={{flex: 1, minWidth: 230}}>
            <div style={{fontSize: 16, fontWeight: 800}}>{site.title}</div>
            <div style={{fontSize: 12.5, color: "var(--ink-3)", display: "flex", gap: 6, alignItems: "center", flexWrap: "wrap"}}>
              🔗 <span className="mono" style={{color: "var(--ink-1)"}}>{lpLink(site).replace(/^https?:\/\//, "")}</span>
              <span>· {custs.length} บัญชี · {site.catalog.length} รายการ</span>
            </div>
          </div>
          <div style={{display: "flex", gap: 6, flexWrap: "wrap"}}>
            <Btn size="sm" kind="ghost" onClick={() => copyLink(lpLink(site), "คัดลอกลิงก์เว็บแล้ว")}>📋 คัดลอกลิงก์</Btn>
            <Btn size="sm" kind="ghost" onClick={() => window.open(lpLink(site), "_blank")}>↗ เปิดเว็บ</Btn>
            <Btn size="sm" kind="ghost" onClick={() => setModal({ kind: "site", f: { ...site } })}>⚙️ ตั้งค่าเว็บ</Btn>
            <Btn size="sm" kind="ghost" onClick={() => setModal({ kind: "diag" })}>🩺 ตรวจระบบ</Btn>
            <Btn size="sm" kind="primary" onClick={() => publishNow()} disabled={pub.state === "busy"}>🚀 เผยแพร่</Btn>
          </div>
        </div>
        <LPPubBar pub={pub} site={site} onRetry={() => publishNow()}/>
      </Card>

      <div style={{marginTop: 12}}>
        <Tabs items={[
          { key: "items", label: "🛍️ สินค้า + ราคา", count: site.catalog.length },
          { key: "custs", label: "👥 บัญชีลูกค้า", count: custs.length },
          { key: "orders", label: "📋 ออเดอร์", count: orderCount },
          { key: "bills", label: "🧾 บิล", count: billCount },
        ]} active={tab} onChange={setTab}/>
      </div>

      {tab === "items" && <LPCatalog site={site} noPrice={noPrice}
        onChange={(fn) => { mutate(n => fn(siteOf(n))); schedule(); }}
        onEdit={(it) => setModal({ kind: "item", f: it ? { ...it } : { _k: lpUid("it"), code: "", name: "", unit: "ชิ้น", price: 0, photo: "", note: "", moq: 1, options: [], show: true, cat: "", _new: true } })}
        onImport={() => setModal({ kind: "import" })}
        onCats={() => setModal({ kind: "cats" })}/>}
      {tab === "custs" && <LPCustomers site={site} custs={custs}
        onAdd={() => setModal({ kind: "cust", f: { site: site.slug, id: "", code: lpGenCode(), name: "", contact: "", phone: "", address: "", taxId: "", active: true, orders: [], bills: [], _new: true } })}
        onEdit={(c) => setModal({ kind: "cust", f: { ...c } })}
        onCopy={(c) => copyLink(`เว็บสั่งงาน: ${lpLink(site)}\nรหัสลูกค้า: ${c.id}\nรหัสผ่าน: ${c.code}`, "คัดลอกข้อมูลเข้าใช้แล้ว — ส่งให้ลูกค้าได้เลย")}
        onDelete={async (c) => {
          if ((c.orders || []).length || (c.bills || []).length) { lpToast("บัญชีนี้มีออเดอร์/บิลอยู่ — ใช้ 'ปิดใช้งาน' แทนการลบ (ข้อมูลจะไม่หาย)"); return; }
          const ok = window.confirmDelete ? await window.confirmDelete(`บัญชี ${c.name || c.id}`, "ลูกค้าจะเข้าเว็บด้วยรหัสนี้ไม่ได้อีก") : confirm("ลบ?");
          if (!ok) return;
          mutate(n => { n.customers = n.customers.filter(x => x.id !== c.id); });
          await lpDelete(lpCustKey(c));
          setPub({ state: "busy", msg: "เปลี่ยนกุญแจแคตตาล็อก…" });
          try { await lpRotateSecret(site.slug); setSt(lpLoad()); setPub({ state: "ok", at: new Date().toISOString(), msg: "ลบบัญชีแล้ว" }); }
          catch (e) { setPub({ state: "err", msg: lpErrText(e) }); }
        }}/>}
      {tab === "orders" && <LPOrders site={site} custs={custs}
        onStatus={(c, o, status) => { mutateCust(c.id, cc => { cc.orders = cc.orders.map(x => (x.no === o.no ? { ...x, status, isNew: false } : x)); }); publishCust(c.id); }}
        onOpen={(c, o) => setModal({ kind: "order", cust: c, o: { ...o } })}
        onBill={(c, o) => {
          if (lpOrderTotals(o, lpVat(site, c)).pending) { lpToast("ยังมีรายการที่ไม่มีราคา — ใส่ราคาให้ครบก่อนออกบิล"); setModal({ kind: "order", cust: c, o: { ...o } }); return; }
          setModal({ kind: "bill", cust: c, order: o });
        }}
        onWO={(c, o) => setModal({ kind: "wo", cust: c, order: o })}
        onGoProject={() => navigate && navigate("projects")}/>}
      {tab === "bills" && <LPBills site={site} custs={custs}
        onPaid={(c, b) => { mutateCust(c.id, cc => { cc.bills = cc.bills.map(x => (x.no === b.no ? { ...x, paid: !x.paid } : x)); }); publishCust(c.id); }}/>}

      {/* ── หน้าต่างต่าง ๆ ── */}
      {modal && modal.kind === "site" && <LPSiteModal f={modal.f} sites={sites} custCount={(st.customers || []).filter(c => c.site === modal.f.slug).length}
        onClose={() => setModal(null)}
        onDelete={async () => {
          const s = modal.f;
          mutate(n => { n.sites = n.sites.filter(x => x.slug !== s.slug); });
          await lpDelete(`site:${s.slug}`); await lpDelete(lpCatKey(s));
          setModal(null); setSlug(""); lpToast(`ลบเว็บ ${s.name} แล้ว`);
        }}
        onSave={(f) => {
          if (f._new) { mutate(n => { n.sites.push({ ...lpNewSite(f.slug), ...f, _new: undefined }); }); setSlug(f.slug); }
          else mutate(n => { const i = n.sites.findIndex(x => x.slug === f.slug); if (i >= 0) n.sites[i] = { ...n.sites[i], ...f }; });
          setModal(null); publishNow(f.slug);
        }}/>}
      {modal && modal.kind === "item" && <LPItemModal f={modal.f} cats={site.categories} onClose={() => setModal(null)}
        onSave={(it) => {
          mutate(n => { const s = siteOf(n); const i = s.catalog.findIndex(x => x._k === it._k); const clean = { ...it, _new: undefined };
            if (i >= 0) s.catalog[i] = clean; else s.catalog.unshift(clean); });
          setModal(null); schedule();
        }}/>}
      {modal && modal.kind === "cats" && <LPCatModal cats={site.categories} catalog={site.catalog} onClose={() => setModal(null)}
        onSave={(list) => {
          const ids = new Set(list.map(c => c.id));
          mutate(n => { const s = siteOf(n); s.categories = list; s.catalog = s.catalog.map(it => (it.cat && !ids.has(it.cat) ? { ...it, cat: "" } : it)); });
          setModal(null); schedule();
        }}/>}
      {modal && modal.kind === "import" && <LPImportModal site={site} onClose={() => setModal(null)}
        onAdd={(items) => { mutate(n => { const s = siteOf(n); s.catalog = [...items, ...s.catalog]; }); setModal(null); lpToast(`เพิ่ม ${items.length} รายการแล้ว — ตั้งราคาได้เลย`); schedule(); }}/>}
      {modal && modal.kind === "cust" && <LPCustModal f={modal.f} site={site} sites={sites} allCusts={st.customers || []} onClose={() => setModal(null)}
        onSave={async (f) => {
          const id = String(f.id || "").trim().toUpperCase();
          const code = String(f.code || "").trim().toUpperCase();
          const all = lpLoad().customers;
          if (!id || !code) { lpToast("ใส่รหัสลูกค้าและรหัสผ่านให้ครบ"); return; }
          if (f._new && all.some(c => c.id === id)) { lpToast(`รหัสลูกค้า ${id} ถูกใช้แล้ว (อาจอยู่คนละเว็บ) — ตั้งรหัสใหม่`); return; }
          const old = all.find(c => c.id === id);
          const disabling = old && old.active !== false && f.active === false;
          const moved = old && old.site !== f.site;
          if (old && old.code !== code) await lpDelete(lpCustKey(old));      // เปลี่ยนรหัสผ่าน = ปิดรหัสเดิมทันที
          const rec = { ...(old || { orders: [], bills: [] }), ...f, id, code, _new: undefined };
          mutate(n => { n.customers = f._new ? [...n.customers, rec] : n.customers.map(c => (c.id === id ? rec : c)); });
          setModal(null);
          try {
            if (disabling || moved) {
              setPub({ state: "busy", msg: "เปลี่ยนกุญแจแคตตาล็อก…" });
              await lpRotateSecret(moved ? old.site : f.site);
              if (moved) await lpPublishSite(f.site);
              setSt(lpLoad());
              setPub({ state: "ok", at: new Date().toISOString(), msg: disabling ? `ปิดบัญชี ${id} แล้ว` : `ย้ายบัญชี ${id} แล้ว` });
            } else {
              const n = lpLoad(); const s = n.sites.find(x => x.slug === rec.site);
              await lpPublishCustomer(s, n.customers.find(c => c.id === id));
              setPub({ state: "ok", at: new Date().toISOString(), msg: `บันทึกบัญชี ${id} แล้ว` });
            }
          } catch (e) { setPub({ state: "err", msg: lpErrText(e) }); }
        }}/>}
      {modal && modal.kind === "wo" && <LPWorkOrderModal site={site} cust={modal.cust} order={modal.order} onClose={() => setModal(null)}
        onDone={(r) => {
          const id = modal.cust.id, no = modal.order.no;
          mutateCust(id, c => { c.orders = c.orders.map(o => (o.no === no ? { ...o, ...r, isNew: false,
            status: LP_STATUSES.indexOf(o.status) < 1 ? "ยืนยันแล้ว" : o.status } : o)); });
          setModal(null); publishCust(id);
          lpToast(`✅ เปิด ${r.woNo} ใน ${r.projectCode} แล้ว — สถานะลูกค้าจะขยับเองตามการ์ดงาน`);
        }}/>}
      {modal && modal.kind === "diag" && <LPDiagModal site={site} custs={custs} onClose={() => setModal(null)}
        onPublish={async () => { await publishNow(); }}/>}
      {modal && modal.kind === "order" && <LPOrderModal o={modal.o} cust={modal.cust} vp={lpVat(site, modal.cust)} onClose={() => setModal(null)}
        onSave={(next) => { mutateCust(modal.cust.id, c => { c.orders = c.orders.map(o => (o.no === next.no ? next : o)); }); const id = modal.cust.id; setModal(null); publishCust(id); }}/>}
      {modal && modal.kind === "bill" && <LPBillModal site={site} order={modal.order} cust={modal.cust} vp={lpVat(site, modal.cust)}
        allBills={(st.customers || []).flatMap(c => c.bills || [])} onClose={() => setModal(null)}
        onSave={(bill) => {
          const id = modal.cust.id;
          mutateCust(id, c => {
            c.bills = [bill, ...(c.bills || [])];
            c.orders = (c.orders || []).map(o => (o.no === bill.orderNo ? { ...o, status: "ส่งของแล้ว", billNo: bill.no, total: bill.total, isNew: false } : o));
          });
          setModal(null); publishCust(id); lpToast(`ออกบิล ${bill.no} แล้ว — ลูกค้าพิมพ์เองได้จากเว็บ`);
        }}/>}
    </>
  );
}
window.LWCPortalPage = LWCPortalPage;

// ═══ 🩺 ตรวจระบบ — ลองล็อกอินแบบเดียวกับหน้าเว็บลูกค้าทุกขั้น แล้วบอกว่าติดตรงไหน ═══
// ใช้กุญแจสาธารณะ track_public ตัวเดียวกับ lwc/portal.js (อ่านได้เฉพาะแถวที่รู้คีย์)
const LP_PUBLIC_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidHJhY2tfcHVibGljIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3ODU4NDEyNTMsImV4cCI6MjEwMTIwMTI1M30.2vG8HXJGz_xXzoO-x4g0MM8YYpkY9-elQU4pvf_i_5I";
async function lpPublicRead(key) {
  const base = (window.SSSDB && window.SSSDB.url) || "https://supabase.triples-creative.com";
  let r;
  try {
    r = await fetch(`${base}/rest/v1/portal_store?key=eq.${encodeURIComponent(key)}&select=data`, {
      headers: { apikey: LP_PUBLIC_KEY, Authorization: "Bearer " + LP_PUBLIC_KEY, Accept: "application/json", "x-portal-key": key },
    });
  } catch (e) { return { status: 0, text: String(e && e.message || e) }; }
  const text = await r.text().catch(() => "");
  let rows = null; try { rows = JSON.parse(text); } catch (e) {}
  return { status: r.status, text, data: Array.isArray(rows) && rows[0] ? rows[0].data : null };
}
function lpPublicProblem(res) {
  if (res.status === 0) return "เบราว์เซอร์เชื่อมต่อฐานข้อมูลไม่ได้ (เน็ต / Cloudflare Tunnel / CORS ไม่ยอมให้ส่ง header x-portal-key)";
  if (res.status === 404) return "ฐานข้อมูลยังไม่รู้จักตาราง portal_store → รัน bash migrate/setup-portal-store.sh บนเครื่อง DATA (สคริปต์จะรีสตาร์ต supabase-rest ให้)";
  if (res.status === 401) return "กุญแจสาธารณะถูกปฏิเสธ (JWT secret ของเครื่อง DATA ไม่ตรงกับกุญแจในหน้าเว็บ)";
  if (res.status === 403) return "role track_public ยังไม่มีสิทธิ์อ่าน portal_store → รัน bash migrate/setup-portal-store.sh อีกครั้ง";
  return `ตอบกลับผิดปกติ (HTTP ${res.status}) ${String(res.text || "").slice(0, 160)}`;
}
function LPDiagModal({ site, custs, onClose, onPublish }) {
  const { Btn, Drawer } = window.UI;
  const [rows, setRows] = useStateLP([]);
  const [busy, setBusy] = useStateLP(true);
  const run = async () => {
    setBusy(true);
    const out = [];
    const push = (ok, title, detail) => { out.push({ ok, title, detail }); setRows(out.slice()); };
    setRows([]);
    // 1) ฝั่งหลังบ้าน: ตารางมีไหม + แถวถูกส่งขึ้นไปแล้วหรือยัง
    const c = lpClient();
    let adminKeys = null;
    if (!c) push(false, "หลังบ้านยังไม่ได้เชื่อมฐานข้อมูล", "ตั้งค่าการเชื่อม Supabase ในโปรแกรมก่อน");
    else {
      const want = [`site:${site.slug}`, lpCatKey(site), ...custs.filter(x => x.id && x.code).map(lpCustKey)];
      const res = await c.from("portal_store").select("key").in("key", want);
      if (res.error) push(false, "หลังบ้านอ่านตาราง portal_store ไม่ได้", lpErrText(res.error));
      else { adminKeys = new Set((res.data || []).map(r => r.key)); push(true, "ตาราง portal_store พร้อมใช้งาน", `พบข้อมูลของเว็บนี้ ${adminKeys.size}/${want.length} แถว`); }
    }
    // 2) ฝั่งลูกค้า: อ่านหน้าตาเว็บด้วยกุญแจสาธารณะ (เหมือนเปิดหน้าล็อกอิน)
    const sres = await lpPublicRead(`site:${site.slug}`);
    if (sres.status !== 200) push(false, "หน้าเว็บลูกค้าเชื่อมต่อฐานข้อมูลไม่ได้", lpPublicProblem(sres));
    else if (!sres.data) push(false, "หน้าเว็บลูกค้ายังไม่เห็นข้อมูลเว็บนี้",
      adminKeys && adminKeys.has(`site:${site.slug}`) ? "มีข้อมูลในตารางแล้ว แต่สิทธิ์ (RLS) ไม่ปล่อยให้อ่าน → รัน bash migrate/setup-portal-store.sh อีกครั้ง" : "ยังไม่ได้เผยแพร่ → กด 🚀 เผยแพร่");
    else push(true, "หน้าเว็บลูกค้าเชื่อมต่อได้", `${sres.data.title || site.title} · ${lpLink(site)}`);
    // 3) ลองล็อกอินทุกบัญชี
    if (sres.status === 200) {
      if (!custs.length) push(false, "ยังไม่มีบัญชีลูกค้าในเว็บนี้", "ไปที่แท็บ 👥 บัญชีลูกค้า → เพิ่มบัญชี แล้วส่งรหัสให้ลูกค้า");
      let catChecked = false;
      for (const cu of custs) {
        const who = `${cu.id} / รหัสผ่าน ${cu.code}`;
        if (!cu.id || !cu.code) { push(false, `บัญชี ${cu.name || "?"}`, "ไม่มีรหัสลูกค้าหรือรหัสผ่าน"); continue; }
        if (cu.active === false) { push(false, `บัญชี ${who}`, "ถูกปิดใช้งานอยู่ — ลูกค้าจะเข้าไม่ได้"); continue; }
        const r = await lpPublicRead(lpCustKey(cu));
        if (r.status !== 200) { push(false, `ล็อกอิน ${who}`, lpPublicProblem(r)); continue; }
        if (!r.data) { push(false, `ล็อกอิน ${who}`, "ไม่พบบัญชีบนเว็บ (ยังไม่ได้เผยแพร่หลังสร้าง/แก้รหัส) → กด 🚀 เผยแพร่"); continue; }
        if (!catChecked && r.data.catKey) {
          catChecked = true;
          const cr = await lpPublicRead(r.data.catKey);
          const n = cr.data && (cr.data.catalog || []).length;
          if (cr.status !== 200 || !cr.data) push(false, "รายการสินค้า", cr.status !== 200 ? lpPublicProblem(cr) : "ยังไม่ได้ส่งรายการสินค้าขึ้นเว็บ (หรือรหัสแคตตาล็อกเปลี่ยน) → กด 🚀 เผยแพร่");
          else push(true, "รายการสินค้า", `ลูกค้าเห็น ${n} รายการ`);
        }
        push(true, `ล็อกอิน ${who}`, `เข้าได้ ✓ ${r.data.name || ""}`);
      }
    }
    setBusy(false);
  };
  useEffectLP(() => { run(); }, []);
  const bad = rows.filter(r => !r.ok).length;
  return (
    <Drawer open title={`🩺 ตรวจระบบเว็บ ${site.name}`} onClose={onClose}
      footer={<><Btn kind="ghost" onClick={onClose}>ปิด</Btn>
        <Btn kind="ghost" onClick={run} disabled={busy}>ตรวจอีกครั้ง</Btn>
        <Btn kind="primary" disabled={busy} onClick={async () => { setBusy(true); try { await onPublish(); } finally { run(); } }}>🚀 เผยแพร่แล้วตรวจใหม่</Btn></>}>
      <div style={{fontSize: 12.5, color: "var(--ink-3)", marginBottom: 10}}>ทดสอบแบบเดียวกับที่ลูกค้าเปิดเว็บและล็อกอินจริง ทีละขั้น</div>
      <div style={{display: "flex", flexDirection: "column", gap: 6}}>
        {rows.map((r, i) => (
          <div key={i} style={{display: "flex", gap: 10, padding: "9px 11px", borderRadius: 9, border: "1px solid var(--line)",
            background: r.ok ? "rgba(21,128,61,.06)" : "rgba(220,38,38,.07)"}}>
            <span style={{fontSize: 16}}>{r.ok ? "✅" : "❌"}</span>
            <div style={{minWidth: 0}}><b style={{fontSize: 13.5}}>{r.title}</b>
              <div style={{fontSize: 12.5, color: r.ok ? "var(--ink-3)" : "#b91c1c", marginTop: 2, wordBreak: "break-word"}}>{r.detail}</div></div>
          </div>
        ))}
        {busy && <div style={{padding: 10, color: "var(--ink-3)", fontSize: 13}}>⏳ กำลังตรวจ…</div>}
        {!busy && !bad && rows.length > 0 && <div style={{padding: 10, fontWeight: 800, color: "#15803d"}}>ทุกอย่างพร้อม — ลูกค้าล็อกอินได้แล้ว</div>}
      </div>
    </Drawer>
  );
}

// ── แถบสถานะการเผยแพร่ ──
function LPPubBar({ pub, site, onRetry }) {
  const { Btn } = window.UI;
  let bg = "var(--surface-2)", fg = "var(--ink-3)", txt = site.publishedAt ? `✅ เผยแพร่ล่าสุด ${lpThai(site.publishedAt)} ${lpTime(site.publishedAt)} น.` : "ยังไม่เคยเผยแพร่ — กด 🚀 เผยแพร่ เพื่อให้ลูกค้าเห็น";
  if (site.dirty && pub.state === "idle") { bg = "rgba(180,83,9,.1)"; fg = "var(--warning)"; txt = "✏️ มีการแก้ไขที่ยังไม่ได้เผยแพร่"; }
  if (pub.state === "wait") { bg = "rgba(180,83,9,.08)"; fg = "var(--warning)"; txt = "⏳ " + pub.msg; }
  if (pub.state === "busy") { bg = "rgba(29,78,216,.08)"; fg = "#1d4ed8"; txt = "🔄 " + pub.msg; }
  if (pub.state === "ok") { bg = "rgba(21,128,61,.09)"; fg = "var(--success)"; txt = `✅ เผยแพร่แล้ว ${lpTime(pub.at)} น.${pub.msg ? " · " + pub.msg : ""}`; }
  if (pub.state === "err") { bg = "rgba(220,38,38,.1)"; fg = "var(--danger)"; txt = "⚠️ เผยแพร่ไม่สำเร็จ: " + pub.msg; }
  return (
    <div style={{display: "flex", alignItems: "center", gap: 10, padding: "8px 15px", background: bg, color: fg, fontSize: 12.5, fontWeight: 700,
      borderTop: "1px solid var(--line)", borderRadius: "0 0 10px 10px"}}>
      <span style={{flex: 1}}>{txt}</span>
      {(pub.state === "err" || (site.dirty && pub.state === "idle")) && <Btn size="sm" kind="ghost" onClick={onRetry}>ลองอีกครั้ง</Btn>}
    </div>
  );
}

// ═══ แท็บ 1: สินค้า + ราคา — หมวดหมู่ · เลือกหลายรายการ · แก้ราคาในตาราง ═══
function LPCatalog({ site, noPrice, onChange, onEdit, onImport, onCats }) {
  const { Btn, Card, fmtM } = window.UI;
  const [q, setQ] = useStateLP("");
  const [cat, setCat] = useStateLP("all");
  const [onlyNoPrice, setOnlyNoPrice] = useStateLP(false);
  const [sel, setSel] = useStateLP(() => new Set());
  const [bulkPrice, setBulkPrice] = useStateLP("");
  const [bulkCat, setBulkCat] = useStateLP("");
  const cats = site.categories || [];
  const catIds = new Set(cats.map(c => c.id));
  const all = site.catalog;

  const list = all.filter(it => {
    if (cat === "__none" ? (it.cat && catIds.has(it.cat)) : (cat !== "all" && it.cat !== cat)) return false;
    if (onlyNoPrice && Number(it.price) > 0) return false;
    const s = q.trim().toLowerCase();
    return !s || `${it.name || ""} ${it.code || ""} ${it.note || ""}`.toLowerCase().includes(s);
  });
  useEffectLP(() => { setSel(new Set()); }, [site.slug]);
  const selList = all.filter(it => sel.has(it._k));
  const allOn = list.length > 0 && list.every(it => sel.has(it._k));
  const toggle = (k) => setSel(p => { const n = new Set(p); n.has(k) ? n.delete(k) : n.add(k); return n; });
  const toggleAll = () => setSel(p => { const n = new Set(p); if (allOn) list.forEach(it => n.delete(it._k)); else list.forEach(it => n.add(it._k)); return n; });
  const patchSel = (patch, msg) => {
    const ks = new Set(sel);
    onChange(s => { s.catalog = s.catalog.map(it => (ks.has(it._k) ? { ...it, ...patch } : it)); });
    lpToast(msg || `แก้ ${ks.size} รายการแล้ว`);
  };
  const patchOne = (k, patch) => onChange(s => { s.catalog = s.catalog.map(it => (it._k === k ? { ...it, ...patch } : it)); });
  const countIn = (id) => all.filter(it => (id === "__none" ? !(it.cat && catIds.has(it.cat)) : it.cat === id)).length;

  const chip = (id, label, n) => (
    <button key={id} onClick={() => setCat(id)} style={{padding: "6px 13px", borderRadius: 99, border: `1.5px solid ${cat === id ? site.color : "var(--line-strong)"}`,
      background: cat === id ? site.color : "var(--surface)", color: cat === id ? "#fff" : "var(--ink-2)", fontWeight: 700, fontSize: 12.5,
      cursor: "pointer", fontFamily: "inherit", whiteSpace: "nowrap"}}>
      {label} <span style={{opacity: 0.7, fontWeight: 600}}>{n}</span>
    </button>
  );

  return (
    <Card flush>
      {/* แถบเครื่องมือ */}
      <div style={{padding: "12px 14px", display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center", borderBottom: "1px solid var(--line)"}}>
        <input className="input" style={{flex: "1 1 220px", maxWidth: 320}} placeholder="🔍 ค้นหาชื่อ / รหัส…" value={q} onChange={e => setQ(e.target.value)}/>
        <label style={{display: "flex", alignItems: "center", gap: 6, fontSize: 12.5, fontWeight: 700, color: "var(--ink-2)", cursor: "pointer"}}>
          <input type="checkbox" checked={onlyNoPrice} onChange={e => setOnlyNoPrice(e.target.checked)}/> เฉพาะที่ยังไม่ตั้งราคา
        </label>
        <span style={{flex: 1}}/>
        <Btn size="sm" kind="ghost" onClick={onCats}>🗂️ จัดการหมวดหมู่</Btn>
        <Btn size="sm" kind="ghost" onClick={onImport}>📚 ดึงจากคลังชิ้นงาน</Btn>
        <Btn size="sm" kind="primary" icon={I.plus} onClick={() => onEdit(null)}>เพิ่มรายการ</Btn>
      </div>
      {/* หมวดหมู่ */}
      <div style={{padding: "10px 14px", display: "flex", gap: 6, overflowX: "auto", borderBottom: "1px solid var(--line)"}}>
        {chip("all", "ทั้งหมด", all.length)}
        {cats.map(c => chip(c.id, `${c.icon ? c.icon + " " : ""}${c.name}`, countIn(c.id)))}
        {chip("__none", "ไม่มีหมวด", countIn("__none"))}
      </div>
      {noPrice > 0 && (
        <div style={{padding: "8px 14px", background: "rgba(180,83,9,.08)", color: "var(--warning)", fontSize: 12.5, fontWeight: 700, borderBottom: "1px solid var(--line)"}}>
          ⚠️ {noPrice} รายการยังไม่ตั้งราคา — ลูกค้าจะเห็นเป็น "สอบถามราคา" (สั่งได้ แล้วเราเสนอราคากลับ) · ติ๊กเลือกหลายรายการแล้วตั้งราคาพร้อมกันได้
        </div>
      )}
      {/* แถบจัดการหลายรายการ */}
      {sel.size > 0 && (
        <div style={{position: "sticky", top: 56, zIndex: 5, padding: "9px 14px", background: site.color, color: "#fff", display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center"}}>
          <b style={{fontSize: 13.5}}>✓ เลือก {sel.size} รายการ</b>
          <span style={{width: 1, height: 22, background: "rgba(255,255,255,.35)"}}/>
          <input type="number" min="0" step="0.01" placeholder="ราคา/หน่วย" value={bulkPrice} onChange={e => setBulkPrice(e.target.value)}
            style={{width: 110, height: 30, borderRadius: 7, border: 0, padding: "0 8px", fontFamily: "inherit", color: "#111"}}/>
          <button className="btn sm" style={{background: "#fff", color: "#111"}} onClick={() => {
            if (bulkPrice === "" || !(Number(bulkPrice) >= 0)) { lpToast("ใส่ราคาก่อน"); return; }
            patchSel({ price: LP_R2(bulkPrice) }, `ตั้งราคา ฿${fmtM(bulkPrice)} ให้ ${sel.size} รายการแล้ว`); setBulkPrice("");
          }}>💰 ตั้งราคา</button>
          <select value={bulkCat} onChange={e => setBulkCat(e.target.value)}
            style={{height: 30, borderRadius: 7, border: 0, padding: "0 6px", fontFamily: "inherit", color: "#111"}}>
            <option value="">— เลือกหมวด —</option>
            {cats.map(c => <option key={c.id} value={c.id}>{c.icon ? c.icon + " " : ""}{c.name}</option>)}
            <option value="__none">ไม่มีหมวด</option>
          </select>
          <button className="btn sm" style={{background: "#fff", color: "#111"}} onClick={() => {
            if (!bulkCat) { lpToast("เลือกหมวดก่อน"); return; }
            patchSel({ cat: bulkCat === "__none" ? "" : bulkCat }, `ย้าย ${sel.size} รายการแล้ว`); setBulkCat("");
          }}>🗂️ ย้ายหมวด</button>
          <button className="btn sm" style={{background: "rgba(255,255,255,.18)", color: "#fff", border: "1px solid rgba(255,255,255,.4)"}} onClick={() => patchSel({ show: true }, `แสดง ${sel.size} รายการ`)}>👁 แสดง</button>
          <button className="btn sm" style={{background: "rgba(255,255,255,.18)", color: "#fff", border: "1px solid rgba(255,255,255,.4)"}} onClick={() => patchSel({ show: false }, `ซ่อน ${sel.size} รายการ`)}>🙈 ซ่อน</button>
          <button className="btn sm" style={{background: "#fee2e2", color: "#b91c1c"}} onClick={async () => {
            const ok = window.confirmDelete ? await window.confirmDelete(`${sel.size} รายการ`, "ลบออกจากเว็บนี้ (รายการในคลังชิ้นงานไม่ถูกลบ)") : confirm("ลบ?");
            if (!ok) return;
            const ks = new Set(sel); onChange(s => { s.catalog = s.catalog.filter(it => !ks.has(it._k)); }); setSel(new Set());
            lpToast(`ลบ ${ks.size} รายการแล้ว`);
          }}>🗑️ ลบ</button>
          <span style={{flex: 1}}/>
          <button className="btn sm" style={{background: "transparent", color: "#fff", border: "1px solid rgba(255,255,255,.4)"}} onClick={() => setSel(new Set())}>ยกเลิกเลือก</button>
        </div>
      )}
      {all.length === 0 ? (
        <div style={{padding: 30, textAlign: "center", color: "var(--ink-3)", fontSize: 13}}>
          ยังไม่มีรายการ — กด "📚 ดึงจากคลังชิ้นงาน" แล้วติ๊กเลือกเฉพาะชิ้นที่ลูกค้ารายนี้สั่ง
        </div>
      ) : (
        <div className="table-wrap"><table className="t">
          <thead><tr>
            <th style={{width: 34}}><input type="checkbox" checked={allOn} onChange={toggleAll} title="เลือกทั้งหมดที่แสดง"/></th>
            <th style={{width: 62}}>รูป</th><th>ชื่อรายการ</th><th style={{width: 150}}>หมวด</th>
            <th className="right" style={{width: 128}}>ราคา/หน่วย (฿)</th><th style={{width: 66}}>หน่วย</th>
            <th className="right" style={{width: 58}}>ขั้นต่ำ</th><th style={{width: 104}}></th>
          </tr></thead>
          <tbody>
            {list.map(it => (
              <tr key={it._k} style={{opacity: it.show === false ? 0.45 : 1, background: sel.has(it._k) ? "var(--brand-bg, rgba(15,118,110,.07))" : undefined}}>
                <td><input type="checkbox" checked={sel.has(it._k)} onChange={() => toggle(it._k)}/></td>
                <td>{it.photo
                  ? <img src={it.photo} alt="" loading="lazy" style={{width: 50, height: 40, objectFit: "contain", background: "#fff", borderRadius: 6, border: "1px solid var(--line)"}}/>
                  : <div style={{width: 50, height: 40, borderRadius: 6, border: "1px dashed var(--line-strong)", display: "grid", placeItems: "center", fontSize: 16, opacity: 0.4}}>📦</div>}</td>
                <td style={{cursor: "pointer"}} onClick={() => onEdit(it)}>
                  <div style={{fontWeight: 700}}>{it.name}{it.show === false && <span style={{fontSize: 10.5, color: "var(--ink-3)", fontWeight: 600}}> · ซ่อนอยู่</span>}</div>
                  <div style={{fontSize: 11, color: "var(--ink-3)"}}>{[it.code, it.note, (it.options || []).length ? `⚙️ ${(it.options || []).map(o => o.label).join(", ")}` : ""].filter(Boolean).join(" · ")}</div>
                </td>
                <td><select className="input" style={{height: 30, fontSize: 12.5, padding: "0 6px"}} value={it.cat && catIds.has(it.cat) ? it.cat : ""}
                  onChange={e => patchOne(it._k, { cat: e.target.value })}>
                  <option value="">— ไม่มีหมวด —</option>
                  {cats.map(c => <option key={c.id} value={c.id}>{c.icon ? c.icon + " " : ""}{c.name}</option>)}
                </select></td>
                <td className="right">
                  <input key={it._k + ":" + it.price} className="input" type="number" min="0" step="0.01" defaultValue={Number(it.price) > 0 ? it.price : ""}
                    placeholder="ใส่ราคา" style={{height: 30, textAlign: "right", fontWeight: 700, width: 112,
                      borderColor: Number(it.price) > 0 ? undefined : "var(--warning)"}}
                    onKeyDown={e => { if (e.key === "Enter") e.currentTarget.blur(); }}
                    onBlur={e => { const v = e.target.value === "" ? 0 : LP_R2(e.target.value); if (v !== (Number(it.price) || 0)) patchOne(it._k, { price: v }); }}/>
                </td>
                <td style={{fontSize: 12.5}}>{it.unit || "ชิ้น"}</td>
                <td className="right" style={{fontSize: 12.5}}>{Number(it.moq) > 1 ? it.moq : "—"}</td>
                <td><div style={{display: "flex", gap: 3}}>
                  <Btn size="sm" kind="ghost" onClick={() => patchOne(it._k, { show: it.show === false })} title={it.show === false ? "แสดงให้ลูกค้าเห็น" : "ซ่อนจากลูกค้า"}>{it.show === false ? "🙈" : "👁"}</Btn>
                  <Btn size="sm" kind="ghost" onClick={() => onEdit(it)}>✏️</Btn>
                </div></td>
              </tr>
            ))}
            {!list.length && <tr><td colSpan={8} style={{padding: 22, textAlign: "center", color: "var(--ink-3)"}}>ไม่พบรายการตามตัวกรอง</td></tr>}
          </tbody>
        </table></div>
      )}
    </Card>
  );
}

// ═══ แท็บ 2: บัญชีลูกค้าของเว็บนี้ ═══
function LPCustomers({ site, custs, onAdd, onEdit, onCopy, onDelete }) {
  const { Btn, Badge, Card } = window.UI;
  return (
    <Card flush title={`บัญชีลูกค้าที่เข้าเว็บ ${site.name} ได้ (${custs.length})`}
      sub="1 บัญชี = 1 รหัสลูกค้า + รหัสผ่าน · แต่ละบัญชีมีออเดอร์และบิลของตัวเอง · รหัสลูกค้าห้ามซ้ำกันทุกเว็บ"
      right={<Btn size="sm" kind="primary" icon={I.plus} onClick={onAdd}>เพิ่มบัญชีลูกค้า</Btn>}>
      {!custs.length ? (
        <div style={{padding: 28, textAlign: "center", color: "var(--ink-3)", fontSize: 13}}>ยังไม่มีบัญชี — กด "เพิ่มบัญชีลูกค้า" แล้วส่งรหัสให้ลูกค้า</div>
      ) : (
        <div className="table-wrap"><table className="t">
          <thead><tr><th>ชื่อ / บริษัท</th><th>รหัสลูกค้า</th><th>รหัสผ่าน</th><th className="right">ออเดอร์</th><th>สถานะ</th><th style={{width: 250}}></th></tr></thead>
          <tbody>{custs.map(c => (
            <tr key={c.id} style={{opacity: c.active === false ? 0.55 : 1}}>
              <td><div style={{fontWeight: 700}}>{c.name || "—"}</div><div style={{fontSize: 11.5, color: "var(--ink-3)"}}>{[c.contact, c.phone].filter(Boolean).join(" · ")}</div></td>
              <td className="mono" style={{fontWeight: 700}}>{c.id}</td>
              <td className="mono" style={{letterSpacing: "0.05em"}}>{c.code}</td>
              <td className="right">{(c.orders || []).length}{(c.orders || []).some(o => o.isNew) && <span style={{color: "#dc2626"}}> ●</span>}</td>
              <td><Badge tone={c.active === false ? "outline" : "success"} dot>{c.active === false ? "ปิดใช้งาน" : "ใช้งานได้"}</Badge></td>
              <td><div style={{display: "flex", gap: 4, justifyContent: "flex-end"}}>
                <Btn size="sm" kind="ghost" onClick={() => onCopy(c)}>📋 คัดลอกรหัสเข้าใช้</Btn>
                <Btn size="sm" kind="ghost" onClick={() => onEdit(c)}>✏️</Btn>
                <Btn size="sm" kind="ghost" onClick={() => onDelete(c)}>🗑️</Btn>
              </div></td>
            </tr>
          ))}</tbody>
        </table></div>
      )}
    </Card>
  );
}

// ═══ แท็บ 3: ออเดอร์ทุกบัญชีในเว็บนี้ ═══
function LPOrders({ site, custs, onStatus, onOpen, onBill, onWO, onGoProject }) {
  const { Btn, Badge, Card, fmtM } = window.UI;
  const [f, setF] = useStateLP("all");
  const rows = custs.flatMap(c => (c.orders || []).map(o => ({ c, o })))
    .sort((a, b) => String(b.o.at || "").localeCompare(String(a.o.at || "")));
  const pass = (o) => f === "all" || (f === "new" ? o.isNew : f === "doing" ? !["ส่งของแล้ว", "ยกเลิก"].includes(o.status) : o.status === "ส่งของแล้ว");
  const list = rows.filter(r => pass(r.o));
  const fb = (k, l, n) => <Btn key={k} size="sm" kind={f === k ? "primary" : "ghost"} onClick={() => setF(k)}>{l} ({n})</Btn>;
  return (
    <Card flush title={`ออเดอร์จากเว็บ ${site.name}`} sub="กดที่ออเดอร์เพื่อใส่ราคางานสั่งทำ / ตอบกลับ · ออกบิลเมื่อผลิตเสร็จหรือส่งของ"
      right={<div style={{display: "flex", gap: 4, flexWrap: "wrap"}}>
        {fb("all", "ทั้งหมด", rows.length)}{fb("new", "🔴 ใหม่", rows.filter(r => r.o.isNew).length)}
        {fb("doing", "กำลังทำ", rows.filter(r => !["ส่งของแล้ว", "ยกเลิก"].includes(r.o.status)).length)}
        {fb("done", "ส่งแล้ว", rows.filter(r => r.o.status === "ส่งของแล้ว").length)}
      </div>}>
      {!list.length ? <div style={{padding: 28, textAlign: "center", color: "var(--ink-3)", fontSize: 13}}>ยังไม่มีออเดอร์ในกลุ่มนี้</div> : (
        <div style={{display: "flex", flexDirection: "column", gap: 10, padding: 12}}>
          {list.map(({ c, o }) => {
            const t = lpOrderTotals(o, lpVat(site, c));
            return (
              <div key={c.id + o.no} style={{border: `1.5px solid ${o.isNew ? site.color : "var(--line)"}`, borderRadius: 11, background: "var(--surface)", overflow: "hidden"}}>
                <div style={{display: "flex", alignItems: "center", gap: 10, padding: "10px 13px", background: "var(--surface-2)", flexWrap: "wrap"}}>
                  <b className="mono" style={{fontSize: 13.5}}>{o.no}</b>
                  {o.isNew && <span className="badge danger" style={{fontSize: 10}}>ใหม่</span>}
                  <span style={{fontSize: 12.5, fontWeight: 700}}>👤 {c.name || c.id}</span>
                  <span style={{fontSize: 12, color: "var(--ink-3)"}}>{lpThai(o.at)}{o.due ? ` · ต้องการรับ ${lpThai(o.due)}` : ""}</span>
                  <span style={{flex: 1}}/>
                  {t.pending && <span className="badge warning" style={{fontSize: 10}}>รอเสนอราคา</span>}
                  <Badge tone={o.status === "ส่งของแล้ว" ? "success" : "outline"} dot>{o.status}</Badge>
                  <b className="amount">{o.total != null ? `฿${fmtM(o.total)}` : (t.pending ? "—" : `฿${fmtM(t.total)}`)}</b>
                </div>
                <div style={{padding: "8px 13px"}}>
                  <div className="table-wrap"><table className="t" style={{fontSize: 12.5}}>
                    <tbody>{(o.items || []).map((it, i) => (
                      <tr key={i}>
                        <td>{it.name}{it.kind === "custom" && <span className="badge warning" style={{fontSize: 9.5, marginLeft: 5}}>สั่งทำ</span>}
                          {it.opts && Object.keys(it.opts).length ? <div style={{fontSize: 11, color: "var(--ink-3)"}}>{Object.values(it.opts).join(" · ")}</div> : null}
                          {(it.photos || []).length ? <div style={{display: "flex", gap: 4, marginTop: 4}}>{it.photos.slice(0, 4).map((u, k) => (
                            <img key={k} src={u} alt="" onClick={() => window.openPhotoZoom && window.openPhotoZoom(it.photos, k)}
                              style={{width: 46, height: 38, objectFit: "cover", borderRadius: 5, border: "1px solid var(--line)", cursor: "zoom-in"}}/>))}</div> : null}
                        </td>
                        <td className="right" style={{width: 90, whiteSpace: "nowrap"}}>{it.qty} {it.unit}</td>
                        <td className="right" style={{width: 110}}>{it.price == null || it.price === "" ? <span style={{color: "var(--warning)"}}>รอเสนอราคา</span> : `฿${fmtM(LP_R2((it.price || 0) * (it.qty || 0)))}`}</td>
                      </tr>
                    ))}</tbody>
                  </table></div>
                  {o.note && <div style={{fontSize: 12.5, color: "var(--ink-3)", marginTop: 6}}>📝 {o.note}</div>}
                  <div style={{display: "flex", gap: 6, marginTop: 9, flexWrap: "wrap", alignItems: "center"}}>
                    <select className="input" style={{width: 150, height: 30, fontSize: 12.5}} value={o.status} onChange={e => onStatus(c, o, e.target.value)}>
                      {LP_STATUSES.concat(["ยกเลิก"]).map(s => <option key={s}>{s}</option>)}
                    </select>
                    <Btn size="sm" kind="ghost" onClick={() => onOpen(c, o)}>💬 ใส่ราคา/ตอบกลับ</Btn>
                    {o.woNo
                      ? <button className="badge" onClick={onGoProject} title="ไปหน้าโปรเจกต์"
                          style={{fontSize: 11, cursor: "pointer", border: "1px solid var(--line-strong)", background: "var(--surface-2)", fontFamily: "inherit"}}>
                          📋 {o.woNo} · {o.projectCode} ↗</button>
                      : o.status !== "ยกเลิก" && <Btn size="sm" kind="ghost" onClick={() => onWO(c, o)}>📋 เปิดใบสั่งงาน</Btn>}
                    <span style={{flex: 1}}/>
                    {!o.billNo && o.status !== "ยกเลิก" && <Btn size="sm" kind="primary" onClick={() => onBill(c, o)}>🧾 ออกบิลให้ลูกค้า</Btn>}
                    {o.billNo && <span className="badge success" style={{fontSize: 10.5}}>ออกบิล {o.billNo} แล้ว</span>}
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </Card>
  );
}

// ═══ แท็บ 4: บิลทุกบัญชีในเว็บนี้ ═══
function LPBills({ site, custs, onPaid }) {
  const { Btn, Badge, Card, fmtM } = window.UI;
  const rows = custs.flatMap(c => (c.bills || []).map(b => ({ c, b }))).sort((a, b) => String(b.b.date || "").localeCompare(String(a.b.date || "")));
  const unpaid = rows.filter(r => !r.b.paid).reduce((s, r) => s + (Number(r.b.total) || 0), 0);
  return (
    <Card flush title={`บิลของเว็บ ${site.name} (${rows.length})`} sub={`ลูกค้าเปิดเว็บแล้วพิมพ์เองได้ทันที · ค้างชำระรวม ฿${fmtM(unpaid)}`}>
      {!rows.length ? <div style={{padding: 28, textAlign: "center", color: "var(--ink-3)", fontSize: 13}}>ยังไม่มีบิล — ออกบิลได้จากแท็บออเดอร์</div> : (
        <div className="table-wrap"><table className="t">
          <thead><tr><th>เลขที่</th><th>วันที่</th><th>ลูกค้า</th><th>ออเดอร์</th><th className="right">ก่อน VAT</th><th className="right">VAT</th><th className="right">รวม</th><th>สถานะ</th><th></th></tr></thead>
          <tbody>{rows.map(({ c, b }) => (
            <tr key={c.id + b.no}>
              <td className="mono" style={{fontWeight: 700}}>{b.no}</td>
              <td>{lpThai(b.date)}</td>
              <td>{c.name || c.id}</td>
              <td className="mono" style={{fontSize: 11.5}}>{b.orderNo || "—"}</td>
              <td className="right amount">฿{fmtM(b.subtotal)}</td>
              <td className="right amount">฿{fmtM(b.vat)}</td>
              <td className="right amount" style={{fontWeight: 700}}>฿{fmtM(b.total)}</td>
              <td><Badge tone={b.paid ? "success" : "warning"} dot>{b.paid ? "ชำระแล้ว" : "รอชำระ"}</Badge></td>
              <td><Btn size="sm" kind="ghost" onClick={() => onPaid(c, b)}>{b.paid ? "ยกเลิกชำระ" : "✓ รับเงินแล้ว"}</Btn></td>
            </tr>
          ))}</tbody>
        </table></div>
      )}
    </Card>
  );
}

// ═══ ตั้งค่าเว็บ / สร้างเว็บใหม่ ═══
function LPSiteModal({ f: init, sites, custCount, onClose, onSave, onDelete }) {
  const { Btn, Drawer } = window.UI;
  const [f, setF] = useStateLP(init);
  const upd = (k, v) => setF(p => ({ ...p, [k]: v }));
  const isNew = !!f._new;
  const slugErr = (() => {
    if (!isNew) return "";
    if (!f.slug) return "ตั้งชื่อลิงก์ก่อน";
    if (!/^[a-z0-9][a-z0-9-]{0,29}$/.test(f.slug)) return "ใช้ได้เฉพาะ a-z 0-9 และขีด - (ขึ้นต้นด้วยตัวอักษร/ตัวเลข)";
    if (LP_RESERVED.includes(f.slug)) return "ชื่อนี้ระบบใช้อยู่ — เลือกชื่ออื่น";
    if (sites.some(s => s.slug === f.slug)) return "มีเว็บชื่อนี้แล้ว";
    return "";
  })();
  const pickLogo = (e) => {
    const file = (e.target.files || [])[0]; e.target.value = ""; if (!file) return;
    const r = new FileReader();
    r.onload = () => lpShrink(r.result, 320, 0.85).then(u => upd("logo", u));
    r.readAsDataURL(file);
  };
  const save = () => {
    if (!String(f.name || "").trim()) { lpToast("ใส่ชื่อเว็บก่อน"); return; }
    if (slugErr) { lpToast(slugErr); return; }
    onSave({ ...f, name: f.name.trim(), title: (f.title || "").trim() || `ORDER งาน ${f.name.trim()}`, vatPct: Number(f.vatPct) || 0 });
  };
  return (
    <Drawer open wide title={isNew ? "🌐 สร้างเว็บสั่งงานใหม่" : `⚙️ ตั้งค่าเว็บ ${f.name}`} onClose={onClose}
      footer={<>
        {!isNew && <Btn kind="ghost" onClick={async () => {
          if (custCount) { lpToast("เว็บนี้ยังมีบัญชีลูกค้าอยู่ — ย้าย/ลบบัญชีก่อน หรือใช้ 'ปิดเว็บ' แทน"); return; }
          const ok = window.confirmDelete ? await window.confirmDelete(`เว็บ ${f.name}`, "ลิงก์นี้จะใช้ไม่ได้อีก") : confirm("ลบ?");
          if (ok) onDelete();
        }}>🗑️ ลบเว็บ</Btn>}
        <span style={{flex: 1}}/>
        <Btn kind="ghost" onClick={onClose}>ยกเลิก</Btn>
        <Btn kind="primary" icon={I.check} onClick={save}>{isNew ? "สร้างเว็บ + เผยแพร่" : "บันทึก + เผยแพร่"}</Btn>
      </>}>
      <div style={{display: "flex", flexDirection: "column", gap: 11}}>
        <div className="field-row cols-2">
          <div className="field"><label>ชื่อลูกค้า/เว็บ (สั้น ๆ)</label>
            <input className="input" value={f.name} placeholder="เช่น BOTH" onChange={e => {
              const v = e.target.value; upd("name", v);
              if (isNew) upd("slug", v.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 30));
            }}/></div>
          <div className="field"><label>ชื่อลิงก์ {isNew ? "" : "(เปลี่ยนไม่ได้)"}</label>
            <input className="input mono" value={f.slug} disabled={!isNew} placeholder="both" onChange={e => upd("slug", e.target.value.toLowerCase().trim())}/></div>
        </div>
        <div style={{fontSize: 12.5, padding: "8px 11px", borderRadius: 8, background: slugErr ? "rgba(220,38,38,.08)" : "var(--surface-2)", color: slugErr ? "var(--danger)" : "var(--ink-2)"}}>
          {slugErr ? `⚠️ ${slugErr}` : <>🔗 ลิงก์เว็บ: <b className="mono">{location.origin}/shop/{f.slug}</b></>}
        </div>
        <div className="field"><label>หัวเว็บ (ลูกค้าเห็นตัวใหญ่)</label>
          <input className="input" value={f.title} placeholder={`ORDER งาน ${f.name || "…"}`} onChange={e => upd("title", e.target.value)}/></div>
        <div className="field"><label>คำโปรย (ใต้หัวเว็บ)</label>
          <input className="input" value={f.subtitle} onChange={e => upd("subtitle", e.target.value)}/></div>
        <div className="field"><label>สีประจำเว็บ</label>
          <div style={{display: "flex", gap: 7, alignItems: "center", flexWrap: "wrap"}}>
            {LP_COLORS.map(c => (
              <button key={c} onClick={() => upd("color", c)} title={c}
                style={{width: 30, height: 30, borderRadius: 9, background: c, cursor: "pointer", border: f.color === c ? "3px solid var(--ink-1)" : "2px solid transparent"}}/>
            ))}
            <input type="color" value={f.color} onChange={e => upd("color", e.target.value)} style={{width: 40, height: 32, border: 0, background: "none", cursor: "pointer"}}/>
          </div></div>
        <div className="field"><label>โลโก้ (ไม่บังคับ — ไม่มีจะใช้ตัวอักษรย่อ)</label>
          <div style={{display: "flex", gap: 10, alignItems: "center"}}>
            <div style={{width: 58, height: 58, borderRadius: 15, background: f.color, color: "#fff", display: "grid", placeItems: "center", fontWeight: 800, overflow: "hidden"}}>
              {f.logo ? <img src={f.logo} alt="" style={{width: "100%", height: "100%", objectFit: "cover"}}/> : (f.name || "?").slice(0, 4).toUpperCase()}
            </div>
            <label className="btn sm" style={{cursor: "pointer"}}>📷 เลือกรูป<input type="file" accept="image/*" style={{display: "none"}} onChange={pickLogo}/></label>
            {f.logo && <Btn size="sm" kind="ghost" onClick={() => upd("logo", "")}>ลบโลโก้</Btn>}
          </div></div>
        <div className="field-row cols-2">
          <div className="field"><label>เบอร์โทรติดต่อ (ปุ่มโทรในเว็บ)</label><input className="input mono" value={f.phone} placeholder="098-995-5607" onChange={e => upd("phone", e.target.value)}/></div>
          <div className="field"><label>LINE (@ไอดี หรือ ลิงก์)</label><input className="input" value={f.line} placeholder="@triplescreative" onChange={e => upd("line", e.target.value)}/></div>
        </div>
        <div className="field-row cols-3">
          <div className="field"><label>VAT (%)</label><input className="input" type="number" min="0" step="0.1" value={f.vatPct} onChange={e => upd("vatPct", e.target.value)}/></div>
          <div className="field" style={{gridColumn: "span 2"}}><label>ข้อความราคา</label>
            <input className="input" value={f.priceNote} placeholder="เช่น ราคายังไม่รวม VAT · เครดิต 30 วัน" onChange={e => upd("priceNote", e.target.value)}/></div>
        </div>
        <LPPaySettings pay={f.pay || {}} onChange={(pay) => upd("pay", pay)}/>
        <label style={{display: "flex", alignItems: "center", gap: 8, fontWeight: 700, fontSize: 13.5, cursor: "pointer"}}>
          <input type="checkbox" checked={f.active !== false} onChange={e => upd("active", e.target.checked)}/> เปิดให้ลูกค้าเข้าใช้งาน
        </label>
      </div>
    </Drawer>
  );
}

// ═══ 💳 การรับชำระ (อยู่ในตั้งค่าเว็บ) ═══
function LPPaySettings({ pay, onChange }) {
  const set = (k, v) => onChange({ ...pay, [k]: v });
  const mode = pay.mode || "none";
  const test = lpPayPayload({ ...pay, mode }, 1, "TEST0001", "CUST01");
  const chip = (k, l) => (
    <button key={k} onClick={() => set("mode", k)} style={{padding: "6px 12px", borderRadius: 99, cursor: "pointer", fontFamily: "inherit", fontWeight: 700, fontSize: 12.5,
      border: mode === k ? "2px solid var(--brand)" : "1px solid var(--line-strong)", background: mode === k ? "var(--brand-bg, rgba(15,118,110,.08))" : "var(--surface)"}}>{l}</button>
  );
  return (
    <div style={{border: "1px solid var(--line)", borderRadius: 11, padding: 12}}>
      <div style={{fontWeight: 800, fontSize: 13.5, marginBottom: 8}}>💳 การรับชำระ (QR บนบิลลูกค้า)</div>
      <div style={{display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 10}}>
        {chip("none", "ไม่แสดง QR")}{chip("promptpay", "📱 PromptPay")}{chip("scb", "🟣 SCB Bill Payment")}
      </div>
      {mode === "promptpay" && <div className="field-row cols-2">
        <div className="field"><label>PromptPay (เบอร์มือถือ หรือเลขผู้เสียภาษี 13 หลัก)</label>
          <input className="input mono" value={pay.promptpayId || ""} placeholder="0105xxxxxxxxx" onChange={e => set("promptpayId", e.target.value)}/></div>
        <div className="field"><label>ชื่อบัญชี</label><input className="input" value={pay.accName || ""} onChange={e => set("accName", e.target.value)}/></div>
      </div>}
      {mode === "scb" && <>
        <div className="field-row cols-2">
          <div className="field"><label>Biller ID (15 หลัก — ได้จาก SCB ตอนสมัคร Bill Payment)</label>
            <input className="input mono" value={pay.billerId || ""} placeholder="010555xxxxxxx00" onChange={e => set("billerId", e.target.value)}/></div>
          <div className="field"><label>ชื่อบัญชี</label><input className="input" value={pay.accName || ""} onChange={e => set("accName", e.target.value)}/></div>
        </div>
        <div style={{fontSize: 12, color: "var(--ink-3)", marginTop: 4}}>QR จะใส่ Ref1 = เลขที่บิล · Ref2 = รหัสลูกค้า — เงินเข้าแล้วดูในรายงาน SCB Business Anywhere จับคู่กับบิลได้ทันที</div>
      </>}
      {mode !== "none" && <>
        <div className="field" style={{marginTop: 8}}><label>ข้อความการชำระเงิน (ขึ้นในบิลอัตโนมัติ)</label>
          <input className="input" value={pay.bankText || ""} placeholder="เช่น SCB 123-4-56789-0 บจก.ทริปเปิ้ลเอส ครีเอทีฟ · เครดิต 30 วัน" onChange={e => set("bankText", e.target.value)}/></div>
        <div style={{display: "flex", gap: 12, alignItems: "center", marginTop: 8}}>
          {test ? <LPPayQR payload={test} size={96}/> : <div style={{fontSize: 12, color: "var(--warning)"}}>กรอกข้อมูลให้ครบเพื่อสร้าง QR</div>}
          {test && <div style={{fontSize: 12, color: "var(--ink-3)"}}>QR ทดสอบ ฿1.00 — ลองสแกนด้วยแอปธนาคาร ต้องขึ้นชื่อบัญชีถูกต้อง (ยังไม่ต้องโอน)</div>}
        </div>
      </>}
    </div>
  );
}

// ═══ 📋 เปิดใบสั่งงานจากออเดอร์ ═══
function LPWorkOrderModal({ site, cust, order, onClose, onDone }) {
  const { Btn, Drawer } = window.UI;
  const WC = window.WOCards, PX = window.ProjX;
  const projects = useMemoLP(() => {
    try { return [...(PX.upLoad() || []), ...((window.SSSData && window.SSSData.Projects) || [])]
      .filter(p => p && p.code && p.status !== "เสร็จสิ้น" && p.status !== "ยกเลิก"); } catch (e) { return []; }
  }, []);
  const mine = projects.filter(p => String(p.customer || "").trim() && String(p.customer).trim() === String(cust.name || "").trim());
  const [proj, setProj] = useStateLP("");
  const [types, setTypes] = useStateLP(["cut"]);
  const [due, setDue] = useStateLP(order.due || "");
  const [items, setItems] = useStateLP(() => (order.items || []).map(it => ({
    name: it.name + (it.opts && Object.keys(it.opts).length ? ` (${Object.values(it.opts).join(" · ")})` : ""),
    qty: Number(it.qty) || 1, unit: it.unit || "ชิ้น", code: it.code || "", spec: it.kind === "custom" ? "สั่งทำพิเศษ — ดูรูป/รายละเอียดในออเดอร์" : "" })));
  const [busy, setBusy] = useStateLP(false);
  const order_ = WC ? (WC.ORDER || Object.keys(WC.TYPES)) : [];
  const toggle = (t) => setTypes(p => (p.includes(t) ? p.filter(x => x !== t) : [...p, t]));
  const go = () => {
    if (!items.length) { lpToast("ไม่มีรายการ"); return; }
    setBusy(true);
    try { onDone(lpOpenWorkOrder({ site, cust, order, projectCode: proj, types, due, items })); }
    catch (e) { lpToast("เปิดใบสั่งงานไม่ได้: " + (e.message || e)); setBusy(false); }
  };
  return (
    <Drawer open wide title={`📋 เปิดใบสั่งงาน · ออเดอร์ ${order.no}`} onClose={onClose}
      footer={<><Btn kind="ghost" onClick={onClose}>ยกเลิก</Btn><Btn kind="primary" icon={I.check} disabled={busy} onClick={go}>เปิดใบสั่งงาน</Btn></>}>
      <div style={{display: "flex", flexDirection: "column", gap: 12}}>
        <div className="field"><label>เข้าโปรเจกต์</label>
          <select className="input" value={proj} onChange={e => setProj(e.target.value)}>
            <option value="">➕ สร้างโปรเจกต์ใหม่ (JOB ใหม่) ให้ {cust.name || cust.id}</option>
            {mine.length > 0 && <optgroup label={`โปรเจกต์ของ ${cust.name}`}>{mine.map(p => <option key={p.code} value={p.code}>{p.code} · {p.name}</option>)}</optgroup>}
            <optgroup label="โปรเจกต์อื่นที่เปิดอยู่">{projects.filter(p => !mine.includes(p)).map(p => <option key={p.code} value={p.code}>{p.code} · {p.name}</option>)}</optgroup>
          </select></div>
        <div className="field"><label>การ์ดงาน (สถานีที่ต้องทำ)</label>
          <div style={{display: "flex", gap: 6, flexWrap: "wrap"}}>
            {order_.map(t => { const T = WC.TYPES[t]; const on = types.includes(t); return (
              <button key={t} onClick={() => toggle(t)} style={{padding: "6px 12px", borderRadius: 99, cursor: "pointer", fontFamily: "inherit", fontWeight: 700, fontSize: 12.5,
                border: on ? `2px solid ${T.color}` : "1px solid var(--line-strong)", background: on ? T.bg : "var(--surface)", color: on ? T.color : "var(--ink-2)"}}>
                {on ? "✓ " : ""}{T.emoji} {T.label}</button>); })}
          </div>
          <div style={{fontSize: 11.5, color: "var(--ink-3)", marginTop: 4}}>การ์ดเสร็จครบ → ออเดอร์ลูกค้าขึ้น "ผลิตเสร็จ" เอง · ใส่การ์ด 🚚 งานส่งของ แล้วส่งเสร็จ → "ส่งของแล้ว"</div></div>
        <div className="field" style={{maxWidth: 220}}><label>กำหนดเสร็จ</label><input className="input" type="date" value={due} onChange={e => setDue(e.target.value)}/></div>
        <div className="table-wrap"><table className="t">
          <thead><tr><th>ชิ้นงาน</th><th style={{width: 90}} className="right">จำนวน</th><th style={{width: 70}}>หน่วย</th><th style={{width: 40}}></th></tr></thead>
          <tbody>{items.map((it, i) => (
            <tr key={i}>
              <td><input className="input" style={{height: 30}} value={it.name} onChange={e => setItems(p => p.map((x, j) => (j === i ? { ...x, name: e.target.value } : x)))}/></td>
              <td><input className="input" type="number" min="0" style={{height: 30, textAlign: "right"}} value={it.qty} onChange={e => setItems(p => p.map((x, j) => (j === i ? { ...x, qty: Number(e.target.value) || 0 } : x)))}/></td>
              <td>{it.unit}</td>
              <td><Btn size="sm" kind="ghost" onClick={() => setItems(p => p.filter((_, j) => j !== i))}>✕</Btn></td>
            </tr>))}</tbody>
        </table></div>
        {order.note && <div style={{fontSize: 12.5, padding: "8px 11px", background: "var(--surface-2)", borderRadius: 8}}>📝 ลูกค้าแจ้ง: {order.note}</div>}
      </div>
    </Drawer>
  );
}

// ═══ จัดการหมวดหมู่ ═══
function LPCatModal({ cats, catalog, onClose, onSave }) {
  const { Btn, Drawer } = window.UI;
  const [list, setList] = useStateLP(() => (cats || []).map(c => ({ ...c })));
  const [nm, setNm] = useStateLP("");
  const [ic, setIc] = useStateLP("");
  const count = (id) => catalog.filter(it => it.cat === id).length;
  const move = (i, d) => setList(p => { const n = p.slice(); const j = i + d; if (j < 0 || j >= n.length) return p; [n[i], n[j]] = [n[j], n[i]]; return n; });
  const add = () => {
    if (!nm.trim()) { lpToast("ใส่ชื่อหมวดก่อน"); return; }
    setList(p => [...p, { id: lpUid("c"), name: nm.trim(), icon: ic.trim() }]); setNm(""); setIc("");
  };
  return (
    <Drawer open title="🗂️ หมวดหมู่สินค้า" onClose={onClose}
      footer={<><Btn kind="ghost" onClick={onClose}>ยกเลิก</Btn><Btn kind="primary" icon={I.check} onClick={() => onSave(list.filter(c => c.name.trim()))}>บันทึก</Btn></>}>
      <div style={{fontSize: 12.5, color: "var(--ink-3)", marginBottom: 10}}>ลูกค้าจะเห็นหมวดเรียงตามลำดับนี้ เป็นปุ่มเลื่อนด้านบนเหมือนแอปสั่งอาหาร · ลบหมวดแล้วสินค้าในหมวดจะย้ายไป "ไม่มีหมวด" (ไม่ถูกลบ)</div>
      <div style={{display: "flex", flexDirection: "column", gap: 6}}>
        {list.map((c, i) => (
          <div key={c.id} style={{display: "flex", gap: 6, alignItems: "center", padding: 6, border: "1px solid var(--line)", borderRadius: 9, background: "var(--surface-2)"}}>
            <input className="input" style={{width: 52, textAlign: "center"}} value={c.icon || ""} maxLength={4} placeholder="🔩"
              onChange={e => setList(p => p.map((x, j) => (j === i ? { ...x, icon: e.target.value } : x)))}/>
            <input className="input" style={{flex: 1}} value={c.name} onChange={e => setList(p => p.map((x, j) => (j === i ? { ...x, name: e.target.value } : x)))}/>
            <span style={{fontSize: 11.5, color: "var(--ink-3)", width: 52, textAlign: "right"}}>{count(c.id)} ชิ้น</span>
            <Btn size="sm" kind="ghost" onClick={() => move(i, -1)} disabled={i === 0}>↑</Btn>
            <Btn size="sm" kind="ghost" onClick={() => move(i, 1)} disabled={i === list.length - 1}>↓</Btn>
            <Btn size="sm" kind="ghost" onClick={() => setList(p => p.filter((_, j) => j !== i))}>🗑️</Btn>
          </div>
        ))}
        {!list.length && <div style={{padding: 16, textAlign: "center", color: "var(--ink-3)", fontSize: 13}}>ยังไม่มีหมวด — เพิ่มด้านล่าง เช่น 🔩 ฉากยึด · 🪑 ชุดเฟอร์นิเจอร์ · 🧱 แผ่นตัด</div>}
      </div>
      <div style={{display: "flex", gap: 6, marginTop: 12}}>
        <input className="input" style={{width: 52, textAlign: "center"}} value={ic} maxLength={4} placeholder="🔩" onChange={e => setIc(e.target.value)}/>
        <input className="input" style={{flex: 1}} value={nm} placeholder="ชื่อหมวดใหม่" onChange={e => setNm(e.target.value)} onKeyDown={e => { if (e.key === "Enter") add(); }}/>
        <Btn kind="primary" icon={I.plus} onClick={add}>เพิ่ม</Btn>
      </div>
    </Drawer>
  );
}

// ═══ ดึงจากคลังชิ้นงาน — ติ๊กเลือกเฉพาะชิ้นที่ต้องการ ═══
function LPImportModal({ site, onClose, onAdd }) {
  const { Btn, Drawer } = window.UI;
  const lib = useMemoLP(() => {
    const all = (window.SSSItemLib && window.SSSItemLib.all && window.SSSItemLib.all()) || {};
    return Object.values(all).filter(r => r && r.name).sort((a, b) => (b.at || 0) - (a.at || 0));
  }, []);
  const have = useMemoLP(() => new Set(site.catalog.map(x => String(x.name || "").trim().toLowerCase())), [site.slug]);
  const [q, setQ] = useStateLP("");
  const [sel, setSel] = useStateLP(() => new Set());
  const [cat, setCat] = useStateLP("");
  const [busy, setBusy] = useStateLP("");
  const photoOf = (r) => { const pb = r.photosBy || {}; return (pb.cut || [])[0] || (r.photos || [])[0] || Object.values(pb).flat()[0] || r.photo || ""; };
  const list = lib.filter(r => { const s = q.trim().toLowerCase(); return !s || `${r.name} ${r.material || ""} ${r.lastProject || ""}`.toLowerCase().includes(s); });
  const selectable = list.filter(r => !have.has(r.name.trim().toLowerCase()));
  const allOn = selectable.length > 0 && selectable.every(r => sel.has(r.name));
  const add = async () => {
    const pick = lib.filter(r => sel.has(r.name));
    if (!pick.length) return;
    const out = [];
    for (let i = 0; i < pick.length; i++) {
      setBusy(`กำลังเตรียมรูป ${i + 1}/${pick.length}…`);
      const r = pick[i];
      out.push({ _k: lpUid("it"), code: "", name: r.name.trim(), unit: r.unit || "ชิ้น", price: 0,
        photo: await lpShrink(photoOf(r), 600, 0.8), note: r.material || "", moq: 1, options: [], show: true, cat });
    }
    onAdd(out);
  };
  return (
    <Drawer open wide title="📚 เลือกชิ้นงานจากคลังชิ้นงาน" onClose={onClose}
      footer={<>
        <span style={{fontSize: 12.5, color: "var(--ink-3)"}}>{busy || `เลือก ${sel.size} รายการ`}</span>
        <span style={{flex: 1}}/>
        <Btn kind="ghost" onClick={onClose}>ยกเลิก</Btn>
        <Btn kind="primary" icon={I.plus} disabled={!sel.size || !!busy} onClick={add}>เพิ่ม {sel.size} รายการเข้าเว็บ {site.name}</Btn>
      </>}>
      <div style={{display: "flex", gap: 8, marginBottom: 10, flexWrap: "wrap"}}>
        <input className="input" autoFocus style={{flex: "1 1 220px"}} placeholder="🔍 ค้นชื่อชิ้นงาน / วัสดุ / โปรเจกต์…" value={q} onChange={e => setQ(e.target.value)}/>
        <select className="input" style={{width: 190}} value={cat} onChange={e => setCat(e.target.value)}>
          <option value="">ใส่หมวด: — ไม่มีหมวด —</option>
          {(site.categories || []).map(c => <option key={c.id} value={c.id}>ใส่หมวด: {c.icon ? c.icon + " " : ""}{c.name}</option>)}
        </select>
      </div>
      <label style={{display: "flex", alignItems: "center", gap: 8, padding: "8px 10px", background: "var(--surface-2)", borderRadius: 8, fontWeight: 700, fontSize: 13, cursor: "pointer", marginBottom: 6}}>
        <input type="checkbox" checked={allOn} onChange={() => setSel(p => { const n = new Set(p); if (allOn) selectable.forEach(r => n.delete(r.name)); else selectable.forEach(r => n.add(r.name)); return n; })}/>
        เลือกทั้งหมดที่แสดง ({selectable.length})
      </label>
      <div style={{display: "flex", flexDirection: "column", gap: 4}}>
        {list.map(r => {
          const exists = have.has(r.name.trim().toLowerCase()); const ph = photoOf(r);
          return (
            <label key={r.name} style={{display: "flex", alignItems: "center", gap: 10, padding: "7px 10px", borderRadius: 8, border: "1px solid var(--line)",
              cursor: exists ? "default" : "pointer", opacity: exists ? 0.5 : 1, background: sel.has(r.name) ? "rgba(15,118,110,.07)" : "var(--surface)"}}>
              <input type="checkbox" disabled={exists} checked={sel.has(r.name)} onChange={() => setSel(p => { const n = new Set(p); n.has(r.name) ? n.delete(r.name) : n.add(r.name); return n; })}/>
              {ph ? <img src={ph} alt="" loading="lazy" style={{width: 46, height: 38, objectFit: "contain", background: "#fff", borderRadius: 6, border: "1px solid var(--line)"}}/>
                : <div style={{width: 46, height: 38, borderRadius: 6, border: "1px dashed var(--line-strong)", display: "grid", placeItems: "center", opacity: 0.4}}>📦</div>}
              <div style={{flex: 1, minWidth: 0}}>
                <div style={{fontWeight: 700, fontSize: 13.5}}>{r.name}</div>
                <div style={{fontSize: 11, color: "var(--ink-3)"}}>{[r.material, r.lastProject, r.uses ? `ใช้ ${r.uses} ครั้ง` : ""].filter(Boolean).join(" · ")}</div>
              </div>
              {exists && <span style={{fontSize: 11, fontWeight: 700, color: "var(--ink-3)"}}>มีในเว็บแล้ว</span>}
            </label>
          );
        })}
        {!list.length && <div style={{padding: 20, textAlign: "center", color: "var(--ink-3)"}}>{lib.length ? "ไม่พบชิ้นงานตามคำค้น" : "คลังชิ้นงานยังว่าง — บันทึกใบสั่งงานที่มีชิ้นงานก่อน"}</div>}
      </div>
    </Drawer>
  );
}

// ═══ บัญชีลูกค้า ═══
function LPCustModal({ f: init, site, sites, onClose, onSave }) {
  const { Btn, Drawer } = window.UI;
  const [f, setF] = useStateLP(init);
  const upd = (k, v) => setF(p => ({ ...p, [k]: v }));
  const cur = sites.find(s => s.slug === f.site) || site;
  return (
    <Drawer open title={f._new ? `เพิ่มบัญชีลูกค้า · เว็บ ${site.name}` : `แก้ไข ${f.name || f.id}`} onClose={onClose}
      footer={<><Btn kind="ghost" onClick={onClose}>ยกเลิก</Btn><Btn kind="primary" icon={I.check} onClick={() => onSave(f)}>บันทึก + เผยแพร่</Btn></>}>
      <div style={{display: "flex", flexDirection: "column", gap: 10}}>
        <div className="field-row cols-2">
          <div className="field"><label>รหัสลูกค้า (ใช้ล็อกอิน)</label>
            <input className="input mono" value={f.id} disabled={!f._new} onChange={e => upd("id", e.target.value.toUpperCase().replace(/\s+/g, ""))} placeholder={`${site.name}-01`}/></div>
          <div className="field"><label>รหัสผ่าน</label>
            <div style={{display: "flex", gap: 6}}>
              <input className="input mono" value={f.code} onChange={e => upd("code", e.target.value.toUpperCase().replace(/\s+/g, ""))}/>
              <Btn size="sm" kind="ghost" onClick={() => upd("code", lpGenCode())} title="สุ่มรหัสใหม่">🎲</Btn>
            </div></div>
        </div>
        {!f._new && init.code !== f.code && <div style={{fontSize: 11.5, color: "var(--warning)"}}>⚠️ เปลี่ยนรหัสผ่านแล้ว รหัสเดิมจะใช้เข้าไม่ได้ทันที — อย่าลืมแจ้งลูกค้า</div>}
        {sites.length > 1 && (
          <div className="field"><label>อยู่ในเว็บ</label>
            <select className="input" value={f.site} onChange={e => upd("site", e.target.value)}>
              {sites.map(s => <option key={s.slug} value={s.slug}>{s.name} — /shop/{s.slug}</option>)}
            </select></div>
        )}
        <div className="field"><label>ชื่อลูกค้า / บริษัท (ขึ้นบนบิล)</label>
          <input className="input" value={f.name || ""} onChange={e => upd("name", e.target.value)} placeholder="เช่น บริษัท แอลดับบลิวซี จำกัด (สาขาพนา)"/></div>
        <div className="field-row cols-2">
          <div className="field"><label>ผู้ติดต่อ</label><input className="input" value={f.contact || ""} onChange={e => upd("contact", e.target.value)}/></div>
          <div className="field"><label>เบอร์โทร</label><input className="input mono" value={f.phone || ""} onChange={e => upd("phone", e.target.value)}/></div>
        </div>
        <div className="field"><label>ที่อยู่ (ขึ้นบนบิล)</label>
          <textarea className="textarea" rows={2} value={f.address || ""} onChange={e => upd("address", e.target.value)}/></div>
        <div className="field-row cols-2">
          <div className="field"><label>เลขผู้เสียภาษี</label><input className="input mono" value={f.taxId || ""} onChange={e => upd("taxId", e.target.value)}/></div>
          <div className="field"><label>VAT เฉพาะบัญชีนี้ (%)</label>
            <input className="input" type="number" min="0" step="0.1" value={f.vatPct == null ? "" : f.vatPct} placeholder={`ตามเว็บ (${cur.vatPct == null ? 7 : cur.vatPct}%)`}
              onChange={e => upd("vatPct", e.target.value === "" ? null : Number(e.target.value))}/></div>
        </div>
        <label style={{display: "flex", alignItems: "center", gap: 8, fontWeight: 700, fontSize: 13.5, cursor: "pointer"}}>
          <input type="checkbox" checked={f.active !== false} onChange={e => upd("active", e.target.checked)}/> เปิดใช้งาน (ปิด = ลูกค้าเข้าได้แต่สั่งไม่ได้ เห็นข้อความถูกระงับ)
        </label>
      </div>
    </Drawer>
  );
}

// ═══ แก้ไขรายการสินค้า (ตัวเลือกเสริม + หมวด) ═══
function LPItemModal({ f: init, cats, onClose, onSave }) {
  const { Btn, Drawer } = window.UI;
  const [f, setF] = useStateLP(init);
  const upd = (k, v) => setF(p => ({ ...p, [k]: v }));
  const opts = f.options || [];
  const setOpts = (v) => upd("options", v);
  const pickPhoto = (e) => {
    const file = (e.target.files || [])[0]; e.target.value = "";
    if (!file) return;
    const r = new FileReader();
    r.onload = () => lpShrink(r.result, 800, 0.8).then(u => upd("photo", u));
    r.readAsDataURL(file);
  };
  return (
    <Drawer open wide title={f._new ? "เพิ่มรายการสินค้า" : `แก้ไข ${f.name}`} onClose={onClose}
      footer={<><Btn kind="ghost" onClick={onClose}>ยกเลิก</Btn>
        <Btn kind="primary" icon={I.check} onClick={() => { if (!(f.name || "").trim()) { lpToast("ใส่ชื่อรายการก่อน"); return; } onSave({ ...f, name: f.name.trim(), price: LP_R2(f.price), moq: Math.max(1, Number(f.moq) || 1) }); }}>บันทึก</Btn></>}>
      <div style={{display: "flex", flexDirection: "column", gap: 10}}>
        <div className="field"><label>ชื่อรายการ (ลูกค้าเห็น)</label>
          <input className="input" value={f.name} onChange={e => upd("name", e.target.value)} placeholder="เช่น ฉากยึด LWC 3 มม."/></div>
        <div className="field-row cols-4">
          <div className="field"><label>หมวด</label>
            <select className="input" value={f.cat || ""} onChange={e => upd("cat", e.target.value)}>
              <option value="">— ไม่มีหมวด —</option>
              {(cats || []).map(c => <option key={c.id} value={c.id}>{c.icon ? c.icon + " " : ""}{c.name}</option>)}
            </select></div>
          <div className="field"><label>ราคา/หน่วย (บาท)</label><input className="input" type="number" min="0" step="0.01" value={f.price} onChange={e => upd("price", e.target.value)} placeholder="0 = สอบถามราคา"/></div>
          <div className="field"><label>หน่วย</label><input className="input" value={f.unit || "ชิ้น"} onChange={e => upd("unit", e.target.value)}/></div>
          <div className="field"><label>สั่งขั้นต่ำ</label><input className="input" type="number" min="1" value={f.moq || 1} onChange={e => upd("moq", e.target.value)}/></div>
        </div>
        <div className="field-row cols-2">
          <div className="field"><label>รหัสสินค้า (ไม่บังคับ)</label><input className="input mono" value={f.code || ""} onChange={e => upd("code", e.target.value)}/></div>
          <div className="field"><label>คำอธิบายสั้น</label><input className="input" value={f.note || ""} onChange={e => upd("note", e.target.value)} placeholder="เช่น เหล็กชุบซิงค์ ตัด+พับ+เจาะ 4 รู"/></div>
        </div>
        <div className="field"><label>รูปสินค้า</label>
          <div style={{display: "flex", gap: 10, alignItems: "center"}}>
            {f.photo
              ? <img src={f.photo} alt="" style={{width: 96, height: 74, objectFit: "contain", border: "1px solid var(--line)", borderRadius: 8, background: "#fff"}}/>
              : <div style={{width: 96, height: 74, border: "1.5px dashed var(--line-strong)", borderRadius: 8, display: "grid", placeItems: "center", fontSize: 11, color: "var(--ink-3)"}}>ไม่มีรูป</div>}
            <div style={{display: "flex", flexDirection: "column", gap: 6}}>
              <label className="btn sm" style={{cursor: "pointer"}}>📷 เลือกรูป<input type="file" accept="image/*" style={{display: "none"}} onChange={pickPhoto}/></label>
              {f.photo && <Btn size="sm" kind="ghost" onClick={() => upd("photo", "")}>ลบรูป</Btn>}
            </div>
          </div>
        </div>
        <label style={{display: "flex", alignItems: "center", gap: 8, fontWeight: 700, fontSize: 13, cursor: "pointer"}}>
          <input type="checkbox" checked={f.show !== false} onChange={e => upd("show", e.target.checked)}/> แสดงให้ลูกค้าเห็น
        </label>
        <div style={{borderTop: "1px dashed var(--line)", paddingTop: 10}}>
          <div style={{fontSize: 12.5, fontWeight: 800, marginBottom: 3}}>⚙️ ตัวเลือกเสริม (ลูกค้าเลือกแล้วราคาบวกเพิ่มอัตโนมัติ)</div>
          <div style={{fontSize: 11.5, color: "var(--ink-3)", marginBottom: 8}}>เช่น หัวข้อ "ความหนา" → ตัวเลือก 2 มม. (+0), 3 มม. (+8), 4 มม. (+16)</div>
          {opts.map((o, oi) => (
            <div key={oi} style={{border: "1px solid var(--line)", borderRadius: 9, padding: 9, marginBottom: 8, background: "var(--surface-2)"}}>
              <div style={{display: "flex", gap: 6, marginBottom: 6}}>
                <input className="input" style={{flex: 1}} value={o.label} placeholder="หัวข้อ เช่น ความหนา"
                  onChange={e => setOpts(opts.map((x, i) => (i === oi ? { ...x, label: e.target.value } : x)))}/>
                <Btn size="sm" kind="ghost" onClick={() => setOpts(opts.filter((_, i) => i !== oi))}>🗑️</Btn>
              </div>
              {(o.choices || []).map((c, ci) => (
                <div key={ci} style={{display: "flex", gap: 6, marginBottom: 5}}>
                  <input className="input" style={{flex: 1}} value={c.name} placeholder="ชื่อตัวเลือก"
                    onChange={e => setOpts(opts.map((x, i) => (i === oi ? { ...x, choices: x.choices.map((y, j) => (j === ci ? { ...y, name: e.target.value } : y)) } : x)))}/>
                  <input className="input" type="number" step="0.01" style={{width: 110}} value={c.add} placeholder="+บาท"
                    onChange={e => setOpts(opts.map((x, i) => (i === oi ? { ...x, choices: x.choices.map((y, j) => (j === ci ? { ...y, add: Number(e.target.value) || 0 } : y)) } : x)))}/>
                  <Btn size="sm" kind="ghost" onClick={() => setOpts(opts.map((x, i) => (i === oi ? { ...x, choices: x.choices.filter((_, j) => j !== ci) } : x)))}>✕</Btn>
                </div>
              ))}
              <Btn size="sm" kind="ghost" onClick={() => setOpts(opts.map((x, i) => (i === oi ? { ...x, choices: [...(x.choices || []), { name: "", add: 0 }] } : x)))}>➕ เพิ่มตัวเลือก</Btn>
            </div>
          ))}
          <Btn size="sm" onClick={() => setOpts([...opts, { label: "", choices: [{ name: "", add: 0 }] }])}>➕ เพิ่มหัวข้อตัวเลือก</Btn>
        </div>
      </div>
    </Drawer>
  );
}

// ═══ เสนอราคา / ตอบกลับออเดอร์ ═══
function LPOrderModal({ o: init, cust, vp, onClose, onSave }) {
  const { Btn, Drawer, fmtM } = window.UI;
  const [o, setO] = useStateLP(init);
  const t = lpOrderTotals(o, vp);
  const setPrice = (i, v) => setO(p => ({ ...p, items: p.items.map((x, j) => (j === i ? { ...x, price: v === "" ? null : Number(v) } : x)) }));
  return (
    <Drawer open wide title={`ออเดอร์ ${o.no} · ${cust.name || cust.id}`} onClose={onClose}
      footer={<><Btn kind="ghost" onClick={onClose}>ยกเลิก</Btn>
        <Btn kind="primary" icon={I.check} onClick={() => onSave({ ...o, isNew: false, total: t.pending ? null : t.total })}>บันทึก + แจ้งลูกค้า</Btn></>}>
      <div style={{display: "flex", flexDirection: "column", gap: 10}}>
        <div style={{fontSize: 12.5, color: "var(--ink-3)"}}>สั่งเมื่อ {lpThai(o.at)}{o.due ? ` · ลูกค้าต้องการรับ ${lpThai(o.due)}` : ""}</div>
        <div className="table-wrap"><table className="t">
          <thead><tr><th>รายการ</th><th className="right" style={{width: 92}}>จำนวน</th><th style={{width: 130}}>ราคา/หน่วย</th><th className="right" style={{width: 110}}>รวม</th></tr></thead>
          <tbody>{(o.items || []).map((it, i) => (
            <tr key={i}>
              <td>{it.name}{it.kind === "custom" && <span className="badge warning" style={{fontSize: 9.5, marginLeft: 5}}>สั่งทำ</span>}
                {it.opts && Object.keys(it.opts).length ? <div style={{fontSize: 11, color: "var(--ink-3)"}}>{Object.values(it.opts).join(" · ")}</div> : null}
                {(it.photos || []).length ? <div style={{display: "flex", gap: 4, marginTop: 4}}>{it.photos.map((u, k) => (
                  <img key={k} src={u} alt="" onClick={() => window.openPhotoZoom && window.openPhotoZoom(it.photos, k)}
                    style={{width: 52, height: 42, objectFit: "cover", borderRadius: 5, border: "1px solid var(--line)", cursor: "zoom-in"}}/>))}</div> : null}
              </td>
              <td className="right">{it.qty} {it.unit}</td>
              <td><input className="input" type="number" min="0" step="0.01" value={it.price == null ? "" : it.price}
                placeholder="ใส่ราคา" onChange={e => setPrice(i, e.target.value)} style={{height: 32}}/></td>
              <td className="right amount">{it.price == null ? "—" : `฿${fmtM(LP_R2((it.price || 0) * (it.qty || 0)))}`}</td>
            </tr>
          ))}</tbody>
        </table></div>
        <div style={{display: "flex", justifyContent: "flex-end", gap: 20, fontSize: 13}}>
          <div style={{textAlign: "right", lineHeight: 2}}>
            <div style={{color: "var(--ink-3)"}}>ก่อน VAT</div><div style={{color: "var(--ink-3)"}}>VAT {vp}%</div><div style={{fontWeight: 800}}>รวมทั้งสิ้น</div>
          </div>
          <div style={{textAlign: "right", lineHeight: 2, minWidth: 110}}>
            <div className="amount">฿{fmtM(t.sub)}</div><div className="amount">฿{fmtM(t.vat)}</div>
            <div className="amount" style={{fontWeight: 800, fontSize: 16}}>฿{fmtM(t.total)}</div>
          </div>
        </div>
        {t.pending && <div style={{fontSize: 12, color: "var(--warning)"}}>⚠️ ยังมีรายการที่ไม่ได้ใส่ราคา — ลูกค้าจะเห็นว่า "รอเสนอราคา"</div>}
        {o.note && <div style={{fontSize: 12.5, padding: "8px 11px", background: "var(--surface-2)", borderRadius: 8}}>📝 ลูกค้าแจ้ง: {o.note}</div>}
        <div className="field"><label>ข้อความตอบกลับลูกค้า (ขึ้นในเว็บของลูกค้า)</label>
          <textarea className="textarea" rows={2} value={o.reply || ""} onChange={e => setO(p => ({ ...p, reply: e.target.value }))}
            placeholder="เช่น รับงานแล้วครับ เสนอราคาชิ้นสั่งทำ 45 บาท/ชิ้น เริ่มผลิตวันจันทร์"/></div>
        <div className="field"><label>สถานะ</label>
          <select className="input" value={o.status} onChange={e => setO(p => ({ ...p, status: e.target.value }))}>
            {LP_STATUSES.concat(["ยกเลิก"]).map(s => <option key={s}>{s}</option>)}</select></div>
      </div>
    </Drawer>
  );
}

// ═══ ออกบิล ═══
function LPBillModal({ site, order, cust, vp, allBills, onClose, onSave }) {
  const { Btn, Drawer, fmtM } = window.UI;
  const nextNo = () => {
    const y = new Date().getFullYear() + 543;
    const nums = (allBills || []).map(b => { const m = new RegExp("^BILL-" + y + "-(\\d+)$").exec(b.no || ""); return m ? +m[1] : 0; });
    return `BILL-${y}-${String(Math.max(0, ...nums) + 1).padStart(4, "0")}`;
  };
  const [f, setF] = useStateLP(() => ({
    no: nextNo(), date: lpToday(), deliveredAt: lpToday(), orderNo: order.no,
    title: "ใบส่งของ / ใบแจ้งหนี้", receiver: cust.contact || "", deliverBy: "",
    note: "", payNote: (site && site.pay && site.pay.bankText) || "", discount: 0, paid: false,
    items: (order.items || []).map(it => ({ code: it.code || "", name: it.name + (it.opts && Object.keys(it.opts).length ? ` (${Object.values(it.opts).join(" · ")})` : ""),
      unit: it.unit || "ชิ้น", qty: Number(it.qty) || 0, price: Number(it.price) || 0 })),
  }));
  const upd = (k, v) => setF(p => ({ ...p, [k]: v }));
  const sub = LP_R2((f.items || []).reduce((s, it) => s + LP_R2((it.price || 0) * (it.qty || 0)), 0) - (Number(f.discount) || 0));
  const vat = LP_R2(sub * vp / 100), total = LP_R2(sub + vat);
  return (
    <Drawer open wide title={`ออกบิลจากออเดอร์ ${order.no} · ${cust.name || cust.id}`} onClose={onClose}
      footer={<><Btn kind="ghost" onClick={onClose}>ยกเลิก</Btn>
        <Btn kind="primary" icon={I.check} onClick={() => {
          if ((allBills || []).some(b => b.no === f.no)) { lpToast(`เลขที่บิล ${f.no} ถูกใช้แล้ว`); return; }
          onSave({ ...f, vatPct: vp, subtotal: sub, vat, total, discount: Number(f.discount) || 0 });
        }}>ออกบิล + ส่งให้ลูกค้า</Btn></>}>
      <div style={{display: "flex", flexDirection: "column", gap: 10}}>
        <div className="field-row cols-3">
          <div className="field"><label>เลขที่บิล</label><input className="input mono" value={f.no} onChange={e => upd("no", e.target.value)}/></div>
          <div className="field"><label>วันที่บิล</label><input className="input" type="date" value={f.date} onChange={e => upd("date", e.target.value)}/></div>
          <div className="field"><label>วันที่ส่งของ</label><input className="input" type="date" value={f.deliveredAt} onChange={e => upd("deliveredAt", e.target.value)}/></div>
        </div>
        <div className="field-row cols-3">
          <div className="field"><label>ชื่อเอกสาร</label><input className="input" value={f.title} onChange={e => upd("title", e.target.value)}/></div>
          <div className="field"><label>ผู้รับสินค้า</label><input className="input" value={f.receiver} onChange={e => upd("receiver", e.target.value)}/></div>
          <div className="field"><label>ผู้ส่งสินค้า</label><input className="input" value={f.deliverBy} onChange={e => upd("deliverBy", e.target.value)}/></div>
        </div>
        <div className="table-wrap"><table className="t">
          <thead><tr><th>รายการ</th><th className="right" style={{width: 80}}>จำนวน</th><th style={{width: 110}}>ราคา/หน่วย</th><th className="right" style={{width: 100}}>รวม</th></tr></thead>
          <tbody>{f.items.map((it, i) => (
            <tr key={i}>
              <td><input className="input" value={it.name} style={{height: 30}} onChange={e => upd("items", f.items.map((x, j) => (j === i ? { ...x, name: e.target.value } : x)))}/></td>
              <td><input className="input" type="number" min="0" style={{height: 30, textAlign: "right"}} value={it.qty} onChange={e => upd("items", f.items.map((x, j) => (j === i ? { ...x, qty: Number(e.target.value) || 0 } : x)))}/></td>
              <td><input className="input" type="number" min="0" step="0.01" style={{height: 30}} value={it.price} onChange={e => upd("items", f.items.map((x, j) => (j === i ? { ...x, price: Number(e.target.value) || 0 } : x)))}/></td>
              <td className="right amount">฿{fmtM(LP_R2((it.price || 0) * (it.qty || 0)))}</td>
            </tr>
          ))}</tbody>
        </table></div>
        <div className="field-row cols-2">
          <div className="field"><label>ส่วนลด (บาท)</label><input className="input" type="number" min="0" step="0.01" value={f.discount} onChange={e => upd("discount", e.target.value)}/></div>
          <div className="field"><label>ข้อความการชำระเงิน</label><input className="input" value={f.payNote} onChange={e => upd("payNote", e.target.value)} placeholder="เช่น โอนเข้าบัญชี กสิกรไทย xxx-x-xxxxx-x เครดิต 30 วัน"/></div>
        </div>
        <div className="field"><label>หมายเหตุบนบิล</label><input className="input" value={f.note} onChange={e => upd("note", e.target.value)}/></div>
        {(() => {
          const pay = site && site.pay; const pl = lpPayPayload(pay, total, f.no, cust.id);
          if (!pl) return <div style={{fontSize: 12, color: "var(--ink-3)"}}>💳 ยังไม่ได้ตั้ง QR รับชำระ — ตั้งได้ที่ ⚙️ ตั้งค่าเว็บ → การรับชำระ (PromptPay / SCB)</div>;
          return (
            <div style={{display: "flex", gap: 12, alignItems: "center", padding: 10, border: "1px solid var(--line)", borderRadius: 10, background: "var(--surface-2)"}}>
              <LPPayQR payload={pl} size={110}/>
              <div style={{fontSize: 12.5, lineHeight: 1.6}}>
                <b>💳 QR ชำระเงินจะอยู่ในบิลของลูกค้า</b><br/>
                {pay.mode === "scb" ? <>SCB Bill Payment · Ref1 <b className="mono">{lpRef(f.no)}</b> · Ref2 <b className="mono">{lpRef(cust.id)}</b></> : <>PromptPay {pay.promptpayId}</>}
                <br/>ยอด ฿{fmtM(total)} — สแกนแล้วยอดขึ้นเอง
              </div>
            </div>
          );
        })()}
        <div style={{display: "flex", justifyContent: "flex-end", gap: 20, fontSize: 13, borderTop: "1px solid var(--line)", paddingTop: 10}}>
          <div style={{textAlign: "right", lineHeight: 2}}>
            <div style={{color: "var(--ink-3)"}}>มูลค่าก่อนภาษี</div><div style={{color: "var(--ink-3)"}}>VAT {vp}%</div><div style={{fontWeight: 800}}>ยอดรวมทั้งสิ้น</div>
          </div>
          <div style={{textAlign: "right", lineHeight: 2, minWidth: 120}}>
            <div className="amount">฿{fmtM(sub)}</div><div className="amount">฿{fmtM(vat)}</div>
            <div className="amount" style={{fontWeight: 800, fontSize: 17, color: "var(--brand)"}}>฿{fmtM(total)}</div>
          </div>
        </div>
      </div>
    </Drawer>
  );
}

// ให้เทสต์/ส่วนอื่นเรียกใช้ได้
window.LWCPortal = { normalize: lpNormalize, orderTotals: lpOrderTotals, publishSite: lpPublishSite, catKey: lpCatKey, custKey: lpCustKey };
