// ---- Shared small components ----

const Tag = ({ pillar, children }) => (
  <span className={`tag ${pillar}`}>{children}</span>
);

// stable color hash per instructor name
const nameHash = (s) => {
  let h = 0;
  for (let i=0;i<s.length;i++) h = (h*31 + s.charCodeAt(i)) >>> 0;
  return h % 10;
};
const initial = (name) => {
  // strip role prefix "ผจก.", "ผจก", "ผอ.", "ผอ" etc, take first char of given-name
  const stripped = name.replace(/^(ผจก\.?|ผอ\.?|คุณ|พี่)\s*/, "").trim();
  return stripped.charAt(0);
};
const roleOf = (name) => {
  if (/^ผอ\.?\s*/.test(name)) return "ผู้อำนวยการ";
  if (/^ผจก\.?\s*/.test(name)) return "ผู้จัดการ";
  return "ทีมผู้สอน";
};

// Map normalized names → photo file. Keys are without "ผจก./ผอ./พี่" prefix.
const PHOTO_MAP = {
  "เอ๊ะ": "photos/eh.png",
  "เจต": "photos/toi.png",
  "ต้อย": "photos/toy.png",
  "กระติ๊ก": "photos/kratik.png",
  "กระติก": "photos/kratik.png", // alt spelling
  "บอย": "photos/boy.jpg",
  "อ๋อย": "photos/oy.png",
  "กุ๊ก": "photos/kook.png",
  "นุช": "photos/nuch.png",
  "ธัช": "photos/thach.png",
  "เพ็ชร": "photos/kanpetch.png",
  "พันเอก": "photos/panek.png",
  "แอม": "photos/am.jpg",
  "กุ๊กกิ๊ก": "photos/kookkik.jpg",
  "ลักกี้": "photos/lucky.png",
  "ชัย": "photos/chai.png",
  "แคท": "photos/cat.png",
};
const normName = (name) => name.replace(/^(ผจก\.?|ผอ\.?|คุณ)\s*/, "").replace(/^พี่\s*/, "").trim();
const photoOf = (name) => PHOTO_MAP[normName(name)] || null;

// ชื่อ activity ที่นับเป็น "ชวนตัวแทนใหม่" (recruit) — ใช้ร่วมกันทั้งฝั่ง agent/admin/landing
const RECRUIT_ACTIVITY_NAMES = ["Recruit", "ตัวแทนชวนตัวแทน"];

const MEMBERSHIP_DAYS = 90;
const PAYMENT_SLIP_FOLDER = "payment-slips";
const DAY_MS = 24 * 60 * 60 * 1000;
const membershipExpiresAt = (createdAt) => {
  const registeredAt = new Date(createdAt);
  if (!createdAt || Number.isNaN(registeredAt.getTime())) return null;
  return new Date(registeredAt.getTime() + MEMBERSHIP_DAYS * DAY_MS);
};
const membershipEndDate = (createdAt) => {
  const expiresAt = membershipExpiresAt(createdAt);
  return expiresAt ? expiresAt.toISOString().slice(0, 10) : null;
};
const membershipExpired = (createdAt, now = Date.now()) => {
  const expiresAt = membershipExpiresAt(createdAt);
  return !!expiresAt && now >= expiresAt.getTime();
};
// จำนวนวันที่เหลือก่อนสมาชิกหมดอายุ (นับถอยหลัง) — null ถ้าวันที่ไม่ถูกต้อง, 0 ถ้าหมดแล้ว
const membershipDaysLeft = (createdAt, now = Date.now()) => {
  const expiresAt = membershipExpiresAt(createdAt);
  if (!expiresAt) return null;
  return Math.max(0, Math.ceil((expiresAt.getTime() - now) / DAY_MS));
};
const paymentSlipPath = (agentId) => `${PAYMENT_SLIP_FOLDER}/${agentId}`;
const paymentSlipSrc = (agentId, bust = "") =>
  `${window.SUPABASE_URL}/storage/v1/object/public/avatars/${paymentSlipPath(agentId)}${bust ? `?bust=${bust}` : ""}`;

// One-time scroll-hint: gently rocks the bottom nav left/right on mobile so
// users discover that the menu scrolls. Skips after the first run per browser
// and respects prefers-reduced-motion.
const NAV_HINT_KEY = `${window.TENANT.prefix}.navHintShown`;
const useNavScrollHint = (ref) => {
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (localStorage.getItem(NAV_HINT_KEY)) return;
    if (window.matchMedia("(min-width: 761px)").matches) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    let cancelled = false;
    const wait = (ms) => new Promise(r => setTimeout(r, ms));
    const run = async () => {
      await wait(600);
      if (cancelled) return;
      if (el.scrollWidth <= el.clientWidth + 4) return;
      el.scrollTo({ left: 90, behavior: "smooth" });
      await wait(550);
      if (cancelled) return;
      el.scrollTo({ left: 0, behavior: "smooth" });
      await wait(550);
      if (cancelled) return;
      localStorage.setItem(NAV_HINT_KEY, "1");
    };
    run();
    return () => { cancelled = true; };
  }, [ref]);
};

const Avatar = ({ name, size }) => {
  const photo = photoOf(name);
  return (
    <span className="avatar">
      {photo
        ? <span className={`avatar-circle ${size||""} has-photo`} style={{backgroundImage:`url("${photo}")`}}></span>
        : <span className={`avatar-circle ${size||""} avatar-c${nameHash(name)}`}>{initial(name)}</span>}
      <span className="avatar-name">{name}</span>
    </span>
  );
};

const Eyebrow = ({ children }) => <p className="eyebrow">{children}</p>;

const SectionHead = ({ eyebrow, title, sub, right }) => (
  <header className="section-head">
    <div>
      {eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}
      <h2 className="section-title">{title}</h2>
      {sub && <p className="section-sub">{sub}</p>}
    </div>
    {right}
  </header>
);

// ---- ปุ่มพิมพ์ / PDF ประจำหน้า (วางในช่อง right ของ SectionHead) ----
// กดแล้วเปิดหน้าต่างพิมพ์ของเบราว์เซอร์ (เลือกเครื่องพิมพ์ = กระดาษ, "Save as PDF" = ไฟล์ PDF)
// ตอนพิมพ์ CSS (@media print + body.printing-page) จะจัดหน้าเป็นรายงาน A4: ซ่อน sidebar/ปุ่ม/ตัวกรอง
// แล้วโชว์หัวรายงานมาตรฐาน (.print-head) แทนหัวข้อบนจอ — pattern เดียวกับปุ่มพิมพ์ของ MoneyNeedsForm
// props: title = ชื่อรายงานบนหัวกระดาษ · meta = บรรทัดกำกับ (ช่วงข้อมูล/ขอบเขต ตามตัวกรองที่เลือกอยู่)
//        beforePrint = เตรียมหน้าก่อนพิมพ์ (เช่น กางรายการที่หุบ) คืน fn ไว้คืนสภาพหลังพิมพ์
const PagePrintButton = ({ title, meta = [], beforePrint }) => {
  const ref = React.useRef(null);
  const handlePrint = () => {
    const scene = ref.current ? ref.current.closest(".scene") : null;
    if (!scene) return;
    const restore = beforePrint ? (beforePrint() || null) : null;
    scene.classList.add("print-target");
    document.body.classList.add("printing-page");
    const cleanup = () => {
      scene.classList.remove("print-target");
      document.body.classList.remove("printing-page");
      if (typeof restore === "function") restore();
      window.removeEventListener("afterprint", cleanup);
    };
    window.addEventListener("afterprint", cleanup);
    // หน่วงหนึ่งจังหวะให้ React render ส่วนที่ beforePrint กางออกก่อน ค่อยเปิดหน้าต่างพิมพ์
    setTimeout(() => window.print(), 150);
  };
  const today = new Date().toLocaleDateString("th-TH", { day: "numeric", month: "short", year: "numeric" });
  return (
    <>
      <button type="button" className="btn-edit page-print-btn no-print" onClick={handlePrint} ref={ref}>
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
          <path d="M6 9V3h12v6M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" />
          <rect x="6" y="14" width="12" height="8" rx="1" />
        </svg>
        พิมพ์ / PDF
      </button>
      {/* หัว/ท้ายรายงาน — โชว์เฉพาะตอนพิมพ์ (display:none บนจอ) */}
      <div className="print-head" aria-hidden="true">
        <div className="print-head-id">
          <span className="print-head-brand">{document.title || "Admin Panel"}</span>
          <strong className="print-head-title">{title}</strong>
        </div>
        <div className="print-head-meta">
          {meta.filter(Boolean).map((t, i) => <span key={i}>{t}</span>)}
          <span>พิมพ์เมื่อ {today}</span>
        </div>
      </div>
      <div className="print-foot" aria-hidden="true">
        <span>{document.title || "Admin Panel"} — {title}</span>
        <span>พิมพ์เมื่อ {today}</span>
      </div>
    </>
  );
};

const Legend = () => (
  <div className="legend">
    {PILLARS.map(p => (
      <span key={p.key} className="lg">
        <span className="sw" style={{background: `var(--${p.cls})`}}></span>
        <span>{p.key} · {p.th}</span>
      </span>
    ))}
  </div>
);

const KpiCard = ({ label, value, suffix, sub, feature, progress }) => (
  <div className={`kpi ${feature ? "feature" : ""}`}>
    <div className="kpi-label">{label}</div>
    <div className="kpi-value">
      {value}
      {suffix && <em>{suffix}</em>}
    </div>
    <div className="kpi-sub">{sub}</div>
    <div className="kpi-bar"><div className="kpi-bar-fill" style={{width: `${progress ?? 8}%`}}></div></div>
  </div>
);

const HeroDiagram = () => {
  // 4-quadrant RTMS circular diagram
  const center = 230;
  const r = 180;
  const ringR = 130;
  return (
    <svg viewBox="0 0 460 460" aria-hidden="true">
      <defs>
        <radialGradient id="paper" cx="50%" cy="50%" r="60%">
          <stop offset="0%" stopColor="#FBFAF6" />
          <stop offset="100%" stopColor="#EAE9E2" />
        </radialGradient>
      </defs>
      {/* outer faint ring */}
      <circle cx={center} cy={center} r={r+20} fill="none" stroke="#E6E4DE" strokeDasharray="2 6" />
      <circle cx={center} cy={center} r={r} fill="url(#paper)" stroke="#E7C878" strokeWidth="1" />
      {/* quadrant dividers */}
      <line x1={center-r} y1={center} x2={center+r} y2={center} stroke="#E6E4DE" />
      <line x1={center} y1={center-r} x2={center} y2={center+r} stroke="#E6E4DE" />
      {/* quadrant labels */}
      {[
        {k:"R", x:center-90, y:center-90, color:"#EE6C5A", th:"สรรหา"},
        {k:"T", x:center+90, y:center-90, color:"#5EA485", th:"พัฒนา"},
        {k:"S", x:center-90, y:center+90, color:"#4C4A7D", th:"ติดตาม"},
        {k:"M", x:center+90, y:center+90, color:"#C9A94E", th:"จูงใจ"},
      ].map(q => (
        <g key={q.k}>
          <text x={q.x} y={q.y-4} textAnchor="middle" fontFamily="DM Serif Display" fontStyle="italic" fontSize="56" fill={q.color} opacity="0.9">{q.k}</text>
          <text x={q.x} y={q.y+24} textAnchor="middle" fontFamily="Prompt" fontSize="13" fill="#5A5C63" letterSpacing="2">{q.th}</text>
        </g>
      ))}
      {/* center medallion */}
      <circle cx={center} cy={center} r="58" fill="#2C2C2E" />
      <text x={center} y={center-4} textAnchor="middle" fontFamily="DM Serif Display" fontSize="34" fill="#E7C878" fontStyle="italic">83F</text>
      <text x={center} y={center+18} textAnchor="middle" fontFamily="Prompt" fontSize="10" letterSpacing="3" fill="#9CA0A7">2026</text>
    </svg>
  );
};

const LAUNCH_AT = new Date("2026-06-01T00:00:00+07:00").getTime();

const Countdown = () => {
  const [now, setNow] = React.useState(() => Date.now());
  React.useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(id);
  }, []);

  const diff = LAUNCH_AT - now;
  if (diff <= 0) {
    return (
      <div className="countdown countdown-live">
        <div className="countdown-label">เปิดตัวแล้ว</div>
        <div className="countdown-title">แอปพลิเคชัน 83F ใช้งานได้แล้ววันนี้</div>
      </div>
    );
  }

  const days  = Math.floor(diff / 86400000);
  const hours = Math.floor((diff % 86400000) / 3600000);
  const mins  = Math.floor((diff % 3600000) / 60000);
  const secs  = Math.floor((diff % 60000) / 1000);
  const pad = (n) => String(n).padStart(2, "0");

  return (
    <div className="countdown" role="timer" aria-label="นับถอยหลังถึงวันเปิดตัวแอปพลิเคชัน">
      <div className="countdown-label">เปิดตัวแอปพลิเคชัน · 1 มิถุนายน 2026</div>
      <div className="countdown-grid">
        <div className="countdown-unit"><span className="countdown-num">{days}</span><span className="countdown-tag">วัน</span></div>
        <div className="countdown-sep">:</div>
        <div className="countdown-unit"><span className="countdown-num">{pad(hours)}</span><span className="countdown-tag">ชั่วโมง</span></div>
        <div className="countdown-sep">:</div>
        <div className="countdown-unit"><span className="countdown-num">{pad(mins)}</span><span className="countdown-tag">นาที</span></div>
        <div className="countdown-sep">:</div>
        <div className="countdown-unit"><span className="countdown-num">{pad(secs)}</span><span className="countdown-tag">วินาที</span></div>
      </div>
    </div>
  );
};

// ---- เรียนย้อนหลัง: ฝัง YouTube playlist ในหน้า dashboard (ไม่เด้งออก) ----
const CourseReplaySection = () => {
  const url = window.TENANT.courseReplayUrl || "";
  let listId = "";
  try { listId = new URL(url).searchParams.get("list") || ""; } catch (e) { /* malformed url */ }
  const embedSrc = listId ? `https://www.youtube.com/embed/videoseries?list=${listId}` : "";

  const perks = [
    ["▶", "ดูครบทุกบทเรียนในเพลย์ลิสต์เดียว"],
    ["↻", "ดูซ้ำกี่รอบก็ได้ ไม่จำกัด"],
    ["◷", "เรียนได้ทุกที่ทุกเวลา ตามจังหวะของคุณ"],
  ];

  return (
    <section>
      <SectionHead
        eyebrow="คอร์สเรียนย้อนหลัง"
        title="ดูคอร์สเรียนย้อนหลังได้ทุกบท"
        sub="รวมคลิปคอร์สเรียนทั้งหมดไว้ในที่เดียว ดูซ้ำได้ไม่จำกัด"
      />
      {embedSrc ? (
        <div style={{
          display: "flex", flexWrap: "wrap", gap: 18, alignItems: "stretch",
          background: "var(--card)", border: "1px solid var(--line)",
          borderRadius: 16, padding: 16, boxShadow: "var(--shadow-md)",
        }}>
          {/* ── ตัวเล่นวิดีโอ (จำกัดความกว้างไม่ให้เต็มจอ) ── */}
          <div style={{ flex: "1 1 460px", minWidth: 260, maxWidth: 680 }}>
            <div style={{ position: "relative", width: "100%", paddingBottom: "56.25%", borderRadius: 12, overflow: "hidden", background: "#000", boxShadow: "0 4px 16px rgba(0,0,0,.14)" }}>
              <iframe
                src={embedSrc}
                title="คอร์สเรียนย้อนหลัง"
                style={{ position: "absolute", inset: 0, width: "100%", height: "100%", border: 0 }}
                allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen"
                allowFullScreen
              />
            </div>
          </div>
          {/* ── แผงข้อมูล + ปุ่ม (ใช้พื้นที่แนวนอนที่เหลือ) ── */}
          <div style={{ flex: "1 1 240px", minWidth: 220, display: "flex", flexDirection: "column", justifyContent: "center", gap: 14, padding: "4px 6px" }}>
            <div style={{ fontWeight: 700, fontSize: 16, color: "var(--ink)" }}>เพลย์ลิสต์คอร์สเรียนทั้งหมด</div>
            <ul style={{ listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 11 }}>
              {perks.map(([ic, tx]) => (
                <li key={tx} style={{ display: "flex", gap: 10, alignItems: "flex-start", fontSize: 14, lineHeight: 1.4, color: "var(--ink-soft)" }}>
                  <span style={{ flex: "0 0 auto", color: "var(--brand)", fontWeight: 700 }}>{ic}</span>
                  <span>{tx}</span>
                </li>
              ))}
            </ul>
            {url && <a className="link-btn" href={url} target="_blank" rel="noopener noreferrer" style={{ alignSelf: "flex-start", marginTop: 2 }}>เปิดใน YouTube ↗</a>}
          </div>
        </div>
      ) : (
        <div className="card" style={{ padding: 24, textAlign: "center", opacity: .7 }}>
          ยังไม่ได้ตั้งค่า playlist คอร์สเรียนย้อนหลัง
        </div>
      )}
    </section>
  );
};

// อ่าน url รูป branding (โลโก้/แบนเนอร์/โลโก้ panel) จาก window.Branding + re-render เมื่อเปลี่ยน
const useBranding = () => {
  const [b, setB] = React.useState(() =>
    window.Branding ? window.Branding.get() : { logoUrl: null, bannerUrl: null, panelLogoUrl: null });
  React.useEffect(() => {
    if (!window.Branding) return;
    const update = () => setB(window.Branding.get());
    update();
    return window.Branding.subscribe(update);
  }, []);
  return b;
};

// ---- My Money Needs worksheet — ใช้ร่วมทั้งฝั่งตัวแทน (login-app) + manager (admin-app) ----
// prop `agent` = เจ้าของ worksheet (ตัวแทน หรือ manager ที่ล็อกอินอยู่)
const MoneyNeedsForm = ({ agent, readOnly = false, goals = undefined }) => {
  const ro = !!readOnly;
  const rootRef = React.useRef(null);
  const [form, setForm] = React.useState(null); // null = loading
  const [saving, setSaving] = React.useState(false);
  const [saved, setSaved] = React.useState(false);
  const [err, setErr] = React.useState("");

  const buildForm = (data) => {
    const stored = Array.isArray(data?.expense_items) ? data.expense_items : [];
    const rows = stored.map(e => ({ label: e.label || "", amount: e.amount ? String(e.amount) : "" }));
    while (rows.length < MONEY_NEEDS_DEFAULTS.rows) rows.push({ label: "", amount: "" });
    return {
      expense_items: rows,
      savings_target: data?.savings_target ? String(data.savings_target) : "",
      commission_rate: data?.commission_rate != null ? String(data.commission_rate) : String(MONEY_NEEDS_DEFAULTS.commission),
      case_size: data?.case_size != null ? String(data.case_size) : String(MONEY_NEEDS_DEFAULTS.caseSize),
    };
  };

  React.useEffect(() => {
    if (goals !== undefined) { setForm(buildForm(goals)); return; }
    sb.from("agent_goals").select("*").eq("agent_id", agent.id).maybeSingle()
      .then(({ data }) => setForm(buildForm(data)));
  }, [agent.id, goals]);

  const onlyDigits = (s) => String(s ?? "").replace(/\D/g, "");
  const fmt = (v) => v === "" || v == null ? "" : Number(v).toLocaleString("en-US");
  const patch = (changes) => { setForm(f => ({ ...f, ...changes })); setSaved(false); };
  const setRow = (i, key, val) =>
    patch({ expense_items: form.expense_items.map((r, idx) => idx === i ? { ...r, [key]: val } : r) });

  if (!form) return <div className="loading-msg">กำลังโหลด...</div>;

  const calcInput = {
    expense_items: form.expense_items.map(r => ({ label: r.label, amount: Number(onlyDigits(r.amount)) || 0 })),
    savings_target: Number(onlyDigits(form.savings_target)) || 0,
    commission_rate: Number(form.commission_rate) || 0,
    case_size: Number(onlyDigits(form.case_size)) || 0,
  };
  const c = moneyNeedsCalc(calcInput);
  const money = (n) => Math.round(n).toLocaleString("en-US");

  const save = async () => {
    setSaving(true); setErr("");
    const payload = {
      agent_id: agent.id,
      expense_items: calcInput.expense_items.filter(e => e.label || e.amount),
      savings_target: calcInput.savings_target,
      monthly_income: Math.round(c.requiredIncome),
      commission_rate: calcInput.commission_rate,
      case_size: calcInput.case_size,
      updated_at: new Date().toISOString(),
    };
    const { error } = await sb.from("agent_goals").upsert(payload, { onConflict: "agent_id" });
    if (error) { setSaving(false); setErr("บันทึกไม่สำเร็จ: " + error.message); return; }
    // ให้คะแนนทำ My Money Need ครั้งแรกครั้งเดียว (ตัวเลขคะแนนแก้ได้ที่แอดมิน → จัดการกติกา)
    try {
      const noteKey = `money_needs:${agent.id}`;
      const moneyNeedPts = POINT_RULES.money_need;
      const { data: existing } = await sb.from("agent_activities")
        .select("id").eq("agent_id", agent.id).eq("note", noteKey).limit(1);
      if ((!existing || existing.length === 0) && moneyNeedPts > 0) {
        await sb.from("agent_activities").insert([{
          agent_id: agent.id,
          date: new Date().toISOString().slice(0, 10),
          name: "ทำ My Money Need",
          points: moneyNeedPts,
          note: noteKey,
        }]);
      }
    } catch (_) { /* ให้คะแนนล้มเหลวไม่กระทบการบันทึกแผน */ }
    setSaving(false);
    setSaved(true);
    window.scrollTo({ top: 0, behavior: "smooth" });
  };

  const handlePrint = () => {
    const el = rootRef.current;
    if (el) el.classList.add("mn-print-target");
    document.body.classList.add("printing-mn");
    const cleanup = () => {
      if (el) el.classList.remove("mn-print-target");
      document.body.classList.remove("printing-mn");
      window.removeEventListener("afterprint", cleanup);
    };
    window.addEventListener("afterprint", cleanup);
    window.print();
  };

  return (
    <div className={`mn-form${ro ? " mn-readonly" : ""}`} ref={rootRef}>
      <div className="mn-print-actions">
        <button type="button" className="btn-edit mn-print-btn" onClick={handlePrint}>🖨️ พิมพ์ worksheet</button>
      </div>
      <div className="mn-card">
        <div className="mn-card-head">My Money Needs · ความจำเป็นทางการเงิน</div>
        <div className="mn-exp-headrow"><span>รายละเอียดค่าใช้จ่าย</span><span>ค่าใช้จ่าย / เดือน</span></div>
        {form.expense_items.map((r, i) => (
          <div className="mn-exp-row" key={i}>
            <span className="mn-exp-no">{i + 1}.</span>
            <input className="form-input mn-exp-label" placeholder={i === 0 ? "เช่น ค่าผ่อนบ้าน / ค่ากิน / ค่าเทอมลูก" : ""}
              value={r.label} readOnly={ro} onChange={e => setRow(i, "label", e.target.value)} />
            <div className="mn-amt">
              <input className="form-input" inputMode="numeric" placeholder="0"
                value={fmt(onlyDigits(r.amount))} readOnly={ro} onChange={e => setRow(i, "amount", onlyDigits(e.target.value))} />
              <em>฿</em>
            </div>
          </div>
        ))}
        <div className="mn-exp-row mn-exp-savings">
          <span className="mn-exp-no">10.</span>
          <span className="mn-exp-label mn-savings-label">เงินเก็บที่ต้องการ</span>
          <div className="mn-amt">
            <input className="form-input" inputMode="numeric" placeholder="0"
              value={fmt(onlyDigits(form.savings_target))} readOnly={ro} onChange={e => patch({ savings_target: onlyDigits(e.target.value) })} />
            <em>฿</em>
          </div>
        </div>
        <div className="mn-total">
          <span>รวมรายได้ที่ต้องการ / เดือน</span>
          <strong>{money(c.requiredIncome)} ฿</strong>
        </div>
      </div>

      <div className="mn-card">
        <div className="mn-card-head">แปลงเป้าหมายรายได้เป็นกิจกรรมการขาย · Conversion Worksheet</div>
        <div className="mn-conv-row">
          <div className="mn-conv-cell"><span>รายได้/เดือนที่ต้องการ</span><strong>{money(c.requiredIncome)}</strong></div>
          <span className="mn-op">÷</span>
          <label className="mn-conv-cell mn-input-cell"><span>ค่าเฉลี่ย Commission</span>
            <div className="mn-amt"><input className="form-input" inputMode="numeric" value={form.commission_rate}
              readOnly={ro} onChange={e => patch({ commission_rate: onlyDigits(e.target.value) })} /><em>%</em></div></label>
          <span className="mn-arrow">➜</span>
          <div className="mn-conv-cell mn-conv-out"><span>FYP / เดือน</span><strong>{money(c.fyp)}</strong></div>
        </div>
        <div className="mn-conv-row">
          <div className="mn-conv-cell"><span>FYP / เดือน</span><strong>{money(c.fyp)}</strong></div>
          <span className="mn-op">÷</span>
          <label className="mn-conv-cell mn-input-cell"><span>Case Size</span>
            <div className="mn-amt"><input className="form-input" inputMode="numeric" value={fmt(onlyDigits(form.case_size))}
              readOnly={ro} onChange={e => patch({ case_size: onlyDigits(e.target.value) })} /><em>฿</em></div></label>
          <span className="mn-arrow">➜</span>
          <div className="mn-conv-cell mn-conv-out"><span>จำนวนราย / เดือน</span><strong>{c.cases}</strong></div>
        </div>
        <div className="mn-funnel">
          <div className="mn-funnel-cell"><span>จำนวนราย/เดือน</span><strong>{c.cases}</strong></div>
          <span className="mn-op">× 3</span>
          <div className="mn-funnel-cell"><span>พยายามปิดการขาย</span><strong>{c.closeAttempts}</strong></div>
          <span className="mn-op">× 1.7</span>
          <div className="mn-funnel-cell"><span>นำเสนอขาย</span><strong>{c.presentations}</strong></div>
          <span className="mn-op">× 2</span>
          <div className="mn-funnel-cell"><span>นัดหมาย</span><strong>{c.appointments}</strong></div>
        </div>
      </div>

      <div className="mn-card mn-weekly">
        <div className="mn-weekly-title">สรุปการทำงานต่อสัปดาห์</div>
        <div className="mn-weekly-nums">
          <div><span>นัดหมาย</span><strong>{c.weekly.appointments}</strong></div>
          <span className="mn-colon">:</span>
          <div><span>นำเสนอขาย</span><strong>{c.weekly.presentations}</strong></div>
          <span className="mn-colon">:</span>
          <div><span>พยายามปิด</span><strong>{c.weekly.closeAttempts}</strong></div>
          <span className="mn-colon">:</span>
          <div><span>ปิดได้</span><strong>{c.weekly.closes}</strong></div>
        </div>
      </div>

      <div className="mn-footer">
        <div className="mn-foot-field"><span>ชื่อ-สกุล</span><strong>{agent.name}</strong></div>
        <div className="mn-foot-field"><span>หน่วย</span><strong>{agent.unit || "—"}</strong></div>
      </div>

      {!ro && (
        <>
          {err && <div className="form-error">{err}</div>}
          <div className="goals-save-row">
            {saved && <span className="goals-saved">✓ บันทึกแล้ว</span>}
            <button className="login-btn goals-save-btn" onClick={save} disabled={saving}>
              {saving ? "กำลังบันทึก..." : "บันทึก My Money Need"}
            </button>
          </div>
        </>
      )}
    </div>
  );
};

// ============================================================
//  Shared accessible UI kit — loaded on every page via components.jsx.
//  Provides: useModalA11y, Modal, uiToast(), uiConfirm().
// ============================================================

// Dialog a11y: scroll-lock + Escape-to-close + focus trap + focus restore.
// Apply to any modal by passing its box ref. Safe to reuse across all apps.
const useModalA11y = (isOpen, onClose, boxRef) => {
  React.useEffect(() => {
    if (!isOpen) return;
    const prevActive = document.activeElement;
    const prevOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";

    const focusablesIn = () => {
      const box = boxRef && boxRef.current;
      if (!box) return [];
      return Array.from(box.querySelectorAll(
        'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])'
      )).filter(el => el.offsetParent !== null || el === document.activeElement);
    };

    const t = window.setTimeout(() => {
      const f = focusablesIn();
      if (f.length) f[0].focus();
      else if (boxRef && boxRef.current) boxRef.current.focus();
    }, 0);

    const onKey = (e) => {
      if (e.key === "Escape") { e.stopPropagation(); onClose && onClose(); return; }
      if (e.key === "Tab") {
        const f = focusablesIn();
        if (!f.length) return;
        const first = f[0], last = f[f.length - 1];
        if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
        else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
      }
    };
    document.addEventListener("keydown", onKey, true);
    return () => {
      window.clearTimeout(t);
      document.removeEventListener("keydown", onKey, true);
      document.body.style.overflow = prevOverflow;
      if (prevActive && prevActive.focus) { try { prevActive.focus(); } catch (e) {} }
    };
  }, [isOpen]); // eslint-disable-line
};

// Accessible modal shell. Reuses existing .modal-backdrop/.modal-box styling.
// Renders role=dialog + aria-modal, an accessible-named close button, and wires
// scroll-lock / Escape / focus-trap via useModalA11y.
const SharedModal = ({
  open = true, onClose, title, ariaLabel,
  className = "", boxClassName = "modal-box", backdropClassName = "modal-backdrop",
  showClose = true, closeOnBackdrop = true, children,
}) => {
  const boxRef = React.useRef(null);
  const headingIdRef = React.useRef("mdl-" + Math.random().toString(36).slice(2, 9));
  useModalA11y(open, onClose, boxRef);
  if (!open) return null;
  const headingId = headingIdRef.current;
  return (
    <div className={backdropClassName} onClick={closeOnBackdrop ? onClose : undefined}>
      <div
        ref={boxRef}
        className={`${boxClassName} ${className}`.trim()}
        role="dialog"
        aria-modal="true"
        aria-labelledby={title ? headingId : undefined}
        aria-label={!title ? (ariaLabel || "หน้าต่าง") : undefined}
        tabIndex={-1}
        onClick={(e) => e.stopPropagation()}
      >
        {(title || showClose) && (
          <div className="modal-head">
            {title ? <h3 id={headingId} className="modal-title">{title}</h3> : <span />}
            {showClose && (
              <button type="button" className="modal-close" aria-label="ปิด" onClick={onClose}>×</button>
            )}
          </div>
        )}
        {children}
      </div>
    </div>
  );
};

// ---- Imperative toast + confirm host (one per page) ----
const __uiHost = { push: null, confirm: null };

const ToastHost = () => {
  const [toasts, setToasts] = React.useState([]);
  const [confirmState, setConfirmState] = React.useState(null);
  const [confirmBusy, setConfirmBusy] = React.useState(false);
  const seqRef = React.useRef(0);

  React.useEffect(() => {
    __uiHost.push = (msg, opts = {}) => {
      const id = ++seqRef.current;
      setToasts((t) => [...t, { id, msg, type: opts.type || "" }]);
      const ttl = opts.duration != null ? opts.duration : 4000;
      if (ttl > 0) window.setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), ttl);
    };
    __uiHost.confirm = (opts) => new Promise((resolve) => {
      setConfirmState({ opts: opts || {}, resolve });
    });
    return () => { __uiHost.push = null; __uiHost.confirm = null; };
  }, []);

  const closeConfirm = (val) => {
    if (confirmState) confirmState.resolve(val);
    setConfirmState(null);
    setConfirmBusy(false);
  };

  const co = confirmState && confirmState.opts;

  return (
    <>
      <div className="ui-toast-wrap" role="region" aria-live="polite" aria-label="การแจ้งเตือน">
        {toasts.map((t) => (
          <div key={t.id} className={`ui-toast ${t.type}`} role={t.type === "error" ? "alert" : "status"}>
            {t.type && <span className="ui-toast-icon" aria-hidden="true">{t.type === "error" ? "!" : "✓"}</span>}
            <span className="ui-toast-msg">{t.msg}</span>
            <button type="button" className="ui-toast-close" aria-label="ปิด"
              onClick={() => setToasts((x) => x.filter((y) => y.id !== t.id))}>×</button>
          </div>
        ))}
      </div>
      {confirmState && (
        <SharedModal
          open
          onClose={() => closeConfirm(false)}
          title={co.title || "ยืนยัน"}
          className="ui-confirm-box"
          closeOnBackdrop={!confirmBusy}
          showClose={!confirmBusy}
        >
          <p className="ui-confirm-msg">{co.message}</p>
          <div className="ui-confirm-actions">
            <button type="button" className="btn" onClick={() => closeConfirm(false)} disabled={confirmBusy}>
              {co.cancelText || "ยกเลิก"}
            </button>
            <button
              type="button"
              className={co.danger === false ? "btn btn-primary" : "ui-confirm-danger"}
              onClick={() => closeConfirm(true)}
              disabled={confirmBusy}
            >
              {co.confirmText || "ยืนยัน"}
            </button>
          </div>
        </SharedModal>
      )}
    </>
  );
};

// Public imperative API. Fall back to native dialogs if the host isn't ready.
window.uiToast = (msg, opts) => { if (__uiHost.push) __uiHost.push(msg, opts); else console.log("toast:", msg); };
window.uiConfirm = (opts) => {
  const o = typeof opts === "string" ? { message: opts } : (opts || {});
  return __uiHost.confirm ? __uiHost.confirm(o) : Promise.resolve(window.confirm(o.message || ""));
};

// Mount the host once per page (components.jsx loads on every entry point).
(function mountUiHost() {
  if (window.__uiHostMounted) return;
  const mount = () => {
    if (window.__uiHostMounted) return;
    window.__uiHostMounted = true;
    const el = document.createElement("div");
    el.id = "ui-host-root";
    document.body.appendChild(el);
    ReactDOM.createRoot(el).render(<ToastHost />);
  };
  if (document.body) mount();
  else document.addEventListener("DOMContentLoaded", mount);
})();

// ---- แข่งขัน ทีม vs ทีม: logic + การ์ดโชว์ (แชร์ทั้ง 3 แอป — admin/agent/public) ----
/* @team-competition-logic-start */
// คำนวณคะแนนสะสมของแต่ละทีมในการแข่งขัน "ทีม vs ทีม" เฉพาะยอดที่เกิดในช่วง [start_date, end_date]
// teams: array ของ { id, agentIds: Set<agent_id> } — คืนค่า object คีย์ด้วย team.id
// ape/fyc/cases = ยอดนำส่ง (ทุกแถว) · approved.{ape,fyc,cases} = เฉพาะแถวที่บริษัทอนุมัติแล้ว
// recruit ไม่มีขั้นอนุมัติ (นับจากกิจกรรม) จึงมีชุดเดียว
function buildCompetitionStandings(competition, teams, sales, recruitActs) {
  const inRange = (d) => d >= competition.start_date && d <= competition.end_date;
  const sumFor = (agentIdSet) => {
    const acc = { ape: 0, fyc: 0, cases: 0, recruit: 0, approved: { ape: 0, fyc: 0, cases: 0 } };
    (sales || []).forEach((s) => {
      if (!inRange(s.date) || !agentIdSet.has(s.agent_id)) return;
      acc.ape += Number(s.amount) || 0;
      acc.fyc += Number(s.fyc) || 0;
      acc.cases += 1;
      if (s.approved) {
        acc.approved.ape += Number(s.amount) || 0;
        acc.approved.fyc += Number(s.fyc) || 0;
        acc.approved.cases += 1;
      }
    });
    (recruitActs || []).forEach((r) => {
      if (!inRange(r.date) || !agentIdSet.has(r.agent_id)) return;
      acc.recruit += 1;
    });
    return acc;
  };
  const result = {};
  teams.forEach((t) => { result[t.id] = sumFor(t.agentIds); });
  return result;
}

// metric recruit ไม่มีขั้นอนุมัติ — ใช้แยกว่า UI ควรโชว์คู่ นำส่ง/อนุมัติ หรือไม่
function competitionHasApproval(metric) { return metric !== "recruit"; }

// ค่าที่ใช้ตัดสินแพ้ชนะ: ยอดอนุมัติ (ape/fyc/cases) หรือยอดรวม (recruit)
function competitionDecisiveValue(standing, metric) {
  return (competitionHasApproval(metric) ? standing?.approved?.[metric] : standing?.[metric]) || 0;
}

// รายชื่อผู้แข่งขันของแต่ละทีมพร้อมตัวเลขรายบุคคล (ตามตัวชี้วัดของการแข่งขันนั้นๆ) เรียงมากไปน้อย
// ใช้ sales/recruitActs ชุดเดียวกับ buildCompetitionStandings แค่ไม่รวมเป็นยอดทีมเดียว
// teams: array ของ { id, agentIds: Set<agent_id> }; captainIdsByTeam: { [teamId]: captainAgentId|null }
// คืนค่า object คีย์ด้วย team.id → array ผู้แข่งขันเรียง value มาก→น้อย
function buildCompetitionParticipants(competition, teams, sales, recruitActs, agentsById, captainIdsByTeam) {
  const inRange = (d) => d >= competition.start_date && d <= competition.end_date;
  const metric = competition.metric;
  // value = ยอดอนุมัติ (ตรงกับสกอร์ที่ใช้ตัดสิน) · submitted = ยอดนำส่งทั้งหมด
  // (metric recruit ไม่มีขั้นอนุมัติ — สองค่าเท่ากัน)
  const valueFor = (idSet) => {
    const acc = {};
    idSet.forEach((id) => { acc[id] = { value: 0, submitted: 0 }; });
    (sales || []).forEach((s) => {
      if (!inRange(s.date) || !idSet.has(s.agent_id)) return;
      const amt = metric === "fyc" ? (Number(s.fyc) || 0)
                : metric === "ape" ? (Number(s.amount) || 0)
                : metric === "cases" ? 1 : 0;
      acc[s.agent_id].submitted += amt;
      if (s.approved) acc[s.agent_id].value += amt;
    });
    if (metric === "recruit") {
      (recruitActs || []).forEach((r) => {
        if (!inRange(r.date) || !idSet.has(r.agent_id)) return;
        acc[r.agent_id].value += 1;
        acc[r.agent_id].submitted += 1;
      });
    }
    return acc;
  };
  const result = {};
  teams.forEach((t) => {
    const acc = valueFor(t.agentIds);
    const captainId = captainIdsByTeam[t.id];
    result[t.id] = Object.entries(acc)
      .map(([agent_id, v]) => ({
        agent_id, value: v.value, submitted: v.submitted,
        name: agentsById[agent_id]?.name || "-",
        isCaptain: agent_id === captainId,
      }))
      // เรียงด้วยยอดนำส่งก่อน (คนที่นำส่งแล้วรออนุมัติไม่จมท้ายลิสต์) แล้วตัดด้วยยอดอนุมัติ
      .sort((x, y) => y.submitted - x.submitted || y.value - x.value);
  });
  return result;
}
/* @team-competition-logic-end */

// ป้ายสถานะอนุมัติของดีลปิดได้ (ใช้ทั้งการ์ด/โมดัลฝั่งตัวแทน และตารางดีลฝั่งแอดมิน)
const DealApprovePill = ({ approvedAt }) => (
  approvedAt
    ? <span className="deal-ap-pill ok">✓ อนุมัติแล้ว</span>
    : <span className="deal-ap-pill wait">⏳ รออนุมัติ</span>
);

const COMPETITION_METRICS = [
  { key: "fyc",     label: "NBC" },
  { key: "ape",     label: "APE" },
  { key: "cases",   label: "เคสปิด" },
  { key: "recruit", label: "Recruit" },
];

const CompetitionTeamLogo = ({ team, size = 64 }) => (
  team.logo_path ? (
    <span className="avatar-circle has-photo" style={{ width: size, height: size, backgroundImage: `url("${window.SUPABASE_URL}/storage/v1/object/public/avatars/${team.logo_path}")` }} />
  ) : (
    <span className={`avatar-circle avatar-c${nameHash(team.name || "?")}`} style={{ width: size, height: size, fontSize: size * 0.35 }}>
      {initial(team.name || "?")}
    </span>
  )
);

const CompetitionParticipantsList = ({ name, list, fmt, hasApproval, side }) => (
  <div className={`tc-p-column ${side || ""}`}>
    <div className="tc-p-col-head">
      <span className="tc-p-head-name">{name}<small>{list.length} คน</small></span>
      <span className="tc-p-head-score">{hasApproval ? "อนุมัติ" : "ผลงาน"}</span>
      <span className="tc-p-head-score">{hasApproval ? "นำส่ง" : ""}</span>
    </div>
    {list.length === 0 ? (
      <div className="tc-p-empty">ยังไม่มีผู้แข่งขัน</div>
    ) : (
      <div className="tc-p-list">
        {list.map((p, i) => (
          <div className={`tc-p-row ${i === 0 && p.submitted > 0 ? "top" : ""}`} key={p.agent_id}>
            <span className="tc-p-rank">{i + 1}</span>
            {photoOf(p.name) ? (
              <span className="tc-p-mini-avatar has-photo" style={{ backgroundImage: `url("${photoOf(p.name)}")` }} />
            ) : (
              <span className={`tc-p-mini-avatar avatar-c${nameHash(p.name)}`}>{initial(p.name)}</span>
            )}
            <span className="tc-p-name">
              {p.name}
              {p.isCaptain && <span className="tc-captain-badge" title="หัวหน้าทีม">C</span>}
            </span>
            <span className="tc-p-score approved">{fmt(p.value)}</span>
            <span className="tc-p-score submitted">{hasApproval ? fmt(p.submitted) : ""}</span>
          </div>
        ))}
      </div>
    )}
  </div>
);

const CompetitionVsCard = ({ competition, teams, standings, agentsById, participants }) => {
  const metricInfo = COMPETITION_METRICS.find(m => m.key === competition.metric) || COMPETITION_METRICS[0];
  const today = getNow().toISOString().slice(0, 10);
  const isOver = today > competition.end_date;
  const notStarted = today < competition.start_date;
  const daysLeft = Math.max(0, Math.ceil((new Date(competition.end_date) - new Date(today)) / 86400000));
  const daysToStart = Math.max(0, Math.ceil((new Date(competition.start_date) - new Date(today)) / 86400000));
  const thDate = (iso) => { const d = new Date(iso + "T00:00:00"); return `${d.getDate()} ${TH_MONTHS[d.getMonth()]}`; };
  const captainName = (id) => agentsById[id]?.name || "-";
  const fmt = (n) => Math.round(Number(n) || 0).toLocaleString("en-US");

  // ตัดสินแพ้ชนะด้วย "ยอดอนุมัติ" — ยอดนำส่ง/รออนุมัติเป็นตัวเลขรอง (recruit ไม่มีขั้นอนุมัติ)
  const hasApproval = competitionHasApproval(competition.metric);
  const decisive = (teamId) => competitionDecisiveValue(standings[teamId], competition.metric);
  const submittedOf = (teamId) => standings[teamId]?.[competition.metric] || 0;

  const ranked = teams
    .map(t => ({ ...t, value: decisive(t.id), submitted: submittedOf(t.id) }))
    .sort((x, y) => y.value - x.value);
  const maxVal = ranked[0]?.value || 0;
  const winners = ranked.filter(t => t.value === maxVal);

  // มี 2 ทีมพอดี → คืนดีไซน์ "VS" แบบเดิม (โลโก้ซ้าย-ขวา คั่นกลาง); 3-6 ทีม → leaderboard bars
  const isTwoTeam = teams.length === 2;
  const [teamA, teamB] = teams;
  const aVal = isTwoTeam ? decisive(teamA.id) : 0;
  const bVal = isTwoTeam ? decisive(teamB.id) : 0;
  const aSub = isTwoTeam ? submittedOf(teamA.id) : 0;
  const bSub = isTwoTeam ? submittedOf(teamB.id) : 0;
  // หลอด 2 ทีมใช้สเกลเดียว = ยอดนำส่งรวม: ชั้นจาง = นำส่ง (เต็มหลอดเสมอ), ชั้นทึบ = อนุมัติ
  const twoTeamScale = aSub + bSub;
  const pctOfScale = (n) => twoTeamScale > 0 ? Math.round((n / twoTeamScale) * 100) : 0;
  const aSubPct = twoTeamScale > 0 ? pctOfScale(aSub) : 50;

  return (
    <div className="tc-vs-card">
      <div className="tc-champ-title" aria-hidden="true">
        <strong>CHAMPIONSHIP NIGHT</strong>
      </div>
      {isTwoTeam ? (
        <>
          <div className="tc-vs-sides">
            <div className={`tc-vs-side left ${aVal > bVal ? "leading" : ""}`}>
              <div className="tc-vs-team-head">
                <CompetitionTeamLogo team={teamA} size={76} />
                <div>
                  <div className="tc-vs-name">{teamA.name}</div>
                  <div className="tc-vs-captain">หัวหน้าทีม: {captainName(teamA.captain_agent_id)}</div>
                </div>
              </div>
              <div className="tc-vs-score-label">{metricInfo.label}{hasApproval ? " (อนุมัติ)" : ""}</div>
              <strong className="tc-vs-score">{fmt(aVal)}</strong>
              {hasApproval && (
                <div className="tc-vs-secondary">
                  <span><small>นำส่ง</small><b>{fmt(aSub)}</b></span>
                  <span><small>รออนุมัติ</small><b>{fmt(Math.max(0, aSub - aVal))}</b></span>
                </div>
              )}
            </div>
            <div className="tc-vs-mid">
              <span>VS</span>
              <small>ผลต่าง {fmt(Math.abs(aVal - bVal))}</small>
            </div>
            <div className={`tc-vs-side right ${bVal > aVal ? "leading" : ""}`}>
              <div className="tc-vs-team-head">
                <CompetitionTeamLogo team={teamB} size={76} />
                <div>
                  <div className="tc-vs-name">{teamB.name}</div>
                  <div className="tc-vs-captain">หัวหน้าทีม: {captainName(teamB.captain_agent_id)}</div>
                </div>
              </div>
              <div className="tc-vs-score-label">{metricInfo.label}{hasApproval ? " (อนุมัติ)" : ""}</div>
              <strong className="tc-vs-score">{fmt(bVal)}</strong>
              {hasApproval && (
                <div className="tc-vs-secondary">
                  <span><small>นำส่ง</small><b>{fmt(bSub)}</b></span>
                  <span><small>รออนุมัติ</small><b>{fmt(Math.max(0, bSub - bVal))}</b></span>
                </div>
              )}
            </div>
          </div>
          <div className="tc-vs-bar" aria-label={`${teamA.name} นำส่ง ${fmt(aSub)} ต่อ ${teamB.name} นำส่ง ${fmt(bSub)}`}>
            <div className={`tc-vs-bar-sub left avatar-c${nameHash(teamA.name || "?")}`} style={{ width: `${aSubPct}%` }} />
            <div className={`tc-vs-bar-sub right avatar-c${nameHash(teamB.name || "?")}`} style={{ width: `${100 - aSubPct}%` }} />
            <div className={`tc-vs-bar-ap left avatar-c${nameHash(teamA.name || "?")}`} style={{ width: `${pctOfScale(aVal)}%` }} />
            <div className={`tc-vs-bar-ap right avatar-c${nameHash(teamB.name || "?")}`} style={{ width: `${pctOfScale(bVal)}%` }} />
          </div>
        </>
      ) : (
        <div className="tc-lb-list">
          {(() => {
            // สเกลหลอดยึดยอดนำส่งสูงสุด — หลอดจาง (นำส่ง) เทียบข้ามทีมได้ตรงๆ
            const scale = Math.max(...ranked.map(t => t.submitted), maxVal) || 0;
            const pctOf = (n) => scale > 0 ? Math.round((n / scale) * 100) : 0;
            return ranked.map((t, i) => (
              <div className="tc-lb-row" key={t.id}>
                <div className={`tc-lb-rank ${i === 0 ? "top" : ""}`}>{i + 1}</div>
                <CompetitionTeamLogo team={t} size={32} />
                <div className="tc-lb-info">
                  <div className="tc-vs-name">{t.name}</div>
                  <div className="tc-vs-captain">หัวหน้าทีม: {captainName(t.captain_agent_id)}</div>
                  <div className="tc-lb-bar-track">
                    {hasApproval && <div className={`tc-lb-bar-sub avatar-c${nameHash(t.name || "?")}`} style={{ width: `${pctOf(t.submitted)}%` }} />}
                    <div className={`tc-lb-bar-fill avatar-c${nameHash(t.name || "?")}`} style={{ width: `${pctOf(t.value)}%` }} />
                  </div>
                </div>
                <div className="tc-lb-value">
                  {fmt(t.value)} {metricInfo.label}
                  {hasApproval && <small className={t.submitted > t.value ? "wait" : "same"}>นำส่ง {fmt(t.submitted)}</small>}
                </div>
              </div>
            ));
          })()}
        </div>
      )}
      {isOver ? (
        <div className="tc-vs-banner">
          {winners.length > 1 ? `${winners.map(w => w.name).join(", ")} เสมอ!` : `${winners[0]?.name || "-"} ชนะ!`}
        </div>
      ) : (
        <div className="tc-vs-foot">
          <span className={`tc-live-badge ${notStarted ? "upcoming" : ""}`}>{notStarted ? "UPCOMING" : "LIVE"}</span>
          <span>{notStarted ? `เริ่มแข่ง ${thDate(competition.start_date)}` : "กำลังแข่งขัน"}</span>
          <strong>{notStarted ? `อีก ${daysToStart} วัน` : `เหลือ ${daysLeft} วัน`}</strong>
        </div>
      )}
      <div className="tc-vs-details">
        <span>ตัดสินด้วย {metricInfo.label}{hasApproval ? " (เฉพาะอนุมัติ)" : ""}</span>
        {competition.rules_note && <span>{competition.rules_note}</span>}
      </div>
      {participants && (
        <div className="tc-participants">
          {(isTwoTeam ? teams : ranked).map((t, i) => (
            <CompetitionParticipantsList key={t.id} name={t.name} list={participants[t.id] || []} fmt={fmt} hasApproval={hasApproval} side={i === 0 ? "left" : i === 1 ? "right" : ""} />
          ))}
        </div>
      )}
    </div>
  );
};

// ดึงข้อมูลแมตช์ "ทีม vs ทีม" ที่กำลังแข่งอยู่ตอนนี้ (ถ้ามี) — ใช้ร่วมกันทั้งหน้า public landing
// (sections.jsx, includeParticipants=false) และแท็บตัวแทน (login-app.jsx, includeParticipants=true)
const useActiveTeamCompetition = (includeParticipants) => {
  const [state, setState] = React.useState(null); // null=กำลังโหลด, {empty:true}=ไม่มีแมตช์, {...}=มีข้อมูล

  React.useEffect(() => {
    let alive = true;
    (async () => {
      const [c, t, m, a, s, act] = await Promise.all([
        sb.from("team_competitions").select("*"),
        sb.from("team_competition_teams").select("*"),
        sb.from("team_competition_members").select("*"),
        sb.from("agents").select("id, name"),
        sb.from("agent_ape_sales").select("agent_id, date, amount, fyc, approved").gte("date", SEASON_START),
        sb.from("agent_activities").select("agent_id, date, name, note").gte("date", SEASON_START),
      ]);
      if (!alive) return;
      if (c.error || t.error || m.error || a.error || s.error || act.error) { setState({ empty: true }); return; }
      const comps = c.data || [];
      const today = getNow().toISOString().slice(0, 10);
      // แมตช์ที่กำลังแข่ง (start<=today<=end); ถ้าซ้อนหลายอันเลือกอันล่าสุด (created_at)
      const running = comps
        .filter(x => x.start_date <= today && today <= x.end_date)
        .sort((x, y) => y.created_at.localeCompare(x.created_at))[0];
      // ไม่มีแมตช์ที่กำลังแข่ง → โชว์แมตช์ที่จะเริ่มเร็วที่สุดล่วงหน้า (การ์ดขึ้น "เริ่มแข่ง <วันที่>")
      const upcoming = comps
        .filter(x => x.start_date > today)
        .sort((x, y) => x.start_date.localeCompare(y.start_date))[0];
      const active = running || upcoming;
      if (!active) { setState({ empty: true }); return; }
      const teamRows = (t.data || []).filter(x => x.competition_id === active.id).sort((x, y) => x.side.localeCompare(y.side));
      if (teamRows.length < 2) { setState({ empty: true }); return; }
      const memberIds = (teamId) => new Set((m.data || []).filter(r => r.competition_team_id === teamId).map(r => r.agent_id));
      const recruitActs = (act.data || []).filter(r => RECRUIT_ACTIVITY_NAMES.includes(r.name) || String(r.note || "").includes("ชวน:"));
      const teams = teamRows.map(row => ({ id: row.id, agentIds: memberIds(row.id) }));
      const standings = buildCompetitionStandings(active, teams, s.data || [], recruitActs);
      const agentsById = {}; (a.data || []).forEach(x => { agentsById[x.id] = x; });
      let participants = null;
      if (includeParticipants) {
        const captainIdsByTeam = {};
        teamRows.forEach(row => { captainIdsByTeam[row.id] = row.captain_agent_id || null; });
        participants = buildCompetitionParticipants(active, teams, s.data || [], recruitActs, agentsById, captainIdsByTeam);
      }
      setState({ selected: active, teams: teamRows, standings, agentsById, participants });
    })();
    return () => { alive = false; };
  }, [includeParticipants]);

  if (!state) return { loading: true, empty: false };
  if (state.empty) return { loading: false, empty: true };
  return { loading: false, empty: false, ...state };
};

// การ์ด "กติกาการรับคะแนน" — ใช้ร่วมกันทั้งหน้าแอดมิน (admin-app.jsx) และหน้าตัวแทน (login-app.jsx)
// fetch แถวจาก point_rules (active เท่านั้น) + ต่อท้ายด้วยภารกิจพิเศษที่เปิดอยู่จาก daily_challenges
// ถ้า fetch ไม่สำเร็จ (table ยังไม่ถูกสร้าง ฯลฯ) fallback เป็น 6 ข้อความเดิมที่เคย hardcode
const POINT_RULES_FALLBACK = [
  { id: "fb-checkin",    label: "เช็คอินเข้าคลับ / เข้าอบรม",      unit_note: null,               points: 10 },
  { id: "fb-lms",        label: "เรียนจบบทเรียนใน LMS",             unit_note: "(ต่อบท)",          points: 5 },
  { id: "fb-deal",       label: "สร้างดีลใหม่",                      unit_note: "(ต่อ 1 ดีล)",       points: 5 },
  { id: "fb-money_need", label: "ทำ My Money Need",                  unit_note: "(ครั้งแรก)",        points: 50 },
  { id: "fb-fyc",        label: "ส่งงาน NBC",                        unit_note: "(ทุก 1,000 บาท)",   points: 100 },
  { id: "fb-recruit",    label: "Recruit สำเร็จ พร้อมแนบหลักฐาน",   unit_note: null,               points: 1000 },
];
const PointRulesCard = ({ subtitle }) => {
  const [rows, setRows] = React.useState(POINT_RULES_FALLBACK);

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const today = dateKey(getNow());
        const [pr, ch] = await Promise.all([
          sb.from("point_rules").select("id, label, points, unit_note").eq("active", true).order("sort_order"),
          sb.from("daily_challenges").select("id, title, reward_value")
            .eq("active", true).eq("reward_type", "points_flat")
            .or(`active_from.is.null,active_from.lte.${today}`)
            .or(`active_until.is.null,active_until.gte.${today}`)
            .order("sort_order"),
        ]);
        if (cancelled) return;
        if (pr.error || !pr.data || !pr.data.length) return; // เก็บ fallback ไว้
        const ruleRows = pr.data.map(r => ({ id: r.id, label: r.label, unit_note: r.unit_note, points: r.points }));
        const challengeRows = (ch.data || []).map(c => ({ id: `ch-${c.id}`, label: c.title, unit_note: "(ภารกิจพิเศษ)", points: Number(c.reward_value) || 0 }));
        setRows(ruleRows.concat(challengeRows));
      } catch (_) { /* เก็บ fallback ไว้ */ }
    })();
    return () => { cancelled = true; };
  }, []);

  return (
    <div className="points-rule-card">
      <strong>กติกาการรับคะแนน</strong>
      <small>{subtitle}</small>
      <ul className="points-rule-list">
        {rows.map(r => (
          <li key={r.id}>
            <span className="prl-act">{r.label}{r.unit_note ? ` ${r.unit_note}` : ""}</span>
            <span className="prl-pts">+{r.points.toLocaleString()} คะแนน</span>
          </li>
        ))}
      </ul>
    </div>
  );
};


// ---- Sales/Recruit CRUD (ย้ายจาก login-app.jsx เพื่อใช้ซ้ำใน admin-app.jsx แท็บ "ของฉัน" ของ manager) ----
// fmtDate/todayDateKey/TH_MONTHS คัดลอกมาจาก login-app.jsx ตรงๆ (ของเดิมไม่ได้ derive จาก data.jsx เหมือน 83G30M)
// โค้ดที่ย้ายมาเรียกใช้ชื่อนี้อยู่หลายจุด คัดลอกไว้กันพังแทนที่จะ refactor เพิ่ม
// TH_MONTHS ไม่มีอยู่ใน data.jsx/components.jsx เดิม และ admin-app.jsx ก็มี TH_MONTHS_A แยกของตัวเองอยู่แล้ว
// (คนละชื่อ คนละที่ ด้วยเหตุผลเดียวกัน) — สำเนานี้จำเป็นเพราะ admin.html/manager.html โหลด components.jsx
// ก่อน admin-app.jsx โดยไม่โหลด login-app.jsx เลย จึงไม่มี TH_MONTHS ระดับ global ให้ใช้
const TH_MONTHS = ["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."];
const fmtDate = (iso) => {
  if (!iso) return "-";
  const [y, m, d] = iso.split("-").map(Number);
  return `${d} ${TH_MONTHS[m - 1]} ${y}`;
};
const todayDateKey = () => {
  const d = getNow();
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
};

// ย่อรูปในเครื่องก่อนอัปโหลด (canvas → JPEG) เพื่อประหยัดพื้นที่เก็บ
// ระดับ "สมดุล": ด้านยาวสุด 1600px · คุณภาพ 80% · หมุนตาม EXIF ให้ถูกด้าน
// ไม่ใช่รูป (เช่น PDF) / GIF / ย่อแล้วไม่เล็กลง / ผิดพลาด → คืนไฟล์เดิมเสมอ
const downscaleImage = async (file, maxDim = 1600, quality = 0.8) => {
  try {
    if (!file || !file.type || !file.type.startsWith("image/") || file.type === "image/gif") return file;
    const bitmap = await createImageBitmap(file, { imageOrientation: "from-image" });
    const scale = Math.min(1, maxDim / Math.max(bitmap.width, bitmap.height));
    const w = Math.max(1, Math.round(bitmap.width * scale));
    const h = Math.max(1, Math.round(bitmap.height * scale));
    const canvas = document.createElement("canvas");
    canvas.width = w; canvas.height = h;
    canvas.getContext("2d").drawImage(bitmap, 0, 0, w, h);
    if (bitmap.close) bitmap.close();
    const blob = await new Promise(res => canvas.toBlob(res, "image/jpeg", quality));
    if (!blob || blob.size >= file.size) return file;   // ไม่เล็กลง → ใช้ต้นฉบับ
    const base = (file.name || "photo").replace(/\.[^.]+$/, "");
    return new File([blob], `${base}.jpg`, { type: "image/jpeg", lastModified: Date.now() });
  } catch (e) { return file; }
};

// ============================================================================
//  Sales Tracking (ติดตามการขาย) — ฝั่งตัวแทน · พอร์ตจาก GA83F
// ============================================================================
const salesMoney = (n) => Number(n || 0).toLocaleString("en-US");

const SalesStagePill = ({ stage }) => {
  const s = SALES_STAGE_MAP[stage];
  return <span className={`sales-stage-pill st-${s?.cls || "prospect"}`}>{s?.label || stage}</span>;
};

const getGps = () => new Promise((resolve) => {
  if (!navigator.geolocation) { resolve(null); return; }
  navigator.geolocation.getCurrentPosition(
    p => resolve({ lat: p.coords.latitude, lng: p.coords.longitude, accuracy: p.coords.accuracy }),
    () => resolve(null),
    { enableHighAccuracy: true, timeout: 10000 }
  );
});

// ---- เพิ่ม/แก้ไขลูกค้า ----
const SalesCustomerModal = ({ agent, customer, onClose, onSaved }) => {
  const editing = !!customer?.id;
  const [form, setForm] = React.useState({
    full_name: customer?.full_name || "", nickname: customer?.nickname || "",
    phone: customer?.phone || "", line_id: customer?.line_id || "", email: customer?.email || "",
    gender: customer?.gender || "", occupation: customer?.occupation || "",
    income_range: customer?.income_range || "", address: customer?.address || "",
    source: customer?.source || "", interest: customer?.interest || "",
    health_level: customer?.health_level || "",
    rating: customer?.rating || 0, note: customer?.note || "",
    consent_pdpa: customer?.consent_pdpa || false,
  });
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState("");
  const boxRef = React.useRef(null);
  useModalA11y(true, () => { if (!saving) onClose(); }, boxRef);
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const submit = async () => {
    if (!form.consent_pdpa) { setErr("ต้องได้รับความยินยอม (PDPA) จากลูกค้าก่อนบันทึกข้อมูล"); return; }
    setSaving(true); setErr("");
    const payload = { ...form, full_name: form.full_name.trim(), rating: Number(form.rating) || 0, agent_id: agent.id };
    let error;
    if (editing) ({ error } = await sb.from("sales_customers").update(payload).eq("id", customer.id).eq("agent_id", agent.id));
    else ({ error } = await sb.from("sales_customers").insert([payload]));
    if (error) { setErr("บันทึกไม่สำเร็จ: " + error.message); setSaving(false); return; }
    setSaving(false); onSaved();
  };

  return (
    <div className="modal-backdrop">
      <div className="modal-box sales-modal" ref={boxRef} role="dialog" aria-modal="true" aria-label={editing ? "แก้ไขลูกค้า" : "เพิ่มลูกค้าใหม่"} tabIndex={-1} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <strong>{editing ? "แก้ไขลูกค้า" : "เพิ่มลูกค้าใหม่"}</strong>
          <button className="modal-close" type="button" aria-label="ปิด" onClick={() => !saving && onClose()}>×</button>
        </div>
        <div className="modal-body sales-form">
          <div className="sales-form-grid">
            <label>ชื่อเล่น<input className="form-input" value={form.nickname} onChange={e => set("nickname", e.target.value)} /></label>
            <label>เบอร์โทร<input className="form-input" value={form.phone} onChange={e => set("phone", e.target.value)} /></label>
            <label>เพศ<select className="form-input" value={form.gender} onChange={e => set("gender", e.target.value)}><option value="">—</option>{SALES_GENDERS.map(g => <option key={g}>{g}</option>)}</select></label>
            <label>อาชีพ<input className="form-input" value={form.occupation} onChange={e => set("occupation", e.target.value)} /></label>
            <label>ช่วงรายได้/เดือน<select className="form-input" value={form.income_range} onChange={e => set("income_range", e.target.value)}><option value="">—</option>{SALES_INCOME_RANGES.map(r => <option key={r}>{r}</option>)}</select></label>
            <label>ที่มาของรายชื่อ<input className="form-input" value={form.source} onChange={e => set("source", e.target.value)} placeholder="แนะนำ/ตลาดเย็น/อีเวนต์" /></label>
            <label>ความสนใจ<select className="form-input" value={form.interest} onChange={e => set("interest", e.target.value)}><option value="">—</option>{SALES_INTERESTS.map(i => <option key={i}>{i}</option>)}</select></label>
            <label>ระดับสุขภาพ<select className="form-input" value={form.health_level} onChange={e => set("health_level", e.target.value)}><option value="">—</option>{SALES_HEALTH_LEVELS.map(h => <option key={h}>{h}</option>)}</select></label>
            <label>โอกาสปิด<select className="form-input" value={form.rating} onChange={e => set("rating", e.target.value)}>{[0,1,2,3,4,5].map(n => <option key={n} value={n}>{n} ดาว</option>)}</select></label>
          </div>
          <label>ที่อยู่<textarea className="form-input" rows="2" value={form.address} onChange={e => set("address", e.target.value)} /></label>
          <label>โน้ต<textarea className="form-input" rows="2" value={form.note} onChange={e => set("note", e.target.value)} /></label>
          <label className="sales-consent">
            <input type="checkbox" checked={form.consent_pdpa} onChange={e => set("consent_pdpa", e.target.checked)} />
            <span>ลูกค้ายินยอมให้เก็บและใช้ข้อมูลส่วนบุคคลเพื่อการนำเสนอผลิตภัณฑ์ประกัน (PDPA)</span>
          </label>
          {err && <div className="form-error">{err}</div>}
          <div className="modal-actions">
            <button className="btn-outline" onClick={onClose} disabled={saving}>ยกเลิก</button>
            <button className="login-btn" style={{ width: "auto", padding: "10px 24px" }} onClick={submit} disabled={saving}>{saving ? "กำลังบันทึก..." : "บันทึก"}</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ---- สร้างดีลใหม่ ----
const SalesDealModal = ({ agent, customer, onClose, onSaved }) => {
  const [form, setForm] = React.useState({ expected_ape: "" });
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState("");
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const custName = customer.full_name || customer.nickname || "ลูกค้า";
  const submit = async () => {
    setSaving(true); setErr("");
    // ดึงชื่อดีลและชนิดแบบประกันจากข้อมูลลูกค้า (ความสนใจ)
    const { data: created, error } = await sb.from("sales_deals").insert([{
      customer_id: customer.id, agent_id: agent.id,
      title: `${customer.interest || "ดีล"} ${custName}`.trim(),
      product_type: customer.interest || null,
      expected_ape: Number(form.expected_ape) || 0, stage: "prospect",
    }]).select("id").single();
    if (error) { setErr("สร้างดีลไม่สำเร็จ: " + error.message); setSaving(false); return; }
    // ให้คะแนนสร้างดีลใหม่ (ตัวเลขคะแนนแก้ได้ที่แอดมิน → จัดการกติกา)
    try {
      const dealPts = POINT_RULES.deal;
      if (created?.id && dealPts > 0) {
        await sb.from("agent_activities").insert([{
          agent_id: agent.id,
          date: new Date().toISOString().slice(0, 10),
          name: "สร้างดีลใหม่",
          points: dealPts,
          note: `deal:${created.id}`,
        }]);
      }
    } catch (_) { /* ให้คะแนนล้มเหลวไม่กระทบการสร้างดีล */ }
    setSaving(false); onSaved();
  };
  return (
    <div className="modal-backdrop" onClick={() => !saving && onClose()}>
      <div className="modal-box sales-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><strong>ดีลใหม่ · {custName}</strong><button className="modal-close" onClick={() => !saving && onClose()}>×</button></div>
        <div className="modal-body sales-form">
          <div className="sales-deal-info">
            <span>ลูกค้า: <strong>{custName}</strong></span>
            <span>ความสนใจ: <strong>{customer.interest || "—"}</strong></span>
          </div>
          <label>เป้า APE คาดหวัง (บาท)<input className="form-input" inputMode="numeric" value={form.expected_ape ? Number(form.expected_ape).toLocaleString("en-US") : ""} onChange={e => set("expected_ape", e.target.value.replace(/[^0-9]/g, ""))} /></label>
          {err && <div className="form-error">{err}</div>}
          <div className="modal-actions">
            <button className="btn-outline" onClick={onClose} disabled={saving}>ยกเลิก</button>
            <button className="login-btn" style={{ width: "auto", padding: "10px 24px" }} onClick={submit} disabled={saving}>{saving ? "กำลังสร้าง..." : "สร้างดีล"}</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ---- เลื่อนสเตจ (เก็บข้อมูลเฉพาะสเตจ) ----
const SalesStageMoveModal = ({ agent, deal, toStage, onClose, onSaved }) => {
  const [val, setVal] = React.useState({ next_action_at: "", expected_ape: deal.expected_ape || "", actual_ape: deal.expected_ape || "", actual_fyc: "", lost_reason: SALES_LOST_REASONS[0] });
  const [files, setFiles] = React.useState([]);
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState("");
  const s = SALES_STAGE_MAP[toStage];
  const fileRef = React.useRef(null);
  const boxRef = React.useRef(null);
  useModalA11y(true, () => { if (!saving) onClose(); }, boxRef);
  const set = (k, v) => setVal(p => ({ ...p, [k]: v }));
  const pickFiles = (e) => {
    const picked = Array.from(e.target.files || []);
    const valid = picked.filter(f => f.type.startsWith("image/") && f.size <= 8 * 1024 * 1024);
    if (valid.length !== picked.length) setErr("รองรับเฉพาะรูปภาพ ไม่เกิน 8 MB"); else setErr("");
    setFiles(valid);
  };
  const submit = async () => {
    const payload = { stage: toStage };
    if (toStage === "appointment") { if (!val.next_action_at) { setErr("กรุณาเลือกวันนัด"); return; } payload.next_action_at = val.next_action_at; }
    if (toStage === "closed_won")  { payload.actual_ape = Number(val.actual_ape) || 0; payload.actual_fyc = Number(val.actual_fyc) || 0; payload.closed_at = todayDateKey(); }
    if (toStage === "closed_lost") { payload.lost_reason = val.lost_reason; payload.closed_at = todayDateKey(); }
    setSaving(true); setErr("");
    try {
      if (toStage === "closed_won" && files.length) {
        const paths = [...(deal.won_doc_paths || [])];
        for (let i = 0; i < files.length; i++) {
          const f = await downscaleImage(files[i]);
          const ext = (f.name.split(".").pop() || "jpg").toLowerCase().replace(/[^a-z0-9]/g, "") || "jpg";
          const path = `${agent.id}/${deal.id}/won-${Date.now()}-${i}.${ext}`;
          const { error } = await sb.storage.from("sales-visits").upload(path, f, { upsert: false, contentType: f.type || "image/jpeg" });
          if (error) throw error;
          paths.push(path);
        }
        payload.won_doc_paths = paths;
      }
      const { error } = await sb.from("sales_deals").update(payload).eq("id", deal.id);
      if (error) throw error;
      setSaving(false); onSaved();
    } catch (e) { setErr("อัปเดตไม่สำเร็จ: " + (e?.message || String(e))); setSaving(false); }
  };
  return (
    <div className="modal-backdrop">
      <div className="modal-box sales-modal" ref={boxRef} role="dialog" aria-modal="true" aria-label={`เลื่อนเป็น ${s?.label || ""}`} tabIndex={-1} onClick={e => e.stopPropagation()}>
        <div className="modal-head"><strong>เลื่อนเป็น “{s?.label}”</strong><button className="modal-close" type="button" aria-label="ปิด" onClick={() => !saving && onClose()}>×</button></div>
        <div className="modal-body sales-form">
          {toStage === "appointment" && <label>วัน/เวลานัด<input type="date" className="form-input" value={val.next_action_at} onChange={e => set("next_action_at", e.target.value)} /></label>}
          {toStage === "closed_won" && <div className="sales-money-row">
            <label>APE (บาท)<input className="form-input" inputMode="numeric" value={val.actual_ape ? Number(val.actual_ape).toLocaleString("en-US") : ""} onChange={e => set("actual_ape", e.target.value.replace(/[^0-9]/g, ""))} /></label>
            <label>NBC (บาท)<input className="form-input" inputMode="numeric" value={val.actual_fyc ? Number(val.actual_fyc).toLocaleString("en-US") : ""} onChange={e => set("actual_fyc", e.target.value.replace(/[^0-9]/g, ""))} /></label>
          </div>}
          {toStage === "closed_won" && <label>แนบรูปหลักฐาน (ไม่บังคับ)
            <input ref={fileRef} type="file" accept="image/*" multiple style={{ display: "none" }} onChange={pickFiles} />
            <button type="button" className="btn-outline" style={{ width: "auto", padding: "8px 16px" }} onClick={() => fileRef.current?.click()}>📎 เลือกรูป{files.length ? ` (${files.length})` : ""}</button>
            {files.length > 0 && <div className="sales-visit-thumbs" style={{ marginTop: 8 }}>{files.map((f, i) => <img key={i} src={URL.createObjectURL(f)} alt="" />)}</div>}
          </label>}
          {toStage === "closed_lost" && <label>เหตุผลที่ปิดไม่ได้<select className="form-input" value={val.lost_reason} onChange={e => set("lost_reason", e.target.value)}>{SALES_LOST_REASONS.map(r => <option key={r}>{r}</option>)}</select></label>}
          {err && <div className="form-error">{err}</div>}
          <div className="modal-actions">
            <button className="btn-outline" onClick={onClose} disabled={saving}>ยกเลิก</button>
            <button className="login-btn" style={{ width: "auto", padding: "10px 24px" }} onClick={submit} disabled={saving}>{saving ? "กำลังบันทึก..." : "ยืนยัน"}</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ---- เลื่อนนัด (เปลี่ยนวันนัด โดยคงสเตจ "นัด") ----
const SalesRescheduleModal = ({ deal, onClose, onSaved }) => {
  const [date, setDate] = React.useState("");
  const [reason, setReason] = React.useState(SALES_RESCHEDULE_REASONS[0]);
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState("");
  const nextCount = (deal.reschedule_count || 0) + 1;
  const submit = async () => {
    if (!date) { setErr("กรุณาเลือกวันนัดใหม่"); return; }
    setSaving(true); setErr("");
    const { error } = await sb.from("sales_deals")
      .update({ next_action_at: date, reschedule_count: nextCount }).eq("id", deal.id);
    if (error) { setErr("เลื่อนนัดไม่สำเร็จ: " + error.message); setSaving(false); return; }
    // สเตจไม่เปลี่ยน trigger จึงไม่เขียน log ให้ — เขียนเองเพื่อให้ขึ้นไทม์ไลน์
    await sb.from("sales_stage_logs").insert([{
      deal_id: deal.id, agent_id: deal.agent_id,
      from_stage: "appointment", to_stage: "appointment",
      note: `เลื่อนนัดครั้งที่ ${nextCount}: ${reason} → ${fmtDate(date)}`,
    }]);
    setSaving(false); onSaved();
  };
  return (
    <div className="modal-backdrop" onClick={() => !saving && onClose()}>
      <div className="modal-box sales-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><strong>📅 เลื่อนนัด</strong><button className="modal-close" onClick={() => !saving && onClose()}>×</button></div>
        <div className="modal-body sales-form">
          {deal.next_action_at && <div className="sales-hint">นัดเดิม: {fmtDate(deal.next_action_at)}{deal.reschedule_count ? ` · เลื่อนมาแล้ว ${deal.reschedule_count} ครั้ง` : ""}</div>}
          <label>วันนัดใหม่<input type="date" className="form-input" value={date} onChange={e => setDate(e.target.value)} /></label>
          <label>เหตุผลที่เลื่อน<select className="form-input" value={reason} onChange={e => setReason(e.target.value)}>{SALES_RESCHEDULE_REASONS.map(r => <option key={r}>{r}</option>)}</select></label>
          {err && <div className="form-error">{err}</div>}
          <div className="modal-actions">
            <button className="btn-outline" onClick={onClose} disabled={saving}>ยกเลิก</button>
            <button className="login-btn" style={{ width: "auto", padding: "10px 24px" }} onClick={submit} disabled={saving}>{saving ? "กำลังบันทึก..." : "ยืนยันเลื่อนนัด"}</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ---- เช็คอินเข้าพบ ----
const SalesCheckinModal = ({ agent, deal, customers, onClose, onSaved }) => {
  const [files, setFiles] = React.useState([]);
  const [gps, setGps] = React.useState(null);
  const [gpsState, setGpsState] = React.useState("loading");
  const [note, setNote] = React.useState("");
  const [outcome, setOutcome] = React.useState("");
  const [place, setPlace] = React.useState("");
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState("");
  const fileRef = React.useRef(null);
  const boxRef = React.useRef(null);
  useModalA11y(true, () => { if (!saving) onClose(); }, boxRef);
  const cust = deal ? customers.find(c => c.id === deal.customer_id) : null;

  React.useEffect(() => {
    (async () => { const g = await getGps(); if (g) { setGps(g); setGpsState("ok"); } else setGpsState("denied"); })();
  }, []);

  const pickFiles = (e) => {
    const picked = Array.from(e.target.files || []);
    const valid = picked.filter(f => f.type.startsWith("image/") && f.size <= 8 * 1024 * 1024);
    if (valid.length !== picked.length) setErr("รองรับเฉพาะรูปภาพ ไม่เกิน 8 MB"); else setErr("");
    setFiles(valid);
  };

  const submit = async () => {
    setSaving(true); setErr("");
    try {
      const paths = [];
      for (let i = 0; i < files.length; i++) {
        const f = await downscaleImage(files[i]);
        const ext = (f.name.split(".").pop() || "jpg").toLowerCase().replace(/[^a-z0-9]/g, "") || "jpg";
        const path = `${agent.id}/${deal?.id || "general"}/${Date.now()}-${i}.${ext}`;
        const { error } = await sb.storage.from("sales-visits").upload(path, f, { upsert: false, contentType: f.type || "image/jpeg" });
        if (error) throw error;
        paths.push(path);
      }
      const { error } = await sb.from("sales_visits").insert([{
        deal_id: deal?.id || null, customer_id: deal?.customer_id || null, agent_id: agent.id,
        photo_paths: paths.length ? paths : null,
        lat: gps?.lat ?? null, lng: gps?.lng ?? null, accuracy: gps?.accuracy ?? null,
        place_label: place.trim() || null, note: note.trim() || null, outcome: outcome || null,
      }]);
      if (error) throw error;
      if (deal && (deal.stage === "prospect" || deal.stage === "appointment")) {
        await sb.from("sales_deals").update({ stage: "meeting" }).eq("id", deal.id);
      }
      setSaving(false); onSaved();
    } catch (e) { setErr("เช็คอินไม่สำเร็จ: " + (e?.message || String(e))); setSaving(false); }
  };

  return (
    <div className="modal-backdrop">
      <div className="modal-box sales-modal" ref={boxRef} role="dialog" aria-modal="true" aria-label="เช็คอินเข้าพบ" tabIndex={-1} onClick={e => e.stopPropagation()}>
        <div className="modal-head"><strong>📍 เช็คอินเข้าพบ{cust ? ` · ${cust.full_name}` : ""}</strong><button className="modal-close" type="button" aria-label="ปิด" onClick={() => !saving && onClose()}>×</button></div>
        <div className="modal-body sales-form">
          <div className={`sales-gps sales-gps-${gpsState}`}>
            {gpsState === "loading" && "กำลังขอพิกัด GPS..."}
            {gpsState === "ok" && `พิกัด: ${gps.lat.toFixed(5)}, ${gps.lng.toFixed(5)} (±${Math.round(gps.accuracy)} ม.)`}
            {gpsState === "denied" && "ไม่ได้รับพิกัด GPS — เช็คอินต่อได้แต่ไม่มีตำแหน่ง"}
          </div>
          <button type="button" className="proof-upload-btn" onClick={() => fileRef.current?.click()}>{files.length ? `เลือกแล้ว ${files.length} รูป` : "📷 แนบรูปถ่าย (เลือกได้หลายรูป)"}</button>
          <input ref={fileRef} type="file" accept="image/*" capture="environment" multiple style={{ display: "none" }} onChange={pickFiles} />
          <label>สถานที่ (ถ้ามี)<input className="form-input" value={place} onChange={e => setPlace(e.target.value)} placeholder="เช่น บ้านลูกค้า / ร้านกาแฟ" /></label>
          <label>ผลการพบ<select className="form-input" value={outcome} onChange={e => setOutcome(e.target.value)}><option value="">—</option>{SALES_VISIT_OUTCOMES.map(o => <option key={o}>{o}</option>)}</select></label>
          <label>โน้ต/บันทึกการพบ<textarea className="form-input" rows="3" value={note} onChange={e => setNote(e.target.value)} /></label>
          {deal && (deal.stage === "prospect" || deal.stage === "appointment") && <div className="sales-hint">บันทึกแล้วดีลนี้จะเลื่อนเป็น “เข้าพบ” อัตโนมัติ</div>}
          {err && <div className="form-error">{err}</div>}
          <div className="modal-actions">
            <button className="btn-outline" onClick={onClose} disabled={saving}>ยกเลิก</button>
            <button className="login-btn" style={{ width: "auto", padding: "10px 24px" }} onClick={submit} disabled={saving}>{saving ? "กำลังบันทึก..." : "บันทึกเช็คอิน"}</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ---- รายละเอียดดีล (timeline + เช็คอิน + เลื่อนสเตจ) ----
const SalesDealDetail = ({ agent, deal, customer, refreshKey, onClose, onMove, onCheckin, onReschedule, onDelete, onApproved }) => {
  const [logs, setLogs] = React.useState([]);
  const [visits, setVisits] = React.useState([]);
  const [approving, setApproving] = React.useState(false);
  React.useEffect(() => {
    Promise.all([
      sb.from("sales_stage_logs").select("*").eq("deal_id", deal.id).order("created_at"),
      sb.from("sales_visits").select("*").eq("deal_id", deal.id).order("checked_in_at", { ascending: false }),
    ]).then(([l, v]) => { setLogs(l.data || []); setVisits(v.data || []); });
  }, [deal.id, deal.stage, refreshKey]);
  const fmtTs = (ts) => new Date(ts).toLocaleString("th-TH", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" });

  // manager/admin ที่ขายเอง (บอร์ด "ของฉัน" ในแอป admin/manager) ติ๊กอนุมัติจากโมดัลนี้ได้เลย
  // — ตัวแทนจริง (login.html ไม่มี PANEL_ROLE) เห็นแค่ป้ายสถานะ
  const canApprove = ["admin", "manager"].includes(window.PANEL_ROLE) && deal.stage === "closed_won";
  const toggleApproved = async () => {
    const next = deal.approved_at ? null : new Date().toISOString();
    setApproving(true);
    const { error } = await sb.from("sales_deals")
      .update({ approved_at: next, approved_by: next ? agent?.id || null : null })
      .eq("id", deal.id);
    setApproving(false);
    if (error) { uiToast("บันทึกสถานะอนุมัติไม่สำเร็จ: " + error.message, { type: "error" }); return; }
    onApproved && onApproved();
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal-box sales-modal sales-detail" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><strong>{deal.title || customer?.full_name || "ดีล"}</strong><button className="modal-close" onClick={onClose}>×</button></div>
        <div className="modal-body">
          <div className="sales-detail-meta">
            <SalesStagePill stage={deal.stage} />
            {deal.stage === "closed_won" && <DealApprovePill approvedAt={deal.approved_at} />}
            {customer && <span>{customer.full_name}{customer.phone ? ` · ${customer.phone}` : ""}</span>}
            {deal.product_type && <span>· {deal.product_type}</span>}
          </div>
          <div className="sales-detail-stats">
            <div><span>เป้า APE</span><strong>{salesMoney(deal.expected_ape)} ฿</strong></div>
            {deal.stage === "closed_won" && <div><span>APE จริง</span><strong>{salesMoney(deal.actual_ape)} ฿</strong></div>}
            {deal.stage === "closed_won" && deal.actual_fyc != null && <div><span>NBC</span><strong>{salesMoney(deal.actual_fyc)} ฿</strong></div>}
            {deal.stage === "closed_lost" && <div><span>เหตุผล</span><strong>{deal.lost_reason || "-"}</strong></div>}
            {deal.next_action_at && <div><span>นัดถัดไป</span><strong>{fmtDate(deal.next_action_at)}</strong></div>}
            {deal.reschedule_count > 0 && <div><span>เลื่อนนัด</span><strong>{deal.reschedule_count} ครั้ง</strong></div>}
          </div>
          {canApprove && (
            <label className={`deal-approve-box ${deal.approved_at ? "done" : ""}`}>
              <input type="checkbox" checked={!!deal.approved_at} disabled={approving} onChange={toggleApproved} />
              <span>
                <b>บริษัทอนุมัติแล้ว</b>
                <small>
                  {deal.approved_at
                    ? `✓ อนุมัติเมื่อ ${fmtTs(deal.approved_at)}`
                    : "ติ๊กเมื่อกรมธรรม์ได้รับอนุมัติ — ยอดจะย้ายจาก “รออนุมัติ” ไปนับเป็นสกอร์หลักในการแข่งขันทีม"}
                </small>
              </span>
            </label>
          )}
          {deal.stage === "closed_won" && (deal.won_doc_paths || []).length > 0 && (
            <div className="sales-won-docs">
              <h4 className="sales-sub-h">รูปหลักฐานปิดดีล ({deal.won_doc_paths.length})</h4>
              <div className="sales-visit-thumbs">{deal.won_doc_paths.map((p, i) => <a key={i} href={salesVisitUrl(p)} target="_blank" rel="noreferrer"><img src={salesVisitUrl(p)} alt="" /></a>)}</div>
            </div>
          )}
          {SALES_OPEN_STAGES.includes(deal.stage) && (
            <div className="sales-move-row">
              <span className="sales-move-label">เลื่อนสเตจ:</span>
              {SALES_STAGES.filter((s, i) => i > SALES_STAGES.findIndex(x => x.key === deal.stage)).map(s => (
                <button key={s.key} className={`sales-move-btn st-${s.cls}`} onClick={() => onMove(deal, s.key)}>{s.label}</button>
              ))}
            </div>
          )}
          <div className="sales-detail-actions">
            {deal.stage === "appointment" && <button className="btn-outline" style={{ width: "auto", padding: "8px 18px" }} onClick={() => onReschedule(deal)}>📅 เลื่อนนัด</button>}
            <button className="login-btn" style={{ width: "auto", padding: "8px 18px" }} onClick={() => onCheckin(deal)}>📍 เช็คอินเข้าพบ</button>
            {onDelete && <button className="sales-deal-del" style={{ width: "auto", padding: "8px 18px" }} onClick={() => onDelete(deal)}>🗑 ลบดีล</button>}
          </div>
          <h4 className="sales-sub-h">ไทม์ไลน์</h4>
          <ul className="sales-timeline">
            {logs.map(l => {
              const isReschedule = l.from_stage === l.to_stage;
              return (
                <li key={l.id}>
                  <span className="sales-tl-dot" />
                  <span>{isReschedule ? `📅 ${l.note || "เลื่อนนัด"}` : <>{l.from_stage ? `${salesStageLabel(l.from_stage)} → ` : "เริ่ม: "}{salesStageLabel(l.to_stage)}</>}</span>
                  <em>{fmtTs(l.created_at)}</em>
                </li>
              );
            })}
            {!logs.length && <li className="sales-empty">ยังไม่มีประวัติ</li>}
          </ul>
          <h4 className="sales-sub-h">ประวัติเช็คอิน ({visits.length})</h4>
          <div className="sales-visit-list">
            {visits.map(v => (
              <div key={v.id} className="sales-visit-item">
                <div className="sales-visit-thumbs">{(v.photo_paths || []).map((p, i) => <img key={i} src={salesVisitUrl(p)} alt="" />)}</div>
                <div className="sales-visit-body">
                  {v.outcome && <span className="sales-stage-pill">{v.outcome}</span>}
                  {v.note && <p>{v.note}</p>}
                  <em>{fmtTs(v.checked_in_at)}{v.place_label ? ` · ${v.place_label}` : ""}{v.lat ? <> · <a href={`https://maps.google.com/?q=${v.lat},${v.lng}`} target="_blank" rel="noreferrer">แผนที่</a></> : ""}</em>
                </div>
              </div>
            ))}
            {!visits.length && <div className="sales-empty">ยังไม่มีเช็คอิน</div>}
          </div>
        </div>
      </div>
    </div>
  );
};

// ---- ยืนยันลบดีล (ต้องกรอกรหัสตัวแทน 6 หลักซ้ำ) ----
const SalesDeleteDealModal = ({ agent, deal, customer, onClose, onConfirm }) => {
  const [code, setCode] = React.useState("");
  const [err, setErr] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => { ref.current && ref.current.focus(); }, []);
  const submit = async () => {
    if (!/^\d{6}$/.test(code)) { setErr("กรอกรหัสตัวแทน 6 หลัก"); return; }
    if (code !== String(agent.agent_code)) { setErr("รหัสไม่ถูกต้อง"); return; }
    setBusy(true); setErr("");
    try { await onConfirm(); }
    catch (e) { setErr("ลบไม่สำเร็จ: " + (e?.message || String(e))); setBusy(false); }
  };
  const name = deal.title || customer?.full_name || "ดีล";
  return (
    <div className="modal-backdrop" onClick={() => !busy && onClose()}>
      <div className="modal-box sales-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><strong>🗑 ยืนยันลบดีล</strong><button className="modal-close" onClick={() => !busy && onClose()}>×</button></div>
        <div className="modal-body sales-form">
          <div className="sales-hint">กำลังจะลบดีล “{name}” พร้อมประวัติการเลื่อนสเตจ การลบนี้กู้คืนไม่ได้ — ใส่รหัสตัวแทน 6 หลักเพื่อยืนยัน</div>
          <label>รหัสตัวแทน
            <input ref={ref} className="form-input" type="password" inputMode="numeric" maxLength={6} autoComplete="off"
              value={code} onChange={e => setCode(e.target.value.replace(/\D/g, "").slice(0, 6))}
              onKeyDown={e => e.key === "Enter" && submit()} />
          </label>
          {err && <div className="form-error">{err}</div>}
          <div className="modal-actions">
            <button className="btn-outline" onClick={onClose} disabled={busy}>ยกเลิก</button>
            <button className="btn-delete" style={{ width: "auto", padding: "10px 24px" }} onClick={submit} disabled={busy}>{busy ? "กำลังลบ..." : "ยืนยันลบดีล"}</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ---- Tab หลัก ----
const SalesTab = ({ agent }) => {
  const [loading, setLoading] = React.useState(true);
  const [customers, setCustomers] = React.useState([]);
  const [deals, setDeals] = React.useState([]);
  const [refreshKey, setRefreshKey] = React.useState(0);
  const [view, setView] = React.useState("board");
  const [stageTab, setStageTab] = React.useState("all");
  const [custModal, setCustModal] = React.useState(null);
  const [dealModalCust, setDealModalCust] = React.useState(null);
  const [openDeal, setOpenDeal] = React.useState(null);
  const [moveModal, setMoveModal] = React.useState(null);
  const [checkinModal, setCheckinModal] = React.useState(null);
  const [rescheduleDeal, setRescheduleDeal] = React.useState(null);
  const [deleteTarget, setDeleteTarget] = React.useState(null);

  const load = async () => {
    const [c, d] = await Promise.all([
      sb.from("sales_customers").select("*").eq("agent_id", agent.id).order("created_at", { ascending: false }),
      sb.from("sales_deals").select("*").eq("agent_id", agent.id).order("updated_at", { ascending: false }),
    ]);
    // อย่าล้างรายการทิ้งถ้า refetch ผิดพลาด (ป้องกันลูกค้า "หายไป" หลังสร้างดีล)
    if (!c.error) setCustomers(c.data || []);
    if (!d.error) setDeals(d.data || []);
    setRefreshKey(k => k + 1); setLoading(false);
  };
  React.useEffect(() => { load(); }, [agent.id]);

  const customerOf = (id) => customers.find(c => c.id === id);
  const liveOpenDeal = openDeal ? (deals.find(d => d.id === openDeal.id) || openDeal) : null;
  const stageCounts = SALES_STAGES.reduce((acc, s) => { acc[s.key] = deals.filter(d => d.stage === s.key).length; return acc; }, {});
  const openCount = deals.filter(d => SALES_OPEN_STAGES.includes(d.stage)).length;
  const wonApe = deals.filter(d => d.stage === "closed_won").reduce((a, d) => a + Number(d.actual_ape || 0), 0);
  const visibleDeals = stageTab === "all" ? deals : deals.filter(d => d.stage === stageTab);
  const afterSave = () => { setCustModal(null); setDealModalCust(null); setMoveModal(null); setCheckinModal(null); setRescheduleDeal(null); load(); };
  const deleteCustomer = async (c) => {
    const nDeals = deals.filter(d => d.customer_id === c.id).length;
    const msg = nDeals
      ? `ลบลูกค้า “${c.full_name || c.nickname || "—"}” พร้อมดีล ${nDeals} รายการ?`
      : `ลบลูกค้า “${c.full_name || c.nickname || "—"}”?`;
    if (!confirm(msg)) return;
    const { error: e1 } = await sb.from("sales_deals").delete().eq("customer_id", c.id).eq("agent_id", agent.id);
    const { error: e2 } = await sb.from("sales_customers").delete().eq("id", c.id).eq("agent_id", agent.id);
    if (e1 || e2) { uiToast("ลบไม่สำเร็จ: " + (e1 || e2).message, { type: "error" }); return; }
    load();
  };
  const handleMove = (d, toStage) => {
    if (["appointment", "closed_won", "closed_lost"].includes(toStage)) setMoveModal({ deal: d, toStage });
    else sb.from("sales_deals").update({ stage: toStage }).eq("id", d.id).then(({ error }) => {
      if (error) uiToast("ย้ายสเตจไม่สำเร็จ: " + error.message, { type: "error" });
      load();
    });
  };
  const deleteDeal = (d) => setDeleteTarget(d);
  const confirmDeleteDeal = async () => {
    const d = deleteTarget;
    // sales_visits FK ไม่ได้ cascade ใน GA83F — ลบลูกก่อนกันชน FK
    await sb.from("sales_visits").delete().eq("deal_id", d.id);
    const { error } = await sb.from("sales_deals").delete().eq("id", d.id).eq("agent_id", agent.id);
    if (error) throw error;
    setDeleteTarget(null); setOpenDeal(null); load();
  };

  if (loading) return <section className="scene"><SectionHead eyebrow="01 — Prospecting" title="Prospecting" /><div className="sales-empty">กำลังโหลด...</div></section>;

  return (
    <section className="scene sales-scene">
      <SectionHead eyebrow="01 — Prospecting" title="Prospecting"
        sub="จัดการรายชื่อลูกค้ามุ่งหวัง ติดตามดีลแต่ละสเตจ และเช็คอินตอนเข้าพบ"
        right={<div className="sales-head-actions">
          <button className="login-btn" style={{ width: "auto", padding: "9px 18px" }} onClick={() => setCustModal({})}>+ เพิ่มลูกค้า</button>
        </div>} />

      <div className="sales-summary">
        <div className="sales-sum-card"><span>ดีลที่เปิดอยู่</span><strong>{openCount}</strong></div>
        <div className="sales-sum-card"><span>ปิดได้</span><strong>{stageCounts.closed_won}</strong></div>
        <div className="sales-sum-card"><span>APE ปิดได้</span><strong>{salesMoney(wonApe)}</strong><em>บาท</em></div>
        <div className="sales-sum-card"><span>ลูกค้าทั้งหมด</span><strong>{customers.length}</strong></div>
      </div>

      <div className="sales-viewswitch">
        <button className={view === "board" ? "active" : ""} onClick={() => setView("board")}>บอร์ดดีล</button>
        <button className={view === "customers" ? "active" : ""} onClick={() => setView("customers")}>รายชื่อลูกค้า</button>
      </div>

      {view === "board" && (
        <>
          <div className="sales-stage-tabs">
            <button className={stageTab === "all" ? "active" : ""} onClick={() => setStageTab("all")}>ทั้งหมด <i>{deals.length}</i></button>
            {SALES_STAGES.map(s => (
              <button key={s.key} className={`st-${s.cls} ${stageTab === s.key ? "active" : ""}`} onClick={() => setStageTab(s.key)}>{s.short} <i>{stageCounts[s.key]}</i></button>
            ))}
          </div>
          <div className="sales-deal-grid">
            {visibleDeals.map(d => {
              const c = customerOf(d.customer_id);
              return (
                <button key={d.id} className="sales-deal-card" onClick={() => setOpenDeal(d)}>
                  <div className="sales-deal-top"><SalesStagePill stage={d.stage} />{d.stage === "closed_won" && <DealApprovePill approvedAt={d.approved_at} />}<span className="sales-deal-ape">{salesMoney(d.stage === "closed_won" ? d.actual_ape : d.expected_ape)}฿</span></div>
                  <strong>{d.title || c?.full_name || c?.nickname || "ดีล"}</strong>
                  <span className="sales-deal-cust">👤 {[c?.full_name, c?.nickname && `(${c.nickname})`].filter(Boolean).join(" ") || "—"}{c?.phone ? ` · ${c.phone}` : ""}</span>
                  <span className="sales-deal-foot">
                    {d.product_type && <span className="sales-deal-prod">{d.product_type}</span>}
                    {d.stage === "appointment" && d.next_action_at && <span className="sales-deal-appt">📅 {fmtDate(d.next_action_at)}</span>}
                    {d.reschedule_count > 0 && <span className="sales-deal-resched">เลื่อน {d.reschedule_count} ครั้ง</span>}
                  </span>
                </button>
              );
            })}
            {!visibleDeals.length && <div className="sales-empty">ยังไม่มีดีลในสเตจนี้ — ไปที่ “รายชื่อลูกค้า” เพื่อสร้างดีล</div>}
          </div>
        </>
      )}

      {view === "customers" && (() => {
        // แสดงเฉพาะลูกค้าที่ยังไม่มีดีล — เมื่อสร้างดีลแล้วจะย้ายไปอยู่ในบอร์ดดีล
        const noDealCustomers = customers.filter(c => !deals.some(d => d.customer_id === c.id));
        return (
        <div className="sales-cust-list">
          {noDealCustomers.map(c => {
            const cDeals = deals.filter(d => d.customer_id === c.id);
            return (
              <div key={c.id} className="sales-cust-card">
                <div className="sales-cust-main">
                  <strong>{c.full_name}{c.nickname ? ` (${c.nickname})` : ""}</strong>
                  <span>{[c.phone, c.interest].filter(Boolean).join(" · ") || "—"}</span>
                  <span className="sales-cust-deals">{cDeals.length} ดีล{!c.consent_pdpa ? " · ⚠ ไม่มี consent" : ""}</span>
                </div>
                <div className="sales-cust-actions">
                  <button className="sales-cust-btn del" onClick={() => deleteCustomer(c)}>ลบ</button>
                  <button className="sales-cust-btn edit" onClick={() => setCustModal(c)}>แก้ไข</button>
                  <button className="sales-cust-btn add" onClick={() => setDealModalCust(c)}>+ ดีล</button>
                </div>
              </div>
            );
          })}
          {!noDealCustomers.length && <div className="sales-empty">{customers.length ? "ลูกค้าทุกคนมีดีลแล้ว — ดูได้ที่ “บอร์ดดีล”" : "ยังไม่มีลูกค้า กด “+ เพิ่มลูกค้า” เพื่อเริ่ม"}</div>}
        </div>
        );
      })()}

      {custModal && <SalesCustomerModal agent={agent} customer={custModal.id ? custModal : null} onClose={() => setCustModal(null)} onSaved={afterSave} />}
      {dealModalCust && <SalesDealModal agent={agent} customer={dealModalCust} onClose={() => setDealModalCust(null)} onSaved={afterSave} />}
      {liveOpenDeal && <SalesDealDetail agent={agent} deal={liveOpenDeal} customer={customerOf(liveOpenDeal.customer_id)} refreshKey={refreshKey} onClose={() => setOpenDeal(null)} onMove={handleMove} onCheckin={(d) => setCheckinModal(d)} onReschedule={(d) => setRescheduleDeal(d)} onDelete={deleteDeal} onApproved={load} />}
      {rescheduleDeal && <SalesRescheduleModal deal={rescheduleDeal} onClose={() => setRescheduleDeal(null)} onSaved={afterSave} />}
      {deleteTarget && <SalesDeleteDealModal agent={agent} deal={deleteTarget} customer={customerOf(deleteTarget.customer_id)} onClose={() => setDeleteTarget(null)} onConfirm={confirmDeleteDeal} />}
      {moveModal && <SalesStageMoveModal agent={agent} deal={moveModal.deal} toStage={moveModal.toStage} onClose={() => setMoveModal(null)} onSaved={afterSave} />}
      {checkinModal && <SalesCheckinModal agent={agent} deal={checkinModal.id ? checkinModal : null} customers={customers} onClose={() => setCheckinModal(null)} onSaved={afterSave} />}
    </section>
  );
};

// ============================================================================
//  Recruiting (สรรหาตัวแทน) — ฝั่งตัวแทน · pipeline คู่ขนานกับ Prospecting
//  ตารางเดียว: 1 ผู้สมัคร = 1 แถว (recruit_candidates) + audit (recruit_stage_logs)
// ============================================================================
const RecruitStagePill = ({ stage }) => {
  const s = RECRUIT_STAGE_MAP[stage];
  return <span className={`sales-stage-pill st-${s?.cls || "prospect"}`}>{s?.label || stage}</span>;
};

// ---- เพิ่ม/แก้ไขผู้สมัคร ----
const RecruitCandidateModal = ({ agent, candidate, onClose, onSaved }) => {
  const editing = !!candidate?.id;
  const [form, setForm] = React.useState({
    nickname: candidate?.nickname || "",
    gender: candidate?.gender || "", occupation: candidate?.occupation || "",
    source: candidate?.source || "", motivation: candidate?.motivation || "",
    rating: candidate?.rating || 0, note: candidate?.note || "",
    consent_pdpa: candidate?.consent_pdpa || false,
  });
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState("");
  const boxRef = React.useRef(null);
  useModalA11y(true, () => { if (!saving) onClose(); }, boxRef);
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const submit = async () => {
    if (!form.nickname.trim()) { setErr("กรุณากรอกชื่อเล่นผู้สมัคร"); return; }
    if (!form.consent_pdpa) { setErr("ต้องได้รับความยินยอม (PDPA) จากผู้สมัครก่อนบันทึกข้อมูล"); return; }
    setSaving(true); setErr("");
    // ใช้ชื่อเล่นเป็นชื่อหลัก (full_name เป็น NOT NULL ใน DB) — ฟอร์มไม่เก็บชื่อ-นามสกุลแล้ว
    const payload = { ...form, nickname: form.nickname.trim(), full_name: form.nickname.trim(), rating: Number(form.rating) || 0, agent_id: agent.id };
    let error;
    if (editing) ({ error } = await sb.from("recruit_candidates").update(payload).eq("id", candidate.id).eq("agent_id", agent.id));
    else ({ error } = await sb.from("recruit_candidates").insert([payload]));
    if (error) { setErr("บันทึกไม่สำเร็จ: " + error.message); setSaving(false); return; }
    setSaving(false); onSaved();
  };

  return (
    <div className="modal-backdrop">
      <div className="modal-box sales-modal" ref={boxRef} role="dialog" aria-modal="true" aria-label={editing ? "แก้ไขผู้สมัคร" : "เพิ่มผู้สมัครใหม่"} tabIndex={-1} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <strong>{editing ? "แก้ไขผู้สมัคร" : "เพิ่มผู้สมัครใหม่"}</strong>
          <button className="modal-close" type="button" aria-label="ปิด" onClick={() => !saving && onClose()}>×</button>
        </div>
        <div className="modal-body sales-form">
          <div className="sales-form-grid">
            <label>ชื่อเล่น<input className="form-input" value={form.nickname} onChange={e => set("nickname", e.target.value)} /></label>
            <label>เพศ<select className="form-input" value={form.gender} onChange={e => set("gender", e.target.value)}><option value="">—</option>{SALES_GENDERS.map(g => <option key={g}>{g}</option>)}</select></label>
            <label>อาชีพปัจจุบัน<input className="form-input" value={form.occupation} onChange={e => set("occupation", e.target.value)} /></label>
            <label>รู้จักกันยังไง<select className="form-input" value={form.source} onChange={e => set("source", e.target.value)}><option value="">—</option>{RECRUIT_SOURCES.map(s => <option key={s}>{s}</option>)}</select></label>
            <label>โอกาสสอบ<select className="form-input" value={form.rating} onChange={e => set("rating", e.target.value)}>{[0,1,2,3,4,5].map(n => <option key={n} value={n}>{n} ดาว</option>)}</select></label>
          </div>
          <label>แรงจูงใจ/เหตุผลที่สนใจ<textarea className="form-input" rows="2" value={form.motivation} onChange={e => set("motivation", e.target.value)} placeholder="เช่น อยากมีรายได้เสริม / มองหาอาชีพอิสระ" /></label>
          <label>โน้ต<textarea className="form-input" rows="2" value={form.note} onChange={e => set("note", e.target.value)} /></label>
          <label className="sales-consent">
            <input type="checkbox" checked={form.consent_pdpa} onChange={e => set("consent_pdpa", e.target.checked)} />
            <span>ผู้สมัครยินยอมให้เก็บและใช้ข้อมูลส่วนบุคคลเพื่อการติดต่อสรรหา (PDPA)</span>
          </label>
          {err && <div className="form-error">{err}</div>}
          <div className="modal-actions">
            <button className="btn-outline" onClick={onClose} disabled={saving}>ยกเลิก</button>
            <button className="login-btn" style={{ width: "auto", padding: "10px 24px" }} onClick={submit} disabled={saving}>{saving ? "กำลังบันทึก..." : "บันทึก"}</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ---- เลื่อนสเตจ (เก็บข้อมูลเฉพาะสเตจ) ----
const RecruitStageMoveModal = ({ agent, candidate, toStage, onClose, onSaved }) => {
  const [val, setVal] = React.useState({ next_action_at: "", exam_date: "", started_at: todayDateKey(), drop_reason: RECRUIT_DROP_REASONS[0] });
  const [files, setFiles] = React.useState([]);
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState("");
  const s = RECRUIT_STAGE_MAP[toStage];
  const fileRef = React.useRef(null);
  const boxRef = React.useRef(null);
  useModalA11y(true, () => { if (!saving) onClose(); }, boxRef);
  const set = (k, v) => setVal(p => ({ ...p, [k]: v }));
  const pickFiles = (e) => {
    const picked = Array.from(e.target.files || []);
    const valid = picked.filter(f => (f.type.startsWith("image/") || f.type === "application/pdf") && f.size <= 8 * 1024 * 1024);
    if (valid.length !== picked.length) setErr("รองรับเฉพาะรูปภาพ หรือ PDF ไม่เกิน 8 MB"); else setErr("");
    setFiles(valid);
  };
  const submit = async () => {
    const payload = { stage: toStage };
    if (RECRUIT_APPT_STAGES.includes(toStage)) { if (!val.next_action_at) { setErr("กรุณาเลือกวันนัด"); return; } payload.next_action_at = val.next_action_at; }
    if (toStage === "exam") { if (!val.exam_date) { setErr("กรุณาเลือกวันสอบ/ใบอนุญาต"); return; } payload.exam_date = val.exam_date; }
    if (toStage === "recruited") {
      if (!files.length && !(candidate.doc_paths || []).length) { setErr("กรุณาแนบเอกสารหรือรูปหลักฐานการสอบผ่าน"); return; }
      payload.started_at = val.started_at || todayDateKey();
    }
    if (toStage === "dropped")   { payload.drop_reason = val.drop_reason; }
    else                         { payload.drop_reason = null; }   // ออกจาก dropped (เช่น สอบใหม่) ล้างเหตุผลเดิม
    setSaving(true); setErr("");
    try {
      if (toStage === "recruited" && files.length) {
        const paths = [...(candidate.doc_paths || [])];
        for (let i = 0; i < files.length; i++) {
          const f = await downscaleImage(files[i]);
          const ext = (f.name.split(".").pop() || "jpg").toLowerCase().replace(/[^a-z0-9]/g, "") || "jpg";
          const path = `recruit/${agent.id}/${candidate.id}/start-${Date.now()}-${i}.${ext}`;
          const { error } = await sb.storage.from("sales-visits").upload(path, f, { upsert: false, contentType: f.type || "image/jpeg" });
          if (error) throw error;
          paths.push(path);
        }
        payload.doc_paths = paths;
      }
      const { error } = await sb.from("recruit_candidates").update(payload).eq("id", candidate.id);
      if (error) throw error;
      // เลื่อนเป็น "สอบผ่าน" = นับเป็น Recruit ของระบบ + ให้คะแนน (เท่า Recruit เดิม 1,000)
      // บันทึกผ่าน agent_activities ชื่อ "Recruit" เพื่อให้ไหลเข้าทุกหน้าที่นับ Recruit อยู่แล้ว
      // idempotent: 1 ผู้สมัคร = 1 รายการ (กันนับ/ให้คะแนนซ้ำ)
      if (toStage === "recruited") {
        try {
          const tag = `recruit:${candidate.id}`;
          const recruitPts = POINT_RULES.recruit;
          const { data: exists } = await sb.from("agent_activities").select("id").eq("agent_id", agent.id).ilike("note", `%${tag}%`).limit(1);
          if ((!exists || !exists.length) && recruitPts > 0) {
            const who = candidate.full_name || candidate.nickname || "ผู้สมัคร";
            const proof = (payload.doc_paths || candidate.doc_paths || [])[0] || null;
            await sb.from("agent_activities").insert([{
              agent_id: agent.id, date: todayDateKey(),
              name: "Recruit", points: recruitPts,
              note: `สอบผ่าน: ${who} | ${tag}`, proof_path: proof,
            }]);
          }
        } catch (_) { /* ให้คะแนนล้มเหลวไม่กระทบการเลื่อนสเตจ */ }
      }
      setSaving(false); onSaved();
    } catch (e) { setErr("อัปเดตไม่สำเร็จ: " + (e?.message || String(e))); setSaving(false); }
  };
  return (
    <div className="modal-backdrop">
      <div className="modal-box sales-modal" ref={boxRef} role="dialog" aria-modal="true" aria-label={`เลื่อนเป็น ${s?.label || ""}`} tabIndex={-1} onClick={e => e.stopPropagation()}>
        <div className="modal-head"><strong>เลื่อนเป็น “{s?.label}”</strong><button className="modal-close" type="button" aria-label="ปิด" onClick={() => !saving && onClose()}>×</button></div>
        <div className="modal-body sales-form">
          {RECRUIT_APPT_STAGES.includes(toStage) && <label>วัน/เวลานัด<input type="date" className="form-input" value={val.next_action_at} onChange={e => set("next_action_at", e.target.value)} /></label>}
          {toStage === "exam" && <label>วันสอบ/ใบอนุญาต<input type="date" className="form-input" value={val.exam_date} onChange={e => set("exam_date", e.target.value)} /></label>}
          {toStage === "recruited" && <label>วันที่สอบผ่าน<input type="date" className="form-input" value={val.started_at} onChange={e => set("started_at", e.target.value)} /></label>}
          {toStage === "recruited" && <label>แนบเอกสาร/รูปหลักฐาน (จำเป็น)
            <input ref={fileRef} type="file" accept="image/*,application/pdf" multiple style={{ display: "none" }} onChange={pickFiles} />
            <button type="button" className="btn-outline" style={{ width: "auto", padding: "8px 16px" }} onClick={() => fileRef.current?.click()}>📎 เลือกไฟล์{files.length ? ` (${files.length})` : ""}</button>
            {files.length > 0 && <div className="sales-visit-thumbs" style={{ marginTop: 8 }}>{files.map((f, i) => f.type === "application/pdf"
              ? <span key={i} className="recruit-file-chip">📄 {f.name}</span>
              : <img key={i} src={URL.createObjectURL(f)} alt="" />)}</div>}
          </label>}
          {toStage === "dropped" && <label>เหตุผลที่สอบไม่ผ่าน<select className="form-input" value={val.drop_reason} onChange={e => set("drop_reason", e.target.value)}>{RECRUIT_DROP_REASONS.map(r => <option key={r}>{r}</option>)}</select></label>}
          {toStage === "recruited" && <div className="sales-hint">บันทึกแล้วจะนับเป็น “Recruit” ของระบบ และได้รับ <strong>{POINT_RULES.recruit.toLocaleString()} คะแนน</strong> (นับครั้งเดียวต่อผู้สมัคร)</div>}
          {err && <div className="form-error">{err}</div>}
          <div className="modal-actions">
            <button className="btn-outline" onClick={onClose} disabled={saving}>ยกเลิก</button>
            <button className="login-btn" style={{ width: "auto", padding: "10px 24px" }} onClick={submit} disabled={saving}>{saving ? "กำลังบันทึก..." : "ยืนยัน"}</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ---- เลื่อนนัด / เลื่อนวันสอบ (เปลี่ยนวันโดยคงสเตจเดิม) ----
const RecruitRescheduleModal = ({ candidate, onClose, onSaved }) => {
  const isExam = candidate.stage === "exam";   // สเตจสอบ = แก้ exam_date, อื่น ๆ = แก้ next_action_at
  const [date, setDate] = React.useState("");
  const [reason, setReason] = React.useState(RECRUIT_RESCHEDULE_REASONS[0]);
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState("");
  const nextCount = (candidate.reschedule_count || 0) + 1;
  const stageLabel = recruitStageLabel(candidate.stage);
  const curDate = isExam ? candidate.exam_date : candidate.next_action_at;
  const title = isExam ? "เลื่อนวันสอบ/ใบอนุญาต" : `เลื่อนนัด (${stageLabel})`;
  const submit = async () => {
    if (!date) { setErr(isExam ? "กรุณาเลือกวันสอบใหม่" : "กรุณาเลือกวันนัดใหม่"); return; }
    setSaving(true); setErr("");
    const patch = isExam
      ? { exam_date: date, reschedule_count: nextCount }
      : { next_action_at: date, reschedule_count: nextCount };
    const { error } = await sb.from("recruit_candidates").update(patch).eq("id", candidate.id);
    if (error) { setErr("เลื่อนไม่สำเร็จ: " + error.message); setSaving(false); return; }
    // สเตจไม่เปลี่ยน trigger จึงไม่เขียน log ให้ — เขียนเองเพื่อให้ขึ้นไทม์ไลน์
    await sb.from("recruit_stage_logs").insert([{
      candidate_id: candidate.id, agent_id: candidate.agent_id,
      from_stage: candidate.stage, to_stage: candidate.stage,
      note: `${isExam ? "เลื่อนวันสอบ" : "เลื่อนนัด"}ครั้งที่ ${nextCount}: ${reason} → ${fmtDate(date)}`,
    }]);
    setSaving(false); onSaved();
  };
  return (
    <div className="modal-backdrop" onClick={() => !saving && onClose()}>
      <div className="modal-box sales-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><strong>📅 {title}</strong><button className="modal-close" onClick={() => !saving && onClose()}>×</button></div>
        <div className="modal-body sales-form">
          {curDate && <div className="sales-hint">{isExam ? "วันสอบเดิม" : "นัดเดิม"}: {fmtDate(curDate)}{candidate.reschedule_count ? ` · เลื่อนมาแล้ว ${candidate.reschedule_count} ครั้ง` : ""}</div>}
          <label>{isExam ? "วันสอบใหม่" : "วันนัดใหม่"}<input type="date" className="form-input" value={date} onChange={e => setDate(e.target.value)} /></label>
          <label>เหตุผลที่เลื่อน<select className="form-input" value={reason} onChange={e => setReason(e.target.value)}>{RECRUIT_RESCHEDULE_REASONS.map(r => <option key={r}>{r}</option>)}</select></label>
          {err && <div className="form-error">{err}</div>}
          <div className="modal-actions">
            <button className="btn-outline" onClick={onClose} disabled={saving}>ยกเลิก</button>
            <button className="login-btn" style={{ width: "auto", padding: "10px 24px" }} onClick={submit} disabled={saving}>{saving ? "กำลังบันทึก..." : "ยืนยัน"}</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ---- รายละเอียดผู้สมัคร (timeline + เลื่อนสเตจ) ----
const RecruitDetail = ({ candidate, refreshKey, onClose, onMove, onReschedule, onEdit, onDelete }) => {
  const [logs, setLogs] = React.useState([]);
  React.useEffect(() => {
    sb.from("recruit_stage_logs").select("*").eq("candidate_id", candidate.id).order("created_at")
      .then(l => setLogs(l.data || []));
  }, [candidate.id, candidate.stage, refreshKey]);
  const fmtTs = (ts) => new Date(ts).toLocaleString("th-TH", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" });
  const curIdx = RECRUIT_STAGES.findIndex(x => x.key === candidate.stage);

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal-box sales-modal sales-detail" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><strong>{candidate.full_name || candidate.nickname || "ผู้สมัคร"}</strong><button className="modal-close" onClick={onClose}>×</button></div>
        <div className="modal-body">
          <div className="sales-detail-meta">
            <RecruitStagePill stage={candidate.stage} />
            {candidate.occupation && <span>{candidate.occupation}</span>}
          </div>
          <div className="sales-detail-stats">
            {candidate.source && <div><span>รู้จักจาก</span><strong>{candidate.source}</strong></div>}
            {candidate.rating > 0 && <div><span>โอกาสสอบ</span><strong>{candidate.rating} ดาว</strong></div>}
            {candidate.stage === "exam" && candidate.exam_date && <div><span>วันสอบ/ใบอนุญาต</span><strong>{fmtDate(candidate.exam_date)}</strong></div>}
            {candidate.stage === "recruited" && candidate.started_at && <div><span>สอบผ่าน</span><strong>{fmtDate(candidate.started_at)}</strong></div>}
            {candidate.stage === "dropped" && <div><span>เหตุผล</span><strong>{candidate.drop_reason || "-"}</strong></div>}
            {RECRUIT_APPT_STAGES.includes(candidate.stage) && candidate.next_action_at && <div><span>นัดถัดไป</span><strong>{fmtDate(candidate.next_action_at)}</strong></div>}
            {candidate.reschedule_count > 0 && <div><span>เลื่อนนัด</span><strong>{candidate.reschedule_count} ครั้ง</strong></div>}
          </div>
          {candidate.motivation && <p className="sales-hint" style={{ marginTop: 4 }}>💬 {candidate.motivation}</p>}
          {candidate.stage === "recruited" && (candidate.doc_paths || []).length > 0 && (
            <div className="sales-won-docs">
              <h4 className="sales-sub-h">เอกสาร/รูปหลักฐานสอบผ่าน ({candidate.doc_paths.length})</h4>
              <div className="sales-visit-thumbs">{candidate.doc_paths.map((p, i) => /\.pdf$/i.test(p)
                ? <a key={i} className="recruit-file-chip" href={recruitDocUrl(p)} target="_blank" rel="noreferrer">📄 เอกสาร {i + 1}</a>
                : <a key={i} href={recruitDocUrl(p)} target="_blank" rel="noreferrer"><img src={recruitDocUrl(p)} alt="" /></a>)}</div>
            </div>
          )}
          {RECRUIT_OPEN_STAGES.includes(candidate.stage) && (
            <div className="sales-move-row">
              <span className="sales-move-label">เลื่อนสเตจ:</span>
              {RECRUIT_STAGES.filter((s, i) => i > curIdx).map(s => (
                <button key={s.key} className={`sales-move-btn st-${s.cls}`} onClick={() => onMove(candidate, s.key)}>{s.label}</button>
              ))}
            </div>
          )}
          {candidate.stage === "dropped" && (
            <div className="sales-move-row">
              <span className="sales-move-label">นำกลับเข้า pipeline:</span>
              <button className="sales-move-btn st-exam" onClick={() => onMove(candidate, "exam")}>สอบใหม่</button>
              <button className="sales-move-btn st-prospect" onClick={() => onMove(candidate, "prospect")}>↩︎ เริ่มใหม่ (รายชื่อ)</button>
            </div>
          )}
          <div className="sales-detail-actions">
            {(RECRUIT_APPT_STAGES.includes(candidate.stage) || candidate.stage === "exam") && <button className="btn-outline" style={{ width: "auto", padding: "8px 18px" }} onClick={() => onReschedule(candidate)}>📅 {candidate.stage === "exam" ? "เลื่อนวันสอบ" : "เลื่อนนัด"}</button>}
            <button className="btn-outline" style={{ width: "auto", padding: "8px 18px" }} onClick={() => onEdit(candidate)}>✏️ แก้ไข</button>
            <button className="btn-delete" style={{ width: "auto", padding: "8px 18px" }} onClick={() => onDelete(candidate)}>🗑 ลบ</button>
          </div>
          <h4 className="sales-sub-h">ไทม์ไลน์</h4>
          <ul className="sales-timeline">
            {logs.map(l => {
              const isReschedule = l.from_stage === l.to_stage;
              return (
                <li key={l.id}>
                  <span className="sales-tl-dot" />
                  <span>{isReschedule ? `📅 ${l.note || "เลื่อนนัด"}` : <>{l.from_stage ? `${recruitStageLabel(l.from_stage)} → ` : "เริ่ม: "}{recruitStageLabel(l.to_stage)}</>}</span>
                  <em>{fmtTs(l.created_at)}</em>
                </li>
              );
            })}
            {!logs.length && <li className="sales-empty">ยังไม่มีประวัติ</li>}
          </ul>
        </div>
      </div>
    </div>
  );
};

// ---- ยืนยันลบผู้สมัคร (ต้องกรอกรหัสตัวแทน 6 หลักซ้ำ) ----
const RecruitDeleteModal = ({ agent, candidate, onClose, onConfirm }) => {
  const [code, setCode] = React.useState("");
  const [err, setErr] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => { ref.current && ref.current.focus(); }, []);
  const submit = async () => {
    if (!/^\d{6}$/.test(code)) { setErr("กรอกรหัสตัวแทน 6 หลัก"); return; }
    if (code !== String(agent.agent_code)) { setErr("รหัสไม่ถูกต้อง"); return; }
    setBusy(true); setErr("");
    try { await onConfirm(); }
    catch (e) { setErr("ลบไม่สำเร็จ: " + (e?.message || String(e))); setBusy(false); }
  };
  const name = candidate.full_name || candidate.nickname || "ผู้สมัคร";
  return (
    <div className="modal-backdrop" onClick={() => !busy && onClose()}>
      <div className="modal-box sales-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><strong>🗑 ยืนยันลบผู้สมัคร</strong><button className="modal-close" onClick={() => !busy && onClose()}>×</button></div>
        <div className="modal-body sales-form">
          <div className="sales-hint">กำลังจะลบผู้สมัคร “{name}” พร้อมประวัติการเลื่อนสเตจ การลบนี้กู้คืนไม่ได้ — ใส่รหัสตัวแทน 6 หลักเพื่อยืนยัน</div>
          <label>รหัสตัวแทน
            <input ref={ref} className="form-input" type="password" inputMode="numeric" maxLength={6} autoComplete="off"
              value={code} onChange={e => setCode(e.target.value.replace(/\D/g, "").slice(0, 6))}
              onKeyDown={e => e.key === "Enter" && submit()} />
          </label>
          {err && <div className="form-error">{err}</div>}
          <div className="modal-actions">
            <button className="btn-outline" onClick={onClose} disabled={busy}>ยกเลิก</button>
            <button className="btn-delete" style={{ width: "auto", padding: "10px 24px" }} onClick={submit} disabled={busy}>{busy ? "กำลังลบ..." : "ยืนยันลบ"}</button>
          </div>
        </div>
      </div>
    </div>
  );
};

// ---- Tab หลัก ----
const RecruitTab = ({ agent }) => {
  const [loading, setLoading] = React.useState(true);
  const [cands, setCands] = React.useState([]);
  const [refreshKey, setRefreshKey] = React.useState(0);
  const [stageTab, setStageTab] = React.useState("all");
  const [candModal, setCandModal] = React.useState(null);
  const [openCand, setOpenCand] = React.useState(null);
  const [moveModal, setMoveModal] = React.useState(null);
  const [rescheduleCand, setRescheduleCand] = React.useState(null);
  const [deleteTarget, setDeleteTarget] = React.useState(null);

  const load = async () => {
    const c = await sb.from("recruit_candidates").select("*").eq("agent_id", agent.id).order("updated_at", { ascending: false });
    if (!c.error) setCands(c.data || []);
    setRefreshKey(k => k + 1); setLoading(false);
  };
  React.useEffect(() => { load(); }, [agent.id]);

  const liveOpen = openCand ? (cands.find(c => c.id === openCand.id) || openCand) : null;
  const stageCounts = RECRUIT_STAGES.reduce((acc, s) => { acc[s.key] = cands.filter(c => c.stage === s.key).length; return acc; }, {});
  const openCount = cands.filter(c => RECRUIT_OPEN_STAGES.includes(c.stage)).length;
  const visible = stageTab === "all" ? cands : cands.filter(c => c.stage === stageTab);
  const afterSave = () => { setCandModal(null); setMoveModal(null); setRescheduleCand(null); load(); };
  const handleMove = (c, toStage) => {
    if ([...RECRUIT_APPT_STAGES, "exam", "recruited", "dropped"].includes(toStage)) setMoveModal({ candidate: c, toStage });
    // ย้ายตรง (เช่น dropped → prospect เพื่อเริ่มใหม่) — ล้างเหตุผลที่สอบไม่ผ่านออก
    else sb.from("recruit_candidates").update({ stage: toStage, drop_reason: null }).eq("id", c.id).then(({ error }) => {
      if (error) uiToast("ย้ายสเตจไม่สำเร็จ: " + error.message, { type: "error" });
      load();
    });
  };
  const confirmDelete = async () => {
    const c = deleteTarget;
    const { error } = await sb.from("recruit_candidates").delete().eq("id", c.id).eq("agent_id", agent.id);
    if (error) throw error;
    // ถอน Recruit/คะแนนที่ผูกกับผู้สมัครนี้ (ถ้าเคยเริ่มงาน)
    try { await sb.from("agent_activities").delete().eq("agent_id", agent.id).ilike("note", `%recruit:${c.id}%`); } catch (_) {}
    setDeleteTarget(null); setOpenCand(null); load();
  };

  if (loading) return <section className="scene"><SectionHead eyebrow="02 — Recruiting" title="Recruiting" /><div className="sales-empty">กำลังโหลด...</div></section>;

  return (
    <section className="scene sales-scene">
      <SectionHead eyebrow="02 — Recruiting" title="Recruiting"
        sub="จัดการรายชื่อผู้สนใจร่วมทีม ติดตามแต่ละสเตจ ตั้งแต่หารายชื่อจนสอบผ่าน"
        right={<div className="sales-head-actions">
          <button className="login-btn" style={{ width: "auto", padding: "9px 18px" }} onClick={() => setCandModal({})}>+ เพิ่มผู้สมัคร</button>
        </div>} />

      <div className="sales-summary">
        <div className="sales-sum-card"><span>กำลังคุยอยู่</span><strong>{openCount}</strong></div>
        <div className="sales-sum-card"><span>สอบผ่าน</span><strong>{stageCounts.recruited}</strong></div>
        <div className="sales-sum-card"><span>สอบไม่ผ่าน</span><strong>{stageCounts.dropped}</strong></div>
        <div className="sales-sum-card"><span>ผู้สมัครทั้งหมด</span><strong>{cands.length}</strong></div>
      </div>

      <div className="sales-stage-tabs">
        <button className={stageTab === "all" ? "active" : ""} onClick={() => setStageTab("all")}>ทั้งหมด <i>{cands.length}</i></button>
        {RECRUIT_STAGES.map(s => (
          <button key={s.key} className={`st-${s.cls} ${stageTab === s.key ? "active" : ""}`} onClick={() => setStageTab(s.key)}>{s.short} <i>{stageCounts[s.key]}</i></button>
        ))}
      </div>
      <div className="sales-deal-grid">
        {visible.map(c => (
          <button key={c.id} className="sales-deal-card" onClick={() => setOpenCand(c)}>
            <div className="sales-deal-top"><RecruitStagePill stage={c.stage} />{c.rating > 0 && <span className="sales-deal-ape">{"★".repeat(c.rating)}</span>}</div>
            <strong>{c.full_name || c.nickname || "ผู้สมัคร"}</strong>
            <span className="sales-deal-foot">
              {c.occupation && <span className="sales-deal-prod">{c.occupation}</span>}
              {c.source && <span className="sales-deal-prod">{c.source}</span>}
              {RECRUIT_APPT_STAGES.includes(c.stage) && c.next_action_at && <span className="sales-deal-appt">📅 {fmtDate(c.next_action_at)}</span>}
              {c.reschedule_count > 0 && <span className="sales-deal-resched">เลื่อน {c.reschedule_count} ครั้ง</span>}
            </span>
          </button>
        ))}
        {!visible.length && <div className="sales-empty">{cands.length ? "ยังไม่มีผู้สมัครในสเตจนี้" : "ยังไม่มีผู้สมัคร กด “+ เพิ่มผู้สมัคร” เพื่อเริ่ม"}</div>}
      </div>

      {candModal && <RecruitCandidateModal agent={agent} candidate={candModal.id ? candModal : null} onClose={() => setCandModal(null)} onSaved={afterSave} />}
      {liveOpen && <RecruitDetail candidate={liveOpen} refreshKey={refreshKey} onClose={() => setOpenCand(null)} onMove={handleMove} onReschedule={(c) => setRescheduleCand(c)} onEdit={(c) => setCandModal(c)} onDelete={(c) => setDeleteTarget(c)} />}
      {rescheduleCand && <RecruitRescheduleModal candidate={rescheduleCand} onClose={() => setRescheduleCand(null)} onSaved={afterSave} />}
      {deleteTarget && <RecruitDeleteModal agent={agent} candidate={deleteTarget} onClose={() => setDeleteTarget(null)} onConfirm={confirmDelete} />}
      {moveModal && <RecruitStageMoveModal agent={agent} candidate={moveModal.candidate} toStage={moveModal.toStage} onClose={() => setMoveModal(null)} onSaved={afterSave} />}
    </section>
  );
};

Object.assign(window, { Tag, Eyebrow, SectionHead, PagePrintButton, Legend, KpiCard, HeroDiagram, Avatar, Countdown, CourseReplaySection, MoneyNeedsForm, useBranding, nameHash, initial, roleOf, photoOf, normName, useModalA11y,
  SalesStagePill, RecruitStagePill, SalesCustomerModal, SalesDealModal, SalesStageMoveModal, SalesRescheduleModal, SalesCheckinModal, SalesDealDetail, SalesDeleteDealModal, SalesTab,
  RecruitCandidateModal, RecruitStageMoveModal, RecruitRescheduleModal, RecruitDetail, RecruitDeleteModal, RecruitTab,
  RECRUIT_ACTIVITY_NAMES, buildCompetitionStandings, buildCompetitionParticipants, competitionHasApproval, competitionDecisiveValue, DealApprovePill, useActiveTeamCompetition, COMPETITION_METRICS, CompetitionTeamLogo, CompetitionVsCard, PointRulesCard, TH_MONTHS });
