// ---- Agent login + dashboard (Supabase-backed) ----

const SESSION_KEY = "agentSession";

const AGENT_BADGE_SOURCES = [
  { id: "events",      table: "external_activities", col: "created_at"  },
  { id: "schedule",    table: "class_schedules",     col: "created_at"  },
  { id: "rewards",     table: "rewards",             col: "created_at"  },
  { id: "challenges",  table: "daily_challenges",    col: "created_at"  },
  { id: "lms",         table: "lms_lessons",         col: "created_at"  },
  { id: "leaderboard", table: "agent_activities",    col: "created_at"  },
];

const agentBadgeKey = (id) => `${window.TENANT.prefix}_nav_seen_${id}`;
const getAgentBadgeSeen = (id) => localStorage.getItem(agentBadgeKey(id)) || new Date(0).toISOString();
const setAgentBadgeSeen = (id) => localStorage.setItem(agentBadgeKey(id), new Date().toISOString());
const TH_MONTHS = ["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."];

// จัดเมนูตัวแทนเป็นหมวด (หัวข้อคั่นกลุ่มใน sidebar) — เมนูที่ไม่อยู่หมวดไหนจะไปรวมท้ายสุด
const AGENT_NAV_GROUPS = [
  { label: "งาน & เป้าหมาย",     ids: ["overview", "sales", "recruit", "goals", "goal-track", "qualification", "profile"] },
  { label: "เรียนรู้ & พัฒนา",    ids: ["lms", "course-replay", "schedule", "onboarding"] },
  { label: "กิจกรรม & รางวัล",    ids: ["events", "activities", "challenges", "leaderboard", "rewards", "team-competition"] },
];

const fmtDate = (iso) => {
  if (!iso) return "-";
  const [y, m, d] = iso.split("-").map(Number);
  return `${d} ${TH_MONTHS[m - 1]} ${y}`;
};

const STATUS_CLASS = {
  "เข้าเรียนแล้ว": "ok", "ผ่านแล้ว": "ok",
  "ยังไม่เริ่ม": "pending", "รอตรวจสอบ": "pending",
  "ขาดเรียน": "miss", "กำลังเรียน": "active",
};

const StatusPill = ({ status }) => (
  <span className={`status-pill ${STATUS_CLASS[status] || "pending"}`}>{status}</span>
);

const avatarSrc = (id, bust) =>
  `${window.SUPABASE_URL}/storage/v1/object/public/avatars/${id}?bust=${bust}`;

const publicAvatarObjectUrl = (path) =>
  path ? `${window.SUPABASE_URL}/storage/v1/object/public/avatars/${path}` : "";

const activityProofPath = (row) => {
  if (row?.proof_path) return row.proof_path;
  const match = String(row?.note || "").match(/หลักฐาน:\s*([^\s|]+)/);
  return match ? match[1] : "";
};

const activityNoteText = (row) =>
  String(row?.note || "")
    .replace(/\s*\|?\s*หลักฐาน:\s*[^\s|]+/g, "")
    .replace(/^(fyc|checkin):\S+\s*/i, "")
    .trim();

const isRecruitActivity = (row) =>
  ["Recruit", "ตัวแทนชวนตัวแทน"].includes(row?.name || "") || String(row?.note || "").includes("ชวน:");

const AgentAvatar = ({ agentId, name, size = 64, bust = 0 }) => {
  const [showImg, setShowImg] = React.useState(true);
  const initial = name.replace(/^คุณ\s*/, "").charAt(0);
  React.useEffect(() => { setShowImg(true); }, [bust]);
  return (
    <div className="agent-avatar" style={{ width: size, height: size, fontSize: size * 0.43 }}>
      {showImg && (
        <img
          src={avatarSrc(agentId, bust)}
          onError={() => setShowImg(false)}
          alt=""
        />
      )}
      {!showImg && initial}
    </div>
  );
};

const AvatarUpload = ({ agent, bust, onUploaded }) => {
  const [uploading, setUploading] = React.useState(false);
  const [err, setErr] = React.useState("");
  const inputRef = React.useRef(null);

  const pick = () => inputRef.current && inputRef.current.click();

  const upload = async (e) => {
    const file = e.target.files[0];
    if (!file) return;
    if (file.size > 5 * 1024 * 1024) { setErr("ไฟล์ต้องไม่เกิน 5 MB"); return; }
    setUploading(true);
    setErr("");
    const { error } = await sb.storage.from("avatars").upload(agent.id, file, {
      upsert: true,
      contentType: file.type,
    });
    setUploading(false);
    if (error) { setErr("อัปโหลดไม่สำเร็จ กรุณาลองใหม่"); return; }
    onUploaded(Date.now());
    e.target.value = "";
  };

  const pickCartoon = async (src) => {
    setUploading(true);
    setErr("");
    try {
      const res = await fetch(src);
      const blob = await res.blob();
      const { error } = await sb.storage.from("avatars").upload(agent.id, blob, {
        upsert: true,
        contentType: "image/svg+xml",
      });
      if (error) throw error;
      onUploaded(Date.now());
    } catch (e2) {
      setErr("ตั้งรูปการ์ตูนไม่สำเร็จ กรุณาลองใหม่");
    }
    setUploading(false);
  };

  return (
    <div className="avatar-upload-wrap">
      <AgentAvatar agentId={agent.id} name={agent.name} size={80} bust={bust} />
      <div className="avatar-upload-actions">
        <button className="avatar-upload-btn" onClick={pick} disabled={uploading}>
          {uploading ? "กำลังบันทึก..." : "เปลี่ยนรูปโปรไฟล์"}
        </button>
        <span className="avatar-upload-hint">JPG, PNG, WebP ไม่เกิน 5 MB</span>
        {err && <div className="form-error">{err}</div>}
      </div>
      <input ref={inputRef} type="file" accept="image/jpeg,image/png,image/webp,image/gif"
        style={{ display: "none" }} onChange={upload} />
      <div className="cartoon-picker">
        <span className="cartoon-picker-label">หรือเลือกการ์ตูน</span>
        <div className="cartoon-picker-grid">
          {CARTOON_AVATARS.map(src => (
            <button key={src} type="button" className="cartoon-avatar-btn"
              disabled={uploading} onClick={() => pickCartoon(src)} title="เลือกรูปนี้">
              <img src={src} alt="" />
            </button>
          ))}
        </div>
      </div>
    </div>
  );
};

const classHours = (timeStr) => {
  const parts = timeStr.split(/[–-]/);
  if (parts.length !== 2) return 0;
  const toMin = (t) => { const [h, m] = t.trim().split(":").map(Number); return h * 60 + m; };
  return Math.max(0, (toMin(parts[1]) - toMin(parts[0])) / 60);
};

const agentStats = (a) => {
  const points = a.activities.reduce((s, x) => s + (x.points || 0), 0);
  const studyHours = a.schedule
    .filter(s => s.status === "เข้าเรียนแล้ว")
    .reduce((sum, s) => sum + classHours(s.time), 0);
  return {
    points,
    activityCount: a.activities.length,
    studyHours: Math.round(studyHours * 10) / 10,
    nextClass: a.schedule.find(s => s.status === "ยังไม่เริ่ม") || null,
  };
};

// Fetch full agent object (profile + schedule + activities) from Supabase by UUID
const fetchAgentById = async (id) => {
  const [agentRes, schedRes, actRes] = await Promise.all([
    sb.from("agents").select("*").eq("id", id).single(),
    sb.from("agent_schedule").select("*").eq("agent_id", id).order("date"),
    sb.from("agent_activities").select("*").eq("agent_id", id).order("date"),
  ]);
  if (agentRes.error || !agentRes.data || membershipExpired(agentRes.data.created_at)) return null;
  return { ...agentRes.data, schedule: schedRes.data || [], activities: actRes.data || [] };
};

const notifyAgentLogin = async (agent) => {
  if (!agent?.id || agent.is_admin) return;
  const payload = {
    user_agent: navigator.userAgent || "",
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "",
  };
  await sb.from("admin_notifications").insert({
    kind: "login",
    agent_id: agent.id,
    agent_name: agent.name,
    agent_code: agent.agent_code,
    unit: agent.unit,
    payload,
  });
};

// เก็บสถิติการเข้าใช้งานระบบ — บันทึกทุก login ทั้ง Manager และ Agent
const recordUsageLogin = async (a) => {
  if (!a?.id) return;
  await sb.from("usage_logs").insert({
    agent_id: a.id,
    name: a.name,
    agent_code: a.agent_code,
    unit: a.unit || "",
    role: a.role || (a.is_admin ? "manager" : "member"),
    is_admin: !!a.is_admin,
    user_agent: navigator.userAgent || "",
  });
};

// ---- Login screen ----
const LoginScreen = ({ onLogin, onGoRegister }) => {
  const { logoUrl } = useBranding();
  const [code, setCode] = React.useState("");
  const [error, setError] = React.useState("");
  const [loading, setLoading] = React.useState(false);
  const inputRef = React.useRef(null);

  React.useEffect(() => { inputRef.current && inputRef.current.focus(); }, []);

  const submit = async () => {
    if (!/^\d{6}$/.test(code)) { setError("กรอกให้ครบ 6 หลักก่อนนะ"); return; }
    setLoading(true);
    setError("");
    const { data, error: err } = await sb.from("agents").select("*").eq("agent_code", code).maybeSingle();
    if (err || !data) { setLoading(false); setError("ไม่มีรหัสนี้ในระบบเลย"); return; }
    if (data.is_admin) {
      // Admin / Manager: write back-office auth and route to their panel.
      localStorage.setItem(`${window.TENANT.prefix}.admin.user`, JSON.stringify({
        id: data.id,
        agent_code: data.agent_code,
        login_name: data.login_name || "",
        name: data.name,
        unit: data.unit,
        role: data.role || "manager",
        is_admin: true,
      }));
      await recordUsageLogin(data).catch(() => {});
      window.location.href = data.role === "admin" ? "admin.html" : "manager.html";
      return;
    }
    if (data.training_status === "รอตรวจสอบ") {
      setLoading(false);
      setError("รอแอดมินอนุมัติก่อนนะ");
      return;
    }
    if (membershipExpired(data.created_at)) {
      setLoading(false);
      setError(`สมาชิกหมดอายุแล้วนะ ติดต่อ Admin ด่วนเลย`);
      return;
    }
    const [schedRes, actRes] = await Promise.all([
      sb.from("agent_schedule").select("*").eq("agent_id", data.id).order("date"),
      sb.from("agent_activities").select("*").eq("agent_id", data.id).order("date"),
    ]);
    setLoading(false);
    notifyAgentLogin(data).catch(() => {});
    recordUsageLogin(data).catch(() => {});
    onLogin({ ...data, schedule: schedRes.data || [], activities: actRes.data || [] });
  };

  const onChange = (e) => {
    setCode(e.target.value.replace(/\D/g, "").slice(0, 6));
    if (error) setError("");
  };

  return (
    <div className="login-screen">
      <a className="back-link" href="index.html">← กลับหน้าแรก</a>
      <div className="login-card">
        {logoUrl && <img src={logoUrl} className="login-logo" alt="โลโก้" />}
        <h1 className="login-title">พร้อมลุย</h1>
        <p className="login-sub">ใส่รหัสตัวแทน 6 หลัก แล้วไปดูแดชบอร์ดของคุณกัน</p>

        <label className="login-field">
          <span>เลขประจำตัวตัวแทน</span>
          <input
            ref={inputRef}
            className={`login-input ${error ? "has-error" : ""}`}
            inputMode="numeric"
            autoComplete="off"
            placeholder="● ● ● ● ● ●"
            value={code}
            onChange={onChange}
            onKeyDown={(e) => e.key === "Enter" && submit()}
            disabled={loading}
          />
        </label>

        {error && <div className="login-error">{error}</div>}

        <button className="login-btn" onClick={submit} disabled={loading}>
          {loading ? "กำลังตรวจสอบ..." : "เข้าสู่ระบบ"}
        </button>

        <div className="login-register-link">
          ยังไม่ได้เข้าคลับ?&nbsp;
          <button className="link-btn" onClick={onGoRegister}>สมัครสมาชิก</button>
        </div>
      </div>
    </div>
  );
};

// ---- Register screen ----
// สังกัดในหน้าสมัคร: ใช้ config (TENANT.units ตัด "Admin") เป็น fallback
// แล้วโหลดทับจากตาราง units ใน Supabase (admin จัดการเองในแท็บ "จัดการ Unit")
const UNITS_REG = (window.TENANT.units || []).filter(u => u !== "Admin");

const RegisterScreen = ({ onBack }) => {
  const { logoUrl } = useBranding();
  const [form, setForm] = React.useState({
    agent_code: "", name: "", unit: "",
  });
  const [slip, setSlip] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState("");
  const [done, setDone] = React.useState(false);
  const [units, setUnits] = React.useState(UNITS_REG);

  React.useEffect(() => {
    sb.from("units").select("name").order("sort_order")
      .then(({ data, error }) => {
        if (!error && data && data.length) setUnits(data.map(u => u.name));
      });
  }, []);

  const set = (k, v) => { setForm(f => ({ ...f, [k]: v })); if (error) setError(""); };

  const submit = async () => {
    if (!/^\d{6}$/.test(form.agent_code)) { setError("เลขประจำตัวต้องเป็นตัวเลข 6 หลัก"); return; }
    if (!form.name.trim()) { setError("กรุณากรอกชื่อเล่น"); return; }
    if (!form.unit) { setError("กรุณาเลือกสังกัด"); return; }
    if (!slip) { setError("กรุณาแนบสลิปโอนเงิน"); return; }
    if (!["image/jpeg", "image/png", "image/webp"].includes(slip.type)) { setError("สลิปรองรับเฉพาะ JPG, PNG หรือ WebP"); return; }
    if (slip.size > 5 * 1024 * 1024) { setError("สลิปต้องมีขนาดไม่เกิน 5 MB"); return; }
    setLoading(true);
    setError("");
    const agentId = crypto.randomUUID();
    const { error: slipError } = await sb.storage.from("avatars").upload(paymentSlipPath(agentId), slip, {
      contentType: slip.type,
      upsert: false,
    });
    if (slipError) {
      setLoading(false);
      setError("อัปโหลดสลิปไม่สำเร็จ กรุณาลองใหม่");
      return;
    }
    const { error: err } = await sb.from("agents").insert([{
      id: agentId,
      agent_code: form.agent_code,
      name: form.name.trim(),
      unit: form.unit,
      join_date: new Date().toISOString().split("T")[0], // = วันที่ส่งใบสมัคร (ตั้งอัตโนมัติ)
      training_status: "รอตรวจสอบ",
    }]);
    setLoading(false);
    if (err) {
      await sb.storage.from("avatars").remove([paymentSlipPath(agentId)]);
      if (err.code === "23505") { setError("เลขประจำตัวนี้มีผู้ใช้งานแล้ว กรุณาเลือกรหัสใหม่"); }
      else { setError("เกิดข้อผิดพลาด กรุณาลองใหม่"); }
      return;
    }
    setDone(true);
  };

  if (done) return (
    <div className="login-screen">
      <div className="login-card" style={{ textAlign: "center" }}>
        {logoUrl && <img src={logoUrl} className="login-logo" alt="โลโก้" />}
        <div style={{ fontSize: 40, margin: "8px 0" }}>✓</div>
        <h1 className="login-title">สมัครสำเร็จ</h1>
        <p className="login-sub">
          บัญชีของคุณอยู่ในสถานะ <strong>รอตรวจสอบ</strong><br />
          Admin จะอนุมัติและแจ้งให้ทราบเร็วๆ นี้<br />
          อายุสมาชิก {MEMBERSHIP_DAYS} วันนับจากวันที่สมัคร
        </p>
        <button className="login-btn" onClick={onBack}>กลับไปหน้าล็อกอิน</button>
      </div>
    </div>
  );

  return (
    <div className="login-screen">
      <div className="login-card">
        {logoUrl && <img src={logoUrl} className="login-logo" alt="โลโก้" />}
        <h1 className="login-title">เข้าคลับ 83F</h1>
        <p className="login-sub">กรอกนิดเดียว แล้วมาเริ่มอัปสกิลไปด้วยกัน</p>

        <label className="login-field">
          <span>รหัสตัวแทน</span>
          <input className={`login-input ${error.includes("เลข") ? "has-error" : ""}`}
            inputMode="numeric" maxLength={6} placeholder="เช่น 200001"
            value={form.agent_code}
            onChange={e => set("agent_code", e.target.value.replace(/\D/g, "").slice(0, 6))} />
        </label>
        <label className="login-field">
          <span>ชื่อเล่น</span>
          <input className={`login-input ${error.includes("ชื่อ") ? "has-error" : ""}`}
            placeholder="เช่น บอย"
            value={form.name} onChange={e => set("name", e.target.value)} />
        </label>
        <label className="login-field">
          <span>สังกัด</span>
          <select className={`login-input select-input ${error.includes("สังกัด") ? "has-error" : ""}`}
            value={form.unit}
            onChange={e => set("unit", e.target.value)}>
            <option value="" disabled>— เลือกสังกัด —</option>
            {units.map(u => <option key={u}>{u}</option>)}
          </select>
        </label>
        <label className="login-field">
          <span>แนบสลิปโอนเงิน</span>
          <input className={`login-input slip-input ${error.includes("สลิป") ? "has-error" : ""}`}
            type="file" accept="image/jpeg,image/png,image/webp"
            onChange={e => {
              setSlip(e.target.files[0] || null);
              if (error) setError("");
            }} />
          <small className="register-file-hint">
            {slip ? slip.name : "รองรับ JPG, PNG, WebP ไม่เกิน 5 MB"}
          </small>
        </label>

        {error && <div className="login-error">{error}</div>}

        <button className="login-btn" onClick={submit} disabled={loading}>
          {loading ? "กำลังสมัคร..." : "สมัครสมาชิก"}
        </button>

        <div className="login-register-link">
          <button className="link-btn" onClick={onBack}>← กลับไปล็อกอิน</button>
        </div>
      </div>
      <footer className="login-foot">83F · ระบบตัวแทน</footer>
    </div>
  );
};

// ---- Dashboard tabs ----
const StatCard = ({ label, value, sub }) => (
  <div className="kpi">
    <div className="kpi-label">{label}</div>
    <div className="kpi-value">{value}</div>
    <div className="kpi-sub">{sub}</div>
  </div>
);

const OverviewTab = ({ agent, avatarBust = 0, onRefresh }) => {
  const s = agentStats(agent);
  const [apeSales, setApeSales] = React.useState([]);
  const [rankState, setRankState] = React.useState({ rank: null, totalAgents: 0 });

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      const [salesRes, agentsRes, actsRes] = await Promise.all([
        sb.from("agent_ape_sales").select("*").eq("agent_id", agent.id),
        sb.from("agents").select("id, is_admin, role, login_name"),
        sb.from("agent_activities").select("agent_id, points"),
      ]);
      if (cancelled) return;
      setApeSales(salesRes.data || []);
      const pts = (actsRes.data || []).reduce((m, r) => {
        m[r.agent_id] = (m[r.agent_id] || 0) + (r.points || 0);
        return m;
      }, {});
      const ranked = (agentsRes.data || [])
        .filter(a => !a.is_admin)
        .map(a => ({ id: a.id, points: pts[a.id] || 0 }))
        .sort((a, b) => b.points - a.points);
      const idx = ranked.findIndex(r => r.id === agent.id);
      setRankState({ rank: idx >= 0 ? idx + 1 : null, totalAgents: ranked.length });
    })();
    return () => { cancelled = true; };
  }, [agent.id, agent.activities.length]);

  const today = todayDateKey();
  const monthKey = today.slice(0, 7);
  const apeToday = apeSales.filter(x => x.date === today).reduce((n, x) => n + (x.amount || 0), 0);
  const apeMonth = apeSales.filter(x => (x.date || "").startsWith(monthKey)).reduce((n, x) => n + (x.amount || 0), 0);
  const apeTotal = apeSales.reduce((n, x) => n + (x.amount || 0), 0);
  const fycTotal = apeSales.reduce((n, x) => n + Number(x.fyc || 0), 0);
  const recruitActivities = agent.activities.filter(isRecruitActivity);
  const recruitToday = apeSales.filter(x => x.date === today).reduce((n, x) => n + Number(x.recruit_count || 0), 0)
    + recruitActivities.filter(x => x.date === today).length;
  const recruitMonth = apeSales.filter(x => (x.date || "").startsWith(monthKey)).reduce((n, x) => n + Number(x.recruit_count || 0), 0)
    + recruitActivities.filter(x => (x.date || "").startsWith(monthKey)).length;
  const recruitTotal = apeSales.reduce((n, x) => n + Number(x.recruit_count || 0), 0) + recruitActivities.length;

  const recentActivities = [...agent.activities]
    .sort((a, b) => `${b.date}`.localeCompare(`${a.date}`))
    .slice(0, 5);

  return (
    <section className="scene">
      <div className="dash-hero">
        <AgentAvatar agentId={agent.id} name={agent.name} size={64} bust={avatarBust} />
        <div>
          <h2 className="section-title">{agent.name}</h2>
          <div className="dash-hero-meta">
            <span>เลขประจำตัว · <strong>{agent.agent_code}</strong></span>
            <span>{agent.unit}</span>
            <StatusPill status={agent.training_status} />
          </div>
        </div>
      </div>

      <div className="kpi-row">
        <StatCard label="คะแนนกิจกรรมสะสม" value={s.points.toLocaleString()} sub="คะแนนจากกิจกรรมทั้งหมด" />
        <StatCard label="จำนวนชั่วโมงเรียน" value={s.studyHours} sub="ชั่วโมงจากตารางเรียน" />
        <StatCard label="กิจกรรมที่เข้าร่วม" value={s.activityCount} sub="ครั้งที่เข้าร่วมกิจกรรม" />
        <StatCard label="การ Recruit" value={recruitTotal.toLocaleString("en-US")} sub={`เดือนนี้ ${recruitMonth.toLocaleString("en-US")} คน`} />
        <div className="kpi">
          <div className="kpi-label">สถานะอบรม</div>
          <div className="kpi-status"><StatusPill status={agent.training_status} /></div>
          <div className="kpi-sub">สถานะการอบรมปัจจุบัน</div>
        </div>
      </div>

      <div className="ape-sales-card">
        <div className="ape-sales-header">
          <strong>อันดับคะแนน</strong>
          <span className="ape-sales-sub">อันดับคะแนนสะสมของฉัน</span>
        </div>
        <div className="ape-stat-row">
          <div className="ape-stat">
            <span>อันดับคะแนนสะสม</span>
            <strong>{rankState.rank ? `#${rankState.rank}` : "–"}</strong>
            <em>จากตัวแทน {rankState.totalAgents} คน</em>
          </div>
        </div>
      </div>

      <div className="ape-sales-card">
        <div className="ape-sales-header">
          <strong>กิจกรรมล่าสุด</strong>
          <span className="ape-sales-sub">5 รายการล่าสุดของคุณ</span>
        </div>
        {recentActivities.length === 0 ? (
          <div style={{padding: 16, textAlign: "center", color: "var(--muted)"}}>ยังไม่มีกิจกรรม</div>
        ) : (
          <div className="history-list">
            {recentActivities.map((row, i) => (
              <div className="history-item" key={row.id || i}>
                <div className="history-date">{fmtDate(row.date)}</div>
                <div className="history-body">
                  <strong>{row.name}</strong>
                  {activityNoteText(row) && <span>{activityNoteText(row)}</span>}
                </div>
                <div className="history-end">
                  <span className="history-points">+{row.points} คะแนน</span>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

    </section>
  );
};

const todayDateKey = () => {
  const d = getNow();
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
};
const clubRunsOnDate = (club, date) => {
  const jsDay = date.getDay();
  const dayIdx = jsDay === 0 ? 7 : jsDay;
  const matchesDay = Array.isArray(club.dayIdx) ? club.dayIdx.includes(dayIdx) : club.dayIdx === dayIdx;
  if (!matchesDay) return false;
  const weekOfMonth = Math.floor((date.getDate() - 1) / 7) + 1;
  if (club.weeks === "ทุกสัปดาห์") return true;
  if (club.weeks.includes("สัปดาห์ที่ 3")) return weekOfMonth === 3;
  if (club.weeks.includes("2") && club.weeks.includes("4")) return weekOfMonth === 2 || weekOfMonth === 4;
  if ((club.perMonth || 0) === 2) return weekOfMonth === 1 || weekOfMonth === 3;
  if ((club.perMonth || 0) === 1) return weekOfMonth === 1;
  return true;
};
const plannedSchedule = () => {
  const date = todayDateKey();
  const day = new Date(`${date}T00:00:00+07:00`);
  return CLUBS
    .filter(club => !club.tbd && clubRunsOnDate(club, day) && (!club.activeFrom || date >= club.activeFrom) && (!club.activeUntil || date <= club.activeUntil))
    .map(club => ({
      id: `planned-${date}-${club.id}`,
      planned: true,
      date,
      time: club.time,
      course: club.name,
      topic: club.desc,
      instructor: club.owners.join(", "),
      channel: club.channel,
      checkin_enabled: club.checkinEnabled !== false,
      zoom_enabled: club.zoomEnabled !== false,
      status: "ตามตาราง",
    }));
};
// คลับทั้งหมดมาจาก class_schedules แล้ว — map ผ่าน clubFromRow + ใช้ recurrence เต็ม (สัปดาห์ที่/งดเฉพาะวัน)
// เช็คอินเปิด/ปิดตามค่าใน DB (checkin_enabled) — default เปิด (port จาก 83G30M)
const adminScheduleToday = (rows) => {
  const date = todayDateKey();
  const day = new Date(`${date}T00:00:00+07:00`);
  return (rows || [])
    .map(clubFromRow)
    .filter(club => clubRunsOnDate(club, day)
      && (!club.activeFrom || date >= club.activeFrom)
      && (!club.activeUntil || date <= club.activeUntil)
      && !(Array.isArray(club.skipDates) && club.skipDates.includes(date)))
    .map(club => ({
      id: `planned-${date}-${club.id}`,
      planned: true,
      date,
      time: club.time,
      course: club.name,
      topic: club.desc,
      instructor: (club.owners || []).join(", ") || "-",
      channel: club.channel,
      checkin_enabled: club.checkinEnabled !== false,
      zoom_enabled: club.zoomEnabled !== false,
      status: "ตามตาราง",
    }));
};
const scheduleWithPlan = (rows, adminRows = []) => {
  const today = todayDateKey();
  const todayRows = rows.filter(row => row.date === today);
  const existing = new Set(todayRows.map(row => `${row.date}|${row.course || row.topic}`));
  return [
    ...todayRows,
    ...plannedSchedule(),
    ...adminScheduleToday(adminRows),
  ].filter(row => {
    const key = `${row.date}|${row.course || row.topic}`;
    if (existing.has(key) && row.planned) return false;
    existing.add(key);
    return true;
  }).sort((a, b) => `${a.date} ${a.time}`.localeCompare(`${b.date} ${b.time}`));
};
const checkinClassFor = (row) => {
  const name = row.course || row.topic || "";
  if (name.includes("Morning Mindset") || name.includes("เปิดสวิตช์คิดบวก")) return null;
  if (row.checkin_enabled === false) return null;
  return { match: name, channel: row.channel || "Online", points: POINT_RULES.checkin };
};
const parseClassTime = (row, part) => {
  const parts = row.time && row.time.split(/[–-]/);
  const hm = parts && parts[part]?.trim();
  if (!row.date || !/^\d{2}:\d{2}$/.test(hm || "")) return null;
  const d = new Date(`${row.date}T${hm}:00+07:00`);
  return Number.isNaN(d.getTime()) ? null : d;
};
const checkinWindow = (row) => {
  const openAt = parseClassTime(row, 0);
  const closeAt = parseClassTime(row, 1);
  if (!openAt || !closeAt) return null;
  return { openAt, closeAt };
};
const ZOOM_OPEN_LEAD_MS = 30 * 60 * 1000;
const scheduleCheckinState = (row) => {
  if (row.status === "เข้าเรียนแล้ว") return { key: "checked", label: "เช็คอินแล้ว" };
  if (row.status === "ขาดเรียน") return { key: "closed", label: "ขาดเรียน" };
  if (!checkinClassFor(row)) return { key: row.planned ? "planned" : "not-ready", label: row.planned ? "ตามตาราง" : "รอเปิดคลาส" };
  const window = checkinWindow(row);
  if (!window) return { key: "no-time", label: "รอเวลาเรียน" };
  const now = Date.now();
  // เช็คอินเปิดพร้อมปุ่มซูม (30 นาทีก่อนคลาสเริ่ม) ไม่ต้องรอถึงเวลาเรียนจริง
  if (now < window.openAt.getTime() - ZOOM_OPEN_LEAD_MS) return { key: "upcoming", label: "ยังไม่เปิด" };
  if (now > window.closeAt.getTime()) return { key: "closed", label: "ปิดเช็คอิน" };
  return { key: "open", label: "เช็คอินได้" };
};
const zoomBtnOpen = (row) => {
  const name = row.course || row.topic || "";
  if (name.includes("Morning Mindset") || name.includes("เปิดสวิตช์คิดบวก")) return false;
  if (row.zoom_enabled === false) return false;
  const win = checkinWindow(row);
  if (!win) return false;
  const now = Date.now();
  return now >= win.openAt.getTime() - ZOOM_OPEN_LEAD_MS && now <= win.closeAt.getTime();
};
const CheckinState = ({ row, onCheckin, submitting, canCheckin = false }) => {
  const state = scheduleCheckinState(row);
  const showZoom = zoomBtnOpen(row);
  // บันทึกเช็คอินให้เสร็จก่อน แล้วค่อยพาเข้า Zoom — ถ้าเปิด Zoom ก่อน เบราว์เซอร์มือถือ
  // จะพักหน้าเว็บตอนสลับแอป ทำให้ request เช็คอินหายกลางทาง
  const joinAndCheckin = async () => {
    const zoomTab = window.open("about:blank", "_blank");
    await onCheckin(row);
    if (zoomTab) { zoomTab.opener = null; zoomTab.location = ZOOM_URL; }
  };
  if (!canCheckin) {
    if (showZoom && (state.key === "open" || state.key === "upcoming" || state.key === "checked")) {
      return <a className="schedule-zoom-btn" href={ZOOM_URL} target="_blank" rel="noopener noreferrer">เข้าเรียน</a>;
    }
    if (state.key === "planned" || state.key === "not-ready") return <span className="muted">–</span>;
    return <span className={`status-pill ${state.key === "closed" ? "miss" : "pending"}`}>{state.key === "checked" ? "เข้าเรียนแล้ว" : state.label}</span>;
  }
  if (state.key === "checked") {
    return (
      <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
        {showZoom && <a className="schedule-zoom-btn" href={ZOOM_URL} target="_blank" rel="noopener noreferrer">เข้าเรียน</a>}
        <span className="schedule-checked">
          <span className="status-pill ok">{state.label}</span>
          <strong>+{checkinClassFor(row).points} คะแนน</strong>
        </span>
      </div>
    );
  }
  if (state.key === "open") return (
    <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
      <button className="schedule-zoom-btn" type="button" onClick={joinAndCheckin} disabled={submitting}>
        {submitting ? "กำลังบันทึก..." : "เช็คอิน + เข้าเรียน"}
      </button>
    </div>
  );
  if (state.key === "upcoming" && showZoom) return (
    <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
      <a className="schedule-zoom-btn" href={ZOOM_URL} target="_blank" rel="noopener noreferrer">เข้าเรียน</a>
      <span className="status-pill pending">{state.label}</span>
    </div>
  );
  if (state.key === "planned" || state.key === "not-ready") return <span className="muted">–</span>;
  return <span className={`status-pill ${state.key === "closed" ? "miss" : "pending"}`}>{state.label}</span>;
};
const Onboarding90Tab = ({ agent, onRefresh }) => {
  const [loading, setLoading] = React.useState(true);
  const [enroll, setEnroll] = React.useState(null);
  const [milestones, setMilestones] = React.useState([]);
  const [items, setItems] = React.useState([]);
  const [marks, setMarks] = React.useState(new Set());
  const [approvals, setApprovals] = React.useState(new Set());
  const [ctx, setCtx] = React.useState({ lmsCount: 0, manhaCount: 0, dealWonCount: 0 });
  const nowIso = new Date().toISOString();

  const load = React.useCallback(async () => {
    setLoading(true);
    const [enr, ms, it, mk, ap, lms, cust, deals] = await Promise.all([
      sb.from("agent_onboarding").select("*").eq("agent_id", agent.id).maybeSingle(),
      sb.from("onboarding_milestones").select("*").eq("active", true).order("sort_order"),
      sb.from("onboarding_items").select("*").eq("active", true).order("sort_order"),
      sb.from("agent_onboarding_item_marks").select("item_id").eq("agent_id", agent.id),
      sb.from("agent_onboarding_milestone_approvals").select("milestone_id").eq("agent_id", agent.id),
      sb.from("agent_lms_progress").select("lesson_id").eq("agent_id", agent.id),
      sb.from("sales_customers").select("id").eq("agent_id", agent.id),
      sb.from("sales_deals").select("id").eq("agent_id", agent.id).eq("stage", "closed_won"),
    ]);
    setEnroll(enr.data || null);
    setMilestones(ms.data || []);
    setItems(it.data || []);
    setMarks(new Set((mk.data || []).map((r) => r.item_id)));
    setApprovals(new Set((ap.data || []).map((r) => r.milestone_id)));
    setCtx({
      lmsCount: (lms.data || []).length,
      manhaCount: (cust.data || []).length,
      dealWonCount: (deals.data || []).length,
    });
    setLoading(false);
  }, [agent.id]);
  React.useEffect(() => { load(); }, [load]);

  const toggleMark = async (item) => {
    if (item.auto_source) return;
    if (marks.has(item.id)) {
      await sb.from("agent_onboarding_item_marks").delete()
        .eq("agent_id", agent.id).eq("item_id", item.id);
    } else {
      await sb.from("agent_onboarding_item_marks")
        .insert([{ agent_id: agent.id, item_id: item.id }]);
    }
    await load();
  };

  const msBadge = (st) =>
    st.state === "approved" ? "ผ่าน ✓"
      : st.state === "pending_approval" ? "รออนุมัติ"
      : st.overdue ? "เกินกำหนด" : "กำลังทำ";

  if (loading) return <div className="ob-loading">กำลังโหลด…</div>;
  if (!enroll) {
    return (
      <div className="ob-locked">
        <div className="ob-locked-icon">🔒</div>
        <p>ยังไม่เริ่มแผน 90 วัน — รอผู้จัดการเปิดแผนให้คุณ</p>
      </div>
    );
  }

  const prog = obProgress(items, marks, ctx);
  const dayNo = obDayNumber(enroll.started_at, nowIso);

  const phaseGroups = [1, 2, 3]
    .map((ph) => ({ ph, ms: milestones.filter((m) => m.phase === ph) }))
    .filter((g) => g.ms.length > 0);
  let currentAssigned = false;

  return (
    <div className="ob90">
      <div className="ob90-hero">
        <div className="ob90-hero-eyebrow">แผนออนบอร์ด 90 วัน</div>
        <div className="ob90-hero-day">วันที่ {dayNo} / 90</div>
        <div className="ob90-hero-bar"><span style={{ width: prog.pct + "%" }} /></div>
        <div className="ob90-hero-row">
          <span>ทำแล้ว {prog.done}/{prog.total} ข้อ</span>
          <span>{prog.pct}%</span>
        </div>
      </div>

      {phaseGroups.map((group, idx) => {
        const statuses = group.ms.map((ms) =>
          obMilestoneStatus(ms, items, marks, approvals, ctx, enroll.started_at, nowIso));
        const rail = obPhaseRailState(statuses, currentAssigned);
        currentAssigned = rail.currentAssigned;
        const isLast = idx === phaseGroups.length - 1;

        return (
          <section key={group.ph} className="ob90-phase">
            <h3 className="ob90-phase-title">เฟส {group.ph}</h3>
            <div className="ob90-rail-wrap">
              <div className="ob90-rail">
                <span className={"ob90-dot ob90-dot-" + rail.state + (rail.overdue ? " ob90-dot-overdue" : "")} />
                {!isLast && (
                  <span className={"ob90-seg" + (rail.state === "done" ? " ob90-seg-done" : "") + (rail.overdue ? " ob90-seg-overdue" : "")} />
                )}
              </div>
              <div className="ob90-steps">
                {group.ms.map((ms, i) => {
                  const st = statuses[i];
                  const msItems = items.filter((it) => it.milestone_id === ms.id);
                  return (
                    <div key={ms.id} className={"ob90-step ob90-" + st.state + (st.overdue ? " ob90-overdue" : "")}>
                      <div className="ob90-step-head">
                        <span className="ob90-step-title">{ms.title}</span>
                        <span className="ob90-badge">{msBadge(st)}</span>
                      </div>
                      <ul className="ob90-items">
                        {msItems.map((it) => {
                          const done = obItemDone(it, marks, ctx);
                          return (
                            <li key={it.id} className={done ? "ob90-item done" : "ob90-item"}>
                              <label>
                                <input type="checkbox" checked={done}
                                  disabled={!!it.auto_source || st.state === "approved"}
                                  onChange={() => toggleMark(it)} />
                                <span>{it.title}</span>
                                {it.auto_source && <em className="ob-auto">อัตโนมัติ</em>}
                              </label>
                            </li>
                          );
                        })}
                      </ul>
                    </div>
                  );
                })}
              </div>
            </div>
          </section>
        );
      })}
    </div>
  );
};

const ScheduleTab = ({ agent }) => {
  const canCheckin = !!agent?.id && !agent?.is_admin;
  const [schedule, setSchedule] = React.useState(() => scheduleWithPlan(agent.schedule));
  const [submittingId, setSubmittingId] = React.useState(null);
  const [checkinError, setCheckinError] = React.useState("");
  const [, setTick] = React.useState(0);
  React.useEffect(() => {
    let classQuery = sb.from("class_schedules").select("*").order("day_of_week").order("time");
    if (!agent.is_admin) classQuery = classQuery.neq("visibility", "admin");
    Promise.all([
      sb.from("agent_schedule").select("*").eq("agent_id", agent.id).order("date"),
      classQuery,
    ]).then(([agentRows, adminRows]) => {
      if (agentRows.data) setSchedule(scheduleWithPlan(agentRows.data, adminRows.data || []));
    });
  }, [agent.id]);
  React.useEffect(() => {
    const id = window.setInterval(() => setTick(t => t + 1), 30000);
    return () => window.clearInterval(id);
  }, []);
  const submitCheckin = async (row) => {
    if (!canCheckin) {
      setCheckinError("เช็คอินได้เฉพาะตัวแทนที่ล็อกอินเท่านั้น");
      return;
    }
    if (scheduleCheckinState(row).key !== "open") {
      setCheckinError("อยู่นอกช่วงเวลาเช็คอิน");
      return;
    }
    setSubmittingId(row.id);
    setCheckinError("");
    let checkedId = row.id;
    if (row.planned) {
      const { data: inserted, error: insertError } = await sb.from("agent_schedule").insert([{
        agent_id: agent.id,
        date: row.date,
        topic: row.course || row.topic,
        time: row.time,
        instructor: row.instructor,
        status: "เข้าเรียนแล้ว",
      }]).select("id").single();
      if (insertError) {
        setSubmittingId(null);
        setCheckinError("บันทึกสถานะเช็คอินไม่สำเร็จ");
        return;
      }
      checkedId = inserted.id;
    } else {
      const { data: updatedRows, error: statusError } = await sb.from("agent_schedule")
        .update({ status: "เข้าเรียนแล้ว" })
        .eq("id", row.id)
        .neq("status", "เข้าเรียนแล้ว")
        .select("id");
      if (statusError) {
        setSubmittingId(null);
        setCheckinError("บันทึกสถานะเช็คอินไม่สำเร็จ");
        return;
      }
      if (!updatedRows?.length) {
        setSubmittingId(null);
        setSchedule(rows => rows.map(item => item.id === row.id ? { ...item, status: "เข้าเรียนแล้ว" } : item));
        setCheckinError("คลาสนี้เช็คอินแล้ว");
        return;
      }
    }
    const checkinPts = checkinClassFor(row).points;
    const { error: pointsError } = checkinPts > 0
      ? await sb.from("agent_activities").insert([{
          agent_id: agent.id,
          date: row.date,
          name: `เช็คอิน ${row.topic}`,
          points: checkinPts,
          note: `checkin:${checkedId}`,
        }])
      : { error: null };
    setSubmittingId(null);
    if (pointsError) {
      if (!row.planned) await sb.from("agent_schedule").update({ status: row.status }).eq("id", row.id);
      setCheckinError("เช็คอินแล้ว แต่บันทึกคะแนนไม่สำเร็จ");
      return;
    }
    setSchedule(rows => rows.map(item => item.id === row.id ? { ...item, id: checkedId, status: "เข้าเรียนแล้ว", planned: false } : item));
  };
  const openRow = canCheckin ? schedule.find(row => scheduleCheckinState(row).key === "open") : null;
  const renderCheckin = (row) => <CheckinState row={row} onCheckin={submitCheckin} submitting={submittingId === row.id} canCheckin={canCheckin} />;
  return (
    <section className="scene">
      <SectionHead eyebrow="04 — ตารางเรียน" title="ตารางเรียนของฉัน" sub={canCheckin ? "ตารางคลาสเรียนทั้งหมดพร้อมสถานะการเช็คอิน" : "ตารางคลาสเรียนทั้งหมด"} />

      {openRow && (
        <div className="schedule-next-card">
          <div>
            <span>คลาสที่เช็คอินได้ตอนนี้</span>
            <strong>{checkinClassFor(openRow).match}</strong>
            <p>{fmtDate(openRow.date)} · {openRow.time} · {checkinClassFor(openRow).channel}</p>
            <p>รับ {checkinClassFor(openRow).points} คะแนน</p>
          </div>
          {renderCheckin(openRow)}
        </div>
      )}

      <div className="table-wrap">
        <table className="dash-table schedule-table">
          <thead>
            <tr><th>วันที่</th><th>คลับ / คอร์ส</th><th>เวลา</th><th>รูปแบบ</th><th>เข้าเรียน</th></tr>
          </thead>
          <tbody>
            {schedule.map((row, i) => {
              const classConfig = checkinClassFor(row);
              return (
              <tr key={row.id || i}>
                <td className="nowrap">
                  {fmtDate(row.date)}
                </td>
                <td className="schedule-course">
                  <strong>{classConfig?.match || row.course || row.topic}</strong>
                  {(classConfig || row.course) && <span>{row.topic}</span>}
                  <small>วิทยากร {row.instructor}</small>
                  {row.poster_path && (
                    <a href={classPosterSrc(row.poster_path)} target="_blank" rel="noreferrer" className="schedule-poster-link">
                      <img src={classPosterSrc(row.poster_path)} alt="โปสเตอร์" className="schedule-poster-thumb" />
                    </a>
                  )}
                </td>
                <td className="nowrap">{row.time}</td>
                <td className="nowrap">{row.channel || classConfig?.channel || "-"}</td>
                <td className="nowrap">{renderCheckin(row)}</td>
              </tr>
            );})}
            {!schedule.length && (
              <tr><td colSpan="5" className="schedule-empty">ยังไม่มีคลาสเรียนที่กำหนดไว้</td></tr>
            )}
          </tbody>
        </table>
      </div>

      {checkinError && <div className="schedule-error">{checkinError}</div>}

    </section>
  );
};

const ActivitiesTab = ({ agent }) => {
  const total = agent.activities.reduce((s, x) => s + (x.points || 0), 0);
  return (
    <section className="scene">
      <SectionHead eyebrow="05 — คะแนนกิจกรรม" title="คะแนนกิจกรรม" sub="กิจกรรมที่เข้าร่วมและคะแนนที่ได้รับ" />
      <div className="table-wrap">
        <table className="dash-table">
          <thead>
            <tr><th>วันที่</th><th>ชื่อกิจกรรม</th><th className="num">คะแนนที่ได้รับ</th><th>หมายเหตุ</th></tr>
          </thead>
          <tbody>
            {agent.activities.map((row, i) => (
              <tr key={i}>
                <td className="nowrap">{fmtDate(row.date)}</td>
                <td>{row.name}</td>
                <td className="num"><strong>{row.points}</strong></td>
                <td>
                  {activityNoteText(row) || "-"}
                  {activityProofPath(row) && (
                    <a className="activity-proof-link" href={publicAvatarObjectUrl(activityProofPath(row))} target="_blank" rel="noreferrer">
                      ดูหลักฐาน
                    </a>
                  )}
                </td>
              </tr>
            ))}
          </tbody>
          <tfoot>
            <tr><td colSpan="2">รวมคะแนนสะสม</td><td className="num"><strong>{total.toLocaleString()}</strong></td><td></td></tr>
          </tfoot>
        </table>
      </div>
    </section>
  );
};

const ProfileTab = ({ agent, avatarBust = 0, onAvatarUpdate }) => {
  const userLang = window.I18N ? window.I18N.userOverride : null; // null = ตามค่าเริ่มต้นของระบบ
  const s = agentStats(agent);
  const rows = [
    ["ชื่อตัวแทน", agent.name],
    ["เลขประจำตัว", agent.agent_code],
    ["Unit", agent.unit],
    ["วันที่สมัครสมาชิก", fmtDate(agent.created_at && agent.created_at.slice(0, 10))],
    ["สมาชิกถึงวันที่", fmtDate(membershipEndDate(agent.created_at))],
    ["วันที่เริ่มงาน", fmtDate(agent.join_date)],
    ["สถานะอบรม", <StatusPill status={agent.training_status} />],
    ["คะแนนกิจกรรมสะสม", `${s.points.toLocaleString()} คะแนน`],
    ["จำนวนชั่วโมงเรียน", `${s.studyHours} ชั่วโมง`],
    ["กิจกรรมที่เข้าร่วม", `${s.activityCount} ครั้ง`],
  ];
  return (
    <section className="scene">
      <SectionHead eyebrow="11 — โปรไฟล์" title="โปรไฟล์ตัวแทน" sub="ข้อมูลส่วนตัวและสรุปผลการเรียน" />
      <div className="profile-card" style={{ maxWidth: 560 }}>
        <div style={{ padding: "20px 22px" }}>
          <AvatarUpload agent={agent} bust={avatarBust} onUploaded={onAvatarUpdate} />
        </div>
        {rows.map(([label, value], i) => (
          <div className="profile-row" key={i}>
            <span className="profile-label">{label}</span>
            <span className="profile-value">{value}</span>
          </div>
        ))}
        <div className="profile-row">
          <span className="profile-label">ภาษา</span>
          <span className="profile-value">
            <div className="menu-lang-toggle">
              <button type="button"
                className={userLang === null ? "active" : ""}
                onClick={() => window.I18N && window.I18N.setUserLang(null)}>ค่าเริ่มต้น</button>
              <button type="button"
                className={userLang === "th" ? "active" : ""}
                onClick={() => window.I18N && window.I18N.setUserLang("th")}>ไทย</button>
              <button type="button"
                className={userLang === "en" ? "active" : ""}
                onClick={() => window.I18N && window.I18N.setUserLang("en")}>EN</button>
            </div>
          </span>
        </div>
      </div>
    </section>
  );
};

// ---- My Goals / Dreams ----
// WISH_TYPES / wishTypeOf / wishImageUrl are defined in data.jsx so the
// agent dashboard and the admin panel can share them.

// Image attachment for a single wish item — uploads immediately on pick
const WishImageField = ({ item, onUpload, onClear }) => {
  const [uploading, setUploading] = React.useState(false);
  const [err, setErr] = React.useState("");
  const inputRef = React.useRef(null);

  const pick = (e) => {
    const file = e.target.files[0];
    e.target.value = "";
    if (!file) return;
    if (!["image/jpeg", "image/png", "image/webp"].includes(file.type)) {
      setErr("รองรับ JPG, PNG, WebP เท่านั้น"); return;
    }
    if (file.size > 5 * 1024 * 1024) { setErr("รูปต้องมีขนาดไม่เกิน 5 MB"); return; }
    setErr(""); setUploading(true);
    onUpload(file).then(msg => {
      setUploading(false);
      if (msg) setErr(msg);
    }).catch(e => {
      setUploading(false);
      setErr("เกิดข้อผิดพลาด: " + (e && e.message ? e.message : String(e)));
    });
  };

  return (
    <div className="wish-image-field">
      {item.image_path ? (
        <div className="wish-image-wrap">
          <img src={wishImageUrl(item.image_path, item.image_bust)} alt="รูปภาพประกอบเป้าหมาย" />
          <div className="wish-image-actions">
            <button type="button" onClick={() => inputRef.current && inputRef.current.click()} disabled={uploading}>
              {uploading ? "กำลังอัปโหลด..." : "เปลี่ยนรูป"}
            </button>
            <button type="button" onClick={onClear} disabled={uploading}>ลบรูป</button>
          </div>
        </div>
      ) : (
        <button type="button" className="wish-image-add"
          onClick={() => inputRef.current && inputRef.current.click()} disabled={uploading}>
          {uploading ? "กำลังอัปโหลด..." : "📷 แนบรูปภาพประกอบ"}
        </button>
      )}
      {err && <div className="form-error">{err}</div>}
      <input ref={inputRef} type="file" accept="image/jpeg,image/png,image/webp"
        style={{ display: "none" }} onChange={pick} />
    </div>
  );
};

const goalsHaveContent = (f) => {
  if (!f) return false;
  if (parseInt(String(f.monthly_income || "").replace(/\D/g, ""), 10) > 0) return true;
  if (parseInt(String(f.savings_target || "").replace(/\D/g, ""), 10) > 0) return true;
  if (Array.isArray(f.wish_items) && f.wish_items.length > 0) return true;
  if ((f.goal_1y || "").trim()) return true;
  if ((f.goal_2y || "").trim()) return true;
  if ((f.goal_3y || "").trim()) return true;
  return false;
};

const GoalsCard = ({ agent }) => {
  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 [mode, setMode] = React.useState("edit");

  React.useEffect(() => {
    sb.from("agent_goals").select("*").eq("agent_id", agent.id).maybeSingle()
      .then(({ data }) => {
        const next = data || {
          monthly_income: "", savings_target: "", wish_items: [],
          goal_1y: "", goal_2y: "", goal_3y: "",
        };
        setForm(next);
        setMode(goalsHaveContent(next) ? "summary" : "edit");
      });
  }, [agent.id]);

  const patch = (changes) => { setForm(f => ({ ...f, ...changes })); setSaved(false); };
  const setF = (k, v) => patch({ [k]: v });
  const onlyDigits = (s) => String(s || "").replace(/\D/g, "");
  const onNum = (k) => (e) => setF(k, onlyDigits(e.target.value));
  const numVal = (v) => v ? Number(v).toLocaleString("en-US") : "";

  const wishItems  = Array.isArray(form?.wish_items) ? form.wish_items : [];
  const addWish    = (typeId) => patch({ wish_items: [...wishItems, { id: crypto.randomUUID(), type: typeId }] });
  const removeWish = (idx)    => patch({ wish_items: wishItems.filter((_, i) => i !== idx) });
  const setWish    = (idx, key, val) =>
    patch({ wish_items: wishItems.map((w, i) => i === idx ? { ...w, [key]: val } : w) });
  const clearWishImage = (idx) =>
    patch({ wish_items: wishItems.map((w, i) =>
      i === idx ? { ...w, image_path: undefined, image_bust: undefined } : w) });

  const buildPayload = (f) => ({
    agent_id: agent.id,
    monthly_income: parseInt(onlyDigits(f.monthly_income), 10) || 0,
    savings_target: parseInt(onlyDigits(f.savings_target), 10) || 0,
    wish_items: (Array.isArray(f.wish_items) ? f.wish_items : []).map(w => {
      const cfg = wishTypeOf(w.type);
      if (!cfg) return null;
      const out = { type: w.type };
      if (w.id) out.id = w.id;
      if (w.image_path) { out.image_path = w.image_path; out.image_bust = w.image_bust || 0; }
      cfg.fields.forEach(fld => {
        out[fld.key] = fld.type === "money"
          ? (parseInt(onlyDigits(w[fld.key]), 10) || 0)
          : String(w[fld.key] || "").trim();
      });
      return out;
    }).filter(Boolean),
    goal_1y: (f.goal_1y || "").trim(),
    goal_2y: (f.goal_2y || "").trim(),
    goal_3y: (f.goal_3y || "").trim(),
    updated_at: new Date().toISOString(),
  });

  const save = async () => {
    setSaving(true); setErr("");
    const { error } = await sb.from("agent_goals")
      .upsert(buildPayload(form), { onConflict: "agent_id" });
    setSaving(false);
    if (error) { setErr("บันทึกไม่สำเร็จ: " + error.message); return; }
    setSaved(true);
    setMode("summary");
    window.scrollTo({ top: 0, behavior: "smooth" });
  };

  // Uploads the image, then persists the row so the reference survives a
  // reload. Returns null on success, or an error message string.
  const uploadWishImage = (idx) => async (file) => {
    try {
      const item = wishItems[idx];
      const wishId = (item && item.id) || crypto.randomUUID();
      const path = `wish-images/${wishId}`;
      const { error: upErr } = await sb.storage.from("avatars")
        .upload(path, file, { upsert: true, contentType: file.type || "image/jpeg" });
      if (upErr) {
        console.error("wish image — storage upload failed:", upErr);
        return "อัปโหลดรูปไม่สำเร็จ: " + (upErr.message || "ไม่ทราบสาเหตุ");
      }
      const nextForm = {
        ...form,
        wish_items: wishItems.map((w, i) =>
          i === idx ? { ...w, id: wishId, image_path: path, image_bust: Date.now() } : w),
      };
      setForm(nextForm); setSaved(false);
      const { error: saveErr } = await sb.from("agent_goals")
        .upsert(buildPayload(nextForm), { onConflict: "agent_id" });
      if (saveErr) {
        console.error("wish image — goals save failed:", saveErr);
        return "อัปโหลดรูปแล้ว แต่บันทึกข้อมูลไม่สำเร็จ: " + (saveErr.message || "");
      }
      return null;
    } catch (e) {
      console.error("wish image — unexpected error:", e);
      return "เกิดข้อผิดพลาด: " + (e && e.message ? e.message : String(e));
    }
  };

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

  if (mode === "summary") {
    const monthly = parseInt(onlyDigits(form.monthly_income), 10) || 0;
    const savings = parseInt(onlyDigits(form.savings_target), 10) || 0;
    const items = Array.isArray(form.wish_items) ? form.wish_items : [];
    const timeline = [
      ["goal_1y", "ภายใน 1 ปี"],
      ["goal_2y", "ภายใน 2 ปี"],
      ["goal_3y", "ภายใน 3 ปี"],
    ].filter(([k]) => (form[k] || "").trim());

    return (
      <div className="goals-card dream-summary">
        <div className="dream-hero">
          <div className="dream-hero-eyebrow">สรุปความฝันของฉัน</div>
          <h3 className="dream-hero-title">{agent.name}</h3>
          <p className="dream-hero-sub">เป้าหมายและความฝันที่จะลงมือทำให้สำเร็จ</p>
        </div>

        {(monthly > 0 || savings > 0) && (
          <div className="goals-block dream-block">
            <div className="goals-block-head">💰 เป้าหมายการเงิน</div>
            <div className="dream-money-row">
              {monthly > 0 && (
                <div className="dream-stat">
                  <div className="dream-stat-label">รายได้ต่อเดือนที่ต้องการ</div>
                  <div className="dream-stat-value">{monthly.toLocaleString("en-US")} <em>บาท / เดือน</em></div>
                </div>
              )}
              {savings > 0 && (
                <div className="dream-stat">
                  <div className="dream-stat-label">เงินเก็บที่ต้องการ</div>
                  <div className="dream-stat-value">{savings.toLocaleString("en-US")} <em>บาท</em></div>
                </div>
              )}
            </div>
          </div>
        )}

        {items.length > 0 && (
          <div className="goals-block dream-block">
            <div className="goals-block-head">🎁 สิ่งของที่ต้องการ</div>
            <div className="dream-wish-grid">
              {items.map((w, idx) => {
                const cfg = wishTypeOf(w.type);
                if (!cfg) return null;
                return (
                  <div className="dream-wish-card" key={w.id || idx}>
                    {w.image_path && (
                      <div className="dream-wish-image">
                        <img src={wishImageUrl(w.image_path, w.image_bust)} alt="" />
                      </div>
                    )}
                    <div className="dream-wish-body">
                      <div className="dream-wish-title">{cfg.icon} {cfg.label}</div>
                      <dl className="dream-wish-fields">
                        {cfg.fields.map(fld => {
                          const raw = w[fld.key];
                          const empty = fld.type === "money"
                            ? !(parseInt(String(raw || "").replace(/\D/g, ""), 10) > 0)
                            : !String(raw || "").trim();
                          if (empty) return null;
                          const display = fld.type === "money"
                            ? `${Number(String(raw).replace(/\D/g, "")).toLocaleString("en-US")} ${fld.unit || ""}`.trim()
                            : String(raw);
                          return (
                            <React.Fragment key={fld.key}>
                              <dt>{fld.label}</dt>
                              <dd>{display}</dd>
                            </React.Fragment>
                          );
                        })}
                      </dl>
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        )}

        {timeline.length > 0 && (
          <div className="goals-block dream-block">
            <div className="goals-block-head">🎯 เป้าหมายของฉัน</div>
            <div className="dream-timeline">
              {timeline.map(([k, label]) => (
                <div className="dream-timeline-row" key={k}>
                  <span className="goals-year">{label}</span>
                  <p className="dream-timeline-text">{form[k]}</p>
                </div>
              ))}
            </div>
          </div>
        )}

        <div className="goals-save-row">
          {saved && <span className="goals-saved">✓ บันทึกเป้าหมายแล้ว</span>}
          <button className="login-btn goals-save-btn dream-edit-btn"
            onClick={() => { setMode("edit"); setSaved(false); }}>
            ✏️ แก้ไขเป้าหมาย
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="goals-card">
      <div className="goals-block">
        <div className="goals-block-head">💰 เป้าหมายการเงิน</div>
        <div className="goals-money-row">
          <label className="goals-field">
            <span>รายได้ต่อเดือนที่ต้องการ</span>
            <div className="goals-money-input">
              <input className="form-input" type="text" inputMode="numeric" placeholder="0"
                value={numVal(form.monthly_income)} onChange={onNum("monthly_income")} />
              <em>บาท / เดือน</em>
            </div>
          </label>
          <label className="goals-field">
            <span>เงินเก็บที่ต้องการ</span>
            <div className="goals-money-input">
              <input className="form-input" type="text" inputMode="numeric" placeholder="0"
                value={numVal(form.savings_target)} onChange={onNum("savings_target")} />
              <em>บาท</em>
            </div>
          </label>
        </div>
      </div>

      <div className="goals-block">
        <div className="goals-block-head">🎁 สิ่งของที่ต้องการ</div>
        {wishItems.length === 0 && (
          <p className="wish-empty">ยังไม่มีรายการ — เลือกหมวดด้านล่างเพื่อระบุสิ่งที่คุณต้องการ</p>
        )}
        <div className="wish-list">
          {wishItems.map((w, idx) => {
            const cfg = wishTypeOf(w.type);
            if (!cfg) return null;
            return (
              <div className="wish-item" key={w.id || idx}>
                <div className="wish-item-head">
                  <span className="wish-item-title">{cfg.icon} {cfg.label}</span>
                  <button type="button" className="wish-remove" aria-label="ลบรายการ"
                    onClick={() => removeWish(idx)}>✕</button>
                </div>
                <WishImageField item={w} onUpload={uploadWishImage(idx)}
                  onClear={() => clearWishImage(idx)} />
                <div className="wish-fields">
                  {cfg.fields.map(fld => (
                    <label className="wish-field" key={fld.key}>
                      <span>{fld.label}</span>
                      {fld.type === "money" ? (
                        <div className="goals-money-input">
                          <input className="form-input" type="text" inputMode="numeric" placeholder="0"
                            value={numVal(w[fld.key])}
                            onChange={e => setWish(idx, fld.key, onlyDigits(e.target.value))} />
                          <em>{fld.unit}</em>
                        </div>
                      ) : (
                        <input className="form-input" type="text" placeholder={fld.placeholder || ""}
                          value={w[fld.key] || ""}
                          onChange={e => setWish(idx, fld.key, e.target.value)} />
                      )}
                    </label>
                  ))}
                </div>
              </div>
            );
          })}
        </div>
        <div className="wish-add-row">
          {WISH_TYPES.map(t => (
            <button type="button" key={t.id} className="wish-add-btn" onClick={() => addWish(t.id)}>
              + {t.icon} {t.label}
            </button>
          ))}
        </div>
      </div>

      <div className="goals-block">
        <div className="goals-block-head">🎯 เป้าหมายของฉัน</div>
        <div className="goals-timeline">
          {[
            ["goal_1y", "ภายใน 1 ปี"],
            ["goal_2y", "ภายใน 2 ปี"],
            ["goal_3y", "ภายใน 3 ปี"],
          ].map(([k, label]) => (
            <label className="goals-field" key={k}>
              <span className="goals-year">{label}</span>
              <textarea className="form-input goals-textarea"
                placeholder={`สิ่งที่อยากทำให้สำเร็จ${label}`}
                value={form[k]} onChange={e => setF(k, e.target.value)} />
            </label>
          ))}
        </div>
      </div>

      {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 ? "กำลังบันทึก..." : "บันทึกเป้าหมาย"}
        </button>
      </div>
    </div>
  );
};

// ---- Goals Tab ----
// ---- External Events Tab ----
const actCountdown = (diff) =>
  diff === null ? "ยังไม่ระบุวันที่"
  : diff < 0 ? "ผ่านแล้ว"
  : diff === 0 ? "วันนี้"
  : diff === 1 ? "พรุ่งนี้"
  : `อีก ${diff} วัน`;
const actCountdownCls = (diff) =>
  diff === null ? "none" : diff < 0 ? "past" : diff <= 1 ? "soon" : "up";

const ExternalEventsTab = () => {
  const [activities, setActivities] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [posterModal, setPosterModal] = React.useState(null);
  const [orientations, setOrientations] = React.useState({});

  React.useEffect(() => {
    sb.from("external_activities").select("*")
      .neq("visibility", "manager")
      .order("activity_date", { ascending: true, nullsFirst: false })
      .order("created_at", { ascending: false })
      .then(({ data }) => { setActivities(data || []); setLoading(false); });
  }, []);

  const fmtDate = (d) => {
    if (!d) return "-";
    const [y, m, day] = d.split("-").map(Number);
    return `${day} ${TH_MONTHS[m - 1]} ${y + 543}`;
  };
  const posterSrc = (path) => path
    ? `${window.SUPABASE_URL}/storage/v1/object/public/avatars/${path}`
    : null;

  const todayKey = getNow().toLocaleDateString("sv-SE");          // YYYY-MM-DD (local)
  const dayDiff = (dateStr) => {
    if (!dateStr) return null;
    return Math.round((new Date(dateStr + "T12:00:00") - new Date(todayKey + "T12:00:00")) / 86400000);
  };
  const upcoming = activities
    .filter(a => { const d = dayDiff(a.activity_date); return d === null || d >= 0; })
    .sort((x, y) => {
      const dx = dayDiff(x.activity_date), dy = dayDiff(y.activity_date);
      if (dx === null) return 1; if (dy === null) return -1; return dx - dy;   // เร็วสุดก่อน, ไม่ระบุวันอยู่ท้าย
    });
  const past = activities
    .filter(a => { const d = dayDiff(a.activity_date); return d !== null && d < 0; })
    .sort((x, y) => dayDiff(y.activity_date) - dayDiff(x.activity_date));        // ล่าสุดก่อน

  const renderCard = (a, i) => {
    const src = posterSrc(a.poster_path);
    const diff = dayDiff(a.activity_date);
    const orientation = orientations[a.id] || "landscape";
    return (
      <article key={a.id} className={`activity-row tone-${i % 5}`}>
        {src ? (
          <button className={`activity-row-thumb poster-trigger ${orientation}`} type="button"
            onClick={() => setPosterModal(a)} aria-label={`เปิดโปสเตอร์ ${a.title}`}>
            <img src={src} alt={a.title} loading="lazy"
              onLoad={(e) => setOrientations(o => ({
                ...o,
                [a.id]: e.target.naturalWidth >= e.target.naturalHeight ? "landscape" : "portrait",
              }))} />
          </button>
        ) : (
          <div className={`activity-row-thumb empty ${orientation}`}><span>กิจกรรม</span></div>
        )}
        <div className="activity-row-main">
          <div className="activity-row-top">
            <h3>{a.title}</h3>
            {a.location && <span className="activity-row-tag">{a.location}</span>}
            <span className={`act-countdown ${actCountdownCls(diff)}`}>{actCountdown(diff)}</span>
          </div>
          {a.description && <p>{a.description}</p>}
        </div>
        <div className="activity-row-date">
          <span>{fmtDate(a.activity_date)}</span>
          <strong>{a.time || "-"}</strong>
        </div>
      </article>
    );
  };

  return (
    <section className="scene">
      <SectionHead eyebrow="10 — กิจกรรม" title="กิจกรรม" sub="กิจกรรมภายนอกทั้งหมด" />
      {loading ? <div className="loading-msg">กำลังโหลด...</div> : !activities.length ? (
        <p style={{ padding: "32px", textAlign: "center", color: "var(--muted)" }}>ยังไม่มีกิจกรรม</p>
      ) : (
        <>
          {upcoming.length > 0 && (
            <>
              <div className="club-group-head"><span className="club-group-dot up" />กำลังจะถึง<em>{upcoming.length}</em></div>
              <div className="activity-list">{upcoming.map(renderCard)}</div>
            </>
          )}
          {past.length > 0 && (
            <>
              <div className="club-group-head"><span className="club-group-dot past" />ผ่านไปแล้ว<em>{past.length}</em></div>
              <div className="activity-list past-list">{past.map(renderCard)}</div>
            </>
          )}
        </>
      )}
      {posterModal && (
        <div className="poster-modal-backdrop" onClick={() => setPosterModal(null)}>
          <div className="poster-modal" role="dialog" aria-modal="true" aria-label={`โปสเตอร์ ${posterModal.title}`} onClick={e => e.stopPropagation()}>
            <button className="poster-modal-close" type="button" onClick={() => setPosterModal(null)} aria-label="ปิดโปสเตอร์">×</button>
            <img src={posterSrc(posterModal.poster_path)} alt={posterModal.title} />
          </div>
        </div>
      )}
    </section>
  );
};

const GoalsTab = ({ agent }) => (
  <section className="scene">
    <SectionHead eyebrow="My Money Need" title="My Money Needs · ความจำเป็นทางการเงิน"
      sub="กรอกค่าใช้จ่าย/เดือน ระบบจะคำนวณเป้ารายได้ FYP และกิจกรรมการขายให้อัตโนมัติ" />
    <MoneyNeedsForm agent={agent} />
  </section>
);

// ---- Leaderboard (visible to every agent) ----
// เลือกได้ว่าจัดอันดับด้วยเกณฑ์ไหน: คะแนนรวม / APE / NBC / Recruit (เหมือนฝั่งผู้จัดการ)
const LB_METRICS = [
  { key: "points",  label: "คะแนนรวม", unit: "คะแนน", short: "อันดับคะแนนสะสม" },
  { key: "ape",     label: "APE",      unit: "บาท",   short: "อันดับยอด APE" },
  { key: "fyc",     label: "NBC",      unit: "บาท",   short: "อันดับยอด NBC" },
  { key: "recruit", label: "Recruit",  unit: "คน",    short: "อันดับการชวนตัวแทน" },
];
// แท็บ "ทีม VS ทีม" ฝั่งตัวแทน — เห็นตัวเลขรายบุคคลด้วย (ต่างจากหน้า public landing ที่ซ่อนทั้ง section
// เมื่อไม่มีแมตช์; ที่นี่เป็นแท็บเฉพาะ จึงต้องมี empty-state ข้อความแทนการซ่อน)
const CompetitionAgentSection = () => {
  const { loading, empty, selected, teams, standings, agentsById, participants } = useActiveTeamCompetition(true);
  return (
    <section className="scene">
      <SectionHead eyebrow="ศึกชิงเจ้ายอด" title="ทีม VS ทีม" />
      {loading ? (
        <div className="loading-msg">กำลังโหลด...</div>
      ) : empty ? (
        <p className="sales-empty">ยังไม่มีการแข่งขันในตอนนี้</p>
      ) : (
        <CompetitionVsCard competition={selected} teams={teams} standings={standings} agentsById={agentsById} participants={participants} />
      )}
    </section>
  );
};

const LeaderboardTab = ({ agent }) => {
  const [rows, setRows] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [err, setErr] = React.useState("");
  const [metric, setMetric] = React.useState("points");

  React.useEffect(() => {
    (async () => {
      const [agentsRes, actsRes, salesRes] = await Promise.all([
        sb.from("agents").select("id, name, unit, agent_code, is_admin, role, login_name, created_at"),
        sb.from("agent_activities").select("agent_id, name, note, points"),
        sb.from("agent_ape_sales").select("agent_id, amount, fyc, recruit_count"),
      ]);
      if (agentsRes.error) { setErr(agentsRes.error.message); setLoading(false); return; }
      const pts = (actsRes.data || []).reduce((m, r) => {
        m[r.agent_id] = (m[r.agent_id] || 0) + (r.points || 0);
        return m;
      }, {});
      const sales = (salesRes.data || []).reduce((m, s) => {
        const cur = m[s.agent_id] || { ape: 0, fyc: 0, recruit: 0 };
        cur.ape += Number(s.amount) || 0;
        cur.fyc += Number(s.fyc) || 0;
        cur.recruit += Number(s.recruit_count) || 0;
        m[s.agent_id] = cur;
        return m;
      }, {});
      // การ Recruit ที่บันทึกเป็นกิจกรรม (ไม่ได้อยู่ในยอดขาย APE) — นับ 1 ครั้ง/รายการ
      (actsRes.data || []).filter(isRecruitActivity).forEach(a => {
        const cur = sales[a.agent_id] || { ape: 0, fyc: 0, recruit: 0 };
        cur.recruit += 1;
        sales[a.agent_id] = cur;
      });
      const all = (agentsRes.data || [])
        .filter(a => a.role === "member")
        .map(a => ({
          ...a,
          points: pts[a.id] || 0,
          ape: sales[a.id]?.ape || 0,
          fyc: sales[a.id]?.fyc || 0,
          recruit: sales[a.id]?.recruit || 0,
        }));
      setRows(all);
      setLoading(false);
    })();
  }, []);

  const metricMeta = LB_METRICS.find(m => m.key === metric) || LB_METRICS[0];
  const fmtVal = (r) => `${(r[metric] || 0).toLocaleString()} ${metricMeta.unit}`;
  const ranked = [...rows].sort(
    (a, b) => (b[metric] || 0) - (a[metric] || 0) || (a.name || "").localeCompare(b.name || "", "th")
  );

  const podium = ranked.slice(0, 3);
  const rest = ranked.slice(3, 50);
  const myIdx = ranked.findIndex(r => r.id === agent.id);
  const meInTop = myIdx >= 0 && myIdx < 50;
  const me = myIdx >= 0 ? ranked[myIdx] : null;

  const PODIUM_ORDER = [1, 0, 2]; // render: #2, #1, #3
  const PODIUM_META = [
    { medal: "🥇", cls: "gold",   label: "อันดับ 1" },
    { medal: "🥈", cls: "silver", label: "อันดับ 2" },
    { medal: "🥉", cls: "bronze", label: "อันดับ 3" },
  ];

  const RANK_PALETTE = ["red", "blue", "green", "yellow", "purple", "teal", "pink", "orange"];
  const rankColor = (rank) => RANK_PALETTE[(rank - 1) % RANK_PALETTE.length];

  const RankCard = ({ row, rank }) => {
    const isMe = row.id === agent.id;
    return (
      <div className={`lb-row ${isMe ? "is-me" : ""}`}>
        <AgentAvatar agentId={row.id} name={row.name} size={48} />
        <div className="lb-row-body">
          <strong>{row.name}</strong>
          <span>{fmtVal(row)} · {row.unit}</span>
        </div>
        {isMe && <span className="lb-me-pill">คุณ</span>}
        <span className={`lb-rank-badge color-${rankColor(rank)}`}>{rank}</span>
      </div>
    );
  };

  return (
    <section className="scene leaderboard-scene">
      <SectionHead eyebrow="07 — ตัวตึง" title="ตัวตึงของคลับ"
        sub={loading ? "กำลังโหลด..." : `${metricMeta.short}ของตัวแทนทุกคน · ทั้งหมด ${rows.length} คน`} />

      {!loading && !err && (
        <div className="lb-metric-tabs">
          {LB_METRICS.map(m => (
            <button key={m.key}
              className={`lb-metric-tab ${metric === m.key ? "active" : ""}`}
              onClick={() => setMetric(m.key)}>{m.label}</button>
          ))}
        </div>
      )}

      {err && <div className="form-error">โหลดอันดับไม่สำเร็จ: {err}</div>}
      {loading ? <div className="loading-msg">กำลังโหลด...</div> : !rows.length ? (
        <div style={{ textAlign: "center", padding: 40, color: "var(--muted)" }}>ยังไม่มีข้อมูล</div>
      ) : (
        <>
          <div className="lb-podium">
            {PODIUM_ORDER.map(orderIdx => {
              const r = podium[orderIdx];
              if (!r) return <div className="lb-podium-slot empty" key={orderIdx} />;
              const meta = PODIUM_META[orderIdx];
              const isMe = r.id === agent.id;
              return (
                <div className={`lb-podium-slot ${meta.cls} ${orderIdx === 0 ? "first" : ""}`} key={r.id}>
                  <div className="lb-podium-avatar">
                    <AgentAvatar agentId={r.id} name={r.name} size={orderIdx === 0 ? 96 : 76} />
                    <span className="lb-podium-medal">{orderIdx + 1}</span>
                  </div>
                  <strong className="lb-podium-name">
                    {r.name}{isMe && <span className="lb-me-pill inline">คุณ</span>}
                  </strong>
                  <span className="lb-podium-points">{fmtVal(r)}</span>
                  {r.unit && <span className="lb-podium-unit">{r.unit}</span>}
                </div>
              );
            })}
          </div>

          <div className="lb-list">
            {rest.map(r => {
              const rank = ranked.findIndex(x => x.id === r.id) + 1;
              return <RankCard key={r.id} row={r} rank={rank} />;
            })}
            {me && !meInTop && (
              <>
                <div className="lb-divider">· · ·</div>
                <RankCard row={me} rank={myIdx + 1} />
              </>
            )}
          </div>
        </>
      )}
    </section>
  );
};

// ---- Rewards shop + my redemption history ----
const RewardStatusPill = ({ status }) => (
  <span className={`status-pill ${REDEMPTION_STATUS_CLASS[status] || "pending"}`}>
    {REDEMPTION_STATUS_LABEL[status] || status}
  </span>
);

// ---- Reward Journey: bandai-style vertical path ----
const REDEMPTION_CLAIMED_STATUSES = ["approved", "delivered"];

const RewardJourney = ({ rewards, redemptions, lifetime, available, onRedeem }) => {
  // Group redemptions by reward_id once
  const redByReward = React.useMemo(() => {
    const m = {};
    (redemptions || []).forEach(r => {
      (m[r.reward_id] = m[r.reward_id] || []).push(r);
    });
    return m;
  }, [redemptions]);

  // Build rungs sorted by cost DESC (top of page = highest tier).
  // รางวัลแลกซ้ำได้: ตัวแทนเลือกเองว่าจะแลกตอนนี้หรือเก็บคะแนนรอแลกใหญ่กว่า
  // State per rung (มุ่งสะท้อนสถานะ "พร้อมแลกไหม" เป็นหลัก):
  //   available — lifetime ≥ cost + คะแนนคงเหลือพอ + ยังมีของ → "แลกเลย" (ไม่จำกัดจำนวนครั้ง)
  //   reached   — lifetime ≥ cost แต่ยังแลกตอนนี้ไม่ได้ (คะแนนคงเหลือไม่พอ / ของหมด)
  //   locked    — lifetime ยังไม่ถึง cost
  // ส่วน claimCount/pendingCount แสดงเป็น badge เสริม ไม่ใช่ state หลัก
  const rungs = React.useMemo(() => {
    const sorted = [...(rewards || [])].sort((a, b) => b.cost_points - a.cost_points);
    return sorted.map((r, i) => {
      const my = redByReward[r.id] || [];
      const claimCount   = my.filter(x => REDEMPTION_CLAIMED_STATUSES.includes(x.status)).length;
      const pendingCount = my.filter(x => x.status === "pending").length;
      const inStock = r.stock == null || r.stock > 0;
      let state;
      if (lifetime < r.cost_points)               state = "locked";
      else if (available >= r.cost_points && inStock) state = "available";
      else                                         state = "reached";
      const needed = Math.max(0, r.cost_points - lifetime);
      const step = sorted.length - i; // top = highest step number
      return { ...r, state, needed, step, inStock, claimCount, pendingCount };
    });
  }, [rewards, redByReward, lifetime, available]);

  // Auto-scroll to the "current" rung: ขั้นแรกที่แลกได้ ถัดมาคือขั้นถัดไปที่ยังล็อก
  const focusIdx = React.useMemo(() => {
    const i1 = rungs.findIndex(t => t.state === "available");
    if (i1 >= 0) return i1;
    // ขั้นถัดไปที่ใกล้สุด = locked ที่ cost ต่ำสุด (อยู่ล่างสุดในรายการ desc)
    const i2 = [...rungs].reverse().findIndex(t => t.state === "locked");
    if (i2 >= 0) return rungs.length - 1 - i2;
    return -1;
  }, [rungs]);

  const focusRef = React.useRef(null);
  React.useEffect(() => {
    if (focusRef.current) {
      focusRef.current.scrollIntoView({ behavior: "smooth", block: "center" });
    }
  }, [focusIdx]);

  // Hero stat at the top: next milestone progress
  const nextLocked = rungs.slice().reverse().find(t => t.state === "locked");
  const heroPct = nextLocked
    ? Math.min(100, Math.round((lifetime / nextLocked.cost_points) * 100))
    : 100;

  if (!rungs.length) {
    return <div className="loading-msg">Admin ยังไม่ได้เพิ่มของรางวัล</div>;
  }

  return (
    <div className="reward-journey-wrap">
      {nextLocked && (
        <div className="journey-hero">
          <div className="journey-hero-text">
            <span className="journey-hero-label">เป้าหมายถัดไป</span>
            <strong>{tierEmojiFor(nextLocked.cost_points)} {nextLocked.name_th}</strong>
            <span className="journey-hero-needed">
              อีก <strong>{nextLocked.needed.toLocaleString()}</strong> คะแนน
            </span>
          </div>
          <div className="journey-hero-bar">
            <div className="journey-hero-fill" style={{ width: `${heroPct}%` }} />
            <span className="journey-hero-pct">{heroPct}%</span>
          </div>
        </div>
      )}

      <div className="reward-journey">
        {rungs.map((t, i) => {
          const isFocus = i === focusIdx;
          const imgSrc = rewardDisplayImageSrc(t);
          return (
            <div key={t.id}
              ref={isFocus ? focusRef : null}
              className={`reward-rung state-${t.state}${isFocus ? " rung-current" : ""}`}>
              <div className="reward-rung-node">
                <span className="reward-rung-icon">{tierEmojiFor(t.cost_points)}</span>
                <span className="reward-rung-step">ขั้น {t.step}</span>
              </div>
              <div className="reward-rung-connector" />
              <div className="reward-rung-card">
                {imgSrc && (
                  <div className="reward-rung-img"><img src={imgSrc} alt="" /></div>
                )}
                <div className="reward-rung-body">
                  <strong>{t.name_th}</strong>
                  <div className="reward-rung-cost">{t.cost_points.toLocaleString()} คะแนน</div>

                  {t.state === "available" && (
                    <button className="reward-rung-claim" onClick={() => onRedeem(t)}>
                      ⚡ แลกเลย!
                    </button>
                  )}
                  {t.state === "reached" && (
                    <span className="reward-rung-tag reached">
                      {!t.inStock ? "ของรางวัลหมดแล้ว" : "✓ คะแนนสะสมถึงแล้ว · คงเหลือยังไม่พอแลก"}
                    </span>
                  )}
                  {t.state === "locked" && (
                    <span className="reward-rung-tag locked">🔒 อีก {t.needed.toLocaleString()} คะแนน</span>
                  )}

                  <div className="reward-rung-extras">
                    {t.claimCount > 0 && (
                      <span className="reward-rung-badge claimed">🏆 เคยแลกแล้ว {t.claimCount} ครั้ง</span>
                    )}
                    {t.pendingCount > 0 && (
                      <span className="reward-rung-badge pending">⏳ รออนุมัติ {t.pendingCount} รายการ</span>
                    )}
                    {t.stock != null && t.stock > 0 && (
                      <span className="reward-rung-badge stock">เหลือ {t.stock} ชิ้น</span>
                    )}
                  </div>
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
};

// Map cost (in points) → tier emoji for reward journey nodes. Unknown costs
// fall back to a gift box.
const TIER_EMOJI_BY_COST = {
  100: "🥉", 200: "🎖️", 300: "🏵️", 400: "🏅", 500: "🥈",
  600: "⭐", 700: "🌟", 800: "💫", 900: "💎", 1000: "👑",
  1400: "🍽️", 1600: "🧳", 2000: "📚", 4000: "🎨", 6000: "🤖",
  10000: "💵", 30000: "✈️",
};
const tierEmojiFor = (cost) => TIER_EMOJI_BY_COST[cost] || "🎁";

const RewardsTab = ({ agent }) => {
  const [rewards, setRewards] = React.useState([]);
  const [redemptions, setRedemptions] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [view, setView] = React.useState("shop");
  const [confirmReward, setConfirmReward] = React.useState(null);
  const [working, setWorking] = React.useState(false);

  const load = async () => {
    const [rRes, rdRes] = await Promise.all([
      sb.from("rewards").select("*").eq("active", true).order("sort_order"),
      sb.from("reward_redemptions").select("*").eq("agent_id", agent.id).order("requested_at", { ascending: false }),
    ]);
    setRewards(rRes.data || []);
    setRedemptions(rdRes.data || []);
    setLoading(false);
  };

  React.useEffect(() => { load(); }, [agent.id]);

  // Realtime: เมื่อ admin อนุมัติ/ปฏิเสธ/mark delivered คำขอแลกรางวัล
  // ตัวแทนเห็นสถานะใหม่ทันทีโดยไม่ต้อง refresh + คะแนนคงเหลือคำนวณใหม่
  React.useEffect(() => {
    const channel = sb.channel(`rewards-${agent.id}`)
      .on("postgres_changes",
        { event: "*", schema: "public", table: "reward_redemptions", filter: `agent_id=eq.${agent.id}` },
        () => { load(); })
      .on("postgres_changes",
        { event: "*", schema: "public", table: "rewards" },
        () => { load(); })
      .subscribe();
    return () => { sb.removeChannel(channel); };
  }, [agent.id]);

  const { lifetime, available, locked } = computePoints(agent.activities, redemptions);
  const eligibleCount = rewards.filter(r => r.cost_points <= available && (r.stock == null || r.stock > 0)).length;
  const nextReward = rewards
    .filter(r => r.cost_points > available && (r.stock == null || r.stock > 0))
    .sort((a, b) => a.cost_points - b.cost_points)[0];

  const startRedeem = (reward) => {
    if (reward.cost_points > available) { alert("คะแนนคงเหลือไม่พอ"); return; }
    if (reward.stock != null && reward.stock <= 0) { alert("ของรางวัลหมดแล้ว"); return; }
    setConfirmReward(reward);
  };

  const confirmRedeem = async () => {
    if (!confirmReward || working) return;
    setWorking(true);
    const reward = confirmReward;
    // single atomic DB function: balance check + stock decrement + insert
    const { error } = await sb.rpc("redeem_reward", {
      p_agent_id: agent.id,
      p_reward_id: reward.id,
    });
    setWorking(false);
    setConfirmReward(null);
    if (error) {
      if (error.message?.includes("out_of_stock")) {
        alert("ของรางวัลหมดแล้ว กรุณาเลือกรางวัลอื่น");
      } else if (error.message?.includes("insufficient_points")) {
        alert("คะแนนคงเหลือไม่พอ");
      } else {
        alert("แลกไม่สำเร็จ: " + error.message);
      }
      await load();
      return;
    }
    setView("history");
    await load();
  };

  const cancelRedemption = async (r) => {
    if (!window.confirm(`ยกเลิกคำขอแลก "${r.reward_name_snapshot}"?\nคะแนนจะกลับมาให้คุณใช้ได้`)) return;
    const { data: upd, error } = await sb.rpc("cancel_reward_redemption", {
      p_agent_id: agent.id,
      p_redemption_id: r.id,
    });
    if (error) { alert("ยกเลิกไม่สำเร็จ: " + error.message); return; }
    if (!upd) { alert("ไม่สามารถยกเลิกได้ — Admin อาจอนุมัติไปแล้ว"); await load(); return; }
    await load();
  };

  return (
    <section className="scene reward-page">
      <SectionHead eyebrow="09 — ของรางวัล" title="ของรางวัล / แลกคะแนน"
        sub="เลือกของรางวัลที่ใช้คะแนนคงเหลือแลกได้ และติดตามสถานะคำขอของคุณ" />

      <div className="reward-summary">
        <div className="reward-summary-main">
          <span>คะแนนใช้แลกได้</span>
          <strong>{available.toLocaleString()}</strong>
          <small>{eligibleCount > 0 ? `แลกได้ ${eligibleCount} รายการตอนนี้` : "ยังไม่มีรายการที่แลกได้ตอนนี้"}</small>
        </div>
        <div className="reward-summary-side">
          <div>
            <span>สะสมทั้งหมด</span>
            <strong>{lifetime.toLocaleString()}</strong>
          </div>
          <div>
            <span>รออนุมัติ</span>
            <strong>{locked.toLocaleString()}</strong>
          </div>
        </div>
        {nextReward && (
          <div className="reward-next">
            <span>รางวัลถัดไป</span>
            <strong>{nextReward.name_th}</strong>
            <small>อีก {(nextReward.cost_points - available).toLocaleString()} คะแนน</small>
          </div>
        )}
      </div>

      <PointRulesCard subtitle="ทำกิจกรรมเหล่านี้เพื่อสะสมคะแนนไว้แลกของรางวัล" />

      <div className="reward-tabs">
        <button className={`tab-btn ${view === "shop" ? "active" : ""}`} onClick={() => setView("shop")}>
          ของรางวัล
        </button>
        <button className={`tab-btn ${view === "journey" ? "active" : ""}`} onClick={() => setView("journey")}>
          เป้าหมาย
        </button>
        <button className={`tab-btn ${view === "history" ? "active" : ""}`} onClick={() => setView("history")}>
          ประวัติ ({redemptions.length})
        </button>
      </div>

      {loading ? <div className="loading-msg">กำลังโหลด...</div> : view === "journey" ? (
        <RewardJourney
          rewards={rewards}
          redemptions={redemptions}
          lifetime={lifetime}
          available={available}
          onRedeem={startRedeem}
        />
      ) : view === "shop" ? (
        rewards.length === 0 ? (
          <div className="loading-msg">Admin ยังไม่ได้เพิ่มของรางวัล</div>
        ) : (
          <div className="reward-grid">
            {rewards.map(r => {
              const affordable = r.cost_points <= available;
              const inStock = r.stock == null || r.stock > 0;
              const needed = Math.max(0, r.cost_points - available);
              const imgSrc = rewardDisplayImageSrc(r);
              return (
                <article key={r.id} className={`reward-card ${!affordable || !inStock ? "disabled" : ""}`}>
                  {imgSrc ? (
                    <div className="reward-img"><img src={imgSrc} alt={r.name_th} /></div>
                  ) : (
                    <div className="reward-img reward-img-empty">🎁</div>
                  )}
                  <div className="reward-body">
                    <div className="reward-title-row">
                      <strong>{r.name_th}</strong>
                      {affordable && inStock && <span>พร้อมแลก</span>}
                    </div>
                    {r.description && <p>{r.description}</p>}
                    <div className="reward-meta">
                      <span className="reward-cost">{r.cost_points.toLocaleString()} คะแนน</span>
                      {r.stock != null && (
                        <span className={`reward-stock ${r.stock <= 0 ? "out" : ""}`}>
                          {r.stock <= 0 ? "หมดแล้ว" : `เหลือ ${r.stock} ชิ้น`}
                        </span>
                      )}
                    </div>
                    <button className="login-btn" style={{ width: "100%" }}
                      disabled={!affordable || !inStock}
                      onClick={() => startRedeem(r)}>
                      {!inStock ? "หมดแล้ว" : !affordable ? `ขาดอีก ${needed.toLocaleString()} คะแนน` : "แลกเลย"}
                    </button>
                  </div>
                </article>
              );
            })}
          </div>
        )
      ) : (
        <div className="reward-history">
          <div className="table-wrap reward-history-table">
            <table className="dash-table">
            <thead>
              <tr><th>วันที่ขอ</th><th>ของรางวัล</th><th className="num">คะแนน</th><th>สถานะ</th><th>หมายเหตุ</th></tr>
            </thead>
            <tbody>
              {redemptions.map(r => (
                <tr key={r.id}>
                  <td className="nowrap">{fmtDate((r.requested_at || "").slice(0, 10))}</td>
                  <td><strong>{r.reward_name_snapshot}</strong></td>
                  <td className="num">{r.cost_points_snapshot.toLocaleString()}</td>
                  <td><RewardStatusPill status={r.status} /></td>
                  <td>
                    {r.status === "pending" && (
                      <button className="link-btn" onClick={() => cancelRedemption(r)}>ยกเลิกคำขอ</button>
                    )}
                    {r.status === "rejected" && r.reject_reason && (
                      <span style={{ color: "var(--muted)" }}>{r.reject_reason}</span>
                    )}
                    {r.status === "delivered" && r.delivered_at && (
                      <span style={{ color: "var(--muted)" }}>ส่ง {fmtDate(r.delivered_at.slice(0,10))}</span>
                    )}
                  </td>
                </tr>
              ))}
              {!redemptions.length && (
                <tr><td colSpan="5" style={{ textAlign: "center", padding: 32, color: "var(--muted)" }}>ยังไม่เคยแลกของรางวัล</td></tr>
              )}
            </tbody>
            </table>
          </div>
          <div className="reward-history-cards">
            {redemptions.map(r => (
              <article className="reward-history-card" key={r.id}>
                <div>
                  <strong>{r.reward_name_snapshot}</strong>
                  <span>{fmtDate((r.requested_at || "").slice(0, 10))} · {r.cost_points_snapshot.toLocaleString()} คะแนน</span>
                </div>
                <RewardStatusPill status={r.status} />
                {r.status === "pending" && (
                  <button className="link-btn" onClick={() => cancelRedemption(r)}>ยกเลิกคำขอ</button>
                )}
                {r.status === "rejected" && r.reject_reason && (
                  <p>{r.reject_reason}</p>
                )}
                {r.status === "delivered" && r.delivered_at && (
                  <p>ส่ง {fmtDate(r.delivered_at.slice(0,10))}</p>
                )}
              </article>
            ))}
            {!redemptions.length && (
              <div className="reward-history-empty">ยังไม่เคยแลกของรางวัล</div>
            )}
          </div>
        </div>
      )}

      {confirmReward && (
        <div className="modal-backdrop" onClick={() => !working && setConfirmReward(null)}>
          <div className="modal-box" onClick={e => e.stopPropagation()}>
            <div className="modal-head">
              <strong>ยืนยันการแลกของรางวัล</strong>
              <button className="modal-close" onClick={() => !working && setConfirmReward(null)}>×</button>
            </div>
            <div className="modal-body">
              <p style={{ marginBottom: 12 }}>
                คุณต้องการแลก <strong>{confirmReward.name_th}</strong> ในราคา <strong>{confirmReward.cost_points.toLocaleString()} คะแนน</strong>?
              </p>
              <p style={{ color: "var(--muted)", fontSize: 13 }}>
                คะแนนจะถูกหักทันทีเพื่อกันใช้ซ้ำ และจะรอ Admin อนุมัติก่อนจัดส่ง
                คุณยกเลิกได้เองตราบใดที่ยังเป็นสถานะ "รออนุมัติ"
              </p>
              <div className="modal-actions">
                <button className="btn-outline" onClick={() => setConfirmReward(null)} disabled={working}>ยกเลิก</button>
                <button className="login-btn" style={{ width: "auto", padding: "10px 24px" }}
                  onClick={confirmRedeem} disabled={working}>
                  {working ? "กำลังแลก..." : "ยืนยันการแลก"}
                </button>
              </div>
            </div>
          </div>
        </div>
      )}
    </section>
  );
};

// ---- Daily Challenges ----
const CHALLENGE_TYPE_LABEL = {
  checkin:    "เช็คอินคลาส",
  ape_submit: "บันทึก APE",
  fyc_submit: "บันทึก NBC",
  manual:     "Admin มอบให้",
};

const normalizeChallengeUnit = (unit) => String(unit || "").replace(/\s+/g, "").toLowerCase();
const challengeMatchesAgent = (challenge, agent) => {
  if (!challenge?.challenger_type) return true;
  if (challenge.challenger_type === "agent") return challenge.target_agent_id === agent.id;
  if (challenge.challenger_type === "unit") {
    return normalizeChallengeUnit(challenge.challenger_unit) === normalizeChallengeUnit(agent.unit);
  }
  return true;
};
const challengeMetricLabel = (type) => {
  if (type === "case") return "Case";
  if (type === "ape") return "APE";
  if (type === "fyc") return "NBC";
  if (type === "recruit") return "Recruit";
  return "";
};
const challengeMetricUnit = (type) => (type === "case" || type === "recruit") ? "ครั้ง" : "บาท";
const challengeProgressFor = (challenge, metrics) => {
  const type = challenge?.condition_type;
  const target = Number(challenge?.condition_value || 0);
  if (!type || target <= 0) return { eligible: true, current: 0, target, label: "" };
  const current = Number(metrics?.[type] || 0);
  return {
    eligible: current >= target,
    current,
    target,
    label: `${challengeMetricLabel(type)} ${current.toLocaleString()} / ${target.toLocaleString()} ${challengeMetricUnit(type)}`,
  };
};

const DailyChallengesCard = ({ agent, onPointsAwarded }) => {
  const [challenges, setChallenges]   = React.useState([]);
  const [completions, setCompletions] = React.useState([]);
  const [metrics, setMetrics]         = React.useState({ case: 0, ape: 0, fyc: 0, recruit: 0 });
  const [claiming, setClaiming]       = React.useState(null);
  const today = todayDateKey();

  const load = React.useCallback(async () => {
    const [cRes, cpRes, salesRes, recruitRes] = await Promise.all([
      sb.from("daily_challenges").select("*, challenger:challenger_id(name), target_agent:target_agent_id(name)")
        .eq("active", true)
        .or(`active_from.is.null,active_from.lte.${today}`)
        .or(`active_until.is.null,active_until.gte.${today}`)
        .order("sort_order"),
      sb.from("daily_challenge_completions").select("*")
        .eq("agent_id", agent.id).eq("completed_date", today),
      sb.from("agent_ape_sales").select("amount, fyc, recruit_count")
        .eq("agent_id", agent.id).eq("date", today),
      sb.from("agent_activities").select("id")
        .eq("agent_id", agent.id).eq("date", today)
        .in("name", ["Recruit", "ตัวแทนชวนตัวแทน"]),
    ]);
    setChallenges((cRes.data || []).filter(ch => challengeMatchesAgent(ch, agent)));
    setCompletions(cpRes.data || []);
    const todaySales = salesRes.data || [];
    setMetrics({
      case: todaySales.filter(s => Number(s.amount || 0) > 0 || Number(s.fyc || 0) > 0).length,
      ape: todaySales.reduce((n, s) => n + Number(s.amount || 0), 0),
      fyc: todaySales.reduce((n, s) => n + Number(s.fyc || 0), 0),
      recruit: todaySales.reduce((n, s) => n + Number(s.recruit_count || 0), 0) + (recruitRes.data || []).length,
    });
  }, [agent.id, agent.unit, today]);

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

  React.useEffect(() => {
    const ch = sb.channel(`challenges-${agent.id}`)
      .on("postgres_changes", { event: "*", schema: "public", table: "daily_challenge_completions", filter: `agent_id=eq.${agent.id}` }, load)
      .on("postgres_changes", { event: "*", schema: "public", table: "daily_challenges" }, load)
      .subscribe();
    return () => sb.removeChannel(ch);
  }, [agent.id, load]);

  const completionFor = (id) => completions.find(c => c.challenge_id === id);
  const isPending = (id) => completionFor(id)?.points_awarded === -1;
  const isDone = (id) => {
    const row = completionFor(id);
    return !!row && row.points_awarded !== -1;
  };

  const claim = async (challenge) => {
    if (completionFor(challenge.id) || claiming === challenge.id) return;
    if (!challengeMatchesAgent(challenge, agent)) {
      alert("ภารกิจนี้ไม่ได้ระบุให้คุณ");
      await load();
      return;
    }
    if (!challengeProgressFor(challenge, metrics).eligible) {
      alert("ยังทำภารกิจนี้ไม่ถึงเงื่อนไข");
      await load();
      return;
    }
    setClaiming(challenge.id);
    const { error } = await sb.from("daily_challenge_completions").insert([{
      challenge_id: challenge.id,
      agent_id: agent.id,
      completed_date: today,
      points_awarded: -1,
    }]);
    setClaiming(null);
    if (error) {
      if (error.code === "23505") { await load(); return; }
      alert("ส่งคำขอไม่สำเร็จ: " + error.message);
      return;
    }
    await load();
  };

  if (!challenges.length) return null;

  const doneCount = challenges.filter(c => isDone(c.id)).length;
  return (
    <div className="ape-sales-card challenge-section">
      <div className="ape-sales-header">
        <strong>⚡ Daily challenges</strong>
        <span className="ape-sales-sub">
          {today.split("-").reverse().join("/")} &nbsp;·&nbsp;
          {doneCount}/{challenges.length} ภารกิจ
        </span>
      </div>
      <div className="challenge-list">
        {challenges.map(ch => {
          const done    = isDone(ch.id);
          const pending = isPending(ch.id);
          const progress = challengeProgressFor(ch, metrics);
          const loading = claiming === ch.id;
          return (
            <div key={ch.id} className={`challenge-card${done ? " done" : ""}${pending ? " pending" : ""}`}>
              <div className="challenge-icon">
                {ch.image_path
                  ? <img src={challengeImageSrc(ch.image_path)} alt="" className="challenge-thumb" />
                  : (ch.icon || "🎯")}
              </div>
              <div className="challenge-body">
                <div className="challenge-title">{ch.title}</div>
                {ch.description && <div className="challenge-desc">{ch.description}</div>}
                <div className="challenge-meta">
                  {ch.condition_type === "case" && <span className="challenge-condition">🎯 Case ≥ {Number(ch.condition_value).toLocaleString()} ครั้ง</span>}
                  {ch.condition_type === "ape"  && <span className="challenge-condition">🎯 ภารกิจ APE</span>}
                  {ch.condition_type === "fyc"  && <span className="challenge-condition">🎯 NBC ≥ {Number(ch.condition_value).toLocaleString()} บาท</span>}
                  {ch.condition_type === "recruit" && <span className="challenge-condition">🎯 Recruit ≥ {Number(ch.condition_value).toLocaleString()} ครั้ง</span>}
                  {ch.reward_value > 0 && (
                    <span className="challenge-reward">💰 เงินสด {Number(ch.reward_value).toLocaleString()} บาท</span>
                  )}
                  {ch.challenger?.name && (
                    <span className="challenge-challenger">โดย {ch.challenger.name}</span>
                  )}
                  {ch.challenger_type === "unit" && ch.challenger_unit && (
                    <span className="challenge-challenger">ท้าทาย Unit {ch.challenger_unit}</span>
                  )}
                  {ch.challenger_type === "agent" && ch.target_agent?.name && (
                    <span className="challenge-challenger">ท้าทาย {ch.target_agent.name}</span>
                  )}
                  {progress.label && (
                    <span className={`challenge-progress ${progress.eligible ? "ok" : ""}`}>{progress.label}</span>
                  )}
                </div>
              </div>
              <div className="challenge-action">
                {done ? (
                  <span className="challenge-done-badge">✓ รับแล้ว</span>
                ) : pending ? (
                  <span className="challenge-pending-badge">รออนุมัติ</span>
                ) : !progress.eligible ? (
                  <span className="challenge-pending-badge muted">ยังไม่ถึง</span>
                ) : (
                  <button className="challenge-claim-btn" onClick={() => claim(ch)} disabled={loading}>
                    {loading ? "..." : "ส่งขอรับ"}
                  </button>
                )}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
};

const DailyChallengesTab = ({ agent, onRefresh }) => (
  <section className="scene">
    <SectionHead eyebrow="09 — Daily challenges" title="Daily challenges"
      sub="ทำภารกิจพิเศษให้ครบเพื่อรับคะแนนโบนัส — รีเซตใหม่ทุกวัน" />
    <DailyChallengesCard agent={agent} onPointsAwarded={onRefresh} />
  </section>
);

// ---- ตารางเทียบเป้า-ยอดจริงรายเดือน (ใช้ซ้ำ 2 คู่: รายได้↔NBC, FYP↔APE) ----
const mnMoney = (n) => Math.round(n).toLocaleString("en-US");
const mnMonthLabel = (key) => {
  const [y, m] = key.split("-").map(Number);
  return `${TH_MONTHS[m - 1]} ${y + 543}`;
};
const MonthGoalTable = ({ caption, targetHead, achievedHead, target, achievedMap, months }) => {
  const totalAchieved = months.reduce((s, k) => s + (achievedMap[k] || 0), 0);
  const totalTarget = target * months.length;
  return (
    <div style={{ marginTop: 14 }}>
      <div style={{ fontSize: 13, fontWeight: 600, color: "var(--ink)", margin: "0 2px 8px" }}>{caption}</div>
      <div className="table-wrap">
        <table className="dash-table ape-monthly-table">
          <thead>
            <tr>
              <th>เดือน</th>
              <th className="num">{targetHead}</th>
              <th className="num">{achievedHead}</th>
              <th className="num">คงเหลือ</th>
              <th style={{ width: 180, minWidth: 140 }}>ความคืบหน้า</th>
            </tr>
          </thead>
          <tbody>
            {months.map(key => {
              const achieved = achievedMap[key] || 0;
              const pct  = target > 0 ? Math.min(Math.round(achieved / target * 100), 100) : 0;
              const left = Math.max(target - achieved, 0);
              const hit  = achieved >= target;
              return (
                <tr key={key}>
                  <td><strong>{mnMonthLabel(key)}</strong></td>
                  <td className="num" style={{ color: "var(--muted)" }}>{mnMoney(target)}</td>
                  <td className="num">
                    <strong style={{ color: hit ? "var(--t)" : achieved > 0 ? "var(--gold)" : "inherit" }}>
                      {mnMoney(achieved)}
                    </strong>
                  </td>
                  <td className="num" style={{ color: "var(--muted)", fontSize: 13 }}>
                    {left > 0 ? mnMoney(left) : <span style={{ color: "var(--t)" }}>✓</span>}
                  </td>
                  <td>
                    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                      <div style={{ flex: 1, height: 6, background: "var(--line)", borderRadius: 4, overflow: "hidden" }}>
                        <div style={{ height: "100%", borderRadius: 4, width: `${pct}%`, background: hit ? "var(--t)" : "var(--gold)", transition: "width 600ms ease" }} />
                      </div>
                      <span style={{ fontSize: 12, color: "var(--muted)", minWidth: 36, textAlign: "right" }}>{pct}%</span>
                    </div>
                  </td>
                </tr>
              );
            })}
          </tbody>
          <tfoot>
            <tr>
              <td><strong>รวม</strong></td>
              <td className="num"><strong style={{ color: "var(--muted)" }}>{mnMoney(totalTarget)}</strong></td>
              <td className="num"><strong style={{ color: "var(--gold)" }}>{mnMoney(totalAchieved)}</strong></td>
              <td className="num"><strong>{mnMoney(Math.max(totalTarget - totalAchieved, 0))}</strong></td>
              <td />
            </tr>
          </tfoot>
        </table>
      </div>
    </div>
  );
};

// ---- เป้าหมายรายเดือน: เทียบ My Money Need กับยอดจริงที่ปิดได้ ----
// เป้าคงที่ทุกเดือน (แผนเดียว) 2 คู่: รายได้ที่ต้องการ↔NBC ที่ได้รับ, FYP↔APE ที่ปิดได้
// ยอดจริง group ตาม closed_at ของดีล closed_won (fallback updated_at กันยอดหาย)
const MonthlyGoalCompare = ({ goals, deals }) => {
  const calc = moneyNeedsCalc(goals || {});
  const incomeTarget = Math.round(calc.requiredIncome);
  const fypTarget = Math.round(calc.fyp);

  if (!calc.hasData || fypTarget <= 0) {
    return (
      <div className="sales-empty">ยังไม่ได้ตั้ง “My Money Need” — ไปที่เมนู My Money Need เพื่อกรอกค่าใช้จ่าย/เดือน ระบบจะคำนวณเป้ารายได้/FYP แล้วนำมาเทียบกับยอดที่ได้จริงตรงนี้</div>
    );
  }

  const apeMap = {}, fycMap = {};
  (deals || []).forEach(d => {
    if (d.stage !== "closed_won") return;
    const key = String(d.closed_at || d.updated_at || "").slice(0, 7);
    if (key.length !== 7) return;
    apeMap[key] = (apeMap[key] || 0) + Number(d.actual_ape || 0);
    fycMap[key] = (fycMap[key] || 0) + Number(d.actual_fyc || 0);
  });

  const curKey = todayDateKey().slice(0, 7);
  const months = Array.from(new Set([curKey, ...Object.keys(apeMap), ...Object.keys(fycMap)])).sort().reverse();

  return (
    <>
      <MonthGoalTable
        caption="รวมรายได้ที่ต้องการ/เดือน เทียบกับ NBC ที่ได้รับจริง"
        targetHead="เป้ารายได้" achievedHead="NBC ได้รับ"
        target={incomeTarget} achievedMap={fycMap} months={months} />
      <MonthGoalTable
        caption="เป้า FYP/เดือน เทียบกับ APE ที่ปิดได้จริง"
        targetHead="เป้า FYP" achievedHead="APE ปิดได้"
        target={fypTarget} achievedMap={apeMap} months={months} />
    </>
  );
};

// ---- Tab เป้าหมาย: เทียบเป้า My Money Need กับยอดจริงรายเดือน (เมนูแยก) ----
const GoalTrackTab = ({ agent }) => {
  const [loading, setLoading] = React.useState(true);
  const [deals, setDeals] = React.useState([]);
  const [goals, setGoals] = React.useState(null);

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      const [d, g] = await Promise.all([
        sb.from("sales_deals").select("*").eq("agent_id", agent.id).eq("stage", "closed_won"),
        sb.from("agent_goals").select("*").eq("agent_id", agent.id).maybeSingle(),
      ]);
      if (cancelled) return;
      if (!d.error) setDeals(d.data || []);
      if (!g.error) setGoals(g.data || {});
      setLoading(false);
    })();
    return () => { cancelled = true; };
  }, [agent.id]);

  return (
    <section className="scene">
      <SectionHead eyebrow="เป้าหมาย" title="เป้าหมายรายเดือน"
        sub="เทียบเป้าจาก My Money Need กับยอดที่ทำได้จริงในแต่ละเดือน" />
      {loading
        ? <div className="sales-empty">กำลังโหลด...</div>
        : <MonthlyGoalCompare goals={goals} deals={deals} />}
    </section>
  );
};

// ---- Tab คุณวุฒิ: ความคืบหน้าสู่ MDRT/COT/TOT จากยอดเบี้ยที่นำส่งแล้ว ----
// qualMoney / qualHasCrest / QualCrest ย้ายไป data.jsx แล้ว เพราะหน้า admin/manager ใช้ร่วมกัน

const QualificationGauge = ({ pct, caption }) => {
  const dash = 151;   // ความยาวส่วนโค้งครึ่งวงกลม รัศมี 48
  return (
    <div className="qual-gauge">
      <svg viewBox="0 0 120 74" role="img" aria-label={`ความคืบหน้า ${pct} เปอร์เซ็นต์`}>
        <path d="M12 66 A48 48 0 0 1 108 66" fill="none" stroke="var(--paper-2)"
              strokeWidth="13" strokeLinecap="round" />
        <path d="M12 66 A48 48 0 0 1 108 66" fill="none" stroke="var(--brand)"
              strokeWidth="13" strokeLinecap="round"
              strokeDasharray={dash} strokeDashoffset={dash * (1 - Math.min(100, pct) / 100)} />
        <text x="60" y="60" textAnchor="middle" fontSize="25" fontWeight="700" fill="var(--ink)">{pct}%</text>
      </svg>
      {caption && <div className="qual-gauge-cap">{caption}</div>}
    </div>
  );
};

// แถบความคืบหน้าหนึ่งขั้น — แถบทึบ = ยอดนำส่ง (ทุกดีลที่ปิด ไม่ต้องรออนุมัติ)
// ยอดที่ใช้เทียบมาจาก "ชนิดของขั้นนั้น" (เบี้ยหรือค่าคอมฯ) จึงต้องมีป้ายกำกับและสีแยก
// ไม่งั้นตัวเลขคนละสเกลจะถูกอ่านผิดทันที
const QualificationBar = ({ tier, sums }) => {
  const metric = qualMetricOf(tier);
  const submitted = qualTierSum(sums, tier);
  const threshold = Number(tier.threshold) || 1;
  const reached = submitted >= threshold;
  const pctSubmitted = Math.min(100, (submitted / threshold) * 100);
  return (
    <div className="qual-bar">
      <div className="qual-bar-label">
        <QualCrest label={tier.label} reached={reached} size={30} />
        <span>
          {tier.label} <span className={`qual-metric-chip ${metric}`}>{qualMetricLabel(metric)}</span>
          <small>{qualMoney(tier.threshold)}</small>
        </span>
      </div>
      <div>
        <div className="qual-bar-track">
          <div className={`qual-bar-fill ${metric}${reached ? " reached" : ""}`} style={{ width: `${pctSubmitted}%` }} />
        </div>
        <div className="qual-bar-meta">
          <span>ยอดนำส่ง <b>{qualMoney(submitted)}</b></span>
          {reached
            ? <span className="done">ผ่านแล้ว</span>
            : <span>เหลือ <b>{qualMoney(threshold - submitted)}</b></span>}
        </div>
      </div>
    </div>
  );
};

const QualificationMine = ({ round, sales, agentId }) => {
  const nowKey = dateKey(getNow()).slice(0, 7);
  const p = qualProgress(sales, round, round.tiers, agentId, nowKey);
  const tiers = qualSortTiers(round.tiers);
  // ผ่านครบทุกขั้นแล้ว และขั้นสูงสุดเป็นคุณวุฒิ MDRT จริง → เปลี่ยนการ์ดฮีโร่เป็นการ์ดฉลองพร้อมตราเต็มใบ
  const crowned = !p.next && p.reached && qualHasCrest(p.reached.label);
  return (
    <>
      {crowned ? (
        <div className="qual-crown">
          <div className="qual-crown-glow" aria-hidden="true" />
          <div className="qual-crown-body">
            <div className="qual-crown-cap">คุณวุฒิสูงสุดที่ทำได้</div>
            <div className="qual-crown-num">{p.reached.label} · {orgGoalRangeLabel(round)}</div>
            <div className="qual-crown-sub">
              {qualMetricLabel(p.metric)}ที่นำส่งแล้ว {qualMoney(p.submitted)} บาท — ครบทุกขั้นแล้ว
            </div>
          </div>
          <div className="qual-crown-plate">
            <img src="photos/mdrt-crest.png" alt={`ตราคุณวุฒิ ${p.reached.label}`}
                 onError={(e) => { e.currentTarget.parentNode.style.display = "none"; }} />
          </div>
        </div>
      ) : (
      <div className="qual-card qual-wm">
        <div className="qual-hero">
          <div>
            {p.next ? (
              <>
                <div className="qual-hero-cap">เหลืออีกเท่าไรถึง {p.next.label}</div>
                <div className="qual-hero-num">{qualMoney(p.needed)}<span>บาท</span></div>
                <div className="qual-hero-sub">
                  ขั้นนี้วัดด้วย <strong>{qualMetricLabel(p.metric)}</strong> — ทำได้แล้ว{" "}
                  <strong>{qualMoney(p.submitted)}</strong> จากเกณฑ์ {qualMoney(p.next.threshold)} บาท
                </div>
                {p.pace > 0 && p.etaMonth && (
                  <div className="qual-hero-sub pace">
                    ตามจังหวะเฉลี่ยเดือนละ {qualMoney(Math.round(p.pace))} คาดว่าจะถึงราวเดือน {orgMonthLabel(p.etaMonth)}
                  </div>
                )}
                {p.pace > 0 && !p.etaMonth && (
                  <div className="qual-hero-sub late">
                    ตามจังหวะเฉลี่ยเดือนละ {qualMoney(Math.round(p.pace))} ยังไม่ทันภายในรอบนี้ ต้องเร่งอีก
                  </div>
                )}
              </>
            ) : (
              <>
                <div className="qual-hero-cap">คุณวุฒิสูงสุดที่ทำได้</div>
                <div className="qual-hero-num">{p.reached ? p.reached.label : "—"}</div>
                <div className="qual-hero-sub">
                  {p.reached
                    ? `${qualMetricLabel(p.metric)}ที่นำส่งแล้ว ${qualMoney(p.submitted)} บาท — ครบทุกขั้นแล้ว`
                    : "ยังไม่ได้ตั้งขั้นคุณวุฒิในรอบนี้"}
                </div>
              </>
            )}
          </div>
          <QualificationGauge
            pct={p.pct}
            caption={p.next ? `ของเกณฑ์ ${p.next.label}` : (p.reached ? "ครบทุกขั้น" : "ยังไม่ได้ตั้งขั้น")} />
        </div>
      </div>
      )}

      <div className="qual-card">
        <h3>คุณวุฒิทั้งหมดในรอบนี้</h3>
        {tiers.length === 0
          ? <div className="qual-empty">ยังไม่ได้ตั้งขั้นคุณวุฒิในรอบนี้</div>
          : (
            <div className="qual-bars">
              {tiers.map((t) => (
                <QualificationBar key={t.id} tier={t} sums={p.sums} />
              ))}
            </div>
          )}
      </div>
    </>
  );
};

const QualificationBoard = ({ round, sales, agents, meId }) => {
  const board = qualBoard(sales, agents, round, round.tiers);
  return (
    <div className="qual-card">
      <h3>ผู้ถึงคุณวุฒิ · {orgGoalRangeLabel(round)}</h3>
      {board.groups.length === 0
        ? <div className="qual-empty">ยังไม่ได้ตั้งขั้นคุณวุฒิในรอบนี้</div>
        : board.groups.map((g) => (
          <div className="qual-club" key={g.tier.id}>
            <div className="qual-club-head">
              <QualCrest label={g.tier.label} reached={g.people.length > 0 || g.passed > 0} size={26} />
              <span className="qual-club-name">{g.tier.label}</span>
              <span className={`qual-metric-chip ${g.metric}`}>{qualMetricLabel(g.metric)}</span>
              <span className="qual-club-req">{qualMoney(g.tier.threshold)}</span>
              <span className="qual-club-count">
                {g.people.length
                  ? `${g.people.length} คน`
                  : g.passed > 0 ? "ขึ้นขั้นสูงกว่าแล้ว" : "ยังไม่มีใครถึง"}
              </span>
            </div>
            {g.people.length === 0
              ? <div className="qual-club-empty">
                  {g.passed > 0
                    ? "ทุกคนที่ผ่านเกณฑ์ขั้นนี้ ขึ้นไปอยู่ขั้นที่สูงกว่าแล้ว"
                    : g.topBelow > 0
                      ? `ปีนี้ยังไม่มีใครถึงขั้นนี้ — ที่หนึ่งตอนนี้ทำได้ ${qualMoney(g.topBelow)}`
                      : "ปีนี้ยังไม่มีใครถึงขั้นนี้"}
                </div>
              : (
                <div className="qual-people">
                  {g.people.map((p) => (
                    <span className={`qual-person${p.id === meId ? " me" : ""}`} key={p.id}>
                      <span className="qual-person-av">{initial(p.name)}</span>
                      {p.name}
                      <span className="qual-person-amt">{qualMoney(p.value)}</span>
                    </span>
                  ))}
                </div>
              )}
          </div>
        ))}
    </div>
  );
};

const QualificationTab = ({ agent }) => {
  const [loading, setLoading] = React.useState(true);
  const [round, setRound] = React.useState(null);
  const [sales, setSales] = React.useState([]);
  const [agents, setAgents] = React.useState([]);
  const [view, setView] = React.useState("mine");

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      const [r, s, a] = await Promise.all([
        loadActiveQualificationRound(sb),
        sb.from("agent_ape_sales").select("agent_id, date, amount, fyc"),
        sb.from("agents").select("id, name, role, unit"),
      ]);
      if (cancelled) return;
      setRound(r);
      if (!s.error) setSales(s.data || []);
      if (!a.error) setAgents(a.data || []);
      setLoading(false);
    })();
    return () => { cancelled = true; };
  }, [agent.id]);

  return (
    <section className="scene">
      <SectionHead
        eyebrow={round ? `คุณวุฒิ · ${orgGoalRangeLabel(round)}` : "คุณวุฒิ"}
        title="คุณวุฒิของฉัน"
        sub="นับยอดเบี้ยที่นำส่งแล้วทุกดีลที่ปิด ไม่ต้องรอผู้จัดการอนุมัติ" />
      {loading && <div className="sales-empty">กำลังโหลด...</div>}
      {!loading && !round && (
        <div className="sales-empty">ยังไม่ได้ตั้งเกณฑ์คุณวุฒิ — ติดต่อผู้ดูแลระบบ</div>
      )}
      {!loading && round && (
        <>
          <div className="ts-seg" style={{ marginBottom: 16 }}>
            <button type="button" className={`ts-seg-btn${view === "mine" ? " active" : ""}`}
                    onClick={() => setView("mine")}>ของฉัน</button>
            <button type="button" className={`ts-seg-btn${view === "board" ? " active" : ""}`}
                    onClick={() => setView("board")}>กระดาน</button>
          </div>
          {view === "mine"
            ? <QualificationMine round={round} sales={sales} agentId={agent.id} />
            : <QualificationBoard round={round} sales={sales} agents={agents} meId={agent.id} />}
        </>
      )}
    </section>
  );
};

// ---- Dashboard shell ----
const Dashboard = ({ agent, onLogout, onRefresh }) => {
  const [tab, setTab] = React.useState(() => {
    const fromHash = window.location.hash.slice(1);
    return AGENT_NAV.some(n => n.id === fromHash) ? fromHash : "overview";
  });
  const [menuOpen, setMenuOpen] = React.useState(false);
  const [avatarBust, setAvatarBust] = React.useState(0);
  const [newBadges, setNewBadges] = React.useState(new Set());
  const collapseKey = `${window.TENANT.prefix}_agent_sidebar_collapsed`;
  const [collapsed, setCollapsed] = React.useState(() => {
    try { return localStorage.getItem(collapseKey) === "1"; } catch (e) { return false; }
  });
  const toggleCollapsed = () => setCollapsed(c => {
    const next = !c;
    try { localStorage.setItem(collapseKey, next ? "1" : "0"); } catch (e) {}
    return next;
  });
  const sidebarRef = React.useRef(null);
  useNavScrollHint(sidebarRef);

  React.useEffect(() => {
    document.body.style.overflow = menuOpen ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [menuOpen]);

  React.useEffect(() => {
    Promise.all(
      AGENT_BADGE_SOURCES.map(async (s) => {
        const lastSeen = getAgentBadgeSeen(s.id);
        const { count } = await sb.from(s.table)
          .select("*", { count: "exact", head: true })
          .gt(s.col, lastSeen);
        return count > 0 ? s.id : null;
      })
    ).then(results => {
      setNewBadges(new Set(results.filter(Boolean)));
    });
  }, []);

  const goTab = (id) => {
    setTab(id);
    window.location.hash = id;
    setMenuOpen(false);
    setAgentBadgeSeen(id);
    setNewBadges(prev => { const next = new Set(prev); next.delete(id); return next; });
    const main = document.querySelector(".main");
    if (main && main.scrollHeight > main.clientHeight + 1) {
      main.scrollTo({ top: 0, behavior: "smooth" });
    } else {
      window.scrollTo({ top: 0, behavior: "smooth" });
    }
  };
  const label = AGENT_NAV.find(n => n.id === tab)?.label;

  return (
    <div className={`app app-shell ${collapsed ? "sidebar-collapsed" : ""}`}>
      {menuOpen && <div className="mobile-overlay" onClick={() => setMenuOpen(false)} />}

      <button
        className="sidebar-toggle"
        onClick={toggleCollapsed}
        title={collapsed ? "ขยายเมนู" : "หุบเมนู"}
        aria-label={collapsed ? "ขยายเมนู" : "หุบเมนู"}
      >
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
          strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
          <polyline points="15 18 9 12 15 6" />
        </svg>
      </button>

      <aside ref={sidebarRef} className={`sidebar ${menuOpen ? "open" : ""}`}>
        <div className="sidebar-profile">
          <AgentAvatar agentId={agent.id} name={agent.name} size={72} bust={avatarBust} />
          <div className="sidebar-profile-name">{agent.name}</div>
          <div className="sidebar-profile-code">เลขประจำตัว {agent.agent_code}</div>
        </div>
        <nav className="nav">
          <div className="nav-label">MENU</div>
          <a className="nav-item nav-item-home" href="index.html" title="กลับหน้าแรก 83F">
            <span className="nav-icon">{NAV_ICONS.home}</span>
            <span className="nav-text">หน้าหลัก 83F</span>
            <span className="nav-ext" aria-hidden="true">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M7 17L17 7" /><path d="M9 7h8v8" /></svg>
            </span>
          </a>
          {(() => {
            const byId = Object.fromEntries(AGENT_NAV.map(n => [n.id, n]));
            const grouped = new Set();
            const groups = AGENT_NAV_GROUPS
              .map(g => ({ label: g.label, items: g.ids.map(id => byId[id]).filter(Boolean) }))
              .filter(g => g.items.length);
            groups.forEach(g => g.items.forEach(n => grouped.add(n.id)));
            const leftover = AGENT_NAV.filter(n => !grouped.has(n.id));
            if (leftover.length) groups.push({ label: "อื่นๆ", items: leftover });

            let idx = 0;
            const renderItem = (n) => {
              idx += 1;
              return (
                <button key={n.id} className={`nav-item ${tab === n.id ? "active" : ""}`}
                  onClick={(e) => { goTab(n.id); e.currentTarget.scrollIntoView({ inline: "center", block: "nearest", behavior: "smooth" }); }}
                  title={n.label}>
                  {n.icon}
                  <span className="nav-num">{String(idx).padStart(2, "0")}</span>
                  <span className="nav-text">{n.label}</span>
                  {newBadges.has(n.id) && <span className="nav-new-badge">new</span>}
                </button>
              );
            };
            return groups.map(g => (
              <React.Fragment key={g.label}>
                <div className="nav-label nav-group-label">{g.label}</div>
                {g.items.map(renderItem)}
              </React.Fragment>
            ));
          })()}
          {agent.is_admin && (
            <a className="nav-item" href="admin.html" title="Admin Panel"
              style={{ textDecoration: "none" }}>
              {NAV_ICONS.login}
              <span className="nav-num"></span>
              <span className="nav-text">Admin Panel</span>
            </a>
          )}
        </nav>
        <div className="sidebar-foot">
          <button className="logout-btn" onClick={onLogout} title="ออกจากระบบ">
            {NAV_ICONS.logout}
            <span className="nav-text">ออกจากระบบ</span>
          </button>
        </div>
      </aside>

      <main className="main">
        <div className="topbar">
          <button className={`menu-btn ${menuOpen ? "open" : ""}`} onClick={() => setMenuOpen(o => !o)} aria-label="เมนู">
            <span /><span /><span />
          </button>
          <div className="crumbs">
            <strong>Dashboard ตัวแทน</strong> &nbsp;›&nbsp; {label}
          </div>
          <div className="topbar-meta">
            <span><span className="dot"></span>{agent.name}</span>
            <span>เลขประจำตัว {agent.agent_code}</span>
          </div>
        </div>

        {tab === "overview"     && <OverviewTab agent={agent} avatarBust={avatarBust} onRefresh={onRefresh} />}
        {tab === "onboarding"   && <Onboarding90Tab key={agent.id} agent={agent} onRefresh={onRefresh} />}
        {tab === "lms"          && <LmsSection key={agent.id} agent={agent} onRefresh={onRefresh} />}
        {tab === "course-replay" && <CourseReplaySection />}
        {tab === "schedule"     && <ScheduleTab agent={agent} />}
        {tab === "activities"   && <ActivitiesTab agent={agent} />}
        {tab === "leaderboard"  && <LeaderboardTab agent={agent} />}
        {tab === "team-competition" && <CompetitionAgentSection />}
        {tab === "rewards"      && <RewardsTab agent={agent} />}
        {tab === "challenges"   && <DailyChallengesTab agent={agent} onRefresh={onRefresh} />}
        {tab === "events"       && <ExternalEventsTab />}
        {tab === "goals"        && <GoalsTab agent={agent} />}
        {tab === "goal-track"   && <GoalTrackTab key={agent.id} agent={agent} />}
        {tab === "qualification" && <QualificationTab key={agent.id} agent={agent} />}
        {tab === "sales"        && <SalesTab key={agent.id} agent={agent} />}
        {tab === "recruit"      && <RecruitTab key={agent.id} agent={agent} />}
        {tab === "profile"      && <ProfileTab agent={agent} avatarBust={avatarBust} onAvatarUpdate={setAvatarBust} />}

      </main>
    </div>
  );
};

// ---- Root ----
const App = () => {
  const [agent, setAgent] = React.useState(null);
  const [checking, setChecking] = React.useState(true);
  const [fetchError, setFetchError] = React.useState(false);
  const [screen, setScreen] = React.useState(() => window.location.hash === "#register" ? "register" : "login");

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

  const tryRestoreSession = React.useCallback((attempt = 1) => {
    const saved = localStorage.getItem(SESSION_KEY);
    if (saved) {
      setChecking(true);
      setFetchError(false);
      fetchAgentById(saved).then(a => {
        if (a?.is_admin) {
          // Admins/managers are never allowed in the agent dashboard.
          localStorage.removeItem(SESSION_KEY);
          setAgent(null);
          setChecking(false);
        } else if (a) { setAgent(a); setChecking(false); }
        else if (attempt < 3) { setTimeout(() => tryRestoreSession(attempt + 1), 1500); }
        else { setChecking(false); setFetchError(true); }
      });
      return;
    }
    setChecking(false);
  }, []);

  React.useEffect(() => {
    // Already signed in as admin/manager → go straight to their panel.
    try {
      const adminAuth = JSON.parse(localStorage.getItem(`${window.TENANT.prefix}.admin.user`) || "null");
      if (adminAuth?.is_admin) {
        window.location.href = adminAuth.role === "admin" ? "admin.html" : "manager.html";
        return;
      }
    } catch {}
    tryRestoreSession();
  }, []);

  const login = (agentData) => {
    if (agentData.is_admin) {
      // Admins/managers must use the Admin panel.
      localStorage.removeItem(SESSION_KEY);
      setAgent(null);
      return;
    }
    localStorage.setItem(SESSION_KEY, agentData.id);
    setAgent(agentData);
  };
  const logout = () => { localStorage.removeItem(SESSION_KEY); localStorage.removeItem(`${window.TENANT.prefix}.admin.user`); setAgent(null); setFetchError(false); };

  // Refetch agent + schedule + activities so all tabs (rewards, leaderboard,
  // overview, profile) reflect changes immediately after the user records
  // APE or completes an LMS lesson.
  const refreshAgent = React.useCallback(async () => {
    const id = agent?.id;
    if (!id) return;
    const fresh = await fetchAgentById(id);
    if (fresh) setAgent(fresh);
  }, [agent?.id]);

  // Realtime: subscribe to this agent's activity changes. New score rules can
  // still write into agent_activities and the dashboard will update live.
  React.useEffect(() => {
    if (!agent?.id) return;
    const channel = sb.channel(`agent-acts-${agent.id}`)
      .on("postgres_changes",
        { event: "*", schema: "public", table: "agent_activities", filter: `agent_id=eq.${agent.id}` },
        () => { refreshAgent(); })
      .subscribe();
    return () => { sb.removeChannel(channel); };
  }, [agent?.id, refreshAgent]);

  if (checking) return (
    <div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100vh", color: "var(--muted)" }}>
      กำลังโหลด...
    </div>
  );

  if (fetchError) return (
    <div style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 16, height: "100vh", color: "var(--muted)" }}>
      <div>ไม่สามารถโหลดข้อมูลได้ กรุณาลองใหม่</div>
      <button className="login-btn" style={{ width: "auto", padding: "10px 28px" }} onClick={() => tryRestoreSession()}>ลองใหม่</button>
      <button style={{ background: "none", border: "none", color: "var(--muted)", fontSize: 13, cursor: "pointer" }} onClick={logout}>ออกจากระบบ</button>
    </div>
  );

  return agent && !agent.is_admin
    ? <Dashboard agent={agent} onLogout={logout} onRefresh={refreshAgent} />
    : screen === "register"
      ? <RegisterScreen onBack={() => { window.location.hash = ""; setScreen("login"); }} />
      : <LoginScreen onLogin={login} onGoRegister={() => { window.location.hash = "register"; setScreen("register"); }} />;
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
