/* global React, I, UI, SSSData */
// =============== Project extras: work orders, job summary, create/edit, expenses ===============
const { useState: useStatePX } = React;

// ---------- Persistence ----------
const PX_KEY = "sss-project-extras";
// เก็บในหน่วยความจำ + Supabase (ไม่ผ่าน localStorage → ไม่เต็มโควต้า/ข้อมูลไม่หาย)
function pxLoad() {
  if (window.lsSyncGet) return window.lsSyncGet(PX_KEY) || {};
  try { return JSON.parse(localStorage.getItem(PX_KEY) || "{}"); } catch { return {}; }
}
function pxWriteSafe(key, value) {
  const ok = window.lsSetSafe ? window.lsSetSafe(key, value) : (() => { try { localStorage.setItem(key, value); return true; } catch (e) { return false; } })();
  if (!ok) window.toast && window.toast("⚠️ พื้นที่จัดเก็บเต็ม — บันทึกไม่สำเร็จ ลองลบรูปเก่าบางรูปก่อน");
  return ok;
}
function pxSave(all) {
  if (window.lsSyncSet) { window.lsSyncSet(PX_KEY, all); return true; }
  return pxWriteSafe(PX_KEY, JSON.stringify(all));
}
function pxGet(code) { return pxLoad()[code] || {}; }
function pxSet(code, patch) { const all = pxLoad(); all[code] = { ...(all[code] || {}), ...patch }; return pxSave(all); }

const UP_KEY = "sss-projects-user";
function pxUserLoad() { try { return JSON.parse(localStorage.getItem(UP_KEY) || "[]"); } catch { return []; } }
function pxUserSave(list) { return pxWriteSafe(UP_KEY, JSON.stringify(list)); }

// ---------- Urgency (ความเร่งด่วน) per step ----------
const STEP_PRI_ORDER = ["ปกติ", "ด่วน", "ด่วนมาก"];
const STEP_PRI = {
  "ปกติ":    { emoji: "🟢", color: "#15803d", bg: "#f0fdf4", bd: "#86efac" },
  "ด่วน":    { emoji: "🟠", color: "#c2410c", bg: "#fff7ed", bd: "#fdba74" },
  "ด่วนมาก": { emoji: "🔴", color: "#b91c1c", bg: "#fef2f2", bd: "#fca5a5" },
};
// สีประจำ CARD แต่ละลำดับ (หมุนเวียน) — แยกแต่ละ CARD ให้ดูง่าย
const CARD_PALETTE = [
  { bd: "#2563eb", bg: "#eff6ff", ink: "#1d4ed8" }, // ฟ้า
  { bd: "#7c3aed", bg: "#f5f3ff", ink: "#6d28d9" }, // ม่วง
  { bd: "#0d9488", bg: "#f0fdfa", ink: "#0f766e" }, // เขียวมิ้นต์
  { bd: "#db2777", bg: "#fdf2f8", ink: "#be185d" }, // ชมพู
  { bd: "#ea580c", bg: "#fff7ed", ink: "#c2410c" }, // ส้ม
  { bd: "#0891b2", bg: "#ecfeff", ink: "#0e7490" }, // ฟ้าคราม
];

// ---------- Date helpers ----------
const PX_MONTHS = ["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."];
function pxThaiDate(iso) {
  if (!iso) return "—";
  const [y, m, d] = iso.split("-").map(Number);
  return `${d} ${PX_MONTHS[m - 1]} ${y + 543}`;
}
function pxToday() { const d = new Date(); return `${d.getDate()} ${PX_MONTHS[d.getMonth()]} ${d.getFullYear() + 543}`; }
function pxTodayISO() { return new Date().toISOString().slice(0, 10); }

// ---------- Shared print modal ----------
function pxPrint(barTitle, html) {
  document.getElementById("__print_modal")?.remove();
  document.body.classList.remove("printing-modal");
  const modal = document.createElement("div");
  modal.id = "__print_modal";
  modal.className = "print-modal";
  modal.innerHTML = `
    <div class="print-modal-bar no-print">
      <div style="font-weight:600;font-size:14px;flex:1">${barTitle}</div>
      <button id="__print_close" class="pm-btn pm-btn-ghost">ปิด (Esc)</button>
      <button id="__print_btn" class="pm-btn pm-btn-primary">🖨 พิมพ์ / บันทึก PDF</button>
    </div>
    <div class="print-modal-paper">${html}</div>
  `;
  document.body.appendChild(modal);
  document.body.classList.add("printing-modal");
  const cleanup = () => { modal.remove(); document.body.classList.remove("printing-modal"); document.removeEventListener("keydown", onKey); };
  const onKey = (e) => { if (e.key === "Escape") cleanup(); };
  document.addEventListener("keydown", onKey);
  modal.addEventListener("click", e => { if (e.target === modal) cleanup(); });
  document.getElementById("__print_btn").addEventListener("click", () => window.print());
  document.getElementById("__print_close").addEventListener("click", cleanup);
}

function pxDocHeader(C, title, subtitle, accent) {
  return `
    <div style="display:flex;justify-content:space-between;align-items:flex-start;border-bottom:2px solid ${accent};padding-bottom:14px;margin-bottom:18px">
      <div style="display:flex;gap:12px;align-items:center">
        <div style="width:48px;height:48px;background:#0c0a09;color:white;display:grid;place-items:center;font-weight:700;font-size:15px;border-radius:6px">SSS</div>
        <div>
          <div style="font-weight:700;font-size:14px">${C.name}</div>
          <div style="font-size:10.5px;color:#57534e;max-width:320px">${C.address || ""}</div>
          <div style="font-size:10.5px;color:#57534e">เลขผู้เสียภาษี ${C.taxId}</div>
        </div>
      </div>
      <div style="text-align:right">
        <h1 style="margin:0;font-size:22px;color:${accent};font-weight:700">${title}</h1>
        <div style="font-size:10.5px;color:#78716c;letter-spacing:0.08em">${subtitle}</div>
      </div>
    </div>`;
}

// ---------- Work order ----------
function pxNextWoNo() {
  // ใช้ max+1 (ไม่ใช่ count+1) — ลบใบเก่าแล้วเลขไม่ย้อนกลับไปชนกับใบที่ยังอยู่
  // เลขชนกัน = ใบสั่งงานถูก merge ทับกันหาย (no คือ identity ตอน sync)
  const all = pxLoad();
  const used = new Set();
  let max = 0;
  Object.values(all).forEach(x => (x.workOrders || []).forEach(w => {
    if (!w || !w.no) return;
    used.add(w.no);
    const m = /^WO-(\d+)-(\d+)$/.exec(w.no);
    if (m) max = Math.max(max, parseInt(m[2], 10));
  }));
  let n = max + 1, no;
  do { no = "WO-2569-" + String(n).padStart(3, "0"); n++; } while (used.has(no));
  return no;
}

async function printWorkOrderDoc(project, wo) {
  const D = window.SSSData;
  const C = D.Company;
  const accent = "#1d4ed8";
  const prio = wo.priority || "ปกติ";
  const prioColor = prio === "ด่วนมาก" ? "#b91c1c" : prio === "ด่วน" ? "#b45309" : "#15803d";
  const assignees = wo.assignees && wo.assignees.length ? wo.assignees : (wo.assignee ? [wo.assignee] : ["—"]);
  const photos = wo.photos || [];
  const steps = (wo.steps && wo.steps.length)
    ? wo.steps
    : assignees.map((a, i) => ({ name: a, role: (wo.assigneeRoles && wo.assigneeRoles[i]) || "", duty: "" }));

  // รูปคนตามชื่อ (ผู้สั่งงาน/หัวหน้างาน) — ใช้รูป HR (sss-employee-photos) → ChatProfiles → ตัวย่อ
  const woEmpPhotos = (window.lsSyncGet ? (window.lsSyncGet("sss-employee-photos") || {}) : (() => { try { return JSON.parse(localStorage.getItem("sss-employee-photos") || "{}"); } catch { return {}; } })());
  const _normName = (s) => String(s || "").replace(/\s*\(.*\)\s*$/, "").trim();   // ตัด "(ชื่อเล่น)" เพื่อจับคู่
  const avatarByName = (name, size = 48) => {
    const e = (D.Employees || []).find(x => x.name === name) || (D.Employees || []).find(x => _normName(x.name) === _normName(name));
    const lookupName = e ? e.name : name;   // ใช้ชื่อเต็มของพนักงานไปหาโปรไฟล์/รูป
    const prof = window.ChatProfiles ? window.ChatProfiles.get(lookupName) : null;
    const color = prof ? prof.color : accent;
    const photo = (e && woEmpPhotos[e.id]) || (prof && prof.avatar) || "";
    return photo
      ? `<img src="${photo}" style="width:${size}px;height:${size}px;border-radius:50%;object-fit:cover;border:3px solid ${color};flex-shrink:0"/>`
      : `<div style="width:${size}px;height:${size}px;border-radius:50%;background:${color};color:white;display:grid;place-items:center;font-weight:700;font-size:${Math.round(size * 0.34)}px;flex-shrink:0">${String(name || "?").slice(0, 2)}</div>`;
  };

  // ---------- ตัวช่วย Timeline (รูปพนักงาน + การ์ดแต่ละลำดับ) ----------
  const empPhotos = (window.lsSyncGet ? (window.lsSyncGet("sss-employee-photos") || {}) : (() => { try { return JSON.parse(localStorage.getItem("sss-employee-photos") || "{}"); } catch { return {}; } })());
  const avatarOf = (name, size = 70, id = "") => {
    const p = window.ChatProfiles ? window.ChatProfiles.get(name) : null;
    const color = p ? p.color : "#57534e";
    const photo = (id && empPhotos[id]) || (p && p.avatar) || "";
    return photo
      ? `<img title="${name || ""}" src="${photo}" style="width:${size}px;height:${size}px;border-radius:50%;object-fit:cover;border:3px solid ${color};flex-shrink:0"/>`
      : `<div title="${name || ""}" style="width:${size}px;height:${size}px;border-radius:50%;background:${color};color:white;display:grid;place-items:center;font-weight:700;font-size:${Math.round(size * 0.32)}px;flex-shrink:0">${String(name || "?").slice(0, 2)}</div>`;
  };
  // รูปพนักงาน: จำกัดไม่เกิน 5 คน (เกินแสดง +N) เป็นแถวเดียวกะทัดรัด — กันการ์ดเพี้ยน/สูงเกินเมื่อคนเยอะ
  const avatarsOf = (s) => {
    const names = (s.names && s.names.length) ? s.names : [s.name];
    const ids = s.empIds && s.empIds.length ? s.empIds : [];
    const cap = 5, sz = names.length > 1 ? 30 : 46;
    const shown = names.slice(0, cap), extra = names.length - shown.length;
    return `<div style="display:flex;flex-wrap:wrap;gap:4px;flex-shrink:0;max-width:${names.length > 1 ? 78 : 50}px;align-content:flex-start">${shown.map((n, i) => avatarOf(n, sz, ids[i] || "")).join("")}${extra > 0 ? `<div style="width:${sz}px;height:${sz}px;border-radius:50%;background:#e7e5e4;color:#57534e;display:grid;place-items:center;font-weight:800;font-size:12px">+${extra}</div>` : ""}</div>`;
  };
  const cb = `<span style="display:inline-block;width:11px;height:11px;border:1.5px solid #44403c;border-radius:2.5px;vertical-align:-2px;margin-right:5px"></span>`;
  // การ์ดแต่ละลำดับ — ใช้ break-inside:avoid ให้เบราว์เซอร์ตัดหน้าเองที่ขอบการ์ด (ไม่ล้น/ไม่ถูกตัดครึ่ง)
  // ใช้ flex [เลขลำดับ][การ์ด] แทน absolute — กันเพี้ยนตอนตัดข้ามหน้า
  const renderStepCard = (i) => {
    const s = steps[i];
    const pal = CARD_PALETTE[i % CARD_PALETTE.length];
    const pri = STEP_PRI[s.priority] || STEP_PRI["ปกติ"];
    const urgent = s.priority === "ด่วน" || s.priority === "ด่วนมาก";
    const bd = urgent ? pri.color : pal.bd, bg = urgent ? pri.bg : pal.bg, ink = urgent ? pri.color : pal.ink;
    const multi = (s.names && s.names.length > 1);
    return `
      <div style="display:flex;gap:8px;align-items:flex-start;page-break-inside:avoid;break-inside:avoid;margin-bottom:9px">
        <div style="flex-shrink:0;margin-top:5px;width:24px;height:24px;border-radius:50%;background:${bd};color:white;font-size:12px;font-weight:800;display:grid;place-items:center">${i + 1}</div>
        <div style="flex:1;min-width:0">
          <div style="border:1.5px solid ${bd};border-left:6px solid ${bd};border-radius:9px;padding:9px 12px;display:flex;gap:11px;align-items:flex-start;background:${bg}">
            ${avatarsOf(s)}
            <div style="flex:1;min-width:0">
              <div style="display:flex;align-items:center;gap:7px;flex-wrap:wrap">
                <span style="font-size:14px;font-weight:800;color:${ink}">${multi ? (s.names.join(", ")) : s.name}</span>
                <span style="font-size:10px;font-weight:700;color:#fff;background:${pri.color};border-radius:999px;padding:2px 9px;white-space:nowrap">${pri.emoji} ${s.priority || "ปกติ"}</span>
                ${(s.helperNames && s.helperNames.length) ? `<span style="font-size:10px;font-weight:700;color:#6d28d9;background:#f5f3ff;border:1px solid #ddd6fe;border-radius:999px;padding:2px 9px">🤝 ผู้ช่วย: ${s.helperNames.join(", ")}</span>` : ""}
              </div>
              <div style="color:#78716c;font-size:10px;margin:1px 0 5px">${s.role || ""}</div>
              <div style="font-size:12.5px;line-height:1.5;white-space:pre-wrap;font-weight:600;color:#1c1917">${s.duty ? s.duty.replace(/</g, "&lt;") : "................................................................"}</div>
              <div style="margin-top:7px;display:flex;gap:6px;flex-wrap:wrap">
                ${s.planDate ? `<span style="font-size:10px;background:#dbeafe;color:#1d4ed8;border-radius:5px;padding:2px 8px;font-weight:700">📅 ทำ ${pxThaiDate(s.planDate)}</span>` : ""}
                ${s.doneDate ? `<span style="font-size:10px;background:#fef9c3;color:#a16207;border-radius:5px;padding:2px 8px;font-weight:700">🏁 กำหนดเสร็จ ${pxThaiDate(s.doneDate)}</span>` : ""}
                <span style="font-size:10px;background:#f5f5f4;color:#78716c;border-radius:5px;padding:2px 8px">✓ เสร็จจริง ......./......./.......</span>
              </div>
            </div>
            <div style="width:150px;border-left:1px dashed #d6d3d1;padding-left:11px;flex-shrink:0;font-size:10px;line-height:1.85">
              <div>${cb} ทำเสร็จแล้ว</div>
              <div>${cb} <span style="color:#b91c1c">เกิดปัญหา</span></div>
              <div style="color:#78716c;margin-top:1px">หมายเหตุ ........................</div>
              <div style="color:#78716c">..................................</div>
              <div style="color:#78716c">ลงชื่อ ........... ${i < steps.length - 1 ? "" : "วันที่ ......"}</div>
            </div>
          </div>
          ${i < steps.length - 1
            ? `<div style="margin:3px 0 0 2px;font-size:10px;color:${accent};font-weight:700">⬇ ส่งต่อให้ → ${steps[i + 1].name}</div>`
            : `<div style="margin:5px 0 0 2px;font-size:10px;color:#15803d;font-weight:700">🏁 จบงาน — ส่งหัวหน้างานตรวจ + ออกใบประเมินงาน</div>`}
        </div>
      </div>`;
  };

  // รูปภาพ: วัดสัดส่วนจริงก่อน — รูปแนวตั้ง (สูง>กว้าง 1.25 เท่า) ได้เต็มหน้า 1 รูป · แนวนอน/จัตุรัสจับคู่ 2 รูป/หน้า
  // เลิกบีบในกรอบเตี้ย 90มม. → รูปขยายใหญ่ตามสัดส่วนเท่าที่หน้ากระดาษให้ได้
  // ภาพลายเส้นจากโปรแกรมเขียนแบบพิมพ์แล้วล่องหน 2 แบบ: เส้นสว่างบนพื้นโปร่งใส / เส้นจางบนพื้นขาวทั้งแผ่น
  // → ประมวลผลรูปก่อนพิมพ์: แปลงเป็น "เส้นดำสนิทบนพื้นขาว" (รูปถ่ายหน้างานปกติไม่เข้าเงื่อนไข ไม่ถูกแตะ)
  const _prepLineart = (im) => {
    try {
      const W0 = im.naturalWidth || 1, H0 = im.naturalHeight || 1;
      const sc = Math.min(1, 2200 / Math.max(W0, H0));
      const w = Math.max(1, Math.round(W0 * sc)), h = Math.max(1, Math.round(H0 * sc));
      const cv = document.createElement("canvas"); cv.width = w; cv.height = h;
      const cx = cv.getContext("2d"); cx.drawImage(im, 0, 0, w, h);
      const id = cx.getImageData(0, 0, w, h), d = id.data, total = w * h;
      let trans = 0, bright = 0, dark = 0, opaque = 0;
      for (let i = 0; i < d.length; i += 4) {
        if (d[i + 3] < 40) { trans++; continue; }
        opaque++;
        const lum = 0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2];
        if (lum > 235) bright++; else dark++;
      }
      const caseA = trans / total > 0.3 && opaque > 0 && bright / opaque > 0.7;                       // เส้นสว่าง พื้นโปร่งใส
      const caseB = trans / total < 0.05 && bright / total > 0.82 && dark / total > 0.0003 && dark / total < 0.2;   // เส้นจาง พื้นขาวเกือบทั้งแผ่น
      if (!caseA && !caseB) return null;
      for (let i = 0; i < d.length; i += 4) {
        let black;
        if (caseA) black = d[i + 3] > 40;   // พิกเซลของเส้น (ทึบ) → ดำ
        else black = (0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2]) < 247;   // เข้มกว่ากระดาษนิดเดียวก็นับเป็นเส้น
        d[i] = d[i + 1] = d[i + 2] = black ? 0 : 255; d[i + 3] = 255;
      }
      cx.putImageData(id, 0, 0);
      return cv.toDataURL("image/png");
    } catch { return null; }
  };
  const _dims = await Promise.all(photos.map(ph => Promise.race([
    new Promise(res => { const im = new Image(); im.onload = () => { const p = _prepLineart(im); res({ asp: (im.naturalHeight || 1) / (im.naturalWidth || 1), url: p || ph.url, fx: !!p }); }; im.onerror = () => res({ asp: 0.75, url: ph.url, fx: false }); im.src = ph.url; }),
    new Promise(res => setTimeout(() => res({ asp: 0.75, url: ph.url, fx: false }), 3000)),   // รูปโหลดไม่ขึ้น → พิมพ์ตามเดิม ไม่ค้าง
  ])));
  const FULL_H = 225, HALF_H = 106;   // มม. — พื้นที่รูปหลังหักหัวกระดาษ/คำบรรยาย/ท้ายกระดาษ
  const photoPages = [];              // แต่ละหน้า = [{ph, idx, maxH}]
  for (let i = 0; i < photos.length; ) {
    const tall = _dims[i].asp > 1.25;
    const nextWide = i + 1 < photos.length && _dims[i + 1].asp <= 1.25;
    if (!tall && nextWide) {          // จับคู่แนวนอน 2 รูป
      photoPages.push([{ ph: photos[i], idx: i, maxH: HALF_H, url: _dims[i].url, fx: _dims[i].fx }, { ph: photos[i + 1], idx: i + 1, maxH: HALF_H, url: _dims[i + 1].url, fx: _dims[i + 1].fx }]);
      i += 2;
    } else {                          // แนวตั้ง หรือเหลือรูปเดียว → เต็มหน้า
      photoPages.push([{ ph: photos[i], idx: i, maxH: FULL_H, url: _dims[i].url, fx: _dims[i].fx }]);
      i += 1;
    }
  }
  const hasPhotos = photoPages.length > 0;

  const pageFoot = `
    <div style="position:absolute;left:14mm;right:14mm;bottom:7mm;border-top:2px solid ${accent};padding-top:5px;font-size:9.5px;color:#78716c;display:flex;justify-content:space-between">
      <span>${C.name}</span>
      <span>${wo.no} · ออกโดยระบบ SSS</span>
    </div>`;
  // หน้าแบบคงที่ (รายละเอียด/รูป) — สูงเต็มหน้า + footer ล่าง
  const pageWrap = (inner, brk) => `
    <div style="background:white;color:#0c0a09;width:100%;min-height:270mm;padding:12mm 14mm 20mm;box-sizing:border-box;position:relative;${brk ? "page-break-after:always;" : ""}">
      ${inner}
      ${pageFoot}
    </div>`;

  // Signatures หน้า 1 — เฉพาะ 3 ช่องหลัก (ผู้สั่งงาน/หัวหน้างาน/ผู้รับมอบงาน) เพื่อไม่ให้ล้นหน้า
  // (การเซ็นรับงานรายลำดับอยู่ในหน้า Timeline อยู่แล้ว)
  const sigBlock = (avatarName, role, sub) => `<div style="text-align:center"><div style="display:flex;justify-content:center;margin-bottom:3px">${avatarByName(avatarName, 38)}</div><div style="border-top:1px dashed #57534e;padding-top:5px;margin-top:9px"><b>${role}</b><div style="color:#78716c;font-size:10px;margin-top:2px">${sub}</div></div></div>`;
  const firstReceiver = (steps[0] && ((steps[0].names && steps[0].names.join(" / ")) || steps[0].name)) || "";
  const sigBlocks = [
    sigBlock(wo.issuedBy, "ผู้สั่งงาน", `( ${wo.issuedBy || "....................."} ) · ${wo.issueDate}`),
    sigBlock(project.lead, "หัวหน้างาน", `( ${project.lead || "....................."} ) · วันที่ ..........`),
    sigBlock(firstReceiver, "ผู้รับมอบงาน", `( ${firstReceiver || "....................."} ) · วันที่ ..........`),
  ];
  const sigCols = sigBlocks.length;

  const page1 = `
    ${pxDocHeader(C, "ใบสั่งงาน", "WORK ORDER", accent)}
    <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:9px">
      <div style="font-size:12px">เลขที่ <b style="font-family:monospace;font-size:13px">${wo.no}</b> · ออกเมื่อ <b>${wo.issueDate}</b></div>
      <div style="font-size:11px;padding:3px 12px;border:1.5px solid ${prioColor};color:${prioColor};border-radius:99px;font-weight:700">ความสำคัญ: ${prio}</div>
    </div>
    <div style="display:flex;gap:10px;margin-bottom:10px">
      <div style="flex:1;display:flex;align-items:center;gap:10px;border:1.5px solid ${accent};border-radius:9px;padding:7px 11px;background:${accent}0d">
        ${avatarByName(wo.issuedBy, 44)}
        <div><div style="font-size:9.5px;color:#78716c;letter-spacing:0.06em;text-transform:uppercase">ผู้สั่งงาน</div><div style="font-weight:700;font-size:13px">${wo.issuedBy || "—"}</div><div style="font-size:10px;color:#57534e">ออกเมื่อ ${wo.issueDate}</div></div>
      </div>
      <div style="flex:1;display:flex;align-items:center;gap:10px;border:1.5px solid #d6d3d1;border-radius:9px;padding:7px 11px">
        ${avatarByName(project.lead, 44)}
        <div><div style="font-size:9.5px;color:#78716c;letter-spacing:0.06em;text-transform:uppercase">หัวหน้างาน</div><div style="font-weight:700;font-size:13px">${project.lead || "—"}</div><div style="font-size:10px;color:#57534e">${project.code}</div></div>
      </div>
    </div>
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px;font-size:12px">
      <div style="border:1px solid #e7e5e4;border-radius:6px;padding:7px 11px">
        <div style="font-size:9.5px;color:#78716c;letter-spacing:0.05em;margin-bottom:2px">โปรเจกต์</div>
        <div style="font-weight:600">${project.name}</div>
        <div style="font-family:monospace;font-size:10.5px;color:#57534e">${project.code} · ลูกค้า: ${project.customer}</div>
      </div>
      <div style="border:1px solid #e7e5e4;border-radius:6px;padding:7px 11px">
        <div style="font-size:9.5px;color:#78716c;letter-spacing:0.05em;margin-bottom:2px">ผู้รับผิดชอบ (${assignees.length} คน)</div>
        <div style="display:grid;grid-template-columns:${assignees.length > 4 ? "1fr 1fr" : "1fr"};column-gap:10px">
          ${assignees.map((a, i) => `<div style="font-weight:600;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${i + 1}. ${a}${(wo.assigneeRoles && wo.assigneeRoles[i]) ? ` <span style="font-weight:400;color:#57534e;font-size:10px">— ${wo.assigneeRoles[i]}</span>` : ""}</div>`).join("")}
        </div>
      </div>
      <div style="border:1.5px solid ${accent};border-radius:6px;padding:6px 11px;background:${accent}0d;grid-column:1 / -1">
        <div style="font-size:9.5px;color:#78716c;letter-spacing:0.05em;margin-bottom:2px">กำหนดเวลา</div>
        <div style="font-weight:600">เริ่ม ${pxThaiDate(wo.startDate)} → ส่งงาน <span style="color:${accent}">${pxThaiDate(wo.due)}</span></div>
      </div>
    </div>
    <div style="margin-bottom:10px">
      <div style="font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;color:#44403c;margin-bottom:4px">รายละเอียดของงาน</div>
      <div style="border:1px solid #d6d3d1;border-radius:6px;padding:9px 12px;font-size:12.5px;line-height:1.55;min-height:54px;max-height:95mm;overflow:hidden;white-space:pre-wrap">${(wo.detail || "").replace(/</g, "&lt;")}</div>
    </div>
    ${(wo.items && wo.items.length) ? `<div style="margin-bottom:10px">
      <div style="font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;color:#44403c;margin-bottom:4px">รายการส่งมอบ</div>
      <table style="width:100%;border-collapse:collapse;font-size:11.5px">
        <thead><tr style="background:#f7f6f4">
          <th style="border:1px solid #d6d3d1;padding:4px 8px;text-align:left;width:34px">#</th>
          <th style="border:1px solid #d6d3d1;padding:4px 8px;text-align:left">รายการ</th>
          <th style="border:1px solid #d6d3d1;padding:4px 8px;text-align:right;width:80px">จำนวน</th>
          <th style="border:1px solid #d6d3d1;padding:4px 8px;text-align:left;width:70px">หน่วย</th>
        </tr></thead>
        <tbody>${wo.items.map((it, i) => `<tr>
          <td style="border:1px solid #d6d3d1;padding:4px 8px">${i + 1}</td>
          <td style="border:1px solid #d6d3d1;padding:4px 8px">${(it.name || "").replace(/</g, "&lt;")}</td>
          <td style="border:1px solid #d6d3d1;padding:4px 8px;text-align:right">${it.qty != null ? it.qty : ""}</td>
          <td style="border:1px solid #d6d3d1;padding:4px 8px">${(it.unit || "").replace(/</g, "&lt;")}</td>
        </tr>`).join("")}</tbody>
      </table>
    </div>` : ""}
    <div style="font-size:11px;color:#44403c;margin-bottom:7px;padding:6px 12px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:5px">👥 <b>ผู้รับผิดชอบและลำดับการส่งต่องาน (Timeline)</b> — ดูหน้าถัดไป${photos.length > 0 ? ` &nbsp;·&nbsp; 📎 <b>รูปภาพประกอบ ${photos.length} รูป</b> — หน้าท้ายเอกสาร` : ""}</div>
    <div style="font-size:10.5px;color:#78716c;margin-bottom:6px">
      <b>เงื่อนไข:</b> ผู้รับผิดชอบต้องอัพเดทความคืบหน้าพร้อมรูปถ่ายหน้างานในระบบทุกครั้ง · แจ้งหัวหน้างานทันทีหากพบปัญหาที่กระทบกำหนดส่ง
    </div>
    <div style="display:grid;grid-template-columns:repeat(${sigCols}, 1fr);gap:18px;font-size:11px;page-break-inside:avoid;break-inside:avoid;margin-top:8px">
      ${sigBlocks.join("")}
    </div>`;

  const photoPagesHTML = photoPages.map((pair, pi) => {
    const inner = `
      <div style="display:flex;justify-content:space-between;align-items:center;border-bottom:2px solid ${accent};padding-bottom:8px;margin-bottom:10px">
        <div style="display:flex;gap:10px;align-items:center">
          <div style="width:34px;height:34px;background:#0c0a09;color:white;display:grid;place-items:center;font-weight:700;font-size:11px;border-radius:5px">SSS</div>
          <div>
            <div style="font-weight:700;font-size:12.5px">ใบสั่งงาน ${wo.no} — รูปภาพประกอบ</div>
            <div style="font-size:10.5px;color:#57534e">${project.code} · ${project.name}</div>
          </div>
        </div>
        <div style="font-size:10.5px;color:#78716c">ออกเมื่อ ${wo.issueDate}</div>
      </div>
      ${pair.map(({ ph, idx, maxH, url, fx }) => `
        <div style="margin-bottom:5mm;text-align:center">
          <span style="display:inline-block;max-width:100%;border:1px solid #e7e5e4;border-radius:5px;overflow:hidden;background:#ffffff">
            <img src="${url}" style="display:block;max-width:100%;max-height:${maxH}mm;width:auto;height:auto"/>
          </span>
          <div style="font-size:10px;color:#57534e;margin-top:3px">รูปที่ ${idx + 1}${ph.name && !/^camera/.test(ph.name) ? ` · ${ph.name}` : ""}${fx ? ' · <span style="color:#78716c">ปรับเส้นเข้มอัตโนมัติเพื่อการพิมพ์</span>' : ""}</div>
        </div>`).join("")}`;
    return pageWrap(inner, pi < photoPages.length - 1);
  }).join("");

  // ---------- หน้า Timeline ผู้รับผิดชอบ ----------
  // ปล่อยให้เบราว์เซอร์ตัดหน้าเอง (native flow) — การ์ดมี break-inside:avoid จึงไม่ถูกตัดครึ่ง/ไม่ล้น
  // ไม่ fix ความสูง + ใช้ margin จาก @page (8mm) ทุกหน้า → ต่อหน้าใหม่อัตโนมัติได้ไม่จำกัด
  const respHeader = `
    ${pxDocHeader(C, "ผู้รับผิดชอบงาน", `RESPONSIBILITY TIMELINE · ${wo.no}`, accent)}
    <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;font-size:11.5px">
      <div>โปรเจกต์ <b>${project.name}</b> <span style="font-family:monospace;color:#57534e">(${project.code})</span></div>
      <div>กำหนดส่งงาน <b style="color:${accent}">${pxThaiDate(wo.due)}</b></div>
    </div>
    <div style="font-size:10.5px;color:#57534e;margin-bottom:12px;padding:6px 10px;background:#f7f6f4;border-radius:5px">ทำตามลำดับ — เสร็จจากคนแรกแล้วส่งต่อให้คนถัดไป · ติ้กช่อง "ทำเสร็จแล้ว" หรือ "เกิดปัญหา" พร้อมลงชื่อทุกครั้ง</div>`;
  const respSection = `
    <div style="page-break-before:always;${hasPhotos ? "page-break-after:always;" : ""}background:white;color:#0c0a09;padding:4mm 5mm;box-sizing:border-box">
      ${respHeader}
      ${steps.map((s, i) => renderStepCard(i)).join("")}
    </div>`;

  // หน้า 1 มี id เพื่อวัดความสูงจริงแล้ว "ย่อให้พอดี 1 หน้า" เสมอ — เนื้อหาเยอะแค่ไหนก็ไม่ดันลายเซ็นตกหน้า 2 อีก
  const page1HTML = `
    <div id="__wo_page1" style="background:white;color:#0c0a09;width:100%;min-height:268mm;padding:9mm 14mm 16mm;box-sizing:border-box;position:relative;page-break-after:always;">
      ${page1}
      ${pageFoot}
    </div>`;
  const html = page1HTML + respSection + photoPagesHTML;
  pxPrint(`ใบสั่งงาน ${wo.no}`, html);
  // Shrink-to-fit: วัดความสูงหน้า 1 ที่ความกว้างเท่าตอนพิมพ์จริง (A4 210mm − ขอบ @page 8mm×2 = 194mm)
  // ถ้าสูงเกินพื้นที่พิมพ์ (281mm) ให้ย่อทั้งหน้าด้วย zoom — การันตีลายเซ็น+ท้ายกระดาษอยู่หน้า 1 เสมอ
  setTimeout(() => {
    const el = document.getElementById("__wo_page1");
    if (!el) return;
    const prevW = el.style.width;
    el.style.width = "194mm";
    const pxPerMM = el.offsetWidth / 194;
    const budget = 279 * pxPerMM;            // 297 − 8×2 เผื่อปัดเศษ 2mm
    const h = el.scrollHeight;
    el.style.width = prevW;
    if (h > budget) el.style.zoom = String(Math.max(0.55, Math.floor((budget / h) * 1000) / 1000));
  }, 80);
}

function WorkOrderModal({ project, onClose, onSaved, initial }) {
  const D = window.SSSData;
  const { Btn, Drawer, ProductAutocomplete } = window.UI;
  const emps = D.Employees || [];
  const isEditWO = !!initial;
  const [f, setF] = useStatePX({
    no: initial ? initial.no : pxNextWoNo(),
    detail: initial ? (initial.detail || "") : "",
    startDate: initial ? (initial.startDate || pxTodayISO()) : pxTodayISO(),
    due: initial ? (initial.due || project.end || pxTodayISO()) : (project.end || pxTodayISO()),
    priority: initial ? (initial.priority || "ปกติ") : "ปกติ"
  });
  const [steps, setSteps] = useStatePX(() => {
    if (initial && initial.steps && initial.steps.length) {
      return initial.steps.map(s => ({ empIds: s.empIds && s.empIds.length ? s.empIds : (s.empId ? [s.empId] : []), helperIds: s.helperIds || [], duty: s.duty || "", planDate: s.planDate || "", doneDate: s.doneDate || "", priority: s.priority || "ปกติ" }));
    }
    return emps[0] ? [{ empIds: [emps[0].id], helperIds: [], duty: "", planDate: "", doneDate: "", priority: "ปกติ" }] : [{ empIds: [], helperIds: [], duty: "", planDate: "", doneDate: "", priority: "ปกติ" }];
  });
  const [photos, setPhotos] = useStatePX(initial && initial.photos ? [...initial.photos] : []);
  const [showCam, setShowCam] = useStatePX(false);
  // รายการส่งมอบ (ของที่ต้องส่งให้ลูกค้า) — ใช้ดึงไปออกใบส่งของประจำวันอัตโนมัติ
  const [items, setItems] = useStatePX(() => (initial && initial.items && initial.items.length)
    ? initial.items.map(it => ({ name: it.name || "", qty: it.qty != null ? it.qty : 1, unit: it.unit || "ชิ้น" }))
    : [{ name: "", qty: 1, unit: "ชิ้น" }]);
  const updItem = (i, k, v) => setItems(list => list.map((it, j) => j === i ? { ...it, [k]: v } : it));
  const upd = (k, v) => setF(p => ({ ...p, [k]: v }));

  const empOf = (id) => emps.find(e => e.id === id);
  const updStep = (i, k, v) => setSteps(list => list.map((s, j) => j === i ? { ...s, [k]: v } : s));
  const addEmpToStep = (i, id) => { if (!id) return; setSteps(list => list.map((s, j) => j === i && !s.empIds.includes(id) ? { ...s, empIds: [...s.empIds, id], helperIds: (s.helperIds || []).filter(x => x !== id) } : s)); };
  const removeEmpFromStep = (i, id) => setSteps(list => list.map((s, j) => j === i ? { ...s, empIds: s.empIds.filter(x => x !== id) } : s));
  const addHelperToStep = (i, id) => { if (!id) return; setSteps(list => list.map((s, j) => j === i && !(s.helperIds || []).includes(id) && !s.empIds.includes(id) ? { ...s, helperIds: [...(s.helperIds || []), id] } : s)); };
  const removeHelperFromStep = (i, id) => setSteps(list => list.map((s, j) => j === i ? { ...s, helperIds: (s.helperIds || []).filter(x => x !== id) } : s));
  // ── ชุดการทำงาน (เทมเพลต) — บันทึกลำดับพนักงาน/หน้าที่/รายการส่งมอบไว้เรียกใช้ซ้ำ ──
  const WO_TPL_KEY = "sss-wo-templates";
  const tplLoad = () => {
    const v = window.lsSyncGet ? window.lsSyncGet(WO_TPL_KEY) : null;
    if (Array.isArray(v)) return v;
    try { return JSON.parse(localStorage.getItem(WO_TPL_KEY) || "[]") || []; } catch { return []; }
  };
  const tplSave = (list) => { if (window.lsSyncSet) window.lsSyncSet(WO_TPL_KEY, list); else { try { localStorage.setItem(WO_TPL_KEY, JSON.stringify(list)); } catch {} } };
  const [tpls, setTpls] = useStatePX(tplLoad);
  const [tplName, setTplName] = useStatePX("");
  const [showTplSave, setShowTplSave] = useStatePX(false);
  const [showTpls, setShowTpls] = useStatePX(false);
  const saveTpl = () => {
    const name = tplName.trim();
    if (!name) { window.toast && window.toast("ตั้งชื่อชุดก่อน เช่น งานพับถาดมาตรฐาน"); return; }
    const tpl = {
      id: "WT-" + Date.now().toString(36),
      name,
      steps: steps.map(st => ({ empIds: [...(st.empIds || [])], helperIds: [...(st.helperIds || [])], duty: st.duty || "", priority: st.priority || "ปกติ" })),
      detail: f.detail || "",
      priority: f.priority || "ปกติ",
      items: items.filter(it => (it.name || "").trim()).map(it => ({ name: it.name, qty: it.qty, unit: it.unit })),
      by: (window.Session && window.Session.get() && window.Session.get().name) || "",
    };
    const next = [...tplLoad(), tpl];
    setTpls(next); tplSave(next);
    setTplName(""); setShowTplSave(false);
    window.toast && window.toast("บันทึกชุดการทำงาน \"" + name + "\" แล้ว (ซิงค์ทุกเครื่อง)");
  };
  const applyTpl = async (tpl) => {
    const hasWork = steps.some(st => (st.empIds || []).length || (st.duty || "").trim());
    if (hasWork) {
      const ok = await window.confirmDialog({ title: "ใช้ชุด \"" + tpl.name + "\"?", message: "ลำดับงาน/หน้าที่/รายการส่งมอบที่กรอกอยู่จะถูกแทนที่ด้วยชุดนี้", confirmText: "ใช้ชุดนี้" });
      if (!ok) return;
    }
    setSteps((tpl.steps || []).map(st => ({ empIds: [...(st.empIds || [])], helperIds: [...(st.helperIds || [])], duty: st.duty || "", planDate: "", doneDate: "", priority: st.priority || "ปกติ" })));
    if (tpl.items && tpl.items.length) setItems(tpl.items.map(it => ({ name: it.name || "", qty: it.qty != null ? it.qty : 1, unit: it.unit || "ชิ้น" })));
    setF(p => ({ ...p, detail: tpl.detail || p.detail, priority: tpl.priority || p.priority }));
    window.toast && window.toast("ใช้ชุด \"" + tpl.name + "\" แล้ว — ปรับแก้เพิ่มได้เลย");
  };
  const delTpl = async (tpl) => {
    const ok = await (window.confirmDelete ? window.confirmDelete("ชุดการทำงาน " + tpl.name, (tpl.steps || []).length + " ลำดับ") : Promise.resolve(confirm("ลบ?")));
    if (!ok) return;
    const next = tplLoad().filter(x => x.id !== tpl.id);
    setTpls(next); tplSave(next);
  };

  const [dragI, setDragI] = useStatePX(null);
  const dropAt = (i) => { setSteps(list => { if (dragI === null || dragI === i) return list; const n = [...list]; const [m] = n.splice(dragI, 1); n.splice(i, 0, m); return n; }); setDragI(null); };
  const moveStep = (i, dir) => setSteps(list => {
    const j = i + dir;
    if (j < 0 || j >= list.length) return list;
    const next = [...list];
    const tmp = next[i]; next[i] = next[j]; next[j] = tmp;
    return next;
  });

  const resize = (file, max) => window.DeliveryStore && window.DeliveryStore.resizeImage
    ? window.DeliveryStore.resizeImage(file, max)
    : new Promise(res => { const r = new FileReader(); r.onload = () => res(r.result); r.readAsDataURL(file); });
  const pickPhotos = async (e) => {
    const files = [...e.target.files].slice(0, 12);
    e.target.value = "";
    const loaded = [];
    for (const file of files) {
      const url = await resize(file, 600);
      if (url) loaded.push({ url, name: file.name });
    }
    setPhotos(p => [...p, ...loaded].slice(0, 12));
  };

  const save = (andPrint) => {
    if (steps.length === 0) { window.toast && window.toast("กรุณาเพิ่มผู้รับผิดชอบอย่างน้อย 1 คน"); return; }
    for (let i = 0; i < steps.length; i++) {
      if (!steps[i].empIds || steps[i].empIds.length === 0) { window.toast && window.toast(`กรุณาเลือกพนักงานในลำดับที่ ${i + 1} อย่างน้อย 1 คน`); return; }
      if (!steps[i].duty.trim()) { window.toast && window.toast(`กรุณากรอกหน้าที่ในลำดับที่ ${i + 1}`); return; }
    }
    if (!f.detail.trim()) { window.toast && window.toast("กรุณาใส่รายละเอียดของงาน"); return; }
    if (!f.due) { window.toast && window.toast("กรุณากำหนดวันส่งงาน"); return; }
    const woSteps = steps.map(s => {
      const es = s.empIds.map(empOf).filter(Boolean);
      const hs = (s.helperIds || []).map(empOf).filter(Boolean);
      return { empIds: es.map(e => e.id), names: es.map(e => e.name), name: es.map(e => e.name).join(", "), role: es.map(e => `${e.role} · ${e.dept}`).join(" / "), helperIds: hs.map(e => e.id), helperNames: hs.map(e => e.name), duty: s.duty.trim(), planDate: s.planDate || "", doneDate: s.doneDate || "", priority: s.priority || "ปกติ" };
    });
    const allIds = [...new Set(woSteps.flatMap(s => [...s.empIds, ...(s.helperIds || [])]))];
    const allNames = [...new Set(woSteps.flatMap(s => [...s.names, ...(s.helperNames || [])]))];
    const wo = {
      no: f.no,
      steps: woSteps,
      assigneeIds: allIds,
      assignees: allNames,
      assigneeRoles: woSteps.map(s => s.role),
      assignee: allNames.join(", "),
      detail: f.detail.trim(), startDate: f.startDate, due: f.due, priority: f.priority,
      items: items.filter(it => (it.name || "").trim()).map(it => ({ name: it.name.trim(), qty: Number(it.qty) || 1, unit: it.unit || "ชิ้น" })),
      photos: [...photos],
      issuedBy: initial ? initial.issuedBy : ((window.Session && window.Session.get() && window.Session.get().name) || (window.SSSData.Company.currentUser && window.SSSData.Company.currentUser.name) || ""),
      issueDate: initial ? initial.issueDate : pxToday(),
      status: initial ? (initial.status || "เปิดงาน") : "เปิดงาน"
    };
    const existing = pxGet(project.code).workOrders || [];
    const list = isEditWO ? existing.map(x => x.no === wo.no ? wo : x) : [wo, ...existing];
    pxSet(project.code, { workOrders: list });
    window.logActivity && window.logActivity(isEditWO ? "แก้ไขใบสั่งงาน" : "ออกใบสั่งงาน", `${wo.no} → ${wo.assignee} (${project.code})`, isEditWO ? "update" : "create");
    window.toast && window.toast(`${isEditWO ? "บันทึกการแก้ไข" : "ออก"}ใบสั่งงาน ${wo.no} แล้ว`);
    onSaved && onSaved(list);
    onClose();
    if (andPrint) setTimeout(() => printWorkOrderDoc(project, wo), 150);
  };

  // ── ไลท์บ็อกซ์เลือกพนักงานจากรูปหน้า — กดปุ่มเปิด เลือก/เอาออกหลายคน แล้วกดเสร็จ ──
  const nickOf = (name) => { const m = /\(([^)]+)\)/.exec(name || ""); return m ? m[1] : String(name || "").trim().split(/\s+/)[0]; };
  const woPhotos = (window.lsSyncGet ? (window.lsSyncGet("sss-employee-photos") || {}) : {});
  const [facePick, setFacePick] = useStatePX(null);   // { i, kind: "emp" | "helper" }
  const [faceQ, setFaceQ] = useStatePX("");
  const openFacePick = (i, kind) => { setFaceQ(""); setFacePick({ i, kind }); };
  const FaceAvatar = ({ e, size = 52, ring }) => {
    const prof = window.ChatProfiles ? window.ChatProfiles.get(e.name) : null;
    const photo = woPhotos[e.id] || (prof && prof.avatar) || "";
    const color = ring || (prof && prof.color) || "var(--accent)";
    return photo
      ? <img src={photo} alt={e.name} style={{width: size, height: size, borderRadius: "50%", objectFit: "cover", border: `2.5px solid ${color}`}}/>
      : <span style={{width: size, height: size, borderRadius: "50%", background: color, color: "white", display: "grid", placeItems: "center", fontSize: Math.round(size * 0.3), fontWeight: 700}}>{String(e.name || "?").slice(0, 2)}</span>;
  };

  return (
    <Drawer open={true} onClose={onClose} title={`${isEditWO ? "แก้ไขใบสั่งงาน " + f.no : "ออกใบสั่งงาน"} · ${project.code}`} wide xwide
      footer={<>
        <Btn kind="ghost" onClick={onClose}>ยกเลิก</Btn>
        <Btn kind="ghost" icon={I.print} onClick={() => save(true)}>บันทึก + พิมพ์</Btn>
        <Btn kind="primary" icon={I.check} onClick={() => save(false)}>บันทึกใบสั่งงาน</Btn>
      </>}>
      <div style={{display: "flex", flexDirection: "column", gap: 12}}>
        <div style={{padding: "10px 14px", background: "var(--surface-2)", border: "1px solid var(--line)", borderRadius: 8, fontSize: 12.5}}>
          <b>{project.name}</b>
          <div style={{color: "var(--ink-3)", fontSize: 11.5, marginTop: 2}}>ลูกค้า {project.customer} · กำหนดส่งโปรเจกต์ {project.due}</div>
        </div>
        <div className="field-row cols-2">
          <div className="field"><label>เลขที่ใบสั่งงาน</label><input className="input mono" value={f.no} readOnly style={{background: "var(--surface-2)"}}></input></div>
          <div className="field"><label>ความสำคัญ</label>
            <div className="payseg" style={{display: "flex", width: "100%"}}>
              {["ปกติ", "ด่วน", "ด่วนมาก"].map(p => (
                <button key={p} type="button" className={"payseg-btn" + (f.priority === p ? " active" : "")} style={{flex: 1}} onClick={() => upd("priority", p)}>{p}</button>
              ))}
            </div>
          </div>
        </div>

        {/* ชุดการทำงาน — กดปุ่มค่อยกางรายการ · แต่ละชุดโชว์รูปหน้าพนักงาน + หน้าที่ */}
        <div style={{border: "1px dashed var(--line-strong)", borderRadius: 10, padding: "9px 12px", background: "var(--surface-2)"}}>
          <div style={{display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap"}}>
            <button type="button" onClick={() => setShowTpls(v => !v)}
              style={{display: "inline-flex", alignItems: "center", gap: 7, border: "1px solid var(--line-strong)", borderRadius: 8, padding: "6px 12px", background: showTpls ? "var(--ink-1)" : "var(--surface)", color: showTpls ? "var(--surface)" : "var(--ink-1)", fontSize: 13, fontWeight: 700, cursor: "pointer", fontFamily: "inherit"}}>
              📦 ชุดการทำงาน <span style={{fontWeight: 400, opacity: 0.75}}>({tpls.length})</span> {showTpls ? "▲" : "▼"}
            </button>
            {!showTpls && tpls.length > 0 && <span style={{fontSize: 11.5, color: "var(--ink-4)"}}>กดเพื่อเลือกชุดที่บันทึกไว้</span>}
            <span style={{marginLeft: "auto"}}/>
            {!showTplSave
              ? <Btn size="sm" kind="ghost" onClick={() => setShowTplSave(true)}>💾 บันทึกเป็นชุด</Btn>
              : <span style={{display: "inline-flex", gap: 6, alignItems: "center"}}>
                  <input className="input" style={{height: 30, width: 210, fontSize: 12.5}} placeholder="ชื่อชุด เช่น งานพับถาดมาตรฐาน" value={tplName}
                    onChange={e => setTplName(e.target.value)} onKeyDown={e => { if (e.key === "Enter") saveTpl(); }} autoFocus/>
                  <Btn size="sm" kind="primary" onClick={saveTpl}>บันทึก</Btn>
                  <Btn size="sm" kind="ghost" onClick={() => setShowTplSave(false)}>ยกเลิก</Btn>
                </span>}
          </div>
          {showTpls && (
            <div style={{display: "flex", flexDirection: "column", gap: 8, marginTop: 10}}>
              {tpls.length === 0 && <div style={{fontSize: 12, color: "var(--ink-4)", padding: "6px 2px"}}>ยังไม่มีชุดที่บันทึกไว้ — ตั้งค่าลำดับงานด้านล่างแล้วกด "บันทึกเป็นชุด"</div>}
              {tpls.map(tpl => (
                <div key={tpl.id} style={{border: "1px solid var(--line)", borderRadius: 10, background: "var(--surface)", padding: "9px 12px"}}>
                  <div style={{display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap"}}>
                    <b style={{fontSize: 13.5}}>{tpl.name}</b>
                    <span style={{fontSize: 11, color: "var(--ink-4)"}}>{(tpl.steps || []).length} ลำดับ{tpl.by ? ` · โดย ${tpl.by}` : ""}</span>
                    <span style={{marginLeft: "auto", display: "inline-flex", gap: 6}}>
                      <Btn size="sm" kind="primary" onClick={() => { applyTpl(tpl); setShowTpls(false); }}>ใช้ชุดนี้</Btn>
                      <button className="icon-btn" title="ลบชุดนี้" onClick={() => delTpl(tpl)}><I.trash size={13}/></button>
                    </span>
                  </div>
                  <div style={{display: "flex", flexDirection: "column", gap: 5, marginTop: 8}}>
                    {(tpl.steps || []).map((st, i) => {
                      const stEmps2 = (st.empIds || []).map(empOf).filter(Boolean);
                      const stHelp2 = (st.helperIds || []).map(empOf).filter(Boolean);
                      const pri2 = STEP_PRI[st.priority] || STEP_PRI["ปกติ"];
                      return (
                        <div key={i} style={{display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", padding: "4px 6px", background: "var(--surface-2)", borderRadius: 8}}>
                          <span style={{width: 20, height: 20, borderRadius: "50%", background: "var(--accent)", color: "var(--accent-ink)", display: "grid", placeItems: "center", fontSize: 10.5, fontWeight: 800, flexShrink: 0}}>{i + 1}</span>
                          <span style={{display: "inline-flex", gap: 3, alignItems: "center"}}>
                            {stEmps2.slice(0, 6).map(e2 => <FaceAvatar key={e2.id} e={e2} size={26}/>)}
                            {stEmps2.length > 6 && <span style={{fontSize: 10, color: "var(--ink-3)", fontWeight: 700}}>+{stEmps2.length - 6}</span>}
                            {stHelp2.slice(0, 4).map(e2 => <FaceAvatar key={"h" + e2.id} e={e2} size={26} ring="#7c3aed"/>)}
                          </span>
                          <span style={{fontSize: 11.5, fontWeight: 600, color: "var(--ink-2)"}}>
                            {stEmps2.map(e2 => nickOf(e2.name)).join(", ")}
                            {stHelp2.length > 0 && <span style={{color: "#7c3aed"}}> +ผู้ช่วย {stHelp2.map(e2 => nickOf(e2.name)).join(", ")}</span>}
                          </span>
                          <span style={{fontSize: 11.5, color: "var(--ink-3)", flex: 1, minWidth: 120, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap"}}>{st.duty || "—"}</span>
                          {st.priority && st.priority !== "ปกติ" && <span style={{fontSize: 9.5, fontWeight: 700, color: "#fff", background: pri2.color, borderRadius: 999, padding: "1px 8px", flexShrink: 0}}>{pri2.emoji} {st.priority}</span>}
                        </div>
                      );
                    })}
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>

        {/* ผู้รับผิดชอบแบบ Timeline — เลือกพนักงาน + หน้าที่ เรียงลำดับส่งต่อ */}
        <div className="field">
          <label>ผู้รับผิดชอบตามลำดับ (Timeline — เสร็จจากคนแรกแล้วส่งต่อคนถัดไป) *</label>
          <div style={{display: "flex", flexDirection: "column", gap: 4}}>
            {steps.map((st, i) => {
              const stEmps = st.empIds.map(empOf).filter(Boolean);
              const stHelpers = (st.helperIds || []).map(empOf).filter(Boolean);
              const available = emps.filter(e => !st.empIds.includes(e.id) && !(st.helperIds || []).includes(e.id));
              const availableHelpers = emps.filter(e => !st.empIds.includes(e.id) && !(st.helperIds || []).includes(e.id));
              return (
                <React.Fragment key={i}>
                  <div onDragOver={e => { e.preventDefault(); }} onDrop={() => dropAt(i)}
                    style={{border: dragI === i ? "1.5px dashed var(--accent)" : "1px solid var(--line)", borderRadius: 10, padding: 10, display: "flex", gap: 10, alignItems: "flex-start", background: "var(--surface)", opacity: dragI === i ? 0.6 : 1}}>
                    <div draggable onDragStart={() => setDragI(i)} onDragEnd={() => setDragI(null)} title="ลากเพื่อจัดลำดับ"
                      style={{width: 24, height: 24, borderRadius: "50%", background: "var(--accent)", color: "var(--accent-ink)", display: "grid", placeItems: "center", fontSize: 11, fontWeight: 700, flexShrink: 0, marginTop: 4, cursor: "grab"}}>{i + 1}</div>
                    <div style={{flex: 1, display: "flex", flexDirection: "column", gap: 6, minWidth: 0}}>
                      {/* พนักงานหลัก — ชิป + เพิ่ม */}
                      <div style={{fontSize: 13, color: "var(--ink-2)", fontWeight: 700}}>👷 พนักงานที่ทำงาน</div>
                      <div style={{display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center"}}>
                        {stEmps.map(e => (
                          <span key={e.id} style={{display: "inline-flex", alignItems: "center", gap: 5, padding: "4px 9px 4px 5px", background: "var(--surface-2)", border: "1px solid var(--line)", borderRadius: 999, fontSize: 13.5}}>
                            <FaceAvatar e={e} size={30}/>
                            {e.name}
                            <button className="icon-btn" style={{width: 16, height: 16}} title="เอาออก" onClick={() => removeEmpFromStep(i, e.id)}><I.close size={10}/></button>
                          </span>
                        ))}
                        {stEmps.length === 0 && <span style={{fontSize: 11.5, color: "var(--ink-4)"}}>ยังไม่ได้เลือกพนักงาน</span>}
                      </div>
                      <Btn size="sm" kind="ghost" icon={I.plus} onClick={() => openFacePick(i, "emp")}>เลือกพนักงาน (จากรูปหน้า)</Btn>

                      {/* ผู้ช่วย — ชิป + เพิ่ม (ในกรอบการ์ดเดียวกัน) */}
                      <div style={{fontSize: 13, color: "var(--c-violet, #7c3aed)", fontWeight: 700, marginTop: 2}}>🤝 ผู้ช่วย</div>
                      <div style={{display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center"}}>
                        {stHelpers.map(e => (
                          <span key={e.id} style={{display: "inline-flex", alignItems: "center", gap: 5, padding: "4px 9px 4px 5px", background: "#f5f3ff", border: "1px solid #ddd6fe", borderRadius: 999, fontSize: 13.5, color: "#6d28d9"}}>
                            <FaceAvatar e={e} size={30} ring="#7c3aed"/>
                            {e.name}
                            <button className="icon-btn" style={{width: 16, height: 16}} title="เอาออก" onClick={() => removeHelperFromStep(i, e.id)}><I.close size={10}/></button>
                          </span>
                        ))}
                        {stHelpers.length === 0 && <span style={{fontSize: 11.5, color: "var(--ink-4)"}}>ไม่มีผู้ช่วย (ไม่บังคับ)</span>}
                      </div>
                      <Btn size="sm" kind="ghost" icon={I.plus} onClick={() => openFacePick(i, "helper")}>เลือกผู้ช่วย (จากรูปหน้า)</Btn>
                      <textarea className="textarea" value={st.duty} onChange={e => updStep(i, "duty", e.target.value)} style={{minHeight: 56, fontSize: 13.5}}
                        placeholder="หน้าที่ เช่น ตัดแผ่นตามแบบ DWG-021 / เชื่อมประกอบช่วง 1-3 / QC + แพ็ค"></textarea>
                      <div style={{display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap"}}>
                        <span style={{fontSize: 11.5, color: "var(--ink-3)", fontWeight: 600}}>ความเร่งด่วน</span>
                        <div className="payseg" style={{display: "flex"}}>
                          {STEP_PRI_ORDER.map(p => {
                            const cfg = STEP_PRI[p];
                            return (
                              <button key={p} type="button"
                                className={"payseg-btn" + ((st.priority || "ปกติ") === p ? " active" : "")}
                                style={(st.priority || "ปกติ") === p ? {background: cfg.color, color: "#fff", borderColor: cfg.color} : {}}
                                onClick={() => updStep(i, "priority", p)}>{cfg.emoji} {p}</button>
                            );
                          })}
                        </div>
                      </div>
                      <div style={{display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8}}>
                        <div className="field"><label style={{color: "var(--c-blue)"}}>📅 กำหนดทำวันที่</label><input className="input" type="date" value={st.planDate || ""} onChange={e => updStep(i, "planDate", e.target.value)}/></div>
                        <div className="field"><label style={{color: "var(--warning)"}}>🏁 กำหนดเสร็จ (ถ้ามี)</label><input className="input" type="date" value={st.doneDate || ""} onChange={e => updStep(i, "doneDate", e.target.value)}/></div>
                      </div>
                    </div>
                    <div style={{display: "flex", flexDirection: "column", gap: 2}}>
                      <button className="icon-btn" title="เลื่อนขึ้น" onClick={() => moveStep(i, -1)} style={i === 0 ? {opacity: 0.3, pointerEvents: "none"} : {}}><I.chevronUp size={12}/></button>
                      <button className="icon-btn" title="เลื่อนลง" onClick={() => moveStep(i, 1)} style={i === steps.length - 1 ? {opacity: 0.3, pointerEvents: "none"} : {}}><I.chevronDown size={12}/></button>
                      <button className="icon-btn" title="ลบ" onClick={() => setSteps(list => list.filter((_, j) => j !== i))}><I.trash size={12}/></button>
                    </div>
                  </div>
                  {i < steps.length - 1 && (
                    <div style={{paddingLeft: 16, fontSize: 11, color: "var(--ink-3)", fontWeight: 600}}>↓ เสร็จแล้วส่งต่อให้</div>
                  )}
                </React.Fragment>
              );
            })}
            <Btn size="sm" icon={I.plus} onClick={() => setSteps(s => [...s, { empIds: [], helperIds: [], duty: "", planDate: "", doneDate: "", priority: "ปกติ" }])}>เพิ่มลำดับงานถัดไป</Btn>
            <div style={{fontSize: 11, color: "var(--ink-3)"}}>พิมพ์เป็นหน้าที่ 2 ของใบสั่งงาน — กรอบ Timeline พร้อมรูปพนักงาน ช่องติ้ก "ทำเสร็จแล้ว / เกิดปัญหา" และช่องลงชื่อ</div>
          </div>
        </div>

        <div className="field"><label>รายละเอียดของงาน *</label>
          <textarea className="textarea" value={f.detail} onChange={e => upd("detail", e.target.value)} style={{minHeight: 110}}
            placeholder={"เช่น\n- ตัดแผ่นสแตนเลส 304 ตามแบบ DWG-021 จำนวน 8 แผ่น\n- เชื่อมประกอบราวบันไดช่วงที่ 1-3\n- เก็บงานขัดเงา + QC ก่อนส่ง"}></textarea>
        </div>

        {/* รายการส่งมอบ — ดึงไปออกใบส่งของประจำวันได้อัตโนมัติ */}
        <div className="field">
          <label>รายการส่งมอบ (ของที่ต้องส่งให้ลูกค้า — ใช้ตอนออกใบส่งของประจำวัน)</label>
          <div style={{display: "flex", flexDirection: "column", gap: 6}}>
            {items.map((it, i) => (
              <div key={i} style={{display: "grid", gridTemplateColumns: "1fr 72px 84px 30px", gap: 6, alignItems: "center"}}>
                {ProductAutocomplete
                  ? <ProductAutocomplete value={it.name} placeholder="ชื่อสินค้า/งาน — พิมพ์เพื่อค้นหา"
                      onChange={v => updItem(i, "name", v)} onPick={p => setItems(list => list.map((x, j) => j === i ? { ...x, name: p.name, unit: p.unit || x.unit } : x))}/>
                  : <input className="input" value={it.name} onChange={e => updItem(i, "name", e.target.value)} placeholder="ชื่อสินค้า/งาน"/>}
                <input className="input" type="number" min="0" step="any" value={it.qty} onChange={e => updItem(i, "qty", e.target.value)}/>
                <input className="input" value={it.unit} onChange={e => updItem(i, "unit", e.target.value)} placeholder="หน่วย"/>
                <button className="icon-btn" title="ลบรายการ" onClick={() => setItems(list => list.length > 1 ? list.filter((_, j) => j !== i) : list)}><I.trash size={12}/></button>
              </div>
            ))}
          </div>
          <Btn size="sm" icon={I.plus} onClick={() => setItems(list => [...list, { name: "", qty: 1, unit: "ชิ้น" }])} style={{marginTop: 6}}>เพิ่มรายการส่งมอบ</Btn>
          <div style={{fontSize: 11, color: "var(--ink-3)", marginTop: 4}}>ไม่บังคับ — ถ้ากรอกไว้ เวลากดปุ่ม "ออกใบส่งของประจำวัน" ระบบจะดึงรายการนี้ไปให้อัตโนมัติทั้งใบ</div>
        </div>

        {/* Photos */}
        <div className="field">
          <label>รูปภาพประกอบ (แบบ/หน้างาน — พิมพ์ต่อท้ายใบสั่งงาน)</label>
          <div style={{display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap"}}>
            <Btn size="sm" icon={I.camera} onClick={() => setShowCam(true)}>ถ่ายรูป</Btn>
            <label className="btn sm" style={{cursor: "pointer"}}>
              <input type="file" accept="image/*" multiple style={{display: "none"}} onChange={pickPhotos}></input>
              อัพโหลดรูป
            </label>
            <span style={{fontSize: 11, color: "var(--ink-3)"}}>{photos.length > 0 ? `${photos.length} รูป · พิมพ์เพิ่ม ${Math.ceil(photos.length / 2)} หน้า A4 (มีเลขหน้ากำกับ)` : "ไม่บังคับ · 2 รูปต่อ 1 หน้า A4"}</span>
          </div>
          {photos.length > 0 && (
            <div style={{display: "flex", gap: 6, flexWrap: "wrap", marginTop: 8}}>
              {photos.map((p, i) => (
                <div key={i} style={{position: "relative", width: 64, height: 64, borderRadius: 6, overflow: "hidden", border: "1px solid var(--line)"}}>
                  <img src={p.url} alt={p.name} style={{width: "100%", height: "100%", objectFit: "cover"}}/>
                  <span style={{position: "absolute", left: 3, bottom: 3, fontSize: 9, background: "rgba(0,0,0,0.6)", color: "white", padding: "0 5px", borderRadius: 3}}>{i + 1}</span>
                  <button onClick={() => setPhotos(ph => { const t = ph[i]; if (t && t.url && window.markPhotoDeleted) window.markPhotoDeleted(t.url); return ph.filter((_, j) => j !== i); })}
                    style={{position: "absolute", top: 2, right: 2, width: 16, height: 16, padding: 0, borderRadius: "50%", background: "rgba(0,0,0,0.7)", color: "white", border: 0, cursor: "pointer", fontSize: 10, lineHeight: "14px"}}>×</button>
                </div>
              ))}
            </div>
          )}
        </div>

        <div className="field-row cols-2">
          <div className="field"><label>วันที่เริ่มงาน</label><input className="input" type="date" value={f.startDate} onChange={e => upd("startDate", e.target.value)}></input></div>
          <div className="field"><label>กำหนดส่งงาน *</label><input className="input" type="date" value={f.due} onChange={e => upd("due", e.target.value)}></input></div>
        </div>
      </div>
      {showCam && window.CameraCaptureModal && (
        <window.CameraCaptureModal onClose={() => setShowCam(false)} onCapture={(url) => setPhotos(p => [...p, { url, name: "camera-" + Date.now() }].slice(0, 12))}/>
      )}

      {/* ไลท์บ็อกซ์เลือกพนักงานจากรูปหน้า */}
      {facePick && (() => {
        const st = steps[facePick.i];
        if (!st) return null;
        const isHelper = facePick.kind === "helper";
        const inThis = new Set(isHelper ? (st.helperIds || []) : st.empIds);
        const inOther = new Set(isHelper ? st.empIds : (st.helperIds || []));
        const q = faceQ.trim().toLowerCase();
        const list = emps.filter(e => !q || (e.name + " " + (e.role || "") + " " + (e.dept || "")).toLowerCase().includes(q));
        const tone = isHelper ? "#7c3aed" : "var(--accent)";
        const toggle = (id) => {
          if (inOther.has(id)) return;
          if (inThis.has(id)) (isHelper ? removeHelperFromStep : removeEmpFromStep)(facePick.i, id);
          else (isHelper ? addHelperToStep : addEmpToStep)(facePick.i, id);
        };
        return (
          <div onClick={(e) => { if (e.target === e.currentTarget) setFacePick(null); }}
            style={{position: "fixed", inset: 0, zIndex: 420, background: "rgba(12,10,9,0.62)", backdropFilter: "blur(2px)", display: "flex", alignItems: "center", justifyContent: "center", padding: 18}}>
            <div style={{background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 14, width: "100%", maxWidth: "min(1200px, 96vw)", maxHeight: "92vh", display: "flex", flexDirection: "column", overflow: "hidden", boxShadow: "var(--shadow-lg)"}}>
              <div style={{padding: "13px 16px", borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 10}}>
                <span style={{width: 26, height: 26, borderRadius: "50%", background: tone, color: "white", display: "grid", placeItems: "center", fontSize: 12, fontWeight: 800}}>{facePick.i + 1}</span>
                <b style={{fontSize: 14.5}}>{isHelper ? "🤝 เลือกผู้ช่วย" : "👷 เลือกพนักงานที่ทำงาน"} · ลำดับที่ {facePick.i + 1}</b>
                <button className="icon-btn" style={{marginLeft: "auto"}} onClick={() => setFacePick(null)}><I.close size={14}/></button>
              </div>
              <div style={{padding: "10px 16px 0"}}>
                <input className="input" placeholder="🔍 ค้นหาชื่อ / ตำแหน่ง…" value={faceQ} onChange={(e) => setFaceQ(e.target.value)} autoFocus/>
              </div>
              <div style={{padding: 16, overflowY: "auto", display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(112px, 1fr))", gap: 12}}>
                {list.map(e => {
                  const sel = inThis.has(e.id);
                  const busy = inOther.has(e.id);
                  return (
                    <button key={e.id} type="button" onClick={() => toggle(e.id)}
                      title={busy ? `${e.name} — อยู่ในบทบาท${isHelper ? "พนักงานหลัก" : "ผู้ช่วย"}ของลำดับนี้แล้ว` : `${e.name} — ${e.role || ""}`}
                      style={{position: "relative", display: "flex", flexDirection: "column", alignItems: "center", gap: 5, padding: "10px 4px 8px", borderRadius: 12, cursor: busy ? "not-allowed" : "pointer", opacity: busy ? 0.32 : 1,
                        border: sel ? `2px solid ${tone}` : "1px solid var(--line)", background: sel ? "color-mix(in srgb, " + (isHelper ? "#7c3aed" : "var(--accent)") + " 9%, var(--surface))" : "var(--surface)"}}>
                      <FaceAvatar e={e} size={72} ring={sel ? tone : undefined}/>
                      <span style={{fontSize: 13, fontWeight: 700, maxWidth: 106, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap"}}>{nickOf(e.name)}</span>
                      <span style={{fontSize: 10.5, color: "var(--ink-4)", maxWidth: 106, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap"}}>{e.role || ""}</span>
                      {sel && <span style={{position: "absolute", top: 6, right: 6, width: 22, height: 22, borderRadius: "50%", background: tone, color: "white", display: "grid", placeItems: "center", fontSize: 11, fontWeight: 800}}>✓</span>}
                    </button>
                  );
                })}
                {list.length === 0 && <div style={{gridColumn: "1 / -1", textAlign: "center", color: "var(--ink-4)", fontSize: 12.5, padding: 14}}>ไม่พบพนักงานที่ค้นหา</div>}
              </div>
              <div style={{padding: "11px 16px", borderTop: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 10, background: "var(--surface-2)"}}>
                <span style={{fontSize: 12.5, color: "var(--ink-3)"}}>เลือกแล้ว <b style={{color: tone}}>{inThis.size}</b> คน · กดรูปเพื่อเลือก/เอาออก</span>
                <div style={{marginLeft: "auto"}}><Btn kind="primary" onClick={() => setFacePick(null)}>เสร็จ</Btn></div>
              </div>
            </div>
          </div>
        );
      })()}
    </Drawer>
  );
}

// ---------- Job summary (ใบสรุปงาน: ค่าแรง + ค่าวัสดุ) ----------
function printJobSummaryDoc(project) {
  const D = window.SSSData;
  const C = D.Company;
  const preparer = (window.Session && window.Session.get && window.Session.get() && window.Session.get().name) || (C.currentUser && C.currentUser.name) || "";
  const accent = "#0f766e";
  const fmt = (n) => Math.round(n).toLocaleString();

  // ===== ต้นทุนจริงทั้งหมด — มาจากค่าใช้จ่ายที่ผูกกับงานนี้จริงเท่านั้น (ไม่มีสูตรสมมติ) =====
  const isMatCat = (c) => /วัสดุ|วัตถุดิบ/.test(c || "");
  const extraExpenses = pxGet(project.code).expenses || [];
  const linkedExtra = pxGet(project.code).linkedExpenses || [];
  const linkedExp = (D.Expenses || []).filter(e => (project.expenseLinks || []).includes(e.no) || linkedExtra.includes(e.no));
  const rows = [
    ...extraExpenses.map(e => ({ cat: e.category || "อื่น ๆ", detail: e.vendor || "—", note: (e.sku ? `${e.used != null ? e.used : e.qty} ${e.unit || ""}`.trim() : ""), amount: +e.amount || 0 })),
    ...linkedExp.map(e => ({ cat: e.category || "อื่น ๆ", detail: `${e.vendor || "—"} (${e.no})`, note: "", amount: +e.amount || 0 }))
  ];
  const matRows = rows.filter(r => isMatCat(r.cat));
  const laborRows = rows.filter(r => !isMatCat(r.cat));
  const matSum = matRows.reduce((s, r) => s + r.amount, 0);
  const laborSum = laborRows.reduce((s, r) => s + r.amount, 0);
  const totalCost = matSum + laborSum;

  const profit = project.budget - totalCost;
  const margin = project.budget > 0 ? ((profit / project.budget) * 100).toFixed(1) : "0.0";
  const emptyRow = '<tr><td colspan="3" style="padding:10px;text-align:center;color:#a8a29e;font-size:11.5px">— ยังไม่มีรายการที่ผูกกับงานนี้ —</td></tr>';
  const esc = (s) => String(s == null ? "" : s).replace(/</g, "&lt;");

  const rowStyle = 'style="padding:8px 10px;border-bottom:1px solid #f1efeb;font-size:12px"';
  const rowR = 'style="padding:8px 10px;border-bottom:1px solid #f1efeb;font-size:12px;text-align:right;font-variant-numeric:tabular-nums"';

  const html = `<div style="background:white;color:#0c0a09;padding:36px 44px;min-height:90vh">
    ${pxDocHeader(C, "ใบสรุปงาน", "JOB SUMMARY · สรุปค่าแรงและค่าวัสดุ", accent)}
    <div style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:10px;margin-bottom:18px;font-size:12.5px">
      <div style="border:1px solid #e7e5e4;border-radius:6px;padding:10px 14px">
        <div style="font-size:10px;color:#78716c;margin-bottom:2px">โปรเจกต์</div>
        <div style="font-weight:600">${project.name}</div>
        <div style="font-family:monospace;font-size:11px;color:#57534e">${project.code} · ${project.customer}</div>
      </div>
      <div style="border:1px solid #e7e5e4;border-radius:6px;padding:10px 14px">
        <div style="font-size:10px;color:#78716c;margin-bottom:2px">ระยะเวลา</div>
        <div style="font-weight:600">${pxThaiDate(project.start)} → ${pxThaiDate(project.end)}</div>
        <div style="font-size:11px;color:#57534e">หัวหน้างาน ${project.lead}</div>
      </div>
      <div style="border:1px solid #e7e5e4;border-radius:6px;padding:10px 14px">
        <div style="font-size:10px;color:#78716c;margin-bottom:2px">สถานะ · ความคืบหน้า</div>
        <div style="font-weight:600">${project.status} · ${project.progress}%</div>
        <div style="font-size:11px;color:#57534e">จัดทำเมื่อ ${pxToday()}</div>
      </div>
    </div>

    <div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;color:#44403c;margin-bottom:6px">1. ค่าแรง / บริการภายนอก</div>
    <table style="width:100%;border-collapse:collapse;margin-bottom:16px">
      <tr style="background:${accent};color:white"><th style="padding:7px 10px;font-size:10.5px;text-align:left">หมวด</th><th style="padding:7px 10px;font-size:10.5px;text-align:left">รายละเอียด</th><th style="padding:7px 10px;font-size:10.5px;text-align:right">จำนวนเงิน (บาท)</th></tr>
      ${laborRows.length ? laborRows.map(r => `<tr><td ${rowStyle}>${esc(r.cat)}</td><td ${rowStyle}>${esc(r.detail)}</td><td ${rowR}>${fmt(r.amount)}</td></tr>`).join("") : emptyRow}
      <tr style="background:#f3f1ee;font-weight:700"><td colspan="2" style="padding:9px 10px;font-size:12px">รวมค่าแรง / บริการภายนอก</td><td style="padding:9px 10px;font-size:12.5px;text-align:right">${fmt(laborSum)}</td></tr>
    </table>

    <div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;color:#44403c;margin-bottom:6px">2. ค่าวัสดุ</div>
    <table style="width:100%;border-collapse:collapse;margin-bottom:16px">
      <tr style="background:${accent};color:white"><th style="padding:7px 10px;font-size:10.5px;text-align:left">หมวด</th><th style="padding:7px 10px;font-size:10.5px;text-align:left">รายละเอียด</th><th style="padding:7px 10px;font-size:10.5px;text-align:right">จำนวนเงิน (บาท)</th></tr>
      ${matRows.length ? matRows.map(r => `<tr><td ${rowStyle}>${esc(r.cat)}</td><td ${rowStyle}>${esc(r.detail)}${r.note ? ` · ${esc(r.note)}` : ""}</td><td ${rowR}>${fmt(r.amount)}</td></tr>`).join("") : emptyRow}
      <tr style="background:#f3f1ee;font-weight:700"><td colspan="2" style="padding:9px 10px;font-size:12px">รวมค่าวัสดุ</td><td style="padding:9px 10px;font-size:12.5px;text-align:right">${fmt(matSum)}</td></tr>
    </table>

    <div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;color:#44403c;margin-bottom:6px">3. สรุปต้นทุนและกำไร</div>
    <table style="width:100%;border-collapse:collapse;margin-bottom:24px;font-size:12.5px">
      <tr><td style="padding:7px 10px;color:#57534e">รวมค่าแรง / บริการภายนอก</td><td style="padding:7px 10px;text-align:right;font-variant-numeric:tabular-nums">${fmt(laborSum)}</td></tr>
      <tr><td style="padding:7px 10px;color:#57534e">รวมค่าวัสดุ</td><td style="padding:7px 10px;text-align:right;font-variant-numeric:tabular-nums">${fmt(matSum)}</td></tr>
      <tr style="border-top:1px solid #d6d3d1"><td style="padding:8px 10px;font-weight:600">ต้นทุนรวมทั้งหมด</td><td style="padding:8px 10px;text-align:right;font-weight:700">${fmt(totalCost)}</td></tr>
      <tr><td style="padding:7px 10px;color:#57534e">มูลค่างาน (Revenue)</td><td style="padding:7px 10px;text-align:right;font-variant-numeric:tabular-nums">${fmt(project.budget)}</td></tr>
      <tr style="border-top:2px solid ${accent};background:${accent}0d"><td style="padding:10px;font-weight:700;font-size:13.5px">กำไรขั้นต้น (Margin ${margin}%)</td><td style="padding:10px;text-align:right;font-weight:700;font-size:15px;color:${profit >= 0 ? "#15803d" : "#b91c1c"}">฿${fmt(profit)}</td></tr>
    </table>

    <div style="display:grid;grid-template-columns:1fr 1fr;gap:40px;font-size:11px">
      <div style="text-align:center"><div style="border-top:1px dashed #57534e;padding-top:6px;margin-top:40px"><b>ผู้จัดทำ</b><div style="color:#78716c;font-size:10px;margin-top:2px">${preparer ? "( " + preparer + " ) · " : ""}${pxToday()}</div></div></div>
      <div style="text-align:center"><div style="border-top:1px dashed #57534e;padding-top:6px;margin-top:40px"><b>ผู้อนุมัติ</b><div style="color:#78716c;font-size:10px;margin-top:2px">( ............................ ) · ............................</div></div></div>
    </div>
    <div style="margin-top:30px;padding-top:8px;border-top:2px solid ${accent};font-size:9.5px;color:#78716c;display:flex;justify-content:space-between">
      <span>${C.name}</span><span>${project.code} · ใบสรุปงาน · ออกโดยระบบ SSS</span>
    </div>
  </div>`;
  pxPrint(`ใบสรุปงาน — ${project.code}`, html);
  window.logActivity && window.logActivity("ออกใบสรุปงาน", `${project.code} — ${project.name}`, "create");
}

// ---------- Create / Edit project ----------
function pxNextJobCode() {
  const D = window.SSSData;
  const nums = [...pxUserLoad(), ...(D.Projects || [])]
    .map(p => (String(p.code).match(/JOB-2569-(\d+)/) || [])[1])
    .filter(Boolean).map(Number);
  const n = (nums.length ? Math.max(...nums) : 0) + 1;
  return "JOB-2569-" + String(n).padStart(3, "0");
}

function ProjectFormModal({ project, onClose, onSaved }) {
  const D = window.SSSData;
  const { Btn, Drawer } = window.UI;
  const isEdit = !!project;
  const emps = (D.Employees || []).filter(e => /ผลิต|ออกแบบ/.test(e.dept || "") || true);
  const [f, setF] = useStatePX(() => isEdit ? { ...project } : {
    code: pxNextJobCode(), name: "", customer: "", budget: 0, actual: 0,
    progress: 0, status: "ร่าง", lead: emps[0]?.name || "",
    start: pxTodayISO(), end: pxTodayISO()
  });
  const upd = (k, v) => setF(p => ({ ...p, [k]: v }));

  const save = (thenWorkOrder) => {
    if (!f.name.trim()) { window.toast && window.toast("กรุณาใส่ชื่อโปรเจกต์"); return; }
    if (!f.customer.trim()) { window.toast && window.toast("กรุณาใส่ชื่อลูกค้า"); return; }
    const due = pxThaiDate(f.end);
    const record = { ...f, name: f.name.trim(), customer: f.customer.trim(), budget: +f.budget || 0, due };
    if (isEdit) {
      const users = pxUserLoad();
      if (users.some(u => u.code === record.code)) {
        pxUserSave(users.map(u => u.code === record.code ? { ...u, ...record } : u));
      } else {
        pxSet(record.code, { fields: { name: record.name, customer: record.customer, budget: record.budget, lead: record.lead, start: record.start, end: record.end, due } });
      }
      window.toast && window.toast("บันทึกการแก้ไขโปรเจกต์แล้ว");
    } else {
      record.updates = [{ date: pxTodayISO(), who: (window.Session && window.Session.get() && window.Session.get().name) || (window.SSSData.Company.currentUser && window.SSSData.Company.currentUser.name) || "", text: "สร้างโปรเจกต์", type: "milestone" }];
      record.expenseLinks = [];
      pxUserSave([record, ...pxUserLoad()]);
      window.logActivity && window.logActivity("สร้างโปรเจกต์", `${record.code} — ${record.name}`, "create");
      window.toast && window.toast(`สร้างโปรเจกต์ ${record.code} แล้ว`);
    }
    onSaved && onSaved(record, thenWorkOrder);
    onClose();
  };

  return (
    <Drawer open={true} onClose={onClose} title={isEdit ? `แก้ไขโปรเจกต์ · ${f.code}` : "สร้างโปรเจกต์ใหม่"} wide
      footer={<>
        <Btn kind="ghost" onClick={onClose}>ยกเลิก</Btn>
        {!isEdit && <Btn kind="ghost" icon={I.doc} onClick={() => save(true)}>บันทึก + ออกใบสั่งงาน</Btn>}
        <Btn kind="primary" icon={I.check} onClick={() => save(false)}>{isEdit ? "บันทึกการแก้ไข" : "บันทึกโปรเจกต์"}</Btn>
      </>}>
      <div style={{display: "flex", flexDirection: "column", gap: 12}}>
        <div className="field-row cols-2">
          <div className="field"><label>รหัสโปรเจกต์</label><input className="input mono" value={f.code} readOnly style={{background: "var(--surface-2)"}}></input></div>
          <div className="field"><label>หัวหน้างาน</label>
            <select className="input" value={f.lead} onChange={e => upd("lead", e.target.value)}>
              {emps.map(e => <option key={e.id} value={e.name}>{e.name} — {e.role}</option>)}
            </select>
          </div>
        </div>
        <div className="field"><label>ชื่อโปรเจกต์ *</label><input className="input" value={f.name} onChange={e => upd("name", e.target.value)} placeholder="เช่น ราวบันไดสแตนเลส โครงการ..."></input></div>
        <div className="field"><label>ลูกค้า *</label><input className="input" value={f.customer} onChange={e => upd("customer", e.target.value)} placeholder="ชื่อบริษัทลูกค้า"></input></div>
        <div className="field-row cols-2">
          <div className="field"><label>มูลค่างาน / งบประมาณ (บาท)</label><input className="input" type="number" value={f.budget} onChange={e => upd("budget", +e.target.value)}></input></div>
          <div className="field"><label>สถานะเริ่มต้น</label>
            <select className="input" value={f.status} onChange={e => upd("status", e.target.value)}>
              {["ร่าง", "ออกแบบ 3D", "กำลังผลิต", "ตรวจสอบคุณภาพ", "รอเก็บเงิน", "ส่งงาน"].map(s => <option key={s}>{s}</option>)}
            </select>
          </div>
        </div>
        <div className="field-row cols-2">
          <div className="field"><label>วันที่เริ่ม</label><input className="input" type="date" value={f.start} onChange={e => upd("start", e.target.value)}></input></div>
          <div className="field"><label>กำหนดส่ง</label><input className="input" type="date" value={f.end} onChange={e => upd("end", e.target.value)}></input></div>
        </div>
        {!isEdit && (
          <div style={{padding: "10px 14px", background: "var(--brand-bg, var(--surface-2))", border: "1px solid var(--line)", borderRadius: 8, fontSize: 12, color: "var(--ink-3)"}}>
            หลังบันทึก สามารถ <b style={{color: "var(--ink-1)"}}>ออกใบสั่งงาน</b> เพื่อกำหนดผู้รับผิดชอบ รายละเอียดงาน และกำหนดส่งได้ทันที
          </div>
        )}
      </div>
    </Drawer>
  );
}

// ---------- Add expense (หลายรายการ + แหล่ง: คลัง / รอซื้อ / กรอกเอง) ----------
function ProjectExpenseModal({ project, onClose, onSaved }) {
  const { Btn, fmt0, ProductAutocomplete } = window.UI;
  const D = window.SSSData;
  const PR = window.PurchaseRequests;
  const [category, setCategory] = useStatePX("ค่าวัสดุ/วัตถุดิบ");
  const [vendor, setVendor] = useStatePX("");
  const [showPRAdd, setShowPRAdd] = useStatePX(false);
  const pendingPRs = PR ? PR.pending(project.code) : [];
  const blankLine = () => ({ source: "stock", name: "", sku: "", unit: "ชิ้น", cost: 0, qty: 1, amount: "", prId: "" });
  const [lines, setLines] = useStatePX(() => [blankLine()]);
  const updLine = (i, patch) => setLines(ls => ls.map((l, j) => j === i ? { ...l, ...patch } : l));
  const removeLine = (i) => setLines(ls => ls.length > 1 ? ls.filter((_, j) => j !== i) : ls);
  const lineAmount = (l) => (l.amount !== "" && l.amount != null) ? (+l.amount || 0) : Math.round((l.cost || 0) * (Number(l.qty) || 0));
  const total = lines.reduce((s, l) => s + lineAmount(l), 0);

  const pickStock = (i, p) => updLine(i, { name: p.name, sku: p.sku, unit: p.unit || "หน่วย", cost: p.cost != null ? p.cost : (p.price || 0), amount: "" });
  const pickPR = (i, prId) => {
    const r = pendingPRs.find(x => x.id === prId);
    if (!r) { updLine(i, { prId: "", name: "" }); return; }
    updLine(i, { prId, name: r.name, unit: r.unit || "ชิ้น", qty: r.qty || 1, amount: r.est || "", cost: 0, vendor: r.vendor });
  };
  const setSource = (i, source) => updLine(i, { source, name: "", sku: "", prId: "", amount: "", qty: 1, cost: 0 });

  const save = () => {
    const valid = lines.filter(l => (l.name || "").trim() && lineAmount(l) > 0);
    if (!valid.length) { window.toast && window.toast("กรอกอย่างน้อย 1 รายการ พร้อมจำนวนเงิน"); return; }
    const amount = valid.reduce((s, l) => s + lineAmount(l), 0);
    const expId = "PE-" + Date.now();
    const exp = {
      id: expId, date: pxToday(), category,
      vendor: vendor.trim() || (valid.length > 1 ? `${valid.length} รายการ` : valid[0].name.trim()),
      amount, status: "บันทึกแล้ว", by: window.actorName ? window.actorName() : "",
      lines: valid.map(l => ({ source: l.source, name: l.name.trim(), sku: l.sku || "", unit: l.unit, qty: Number(l.qty) || 0, amount: lineAmount(l), prId: l.prId || "" }))
    };
    const list = [...(pxGet(project.code).expenses || []), exp];
    pxSet(project.code, { expenses: list });
    // คลัง: ตัดสต็อก + ลงการเคลื่อนไหว
    const now = new Date().toLocaleString("th-TH", { day: "2-digit", month: "2-digit", year: "2-digit", hour: "2-digit", minute: "2-digit" });
    let stockTouched = false;
    valid.forEach(l => {
      if (l.source === "stock" && l.sku && (Number(l.qty) || 0) > 0) {
        const prod = (D.Products || []).find(p => p.sku === l.sku);
        if (prod) {
          prod.stock = Math.max(0, Math.round(((Number(prod.stock) || 0) - (Number(l.qty) || 0)) * 100) / 100);
          D.InventoryMoves = D.InventoryMoves || [];
          D.InventoryMoves.unshift({ date: now, type: "เบิกใช้งาน", sku: l.sku, name: l.name, qty: -(Number(l.qty) || 0), ref: project.code, by: exp.by });
          stockTouched = true;
        }
      }
    });
    if (stockTouched) { window.dispatchEvent(new Event("sss-products-changed")); window.dispatchEvent(new Event("sss-stock-changed")); }
    // รอซื้อ: ทำเครื่องหมายซื้อแล้ว + ผูกกับค่าใช้จ่ายนี้
    const prIds = valid.filter(l => l.source === "request" && l.prId).map(l => l.prId);
    if (prIds.length && PR) PR.markBought(prIds, expId);
    window.toast && window.toast(`เพิ่มค่าใช้จ่าย ฿${amount.toLocaleString()} (${valid.length} รายการ) แล้ว`);
    onSaved && onSaved(list);
    onClose();
  };

  const srcBtn = (i, v, label) => (
    <button type="button" className={"payseg-btn" + (lines[i].source === v ? " active" : "")} style={{flex: 1, padding: "6px 4px", fontSize: 11.5}} onClick={() => setSource(i, v)}>{label}</button>
  );

  return (
    <>
      <div className="scrim" style={{zIndex: 80}} onClick={onClose}></div>
      <div style={{position: "fixed", left: "50%", top: "50%", transform: "translate(-50%, -50%)", zIndex: 81, background: "var(--surface)", border: "1px solid var(--line)", borderRadius: 12, padding: 18, width: 520, maxWidth: "96vw", maxHeight: "94vh", overflow: "auto", boxShadow: "var(--shadow-lg)"}}>
        <div style={{fontSize: 14.5, fontWeight: 700, marginBottom: 12}}>เพิ่มค่าใช้จ่าย · {project.code}</div>
        <div className="field-row" style={{display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, marginBottom: 12}}>
          <div className="field"><label>หมวด</label>
            <select className="input" value={category} onChange={e => setCategory(e.target.value)}>
              {["ค่าวัสดุ/วัตถุดิบ", "ค่าแรงภายนอก", "ค่าขนส่ง", "ค่าเครื่องมือ/อุปกรณ์", "อื่น ๆ"].map(c => <option key={c}>{c}</option>)}
            </select>
          </div>
          <div className="field"><label>ผู้ขาย / ซื้อจาก</label><input className="input" value={vendor} onChange={e => setVendor(e.target.value)} placeholder="เช่น ร้านวัสดุสามพราน"/></div>
        </div>

        <div className="label mb">รายการ ({lines.length}) — ออกใบค่าใช้จ่ายใบเดียวได้หลายรายการ</div>
        <div style={{display: "flex", flexDirection: "column", gap: 10}}>
          {lines.map((l, i) => (
            <div key={i} style={{border: "1px solid var(--line)", borderRadius: 10, padding: 10, background: "var(--surface-2)"}}>
              <div style={{display: "flex", gap: 8, alignItems: "center", marginBottom: 8}}>
                <div className="payseg" style={{display: "flex", flex: 1}}>
                  {srcBtn(i, "stock", "ดึงจากคลัง")}
                  {srcBtn(i, "request", "จากรอซื้อ")}
                  {srcBtn(i, "manual", "กรอกเอง")}
                </div>
                <button className="icon-btn" title="ลบรายการ" onClick={() => removeLine(i)}><I.trash size={13}/></button>
              </div>

              {l.source === "stock" && (
                <ProductAutocomplete value={l.name} placeholder="พิมพ์ค้นหาสินค้าในคลัง"
                  onChange={v => updLine(i, { name: v, sku: "" })} onPick={p => pickStock(i, p)}/>
              )}
              {l.source === "request" && (
                <div>
                  {pendingPRs.length === 0
                    ? <div style={{fontSize: 12, color: "var(--ink-3)", padding: "6px 2px"}}>ยังไม่มีรายการรอซื้อของโปรเจกต์นี้</div>
                    : <select className="input" value={l.prId} onChange={e => pickPR(i, e.target.value)}>
                        <option value="">— เลือกจากรายการรอซื้อ —</option>
                        {pendingPRs.map(r => <option key={r.id} value={r.id}>{r.name} · {r.qty} {r.unit}{r.vendor ? " · " + r.vendor : ""}</option>)}
                      </select>}
                  <button type="button" className="btn sm" style={{marginTop: 6}} onClick={() => setShowPRAdd(true)}>+ เพิ่มรายการรอซื้อใหม่</button>
                </div>
              )}
              {l.source === "manual" && (
                <input className="input" value={l.name} onChange={e => updLine(i, { name: e.target.value })} placeholder="รายละเอียดค่าใช้จ่าย"/>
              )}

              <div className="field-row" style={{display: "grid", gridTemplateColumns: "1fr 1fr 1.2fr", gap: 8, marginTop: 8}}>
                <div className="field"><label>จำนวน</label><input className="input" type="number" min="0" step="any" value={l.qty} onChange={e => updLine(i, { qty: e.target.value, amount: l.source === "stock" ? "" : l.amount })}/></div>
                <div className="field"><label>หน่วย</label><input className="input" value={l.unit} onChange={e => updLine(i, { unit: e.target.value })}/></div>
                <div className="field"><label>จำนวนเงิน (บาท)</label><input className="input" type="number" min="0" value={l.amount !== "" ? l.amount : (lineAmount(l) || 0)} onChange={e => updLine(i, { amount: e.target.value })}/></div>
              </div>
              {l.source === "stock" && l.sku && <div style={{fontSize: 11, color: "var(--ink-3)", marginTop: 4}}>ทุน ฿{fmt0(l.cost)}/{l.unit} · บันทึกแล้วจะตัดสต็อก {l.qty} {l.unit}</div>}
            </div>
          ))}
        </div>
        <div style={{marginTop: 8}}>
          <Btn size="sm" icon={I.plus} onClick={() => setLines(ls => [...ls, blankLine()])}>เพิ่มรายการ</Btn>
        </div>

        <div style={{display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 14, paddingTop: 12, borderTop: "1px solid var(--line)"}}>
          <div style={{fontSize: 13}}>รวมทั้งสิ้น <b style={{fontSize: 16, color: "var(--accent)"}}>฿{fmt0(total)}</b></div>
          <div style={{display: "flex", gap: 8}}>
            <Btn kind="ghost" onClick={onClose}>ยกเลิก</Btn>
            <Btn kind="primary" icon={I.check} onClick={save}>บันทึกใบค่าใช้จ่าย</Btn>
          </div>
        </div>
      </div>
      {showPRAdd && window.PREditModal && (
        <window.PREditModal
          initial={{ name: "", qty: 1, unit: "ชิ้น", vendor: vendor || "", projectCode: project.code, est: "", note: "" }}
          onClose={() => setShowPRAdd(false)}
          onSave={(rec) => { window.PurchaseRequests.add({ ...rec, projectCode: project.code, projectName: project.name }); window.toast && window.toast(`เพิ่ม “${rec.name}” เข้ารายการรอซื้อแล้ว`); }}/>
      )}
    </>
  );
}

window.ProjX = { pxGet, pxSet, upLoad: pxUserLoad, upSave: pxUserSave, pxThaiDate, pxToday, pxTodayISO, printWorkOrderDoc, printJobSummaryDoc };
window.WorkOrderModal = WorkOrderModal;
window.ProjectFormModal = ProjectFormModal;
window.ProjectExpenseModal = ProjectExpenseModal;
