// ---- Sections ----

const fmt = (n) => n.toLocaleString("th-TH");

const LiveBadge = () => (
  <span className="live-badge">
    <i className="live-dot" aria-hidden="true" />
    Live
  </span>
);

// นับถอยหลังก่อนถึงเวลาคลาส — ติ๊กเองทุก 1 วินาที (แยกจาก liveNow ของ
// TrainingSection ที่อัปเดตทุก 30 วิ) เพื่อให้ตัวเลขเดินลื่นโดยไม่ re-render ทั้ง section
const ClassCountdown = ({ target }) => {
  const [now, setNow] = React.useState(() => Date.now());
  React.useEffect(() => {
    const timer = window.setInterval(() => setNow(Date.now()), 1000);
    return () => window.clearInterval(timer);
  }, []);
  const totalSec = Math.max(0, Math.round((target.getTime() - now) / 1000));
  const mm = String(Math.floor(totalSec / 60)).padStart(2, "0");
  const ss = String(totalSec % 60).padStart(2, "0");
  return (
    <div className="class-countdown" role="timer" aria-label={`คลาสเริ่มในอีก ${mm} นาที ${ss} วินาที`}>
      <span className="class-countdown-dot" aria-hidden="true" />
      <span className="class-countdown-label">เริ่มในอีก</span>
      <span className="class-countdown-time" aria-hidden="true">
        <b>{mm}</b><i>:</i><b>{ss}</b>
      </span>
      <span className="class-countdown-unit">นาที</span>
    </div>
  );
};

const ClubMiniIcon = ({ type }) => {
  const paths = {
    calendar: (
      <>
        <rect x="4" y="5" width="16" height="16" rx="3" />
        <path d="M8 3v4M16 3v4M4 10h16M8 14h3M8 17h5" />
      </>
    ),
    clock: (
      <>
        <circle cx="12" cy="12" r="9" />
        <path d="M12 7v5l4 2" />
      </>
    ),
    users: (
      <>
        <circle cx="9" cy="9" r="3.2" />
        <path d="M3.5 20a6.5 6.5 0 0 1 13 0" />
        <circle cx="17" cy="10" r="2.5" />
        <path d="M16.5 20a5 5 0 0 1 5-4.8" />
      </>
    ),
    star: <path d="m12 3 2.7 5.5 6 .9-4.3 4.2 1 5.9L12 16.7 6.6 19.5l1-5.9-4.3-4.2 6-.9L12 3Z" />,
    pin: (
      <>
        <path d="M12 21c4-4.5 6-7.6 6-10.5a6 6 0 1 0-12 0C6 13.4 8 16.5 12 21Z" />
        <circle cx="12" cy="10.5" r="2.3" />
      </>
    ),
    hourglass: <path d="M6 3h12M6 21h12M8 3v4l4 4 4-4V3M8 21v-4l4-4 4 4v4" />,
  };
  return (
    <span className="club-mini-icon" aria-hidden="true">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
        {paths[type]}
      </svg>
    </span>
  );
};

const compactCourseKey = (text) =>
  String(text || "")
    .toLowerCase()
    .replace(/\s+/g, "")
    .replace(/club|คลับ/g, "");

const clubMatchesCheckin = (club, row) => {
  const clubKey = compactCourseKey(club.name);
  const rowKey = compactCourseKey(row.course || row.topic);
  return !!clubKey && !!rowKey && (rowKey.includes(clubKey) || clubKey.includes(rowKey));
};

// วัดความกว้างจริงของแถว แล้วคืนจำนวนชื่อที่แสดงได้พอดี (เผื่อที่ให้ปุ่ม +N เมื่อมีคนล้น)
const useFitCount = (total) => {
  const ref = React.useRef(null);
  const [count, setCount] = React.useState(total);
  React.useLayoutEffect(() => {
    const el = ref.current;
    if (!el) return;
    const BADGE = 44; // ความกว้างที่กันไว้ให้ปุ่ม +N
    const measure = () => {
      const cw = el.clientWidth;
      if (!cw) return;
      const item = el.querySelector('.club-checkin-person');
      const cs = getComputedStyle(el);
      const gap = parseFloat(cs.columnGap || cs.gap) || 12;
      const itemW = item ? item.getBoundingClientRect().width : 48;
      const fitAll = Math.floor((cw + gap) / (itemW + gap));
      if (fitAll >= total) { setCount(total); return; }
      const k = Math.max(1, Math.floor((cw - BADGE) / (itemW + gap)));
      setCount(Math.min(k, total));
    };
    measure();
    if (typeof ResizeObserver === 'undefined') return;
    const ro = new ResizeObserver(measure);
    ro.observe(el);
    return () => ro.disconnect();
  }, [total]);
  return [ref, count];
};

const ClubCheckinRoster = ({ attendees, clubName, onOpenAll }) => {
  const [listRef, visibleCount] = useFitCount(attendees.length);
  if (!attendees.length) return null;
  const visible = attendees.slice(0, visibleCount);
  const extra = attendees.length - visible.length;
  return (
    <div className="club-checkins">
      <div className="club-checkins-head">
        <span>เช็คอินสัปดาห์นี้</span>
      </div>
      <div className="club-checkin-list" ref={listRef}>
        {visible.map(person => (
          <span className="club-checkin-person" key={person.id}>
            <span>{person.name}</span>
          </span>
        ))}
        {extra > 0 && (
          <button className="club-checkin-more" type="button" onClick={() => onOpenAll({ clubName, attendees })}>
            +{extra}
          </button>
        )}
      </div>
    </div>
  );
};

const OverviewSection = ({ liveSales, orgGoal }) => {
  const { bannerUrl } = useBranding();
  const liveTotal   = (liveSales || []).reduce((s, r) => s + r.amount, 0);
  const recruitTotal = (liveSales || []).reduce((s, r) => s + Number(r.recruit_count || 0), 0);
  // แผน active จากเมนูแอดมิน "เป้าหมายใหญ่" มาก่อน — ไม่มีแผน → ค่าเดิมจาก config.js
  const apeTarget   = orgGoal ? (Number(orgGoal.ape_target) || 0) : TARGET.fyp;
  const apeBaseline = orgGoal ? (Number(orgGoal.baseline_ape) || 0) : BASELINE.fyp;
  const manTarget   = orgGoal ? (orgGoal.manpower_target == null ? null : Number(orgGoal.manpower_target)) : TARGET.agents;
  const manBaseline = orgGoal ? (Number(orgGoal.baseline_agents) || 0) : BASELINE.agents;
  const totalDone   = apeBaseline + liveTotal;
  const fypProgress = apeTarget > 0 ? Math.min(Math.round(totalDone / apeTarget * 100), 100) : 0;
  const fypRemaining = Math.max(apeTarget - totalDone, 0);
  const currentManpower = manBaseline + recruitTotal;
  const manpowerRemaining = manTarget ? Math.max(manTarget - currentManpower, 0) : 0;
  const agentProgress = manTarget ? Math.min(Math.round(currentManpower / manTarget * 100), 100) : 0;
  const apeTargetLabel = apeTarget.toLocaleString("en-US");
  const rangeLabel = orgGoal ? orgGoalRangeLabel(orgGoal) : "1 มิ.ย.–31 ธ.ค. 2569";
  const loading = liveSales === null;

  // ยอดตามช่วงเวลา (ย้ายมาจาก section เป้าหมาย APE เพื่อให้การ์ดอยู่ต่อกัน — เหมือน 83G30M)
  const now = getNow();
  const todayKey = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,"0")}-${String(now.getDate()).padStart(2,"0")}`;
  const monthKey = todayKey.slice(0, 7);
  const week = apeWeekRange();
  const todayTotal = (liveSales || []).filter(r => r.date === todayKey).reduce((s, r) => s + r.amount, 0);
  const weekTotal  = (liveSales || []).filter(r => r.date >= week.from && r.date <= week.to).reduce((s, r) => s + r.amount, 0);
  const monthTotal = (liveSales || []).filter(r => r.date.startsWith(monthKey)).reduce((s, r) => s + r.amount, 0);
  const todayRecruit = (liveSales || []).filter(r => r.date === todayKey).reduce((s, r) => s + Number(r.recruit_count || 0), 0);
  const weekRecruit  = (liveSales || []).filter(r => r.date >= week.from && r.date <= week.to).reduce((s, r) => s + Number(r.recruit_count || 0), 0);
  const monthRecruit = (liveSales || []).filter(r => r.date.startsWith(monthKey)).reduce((s, r) => s + Number(r.recruit_count || 0), 0);
  // อัตราเป้า วัน/สัปดาห์/เดือน — แผน active ใช้เป้าเดือนปัจจุบันของแผน (÷22 วันทำการ ÷4 สัปดาห์),
  // ไม่มีแผน → ค่าคงที่ APE_RATE เดิม
  const monthTargetNow = orgGoal
    ? (Number((orgGoal.months || []).find(x => x.month === monthKey)?.target) || 0)
    : APE_RATE.monthly;
  const rate = {
    monthly: monthTargetNow,
    weekly:  orgGoal ? Math.round(monthTargetNow / 4)  : APE_RATE.weekly,
    daily:   orgGoal ? Math.round(monthTargetNow / 22) : APE_RATE.daily,
  };
  const ratePct = (done, tgt) => tgt > 0 ? Math.min(Math.round(done / tgt * 100), 100) : 0;
  const rateSub = (tgt, per) => tgt > 0 ? `เป้า ${tgt.toLocaleString("en-US")} / ${per}` : "ยังไม่ตั้งเป้าเดือนนี้";
  return (
    <section className="scene" id="overview">
      {bannerUrl && <img src={bannerUrl} className="hero-banner" alt="แบนเนอร์" />}
      <div className="hero">
        <div>
          <Eyebrow>{BRAND.company} · สำนักงาน {BRAND.office} · {rangeLabel}</Eyebrow>
          {orgGoal ? (
            <h1 className="display">
              พิชิต <em>{apeTargetLabel} APE</em><br/>
              {orgGoal.name}
            </h1>
          ) : (
            <h1 className="display">
              83F Advisor 2026
            </h1>
          )}
          {orgGoal ? (
            <p className="section-sub" style={{marginBottom: 24}}>
              แผนงานเชิงระบบของสำนักงาน {BRAND.office} — เป้ารวม
              <strong> {apeTargetLabel} APE </strong>
              ช่วง {rangeLabel}{manTarget ? ` · เป้าตัวแทนปลายแผน ${manTarget.toLocaleString("en-US")} คน` : ""}
            </p>
          ) : (
            <p className="section-sub" style={{marginBottom: 24}}>
              KTAXA Advisor คือโครงการใหม่ เพื่อมอบโอกาสให้ที่ปรึกษาทางการเงินหรือตัวแทนประกันชีวิตยุคใหม่ ได้สร้างเส้นทางอาชีพของตนเอง กับ กรุงไทย-แอกซ่า ประกันชีวิต <strong>“กับโอกาสที่จะสร้างเส้นทางความสำเร็จ…ในแบบที่เป็นคุณ”</strong>
            </p>
          )}
          <div className="temporary-register-actions">
            <p className="temporary-register-copy">เปิดรับสมัครเข้าโครงการ {orgGoal ? `${BRAND.office} ${orgGoal.name}` : "83F"}</p>
            <a className="temporary-register-btn" href="login.html#register">สนใจสมัครคลิก</a>
          </div>
        </div>
      </div>

      <div className="kpi-row">
        <KpiCard
          feature
          label="ยอดคงเหลือที่ต้องทำ"
          value={loading ? "..." : `${(fypRemaining / 1000000).toFixed(2)} M`}
          suffix="APE"
          sub={`เป้ารวม ${apeTargetLabel} · ทำได้แล้ว ${totalDone.toLocaleString("en-US")} · ${fypProgress}% ของเป้า`}
          progress={fypProgress}
        />
        <KpiCard
          label={manTarget ? "ตัวแทนปลายแผน" : "ตัวแทนปัจจุบัน"}
          value={manTarget || currentManpower.toLocaleString("en-US")}
          suffix="manpower"
          sub={manTarget
            ? `ปัจจุบัน ${currentManpower.toLocaleString("en-US")} manpower · ต้องเพิ่มอีก ${manpowerRemaining.toLocaleString("en-US")}`
            : `นับจากการ Recruit ตั้งแต่เริ่มแผน`}
          progress={manTarget ? agentProgress : 100}
        />
        <KpiCard
          label="การ Recruit"
          value={loading ? "..." : recruitTotal.toLocaleString("en-US")}
          suffix="คน"
          sub={loading ? "นับรวมเป็น manpower แล้ว" : `เดือนนี้ ${monthRecruit.toLocaleString("en-US")} · สัปดาห์นี้ ${weekRecruit.toLocaleString("en-US")} · วันนี้ ${todayRecruit.toLocaleString("en-US")}`}
          progress={Math.min(recruitTotal, 100)}
        />
        <KpiCard
          label="Unit ปฏิบัติการ"
          value={TARGET.units}
          suffix="Unit"
          sub="ทีมปฏิบัติการหลักของสำนักงาน"
          progress={0}
        />
      </div>

      <div className="divider-label">
        ยอด APE ตามช่วงเวลา
        {loading && <span style={{ fontSize: 12, color: "var(--muted)", marginLeft: 8 }}>กำลังโหลด...</span>}
      </div>
      <div className="kpi-row">
        <KpiCard
          label="ยอดเดือนนี้"
          value={loading ? "..." : monthTotal.toLocaleString("en-US")}
          suffix="APE"
          sub={rateSub(rate.monthly, "เดือน")}
          progress={ratePct(monthTotal, rate.monthly)}
        />
        <KpiCard
          label="ยอดสัปดาห์นี้"
          value={loading ? "..." : weekTotal.toLocaleString("en-US")}
          suffix="APE"
          sub={rateSub(rate.weekly, "สัปดาห์")}
          progress={ratePct(weekTotal, rate.weekly)}
        />
        <KpiCard
          label="ยอดวันนี้"
          value={loading ? "..." : todayTotal.toLocaleString("en-US")}
          suffix="APE"
          sub={rateSub(rate.daily, "วัน")}
          progress={ratePct(todayTotal, rate.daily)}
        />
        <KpiCard
          label="ยอดรวมที่ทำได้"
          value={loading ? "..." : `${(totalDone / 1000000).toFixed(2)} M`}
          suffix="APE"
          sub={`คงเหลือ ${fypRemaining.toLocaleString("en-US")} · ${fypProgress}%`}
          progress={fypProgress}
        />
      </div>
    </section>
  );
};

// --- Training Clubs section ---
const TrainingSection = () => {
  const [view, setView] = React.useState("week");
  const [liveNow, setLiveNow] = React.useState(() => new Date());
  const [adminSchedules, setAdminSchedules] = React.useState([]);
  const [externalActivities, setExternalActivities] = React.useState([]);
  const [weeklyCheckins, setWeeklyCheckins] = React.useState([]);
  const [posterModal, setPosterModal] = React.useState(null);
  const [checkinModal, setCheckinModal] = React.useState(null);
  const [posterOrientations, setPosterOrientations] = React.useState({});
  const [sessionAgent, setSessionAgent] = React.useState(null);
  const [checkinBusyId, setCheckinBusyId] = React.useState(null);
  const [checkinMsg, setCheckinMsg] = React.useState(null); // { clubId, type, text }
  const currentPlanMonthIndex = Math.min(Math.max(new Date().getMonth() - 4, 0), MONTHS.length - 1);
  const [monthIndex, setMonthIndex] = React.useState(currentPlanMonthIndex);
  const DAYS = ["จ", "อ", "พ", "พฤ", "ศ", "ส", "อา"];
  const DAY_FULL = ["จันทร์", "อังคาร", "พุธ", "พฤหัส", "ศุกร์", "เสาร์", "อาทิตย์"];
  const DATE_MONTHS = ["ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค."];
  const SLOTS = [
    { id: "morning", label: "08:30", sub: "Morning" },
    { id: "afternoon", label: "13:00", sub: "Afternoon" },
    { id: "evening", label: "20:00", sub: "Evening" },
  ];

  React.useEffect(() => {
    const timer = window.setInterval(() => setLiveNow(new Date()), 30000);
    return () => window.clearInterval(timer);
  }, []);

  React.useEffect(() => {
    sb.from("class_schedules").select("*").neq("visibility", "admin").order("day_of_week").order("time")
      .then(({ data }) => setAdminSchedules(data || []));
    sb.from("external_activities").select("id, title, time, activity_date, visibility")
      .neq("visibility", "manager")
      .then(({ data }) => setExternalActivities(data || []));
  }, []);

  // ดึงตัวตนตัวแทนที่ล็อกอินอยู่ (agentSession เก็บแค่ id) เพื่อให้เช็คอินจากหน้านี้ได้
  // admin/manager ไม่ให้เช็คอิน (เหมือนใน dashboard)
  React.useEffect(() => {
    const id = localStorage.getItem("agentSession");
    if (!id) { setSessionAgent(null); return; }
    let alive = true;
    sb.from("agents").select("id, name, agent_code, is_admin").eq("id", id).single()
      .then(({ data }) => { if (alive) setSessionAgent(data && !data.is_admin ? data : null); });
    return () => { alive = false; };
  }, []);

  const adminClubs = adminSchedules.flatMap(row => {
    const specificDate = row.specific_date || null;
    const base = {
      name: row.title,
      desc: row.note || "ตารางเรียนที่แอดมินเพิ่ม",
      weeks: row.weeks || "ทุกสัปดาห์",
      time: row.time,
      channel: row.channel || "Online",
      owners: Array.isArray(row.owners) ? row.owners : [],
      pillar: row.pillar || "t",
      perMonth: row.per_month != null ? Number(row.per_month) : 4,
      skipDates: Array.isArray(row.skip_dates) ? row.skip_dates : [],
      adminManaged: true,
      poster: row.poster_path ? classPosterSrc(row.poster_path) : null,
    };
    // ครั้งเดียว (มีวันที่เจาะจง) → การ์ดเดียวตามวันของ specific_date
    if (specificDate) {
      const jsDay = new Date(`${specificDate}T00:00:00`).getDay();
      const dayIdx = jsDay === 0 ? 7 : jsDay;
      return [{
        ...base,
        id: `admin-${row.id}`,
        day: DAY_FULL[dayIdx - 1] || "-",
        dayIdx,
        date: specificDate, activeFrom: specificDate, activeUntil: specificDate,
      }];
    }
    // รายสัปดาห์ → 1 การ์ดต่อวันใน day_idx (fallback เป็น day_of_week ถ้าไม่มี day_idx)
    const days = (Array.isArray(row.day_idx) && row.day_idx.length)
      ? row.day_idx.map(Number).filter(Boolean)
      : (row.day_of_week ? [Number(row.day_of_week)] : []);
    return days.map(dayIdx => ({
      ...base,
      id: `admin-${row.id}-${dayIdx}`,
      day: DAY_FULL[dayIdx - 1] || "-",
      dayIdx,
      activeFrom: row.active_from || null,
    }));
  });
  const allClubs = [...CLUBS, ...adminClubs];
  const monthlyClubs = allClubs.filter(c => !c.tbd);
  const activeMonth = MONTHS[monthIndex];
  const calendarMonth = monthIndex + 4; // May 2026 is zero-based month 4
  const monthlyEvents = (window.CALENDAR_EVENTS || []).filter(event => {
    const date = new Date(`${event.date}T00:00:00`);
    return date.getFullYear() === 2026 && date.getMonth() === calendarMonth;
  });
  const daysInMonth = new Date(2026, calendarMonth + 1, 0).getDate();
  const firstDayIdx = new Date(2026, calendarMonth, 1).getDay() || 7;
  const leadingDays = firstDayIdx - 1;
  const dateKeyFor = (date) =>
    `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
  const clubRunsOnDate = (club, date) => {
    const dateKey = dateKeyFor(date);
    if ((club.activeFrom && dateKey < club.activeFrom) || (club.activeUntil && dateKey > club.activeUntil)) return false;
    if (Array.isArray(club.skipDates) && club.skipDates.includes(dateKey)) return false;
    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;
  };
  // เหมือน clubRunsOnDate แต่ "ไม่นับวันงด" — ใช้หาว่ารอบปกติถัดไปตรงกับวันที่ถูกงดไหม
  const clubMatchesRecurrence = (club, date) => {
    const dateKey = dateKeyFor(date);
    if ((club.activeFrom && dateKey < club.activeFrom) || (club.activeUntil && dateKey > club.activeUntil)) return false;
    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;
  };
  // ถ้ารอบปกติ"ถัดไป" (รวมวันนี้) ตรงกับวันที่ถูกงด → คืนค่า dateKey เพื่อโชว์ป้าย "เลื่อน"
  const clubPostponedDateKey = (club) => {
    if (!Array.isArray(club.skipDates) || !club.skipDates.length) return null;
    const start = new Date(liveNow); start.setHours(0, 0, 0, 0);
    for (let i = 0; i < 70; i++) {
      const d = new Date(start); d.setDate(start.getDate() + i);
      if (clubMatchesRecurrence(club, d)) {
        const key = dateKeyFor(d);
        return club.skipDates.includes(key) ? key : null;
      }
    }
    return null;
  };
  const fmtSkipDate = (key) => {
    const [y, m, d] = (key || "").split("-").map(Number);
    if (!y) return "";
    return `${d} ${DATE_MONTHS[m - 1]} ${y + 543}`;
  };
  const sessionTimeOnDate = (hm, date) => {
    const [hour, minute] = (hm || "").split(":").map(Number);
    if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null;
    const timestamp = new Date(date);
    timestamp.setHours(hour, minute, 0, 0);
    return timestamp;
  };
  const clubIsLiveOnDate = (club, date) => {
    if (!clubRunsOnDate(club, date)) return false;
    const [startHM, endHM] = (club.time || "").split(/[–-]/).map(s => s.trim());
    const start = sessionTimeOnDate(startHM, date);
    const end = sessionTimeOnDate(endHM, date);
    if (!start || !end) return false;
    if (end < start) end.setDate(end.getDate() + 1);
    return liveNow >= start && liveNow <= end;
  };
  const scheduleItemIsLiveOnDate = (item, date) => {
    if (item.date && item.date !== dateKeyFor(date)) return false;
    if (item.date) {
      const [startHM, endHM] = (item.time || "").split(/[–-]/).map(s => s.trim());
      const start = sessionTimeOnDate(startHM, date);
      const end = sessionTimeOnDate(endHM, date);
      if (!start || !end) return false;
      if (end < start) end.setDate(end.getDate() + 1);
      return liveNow >= start && liveNow <= end;
    }
    return clubIsLiveOnDate(item, date);
  };
  const sessionEndOnDate = (timeStr, date) => {
    const [startHM, endHM] = (timeStr || "").split(/[–-]/).map(s => s.trim());
    const start = sessionTimeOnDate(startHM, date);
    const end = sessionTimeOnDate(endHM, date);
    if (!start || !end) return null;
    if (end < start) end.setDate(end.getDate() + 1);
    return end;
  };
  const scheduleItemIsPastOnDate = (item, date) => {
    if (item.date && item.date !== dateKeyFor(date)) return false;
    const end = sessionEndOnDate(item.time, date);
    if (!end) return false;
    return liveNow > end;
  };
  const ZOOM_OPEN_LEAD_MS = 30 * 60 * 1000;
  const clubZoomOpenOnDate = (club, date) => {
    if (club.zoomEnabled === false) return false;
    if (!clubRunsOnDate(club, date)) return false;
    const [startHM, endHM] = (club.time || "").split(/[–-]/).map(s => s.trim());
    const start = sessionTimeOnDate(startHM, date);
    const end = sessionTimeOnDate(endHM, date);
    if (!start || !end) return false;
    if (end < start) end.setDate(end.getDate() + 1);
    return liveNow >= new Date(start.getTime() - ZOOM_OPEN_LEAD_MS) && liveNow <= end;
  };
  // ช่วงเปิดให้เช็คอิน: 30 นาทีก่อนคลาสเริ่มจนจบคลาส (เหมือน dashboard)
  // ปิดสำหรับคลาสที่ตั้ง checkinEnabled:false (เช่น Morning Mindset) และคลาสที่แอดมินจัดเอง
  const clubCheckinOpenOnDate = (club, date) => {
    if (club.checkinEnabled === false || club.adminManaged) return false;
    if (!clubRunsOnDate(club, date)) return false;
    const [startHM, endHM] = (club.time || "").split(/[–-]/).map(s => s.trim());
    const start = sessionTimeOnDate(startHM, date);
    const end = sessionTimeOnDate(endHM, date);
    if (!start || !end) return false;
    if (end < start) end.setDate(end.getDate() + 1);
    return liveNow >= new Date(start.getTime() - ZOOM_OPEN_LEAD_MS) && liveNow <= end;
  };
  const timeValue = (time) => {
    const [hour = "0", minute = "0"] = (time || "").split(/[–-]/)[0].split(":");
    return Number(hour) * 60 + Number(minute);
  };
  const cellsFor = (slot, dayIdx, date) => {
    return monthlyClubs.filter(c => {
      const matchesDay = Array.isArray(c.dayIdx) ? c.dayIdx.includes(dayIdx) : c.dayIdx === dayIdx;
      if (!matchesDay || !clubRunsOnDate(c, date)) return false;
      if (c.adminManaged) {
        const start = timeValue(c.time);
        if (slot === "morning") return start < 12 * 60;
        if (slot === "afternoon") return start >= 12 * 60 && start < 18 * 60;
        if (slot === "evening") return start >= 18 * 60;
      }
      if (slot === "morning") return c.id === "morning-mindset";
      if (slot === "afternoon") return c.id === "connect";
      if (slot === "evening") return c.id !== "connect" && c.id !== "morning-mindset";
      return false;
    });
  };
  const firstClubDay = (club) => Array.isArray(club.dayIdx) ? Math.min(...club.dayIdx) : club.dayIdx;
  // Hide one-time (specific-date) classes once their date has passed.
  // Weekly classes have no activeUntil and are always kept.
  const tableTodayKey = dateKeyFor(liveNow);
  // คลับที่มีคลาสสอน "วันนี้" ดันขึ้นการ์ดบนสุด แล้วค่อยเรียงตามวันแรกของสัปดาห์
  const runsToday = (club) => clubRunsOnDate(club, liveNow) ? 1 : 0;
  const tableClubs = allClubs
    .filter(c => !c.activeUntil || c.activeUntil >= tableTodayKey)
    .map((club, index) => ({ club, index }))
    .sort((a, b) =>
      runsToday(b.club) - runsToday(a.club) ||
      firstClubDay(a.club) - firstClubDay(b.club) ||
      a.index - b.index)
    .map(({ club }) => club);
  const todayKey = dateKeyFor(liveNow);
  const liveJsDay = liveNow.getDay();
  const daysFromMon = liveJsDay === 0 ? 6 : liveJsDay - 1;
  const weekStart = new Date(liveNow);
  weekStart.setHours(0, 0, 0, 0);
  weekStart.setDate(weekStart.getDate() - daysFromMon);
  const weekDateAt = (i) => { const d = new Date(weekStart); d.setDate(weekStart.getDate() + i); return d; };
  const weekDates = DAYS.map((_, i) => {
    const d = weekDateAt(i);
    return `${d.getDate()} ${DATE_MONTHS[d.getMonth()]}`;
  });
  const eventsForDate = (date) => {
    const dateKey = dateKeyFor(date);
    return monthlyEvents.filter(event => event.date === dateKey);
  };
  const weeklyEventsFor = (slot, dayIndex) => {
    const date = weekDateAt(dayIndex);
    const dateKey = dateKeyFor(date);
    const calEvents = (window.CALENDAR_EVENTS || [])
      .filter(event => event.date === dateKey)
      .filter(event => {
        const start = timeValue(event.time);
        if (slot === "morning") return start < 12 * 60;
        if (slot === "afternoon") return start >= 12 * 60 && start < 18 * 60;
        if (slot === "evening") return start >= 18 * 60;
        return false;
      });
    const actEvents = externalActivities
      .filter(a => a.activity_date === dateKey)
      .filter(a => {
        const start = timeValue(a.time);
        if (slot === "morning") return start < 12 * 60;
        if (slot === "afternoon") return start >= 12 * 60 && start < 18 * 60;
        if (slot === "evening") return start >= 18 * 60;
        return start === 0; // no time → show in morning
      })
      .map(a => ({ id: `ext-${a.id}`, name: a.title, time: a.time || "", pillar: "s", date: a.activity_date }));
    return [...calEvents, ...actEvents];
  };
  const weekStartKey = dateKeyFor(weekStart);
  const weekEndKey = dateKeyFor(weekDateAt(6));

  const loadCheckins = React.useCallback(() => (
    sb.from("agent_schedule")
      .select("id, date, topic, status, agent_id, agents(id, name, agent_code)")
      .eq("status", "เข้าเรียนแล้ว")
      .gte("date", weekStartKey)
      .lte("date", weekEndKey)
      .then(({ data }) => setWeeklyCheckins(data || []))
  ), [weekStartKey, weekEndKey]);
  React.useEffect(() => { loadCheckins(); }, [loadCheckins]);

  const attendeesForClub = (club) => {
    const seen = new Set();
    return weeklyCheckins
      .filter(row => clubMatchesCheckin(club, row))
      .map(row => row.agents ? { id: row.agents.id || row.agent_id, name: row.agents.name || row.agent_id, code: row.agents.agent_code } : { id: row.agent_id, name: row.agent_id })
      .filter(person => {
        if (!person.id || seen.has(person.id)) return false;
        seen.add(person.id);
        return true;
      })
      .sort((a, b) => String(a.name || "").localeCompare(String(b.name || ""), "th"));
  };
  // ตัวแทนที่ล็อกอินอยู่เช็คอินคลับนี้ในสัปดาห์นี้ไปแล้วหรือยัง
  const checkedInByMe = (club) =>
    !!sessionAgent && weeklyCheckins.some(row => row.agent_id === sessionAgent.id && clubMatchesCheckin(club, row));
  // เช็คอินจากหน้า Landing — บันทึก agent_schedule (เข้าเรียนแล้ว) + คะแนนใน agent_activities
  const submitClubCheckin = async (club) => {
    if (!sessionAgent?.id || checkinBusyId) return;
    if (!clubCheckinOpenOnDate(club, liveNow)) {
      setCheckinMsg({ clubId: club.id, type: "error", text: "อยู่นอกช่วงเวลาเช็คอิน" });
      return;
    }
    if (checkedInByMe(club)) {
      setCheckinMsg({ clubId: club.id, type: "info", text: "เช็คอินคลาสนี้ไปแล้ว" });
      return;
    }
    setCheckinBusyId(club.id);
    setCheckinMsg(null);
    const dateKey = dateKeyFor(liveNow);
    const { data: inserted, error: insertError } = await sb.from("agent_schedule").insert([{
      agent_id: sessionAgent.id,
      date: dateKey,
      topic: club.name,
      time: club.time,
      instructor: (club.owners || []).join(", "),
      status: "เข้าเรียนแล้ว",
    }]).select("id").single();
    if (insertError || !inserted) {
      setCheckinBusyId(null);
      setCheckinMsg({ clubId: club.id, type: "error", text: "บันทึกเช็คอินไม่สำเร็จ ลองใหม่อีกครั้ง" });
      return;
    }
    const checkinPts = POINT_RULES.checkin;
    let pointsError = null;
    if (checkinPts > 0) {
      ({ error: pointsError } = await sb.from("agent_activities").insert([{
        agent_id: sessionAgent.id,
        date: dateKey,
        name: `เช็คอิน ${club.name}`,
        points: checkinPts,
        note: `checkin:${inserted.id}`,
      }]));
    }
    setCheckinBusyId(null);
    setCheckinMsg(pointsError
      ? { clubId: club.id, type: "error", text: "เช็คอินแล้ว แต่บันทึกคะแนนไม่สำเร็จ" }
      : { clubId: club.id, type: "ok", text: checkinPts > 0 ? `เช็คอินสำเร็จ +${checkinPts} คะแนน` : "เช็คอินสำเร็จ" });
    loadCheckins();
  };
  // ปุ่มเดียวกดทีเดียว: บันทึกเช็คอินให้เสร็จก่อน แล้วค่อยพาเข้า Zoom — ถ้าเปิด Zoom ก่อน
  // เบราว์เซอร์มือถือจะพักหน้าเว็บตอนสลับแอป ทำให้ request เช็คอินหายกลางทาง
  // (เปิดแท็บเปล่าไว้ใน gesture เดียวกันกัน popup blocker แล้วค่อยชี้ไป Zoom เมื่อบันทึกเสร็จ)
  const joinAndCheckin = async (club, openZoom) => {
    const zoomTab = openZoom ? window.open("about:blank", "_blank") : null;
    await submitClubCheckin(club);
    if (zoomTab) { zoomTab.opener = null; zoomTab.location = ZOOM_URL; }
  };
  // External activities mapped to calendar items (pillar "s" = distinct colour)
  const activityCalendarItems = externalActivities
    .filter(a => {
      if (!a.activity_date) return false;
      const d = new Date(`${a.activity_date}T00:00:00`);
      return d.getFullYear() === 2026 && d.getMonth() === calendarMonth;
    })
    .map(a => ({
      id: `ext-${a.id}`,
      name: a.title,
      time: a.time || "",
      pillar: "s",
      date: a.activity_date,
    }));

  const monthDays = [
    ...Array.from({ length: leadingDays }, (_, i) => ({ key: `blank-${i}`, blank: true })),
    ...Array.from({ length: daysInMonth }, (_, i) => {
      const day = i + 1;
      const date = new Date(2026, calendarMonth, day);
      const dateKey = dateKeyFor(date);
      return {
        key: `day-${day}`,
        day,
        clubs: [
          ...monthlyClubs.filter(c => clubRunsOnDate(c, date)),
          ...eventsForDate(date),
          ...activityCalendarItems.filter(a => a.date === dateKey),
        ]
          .sort((a, b) => timeValue(a.time) - timeValue(b.time)),
      };
    }),
  ];
  const monthlySessionTotal = monthDays.reduce((total, day) => total + (day.clubs ? day.clubs.length : 0), 0);

  return (
    <section className="scene" id="training">
      <SectionHead
        eyebrow="03 — Training System"
        title="คลาสเรียน"
        right={<Legend />}
      />

      <div className="view-switch" aria-label="เลือกมุมมองตารางเรียน">
        <button type="button" className={view === "week" ? "active" : ""} onClick={() => setView("week")}>สัปดาห์</button>
        <button type="button" className={view === "month" ? "active" : ""} onClick={() => setView("month")}>เดือน</button>
      </div>

      {view === "week" ? (
        <div className="week-grid">
          <div></div>
          {DAYS.map((d, i) => (
            <div key={d} className={`week-head day-${i+1} ${i>=5?'weekend':''} ${dateKeyFor(weekDateAt(i))===todayKey?'today':''}`}>
              <div>{DAY_FULL[i]}</div>
              <span>{weekDates[i]}</span>
            </div>
          ))}
          {SLOTS.map(slot => (
            <React.Fragment key={slot.id}>
              <div className="time-col">
                {slot.label}
                <div style={{fontSize:9, opacity:0.6, marginTop:2}}>{slot.sub}</div>
              </div>
              {DAYS.map((d, i) => {
                const dayIdx = i + 1;
                const date = weekDateAt(i);
                const clubs = [
                  ...cellsFor(slot.id, dayIdx, date),
                  ...weeklyEventsFor(slot.id, i),
                ].sort((a, b) => timeValue(a.time) - timeValue(b.time));
                return (
                  <div key={d+slot.id} className={`slot ${clubs.length===0?'slot-empty':''}`}>
                    {clubs.map(c => {
                      const isLive = scheduleItemIsLiveOnDate(c, date);
                      const isPast = !isLive && scheduleItemIsPastOnDate(c, date);
                      return (
                      <div key={c.id} className={`club-chip ${c.pillar} ${isLive ? 'live' : ''} ${isPast ? 'past' : ''}`} title={c.desc}>
                        <div className="club-name">
                          {c.name}
                          {isLive && <LiveBadge />}
                        </div>
                        <div className="club-meta">
                          <span>{c.time}</span>
                          {c.weeks !== "ทุกสัปดาห์" && <span className="pill">{c.weeks}</span>}
                        </div>
                      </div>
                      );
                    })}
                  </div>
                );
              })}
            </React.Fragment>
          ))}
        </div>
      ) : (
        <div className="monthly-club-panel">
          <div className="month-picker" aria-label="เลือกเดือน">
            <button onClick={() => setMonthIndex(i => Math.max(0, i - 1))} disabled={monthIndex === 0} aria-label="เดือนก่อนหน้า">‹</button>
            <div>
              <span>{monthIndex === currentPlanMonthIndex ? "เดือนปัจจุบัน" : "เดือน"}</span>
              <strong>{activeMonth} 2569</strong>
            </div>
            <button onClick={() => setMonthIndex(i => Math.min(MONTHS.length - 1, i + 1))} disabled={monthIndex === MONTHS.length - 1} aria-label="เดือนถัดไป">›</button>
          </div>
          <article className="monthly-club-card">
            <div className="monthly-club-head">
              <span>ปฏิทินทั้งเดือน</span>
              <strong>{monthlySessionTotal} รอบเรียน</strong>
            </div>
            <div className="month-calendar">
              {DAY_FULL.map((day, i) => (
                <div key={day} className={`month-weekday day-${i+1} ${i >= 5 ? "weekend" : ""}`}>{day}</div>
              ))}
              {monthDays.map(day => (
                <div key={day.key} className={`month-day ${day.blank ? "blank" : ""}`}>
                  {!day.blank && (
                    <>
                      <span className="month-date">{day.day} {activeMonth}</span>
                      <div className="month-events">
                        {day.clubs.map(c => {
                          const eventDate = new Date(2026, calendarMonth, day.day);
                          const isLive = scheduleItemIsLiveOnDate(c, eventDate);
                          const isPast = !isLive && scheduleItemIsPastOnDate(c, eventDate);
                          return (
                          <div key={c.id} className={`month-event ${c.pillar} ${isLive ? 'live' : ''} ${isPast ? 'past' : ''}`} title={`${c.name} · ${c.time}`}>
                            <strong>
                              {c.name}
                              {isLive && <LiveBadge />}
                            </strong>
                            <span>{c.time}</span>
                          </div>
                          );
                        })}
                      </div>
                    </>
                  )}
                </div>
              ))}
            </div>
          </article>
        </div>
      )}

      <div className="club-card-list">
        {tableClubs.map((c, i) => {
          const inSession = clubIsLiveOnDate(c, liveNow);
          const zoomOpen = clubZoomOpenOnDate(c, liveNow);
          const checkinOpen = clubCheckinOpenOnDate(c, liveNow);
          const myCheckin = checkedInByMe(c);
          const attendees = attendeesForClub(c);
          const startHM = (c.time || "").split(/[–-]/)[0].trim();
          const sessionStart = clubRunsOnDate(c, liveNow) ? sessionTimeOnDate(startHM, liveNow) : null;
          const msToStart = sessionStart ? sessionStart.getTime() - liveNow.getTime() : null;
          // โชว์ตัวนับถอยหลังภายใน 30 นาทีก่อนคลาสเริ่ม (ก่อนคลาสจริงเท่านั้น)
          const preClass = !inSession && msToStart != null && msToStart > 0 && msToStart <= ZOOM_OPEN_LEAD_MS;
          const postponedKey = clubPostponedDateKey(c);  // รอบถัดไปถูกงด/เลื่อนไหม
          return (
          <article
            className={`club-schedule-card tone-${i % 5} ${inSession ? "live" : ""} ${c.poster ? "has-poster" : ""}${postponedKey ? " postponed" : ""}`}
            key={c.id}
          >
            {postponedKey && (
              <div className="club-postponed-banner">
                <span className="cpb-label">เลื่อน</span>
                <span className="cpb-date">งดคลาส {fmtSkipDate(postponedKey)}</span>
              </div>
            )}
            <div className="club-schedule-main">
              <div className="club-schedule-copy">
                <span className={`club-status-pill ${c.channel.includes('Offline') ? 'offline' : ''}`}>
                  {c.channel.includes('Offline') ? 'สำนักงาน' : c.channel}
                </span>
                <h3>
                  {c.name}
                  {inSession && <LiveBadge />}
                  {preClass && <ClassCountdown target={sessionStart} />}
                  {c.needsName && <span className="club-note gold">ชื่อเสนอ</span>}
                  {c.tbd && <span className="club-note red">รอข้อมูล</span>}
                </h3>
                <p>{c.desc}</p>
                <span className="club-feature-pill">
                  <ClubMiniIcon type="star" />
                  คลาสสำหรับตัวแทนใหม่
                </span>
                {c.spotlight && c.spotlight.date === todayKey && (
                  <div className="club-spotlight">
                    <div className="club-spotlight-tag">
                      <span className="club-spotlight-star" aria-hidden="true">★</span>
                      {c.spotlight.badge}
                      <span className="club-spotlight-today">วันนี้</span>
                    </div>
                    <div className="club-spotlight-body">
                      <Avatar name={c.spotlight.guest} />
                      <div className="club-spotlight-topics">
                        <span className="club-spotlight-topics-label">หัวข้อ</span>
                        {(Array.isArray(c.spotlight.topic) ? c.spotlight.topic : [c.spotlight.topic]).map(t => (
                          <span className="club-spotlight-topic" key={t}>{t}</span>
                        ))}
                      </div>
                    </div>
                  </div>
                )}
                {sessionAgent && (zoomOpen || (checkinOpen && !myCheckin)) && (
                  checkinOpen && !myCheckin ? (
                    <button
                      type="button"
                      className="schedule-zoom-btn"
                      onClick={e => { e.stopPropagation(); joinAndCheckin(c, zoomOpen); }}
                      disabled={checkinBusyId === c.id}
                    >
                      {checkinBusyId === c.id ? "กำลังบันทึก..." : (zoomOpen ? "เช็คอิน + เข้าเรียน" : "เช็คอิน")}
                    </button>
                  ) : (
                    <a className="schedule-zoom-btn" href={ZOOM_URL} target="_blank" rel="noopener noreferrer"
                      onClick={e => e.stopPropagation()}>เข้าเรียน</a>
                  )
                )}
                {checkinMsg && checkinMsg.clubId === c.id && checkinMsg.type === "error" && (
                  <span className="club-checkin-msg error">{checkinMsg.text}</span>
                )}
              </div>
              <div className="club-schedule-postcol">
                <div className="club-schedule-owners">
                  <span>โค้ช</span>
                  {c.owners.length > 0 ? (
                    <div className="avatar-stack">
                      {c.owners.map(o => <Avatar key={o} name={o} />)}
                    </div>
                  ) : <strong className="muted">แอดมินจัดการ</strong>}
                </div>
                {c.poster ? (
                  <button
                    className={`club-schedule-poster poster-trigger ${posterOrientations[c.id] || "landscape"}`}
                    type="button"
                    onClick={() => setPosterModal(c)}
                    aria-label={`เปิดโปสเตอร์ ${c.name}`}
                    title={`เปิดโปสเตอร์ ${c.name}`}
                  >
                    <img src={c.poster} alt={`โปสเตอร์ ${c.name}`} loading="lazy"
                      onLoad={e => setPosterOrientations(o => ({
                        ...o,
                        [c.id]: e.target.naturalWidth >= e.target.naturalHeight ? "landscape" : "portrait",
                      }))} />
                  </button>
                ) : (
                  <div className={`club-schedule-poster empty ${posterOrientations[c.id] || "landscape"}`}>
                    <span>{c.adminManaged ? "Admin" : "83F"}</span>
                  </div>
                )}
              </div>
            </div>
            <div className="club-schedule-meta">
              <div className="club-schedule-info">
                <ClubMiniIcon type="calendar" />
                <div>
                  <span>วัน/รอบ</span>
                  <strong>{c.day === "จันทร์" ? "ทุกวันจันทร์" : c.day}</strong>
                </div>
              </div>
              <div className="club-schedule-info">
                <ClubMiniIcon type="clock" />
                <div>
                  <span>เวลา</span>
                  <strong>{c.time}</strong>
                </div>
              </div>
              <div className="club-schedule-info club-schedule-checkin-count">
                <ClubMiniIcon type="users" />
                <div>
                  <span>เช็คอินแล้ว</span>
                  <strong>{attendees.length} คน</strong>
                </div>
              </div>
            </div>
            <ClubCheckinRoster attendees={attendees} clubName={c.name} onOpenAll={setCheckinModal} />
          </article>
          );
        })}
      </div>
      {posterModal && (
        <div className="poster-modal-backdrop" onClick={() => setPosterModal(null)}>
          <div className="poster-modal" onClick={e => e.stopPropagation()}>
            <button className="poster-modal-close" type="button" onClick={() => setPosterModal(null)} aria-label="ปิดโปสเตอร์">×</button>
            <img src={posterModal.poster} alt={`โปสเตอร์ ${posterModal.name}`} />
          </div>
        </div>
      )}
      {checkinModal && (
        <div className="poster-modal-backdrop" onClick={() => setCheckinModal(null)}>
          <div className="club-checkin-modal" onClick={e => e.stopPropagation()}>
            <button className="poster-modal-close" type="button" onClick={() => setCheckinModal(null)} aria-label="ปิดรายชื่อ">×</button>
            <div className="club-checkin-modal-head">
              <span>เช็คอินสัปดาห์นี้</span>
              <strong>{checkinModal.clubName}</strong>
              <em>{checkinModal.attendees.length} คน</em>
            </div>
            <div className="club-checkin-modal-list">
              {checkinModal.attendees.map(person => (
                <div className="club-checkin-modal-person" key={person.id}>
                  <div>
                    <strong>{person.name}</strong>
                    {person.code && <span>{person.code}</span>}
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>
      )}
    </section>
  );
};

// Roster of all instructors aggregated from CLUBS
const InstructorRoster = () => {
  const rosterHidden = new Set([]);
  // Dedupe by normalized name; pick the cleanest variant as canonical display.
  const map = new Map();
  CLUBS.forEach(c => c.owners.forEach(o => {
    const key = normName(o);
    if (rosterHidden.has(key)) return;
    if (!map.has(key)) map.set(key, { display: o, variants: new Set([o]), clubs: new Set() });
    const entry = map.get(key);
    entry.variants.add(o);
    entry.clubs.add(c.name);
    // Prefer variant without "พี่" prefix as display
    if (/^(ผจก\.|ผอ\.)\s*[^พี่]/.test(o) && /พี่/.test(entry.display)) entry.display = o;
  }));
  const list = [...map.values()].sort((a,b) => a.display.localeCompare(b.display, 'th'));
  return (
    <div className="instructor-grid">
      {list.map(({ display, clubs }) => {
        const photo = photoOf(display);
        return (
          <div className="instructor-card" key={display}>
            {photo
              ? <span className="avatar-circle lg has-photo" style={{backgroundImage:`url("${photo}")`}}></span>
              : <span className={`avatar-circle lg avatar-c${nameHash(display)}`}>{initial(display)}</span>}
            <div>
              <div className="role">{roleOf(display)}</div>
              <div className="nm">{display}</div>
              <div className="clubs">{[...clubs].join(' · ')}</div>
            </div>
          </div>
        );
      })}
    </div>
  );
};

// --- Activities section ---
const ActivitiesSection = () => (
  <section className="scene" id="activities">
    <SectionHead
      eyebrow="04 — Core Activities"
      title="กิจกรรมหลักที่ขับเคลื่อนเป้า"
      sub="3 กิจกรรมเสาหลักที่สร้าง Pipeline ตัวแทนใหม่และโอกาสขายอย่างต่อเนื่องตลอดปี"
    />

    <div className="activities">
      {ACTIVITIES.map(a => (
        <div className="act-card" key={a.id}>
          <Tag pillar={a.tag.toLowerCase()}>{a.tag} · {PILLARS.find(p=>p.key===a.tag).name}</Tag>
          <h4>{a.name}</h4>
          <p>{a.desc}</p>
          <dl className="act-meta">
            <div><dt>ความถี่</dt><dd>{a.freq}</dd></div>
            <div><dt>ตลอดปี</dt><dd>{a.timesPerYear} ครั้ง</dd></div>
            <div style={{gridColumn:'1 / -1'}}><dt>KPI</dt><dd style={{fontSize:13, fontFamily:'IBM Plex Sans Thai', fontWeight:400, color:'var(--ink-soft)'}}>{a.kpi}</dd></div>
          </dl>
        </div>
      ))}
    </div>

    <div className="note">
      <strong>ยังไม่กำหนด:</strong> Contest รายไตรมาส, Recognition ประจำเดือน, Mid-Year Convention, Year-End Gala — จะเติมรายละเอียดเมื่อได้ข้อมูลเพิ่ม
    </div>
  </section>
);

// --- APE Goal section ---
// Plan months: Jun(5)–Dec(11) mapped to APE_MONTHLY_PROGRESS index 0–6
const PLAN_JS_MONTHS = [5, 6, 7, 8, 9, 10, 11];

const APE_REFRESH_MS = 30000;

const apeWeekRange = () => {
  const now = getNow();
  const js = now.getDay(); // 0=Sun
  const diffToMon = js === 0 ? -6 : 1 - js;
  const mon = new Date(now); mon.setDate(now.getDate() + diffToMon);
  const sun = new Date(mon); sun.setDate(mon.getDate() + 6);
  const fmt = d => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}`;
  return { from: fmt(mon), to: fmt(sun) };
};

const ApeGoalSection = ({ liveSales, orgGoal }) => {
  const monthMap = {};
  (liveSales || []).forEach(r => {
    const m = new Date(r.date + "T00:00:00").getMonth();
    monthMap[m] = (monthMap[m] || 0) + r.amount;
  });
  // ยอดจริงรายเดือนแบบ key 'YYYY-MM' — ใช้จับคู่กับเป้ารายเดือนของแผน active (ข้ามปีได้)
  const byMonthKey = {};
  (liveSales || []).forEach(r => {
    const k = String(r.date).slice(0, 7);
    byMonthKey[k] = (byMonthKey[k] || 0) + r.amount;
  });

  const progress = orgGoal
    ? (orgGoal.months || []).map(m => ({
        month: orgMonthLabel(m.month),
        focus: m.focus || "",
        target: Number(m.target) || 0,
        achieved: byMonthKey[m.month] || 0,
      }))
    : APE_MONTHLY_PROGRESS.map((m, i) => ({
        ...m,
        achieved: liveSales ? (monthMap[PLAN_JS_MONTHS[i]] || 0) : m.achieved,
      }));

  const totalAchieved  = progress.reduce((s, m) => s + m.achieved, 0);
  const apeTargetLabel = (orgGoal ? (Number(orgGoal.ape_target) || 0) : TARGET.fyp).toLocaleString("en-US");

  const loading = liveSales === null;

  return (
    <section className="scene" id="ape-goal">
      <SectionHead
        eyebrow="02 — APE Goals"
        title="เป้าหมาย APE"
        sub={`ความคืบหน้ารายเดือนสู่เป้า ${apeTargetLabel} APE · ยอดรายวัน/สัปดาห์/เดือนดูได้จากการ์ดบนหน้าแรก`}
      />

      <div className="divider-label">
        ความคืบหน้ารายเดือน
        {loading && <span style={{ fontSize: 12, color: "var(--muted)", marginLeft: 8 }}>กำลังโหลด...</span>}
      </div>
      <div className="table-wrap">
        <table className="dash-table ape-monthly-table">
          <thead>
            <tr>
              <th>เดือน</th>
              <th className="num">เป้าหมาย</th>
              <th className="num">ทำได้</th>
              <th className="num">คงเหลือ</th>
              <th style={{ width: 180, minWidth: 140 }}>ความคืบหน้า</th>
            </tr>
          </thead>
          <tbody>
            {progress.map(m => {
              const pct  = m.target > 0 ? Math.min(Math.round(m.achieved / m.target * 100), 100) : 0;
              const left = Math.max(m.target - m.achieved, 0);
              const hit  = m.target > 0 && m.achieved >= m.target;
              return (
                <tr key={m.month}>
                  <td>
                    <strong>{m.month}</strong>
                    {m.focus ? <span style={{ color: "var(--muted)", fontSize: 12 }}> · {m.focus}</span> : null}
                  </td>
                  <td className="num" style={{ color: "var(--muted)" }}>{m.target.toLocaleString("en-US")}</td>
                  <td className="num">
                    <strong style={{ color: hit ? "var(--t)" : m.achieved > 0 ? "var(--gold)" : "inherit" }}>
                      {m.achieved.toLocaleString("en-US")}
                    </strong>
                  </td>
                  <td className="num" style={{ color: "var(--muted)", fontSize: 13 }}>
                    {left > 0 ? left.toLocaleString("en-US") : <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)" }}>{progress.reduce((s, m) => s + m.target, 0).toLocaleString("en-US")}</strong></td>
              <td className="num"><strong style={{ color: "var(--gold)" }}>{totalAchieved.toLocaleString("en-US")}</strong></td>
              <td className="num"><strong>{Math.max(progress.reduce((s, m) => s + m.target, 0) - totalAchieved, 0).toLocaleString("en-US")}</strong></td>
              <td />
            </tr>
          </tfoot>
        </table>
      </div>
    </section>
  );
};

// --- Open LMS section ---
const LMS_ACTIVITY_PREFIX = "LMS: ";
// (LMS_POINTS_PER_LESSON ย้ายไป POINT_RULES.lms ใน data.jsx แล้ว — แก้ได้จากแอดมิน)
const lmsTodayKey = () => {
  const d = new Date();
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
};

const formatVideoTime = (seconds) => {
  if (!Number.isFinite(seconds) || seconds < 0) return "0:00";
  const total = Math.floor(seconds);
  const mins = Math.floor(total / 60);
  const secs = String(total % 60).padStart(2, "0");
  return `${mins}:${secs}`;
};

const LessonVideo = ({ src }) => {
  const videoRef = React.useRef(null);
  const shellRef = React.useRef(null);
  const trackRef = React.useRef(null);
  const [playing, setPlaying] = React.useState(false);
  const [muted, setMuted] = React.useState(false);
  const [duration, setDuration] = React.useState(0);
  const [currentTime, setCurrentTime] = React.useState(0);

  React.useEffect(() => {
    setPlaying(false);
    setDuration(0);
    setCurrentTime(0);
  }, [src]);

  const togglePlay = async () => {
    const video = videoRef.current;
    if (!video) return;
    if (video.paused) {
      await video.play();
    } else {
      video.pause();
    }
  };

  const toggleMute = () => {
    const video = videoRef.current;
    if (!video) return;
    video.muted = !video.muted;
    setMuted(video.muted);
  };

  const toggleFullscreen = () => {
    const el = shellRef.current;
    const video = videoRef.current;
    if (!el || !video) return;
    const isFs = !!(document.fullscreenElement || document.webkitFullscreenElement);
    if (isFs) {
      (document.exitFullscreen || document.webkitExitFullscreen || (() => {})).call(document);
    } else if (el.requestFullscreen) {
      el.requestFullscreen();                  // Chrome, Firefox, Edge
    } else if (el.webkitRequestFullscreen) {
      el.webkitRequestFullscreen();            // Safari desktop
    } else if (video.webkitEnterFullscreen) {
      video.webkitEnterFullscreen();           // iOS Safari
    }
  };

  // เลื่อนดูได้อิสระ: คลิก/ลากบนแถบ progress เพื่อกระโดดไปตำแหน่งที่ต้องการ
  const seekToClientX = (clientX) => {
    const video = videoRef.current;
    const track = trackRef.current;
    if (!video || !track || !duration) return;
    const rect = track.getBoundingClientRect();
    const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
    video.currentTime = ratio * duration;
    setCurrentTime(video.currentTime);
  };

  const handleScrubStart = (event) => {
    event.preventDefault();
    seekToClientX(event.clientX);
    const onMove = (e) => seekToClientX(e.clientX);
    const onUp = () => {
      window.removeEventListener("pointermove", onMove);
      window.removeEventListener("pointerup", onUp);
    };
    window.addEventListener("pointermove", onMove);
    window.addEventListener("pointerup", onUp);
  };

  const progress = duration ? Math.min(100, (currentTime / duration) * 100) : 0;

  return (
    <div className="locked-video" ref={shellRef}>
      <video
        ref={videoRef}
        key={src}
        preload="metadata"
        controls={false}
        playsInline
        controlsList="nodownload"
        disablePictureInPicture
        onContextMenu={(event) => event.preventDefault()}
        onLoadedMetadata={(event) => setDuration(event.currentTarget.duration || 0)}
        onPlay={() => setPlaying(true)}
        onPause={() => setPlaying(false)}
        onEnded={() => setPlaying(false)}
        onTimeUpdate={(event) => setCurrentTime(event.currentTarget.currentTime || 0)}
      >
        <source src={src} type="video/mp4" />
      </video>
      <button className="locked-video-hit" type="button" onClick={togglePlay} aria-label={playing ? "หยุดวิดีโอ" : "เล่นวิดีโอ"} />
      {!playing && (
        <button className="locked-video-play" type="button" onClick={togglePlay} aria-label="เล่นวิดีโอ">
          <svg width="28" height="28" viewBox="0 0 28 28" fill="none">
            <polygon points="10,7 21,14 10,21" fill="currentColor" />
          </svg>
        </button>
      )}
      <div className="locked-video-controls">
        <button type="button" onClick={togglePlay}>{playing ? "Pause" : "Play"}</button>
        <span>{formatVideoTime(currentTime)} / {formatVideoTime(duration)}</span>
        <div
          className="locked-video-progress"
          ref={trackRef}
          onPointerDown={handleScrubStart}
          role="slider"
          aria-label="ตำแหน่งวิดีโอ"
          aria-valuemin={0}
          aria-valuemax={Math.round(duration)}
          aria-valuenow={Math.round(currentTime)}
        >
          <div style={{ width: `${progress}%` }} />
        </div>
        <button type="button" onClick={toggleMute}>{muted ? "Unmute" : "Mute"}</button>
        <button type="button" onClick={toggleFullscreen}>Fullscreen</button>
      </div>
    </div>
  );
};

// เอกสารแนบ: รองรับทั้งของใหม่ {name,url,size} และของเก่าที่เป็น string
const lmsNormalizeResources = (raw) =>
  (Array.isArray(raw) ? raw : [])
    .map(item => (typeof item === "string" ? { name: item } : item))
    .filter(it => it && it.name);
const lmsDownloadHref = (r) =>
  `${r.url}${r.url.includes("?") ? "&" : "?"}download=${encodeURIComponent(r.name)}`;
const lmsFormatBytes = (n) => {
  if (n == null) return "";
  if (n < 1024) return `${n} B`;
  if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
  return `${(n / 1024 / 1024).toFixed(1)} MB`;
};

// แปลงลิงก์ YouTube (watch / youtu.be / shorts / live / embed) เป็น embed URL — คืน null ถ้าไม่ใช่ลิงก์ YouTube
const lmsYoutubeEmbedUrl = (url) => {
  const m = (url || "").match(/(?:youtube\.com\/(?:watch\?(?:[^#]*&)?v=|embed\/|shorts\/|live\/)|youtu\.be\/)([\w-]{11})/);
  return m ? `https://www.youtube.com/embed/${m[1]}` : null;
};

// แปลงลิงก์ Spotify (open.spotify.com/... หรือ spotify:...) เป็น embed URL — คืน null ถ้าไม่ใช่ Spotify
const lmsSpotifyEmbedUrl = (url) => {
  const s = url || "";
  let m = s.match(/open\.spotify\.com\/(?:intl-[a-z]{2}\/)?(episode|show|track|playlist|album|artist)\/([A-Za-z0-9]+)/);
  if (!m) m = s.match(/spotify:(episode|show|track|playlist|album|artist):([A-Za-z0-9]+)/);
  return m ? `https://open.spotify.com/embed/${m[1]}/${m[2]}` : null;
};

const mapLmsRow = (r) => ({
  id: r.id,
  title: r.title || "",
  module: r.module || "คลาสเรียนย้อนหลัง",
  duration: r.duration || "— นาที",
  level: r.level || "เริ่มต้น",
  videoUrl: (r.video_url || "").trim(),
  desc: r.description || "",
  outcomes: Array.isArray(r.outcomes) ? r.outcomes : [],
  resources: Array.isArray(r.resources) ? r.resources : [],
});

const hasLessonVideo = (lesson) => !!lesson?.videoUrl;

// แปลง URL ในข้อความให้เป็นลิงก์คลิกได้
const linkifyText = (text) => {
  const str = String(text || "");
  if (!str) return str;
  const parts = str.split(/(https?:\/\/[^\s]+)/g);
  return parts.map((part, i) =>
    /^https?:\/\//.test(part)
      ? <a key={i} href={part} target="_blank" rel="noopener noreferrer" className="lesson-link">{part}</a>
      : part
  );
};

const LmsSection = ({ agent, onRefresh }) => {
  const [lessons, setLessons] = React.useState([]);
  const [lessonsLoading, setLessonsLoading] = React.useState(true);
  const [activeId, setActiveId] = React.useState(null);
  const [completed, setCompleted] = React.useState({});
  const [collapsed, setCollapsed] = React.useState({});
  const [saving, setSaving] = React.useState(false);
  const active = lessons.find(l => l.id === activeId) || lessons[0];
  const completedCount = lessons.filter(l => completed[l.id]).length;
  const progress = lessons.length ? Math.round(completedCount / lessons.length * 100) : 0;

  React.useEffect(() => {
    let alive = true;
    (async () => {
      setLessonsLoading(true);
      const { data } = await sb.from("lms_lessons")
        .select("*")
        .eq("active", true)
        .eq("audience", "agent")
        .order("sort_order")
        .order("created_at");
      if (!alive) return;
      const mapped = (data || []).map(mapLmsRow);
      setLessons(mapped);
      setActiveId(prev => prev || mapped[0]?.id || null);
      // เริ่มต้นให้ทุกกลุ่มคอร์สหุบไว้ ผู้ใช้กดเปิดเองทีละกลุ่ม
      setCollapsed(Object.fromEntries(mapped.map(l => [l.module, true])));
      setLessonsLoading(false);
    })();
    return () => { alive = false; };
  }, []);

  React.useEffect(() => {
    let alive = true;
    (async () => {
      if (!agent?.id || !lessons.length) {
        setCompleted({});
        return;
      }
      const validIds = new Set(lessons.map(l => l.id));
      const { data } = await sb.from("agent_lms_progress")
        .select("lesson_id")
        .eq("agent_id", agent.id)
        .in("lesson_id", Array.from(validIds));
      const map = {};
      (data || []).forEach(r => { if (validIds.has(r.lesson_id)) map[r.lesson_id] = true; });

      if (alive) setCompleted(map);
    })();
    return () => { alive = false; };
  }, [agent?.id, lessons]);

  const toggleComplete = async (lesson) => {
    if (!hasLessonVideo(lesson)) return;
    if (!agent?.id) {
      alert("กรุณาเข้าสู่ระบบก่อนบันทึกความคืบหน้า LMS");
      return;
    }
    if (saving) return;
    const wasDone = !!completed[lesson.id];
    setCompleted(prev => ({ ...prev, [lesson.id]: !wasDone }));
    setSaving(true);
    if (wasDone) {
      const { error: progressError } = await sb.from("agent_lms_progress")
        .delete()
        .eq("agent_id", agent.id)
        .eq("lesson_id", lesson.id);
      if (progressError) {
        setCompleted(prev => ({ ...prev, [lesson.id]: true }));
        alert("เกิดข้อผิดพลาด: " + progressError.message);
      } else {
        await sb.from("agent_activities")
          .delete()
          .eq("agent_id", agent.id)
          .eq("note", lesson.id)
          .like("name", `${LMS_ACTIVITY_PREFIX}%`);
        onRefresh && onRefresh();
      }
    } else {
      const { error: progressError } = await sb.from("agent_lms_progress").upsert({
        agent_id: agent.id,
        lesson_id: lesson.id,
        completed_at: new Date().toISOString(),
      }, { onConflict: "agent_id,lesson_id" });
      if (progressError) {
        setCompleted(prev => ({ ...prev, [lesson.id]: false }));
        alert("เกิดข้อผิดพลาด: " + progressError.message);
        setSaving(false);
        return;
      }
      const lmsPts = POINT_RULES.lms;
      const { error: activityError } = lmsPts > 0
        ? await sb.from("agent_activities").insert([{
            agent_id: agent.id,
            date: lmsTodayKey(),
            name: `${LMS_ACTIVITY_PREFIX}${lesson.title}`,
            points: lmsPts,
            note: lesson.id,
          }])
        : { error: null };
      if (activityError) {
        await sb.from("agent_lms_progress")
          .delete()
          .eq("agent_id", agent.id)
          .eq("lesson_id", lesson.id);
        setCompleted(prev => ({ ...prev, [lesson.id]: false }));
        alert("เกิดข้อผิดพลาด: " + activityError.message);
      } else {
        onRefresh && onRefresh();
      }
    }
    setSaving(false);
  };
  const toggleCollapse = (mod) => setCollapsed(prev => ({ ...prev, [mod]: !prev[mod] }));

  const modules = [...new Set(lessons.map(l => l.module))];
  const grouped = modules.map(mod => ({
    mod,
    lessons: lessons.filter(l => l.module === mod),
  }));

  const IconPlay = () => (
    <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
      <circle cx="9" cy="9" r="8" stroke="currentColor" strokeWidth="1.4"/>
      <polygon points="7.5,6 13,9 7.5,12" fill="currentColor"/>
    </svg>
  );
  const IconCheck = () => (
    <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
      <circle cx="9" cy="9" r="8" stroke="currentColor" strokeWidth="1.4"/>
      <polyline points="5.5,9 8,11.5 12.5,6.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
    </svg>
  );
  const IconChevron = ({ open }) => (
    <svg className={`lms-group-chevron${open ? " open" : ""}`} width="14" height="14" viewBox="0 0 14 14" fill="none">
      <polyline points="3,5 7,9 11,5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" fill="none"/>
    </svg>
  );

  return (
    <section className="scene" id="lms">
      <SectionHead
        eyebrow="03 — LMS"
        title="ระบบเรียนรู้ด้วยตัวเอง"
        sub="เปิดเข้ามาเรียนได้ทันที เลือกบทเรียน ดูเนื้อหา และติ๊กเรียนจบได้ใน browser เครื่องนี้"
      />

      {lessonsLoading ? (
        <div className="loading-msg">กำลังโหลดบทเรียน...</div>
      ) : !active ? (
        <div className="loading-msg">ยังไม่มีบทเรียนในระบบ</div>
      ) : (
      <div className="lms-shell">
        <div className="lms-player">
          <div className="lesson-screen">
            {active.videoUrl ? (
              lmsYoutubeEmbedUrl(active.videoUrl) ? (
                <iframe style={{position:"absolute",inset:0,width:"100%",height:"100%",border:0}}
                  src={lmsYoutubeEmbedUrl(active.videoUrl)} title={active.title} allowFullScreen
                  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" />
              ) : lmsSpotifyEmbedUrl(active.videoUrl) ? (
                <iframe style={{position:"absolute",inset:0,width:"100%",height:"100%",border:0}}
                  src={lmsSpotifyEmbedUrl(active.videoUrl)} title={active.title} loading="lazy"
                  allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" />
              ) : (
                <LessonVideo src={active.videoUrl} />
              )
            ) : (
              <div style={{position:"absolute",inset:0,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:10,color:"rgba(246,241,230,0.4)"}}>
                <svg width="48" height="48" viewBox="0 0 48 48" fill="none"><circle cx="24" cy="24" r="22" stroke="currentColor" strokeWidth="2"/><polygon points="19,15 37,24 19,33" fill="currentColor"/></svg>
                <span style={{fontSize:13}}>ยังไม่มีวิดีโอ</span>
              </div>
            )}
          </div>

          <div className="lesson-main">
            <div className="lesson-head">
              <Tag pillar="t">Open Access</Tag>
              <span className="lesson-meta">{active.module} · {active.duration}</span>
            </div>
            <h3>{active.title}</h3>
            <p className="lesson-desc">{linkifyText(active.desc)}</p>
            {hasLessonVideo(active) && (
              <button className={`complete-btn ${completed[active.id] ? "done" : ""}`}
                onClick={() => toggleComplete(active)} disabled={saving}>
                {completed[active.id] ? "✓ เรียนจบแล้ว" : "ติ๊กว่าเรียนจบ"}
              </button>
            )}

            <div className="lesson-divider" />

            <h4 className="lesson-section-title">เรียนแล้วทำได้</h4>
            <ul className="lesson-outcomes">
              {active.outcomes.map(item => <li key={item}>{item}</li>)}
            </ul>

            {lmsNormalizeResources(active.resources).length > 0 && (
              <>
                <div className="lesson-divider" />
                <h4 className="lesson-section-title">เอกสารประกอบ</h4>
                <div className="resource-list">
                  {lmsNormalizeResources(active.resources).map((r, i) =>
                    r.url ? (
                      <a key={i} href={lmsDownloadHref(r)} className="resource-file"
                        target="_blank" rel="noreferrer" download>
                        <span className="resource-file-icon" aria-hidden="true">⬇</span>
                        <span className="resource-file-name">{r.name}</span>
                        {r.size != null && <em>{lmsFormatBytes(r.size)}</em>}
                      </a>
                    ) : (
                      <span key={i}>{r.name}</span>
                    )
                  )}
                </div>
              </>
            )}
          </div>
        </div>

        <aside className="lms-lessons" aria-label="Lesson list">
          <div className="lms-sidebar-head">
            <strong>Course Content</strong>
            <span>{completedCount}/{lessons.length} บทเรียน</span>
          </div>
          <div className="lms-progress-bar"><div style={{width:`${progress}%`}}></div></div>

          {grouped.map(({ mod, lessons: modLessons }) => {
            const doneMod = modLessons.filter(l => completed[l.id]).length;
            const totalMin = modLessons.reduce((sum, l) => {
              const m = l.duration.match(/(\d+)/);
              return sum + (m ? parseInt(m[1]) : 0);
            }, 0);
            const modProgress = modLessons.length ? Math.round(doneMod / modLessons.length * 100) : 0;
            const isOpen = !collapsed[mod];
            const hasActive = modLessons.some(l => l.id === active.id);
            return (
              <div key={mod} className={`lms-group${isOpen ? " open" : ""}${hasActive ? " has-active" : ""}`}>
                <button className="lms-group-head" onClick={() => toggleCollapse(mod)} aria-expanded={isOpen}>
                  <div className="lms-group-copy">
                    <div className="lms-group-kicker">Course</div>
                    <strong>{mod}</strong>
                    <span>{modLessons.length} บทเรียน · {totalMin} นาที</span>
                  </div>
                  <div className="lms-group-status">
                    <strong>{doneMod}/{modLessons.length}</strong>
                    <span>{modProgress}%</span>
                    <IconChevron open={isOpen} />
                  </div>
                </button>
                <div className="lms-group-progress"><div style={{ width: `${modProgress}%` }}></div></div>
                {isOpen && (
                  <div className="lms-group-panel">
                    {modLessons.map((lesson) => {
                      const globalIdx = lessons.indexOf(lesson);
                      const isDone = !!completed[lesson.id];
                      const isActive = active.id === lesson.id;
                      return (
                        <button
                          key={lesson.id}
                          className={`lms-lesson${isActive ? " active" : ""}${isDone ? " done" : ""}`}
                          onClick={() => setActiveId(lesson.id)}
                        >
                          <div className="lms-lesson-text">
                            <b>{String(globalIdx + 1).padStart(2, "0")}: {lesson.title}</b>
                            <small>{isDone ? "เรียนจบแล้ว" : lesson.duration}</small>
                          </div>
                          <span className="lms-lesson-icon">
                            {isDone ? <IconCheck /> : <IconPlay />}
                          </span>
                        </button>
                      );
                    })}
                  </div>
                )}
              </div>
            );
          })}
        </aside>
      </div>
      )}
    </section>
  );
};

// --- Daily Challenges public teaser ---
const DailyChallengesPublicSection = () => {
  const [challenges, setChallenges] = React.useState(null);
  const today = new Date().toLocaleDateString("sv-SE"); // YYYY-MM-DD

  React.useEffect(() => {
    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")
      .then(({ data }) => setChallenges(data || []));
  }, [today]);

  const empty = challenges !== null && challenges.length === 0;

  const REWARD_LABEL = (ch) =>
    ch.reward_value > 0 ? `💰 เงินสด ${Number(ch.reward_value).toLocaleString()} บาท` : null;

  return (
    <section className="scene" id="daily-challenges">
      <SectionHead
        eyebrow="Daily challenges · วันนี้"
        title="ภารกิจพิเศษประจำวันนี้"
        sub="ทำภารกิจให้ครบ รับคะแนนโบนัสพิเศษ — เข้าสู่ระบบเพื่อรับรางวัล"
      />

      {empty && (
        <div className="pub-challenge-empty">ยังไม่มีการท้าทายในวันนี้ — กลับมาใหม่พรุ่งนี้ 🎯</div>
      )}

      <div className="pub-challenge-grid">
        {(challenges || []).map(ch => {
          const reward = REWARD_LABEL(ch);
          return (
            <div key={ch.id} className="pub-challenge-card">
              <div className="pub-challenge-icon">
                {ch.image_path
                  ? <img src={challengeImageSrc(ch.image_path)} alt="" className="challenge-thumb" />
                  : (ch.icon || "🎯")}
              </div>
              <div className="pub-challenge-body">
                <div className="pub-challenge-title">{ch.title}</div>
                {ch.description && <div className="pub-challenge-desc">{ch.description}</div>}
                {ch.condition_type === "case" && <div className="pub-challenge-condition">🎯 Case ≥ {Number(ch.condition_value).toLocaleString()} ราย</div>}
                {ch.condition_type === "ape"  && <div className="pub-challenge-condition">🎯 ภารกิจ APE</div>}
                {ch.condition_type === "fyc"  && <div className="pub-challenge-condition">🎯 NBC ≥ {Number(ch.condition_value).toLocaleString()} บาท</div>}
                {reward && <div className="pub-challenge-reward">{reward}</div>}
                {ch.challenger?.name && (
                  <div className="pub-challenge-challenger">โดย {ch.challenger.name}</div>
                )}
                {ch.challenger_type === "unit" && ch.challenger_unit && (
                  <div className="pub-challenge-challenger">ท้าทาย Unit {ch.challenger_unit}</div>
                )}
                {ch.challenger_type === "agent" && ch.target_agent?.name && (
                  <div className="pub-challenge-challenger">ท้าทาย {ch.target_agent.name}</div>
                )}
              </div>
            </div>
          );
        })}
      </div>

    </section>
  );
};

// ---- Live sales recognition feed (public home page) ----
const timeAgo = (iso) => {
  if (!iso) return "";
  const then = new Date(iso).getTime();
  if (Number.isNaN(then)) return "";
  const diff = Date.now() - then;
  const min = Math.floor(diff / 60000);
  if (min < 1) return "เมื่อสักครู่";
  if (min < 60) return `${min} นาทีที่แล้ว`;
  const hr = Math.floor(min / 60);
  if (hr < 24) return `${hr} ชม.ที่แล้ว`;
  const day = Math.floor(hr / 24);
  if (day < 7) return `${day} วันที่แล้ว`;
  return new Date(iso).toLocaleDateString("th-TH", { day: "numeric", month: "short" });
};

const SellerAvatar = ({ id, name, size = 44 }) => {
  const [failed, setFailed] = React.useState(false);
  const dim = { width: size, height: size };
  if (failed || !id) {
    return (
      <span className={`sales-feed-avatar fallback avatar-c${nameHash(name || "?")}`} style={dim}>
        {initial(name || "?")}
      </span>
    );
  }
  return (
    <img
      className="sales-feed-avatar"
      style={dim}
      src={`${window.SUPABASE_URL}/storage/v1/object/public/avatars/${id}`}
      alt={name || ""}
      loading="lazy"
      onError={() => setFailed(true)}
    />
  );
};

// RECRUIT_ACTIVITY_NAMES ย้ายไป components.jsx แล้ว (โหลดก่อนไฟล์นี้เสมอ) — ใช้เป็น global

const SalesFeedSection = () => {
  const [feed, setFeed] = React.useState(null);

  React.useEffect(() => {
    let alive = true;
    const load = async () => {
      const [salesRes, recruitRes, agentsRes] = await Promise.all([
        sb.from("agent_ape_sales")
          .select("id, agent_id, amount, recruit_count, note, date, created_at")
          .order("created_at", { ascending: false })
          .limit(30),
        sb.from("agent_activities")
          .select("id, agent_id, name, date, created_at")
          .in("name", RECRUIT_ACTIVITY_NAMES)
          .order("created_at", { ascending: false })
          .limit(30),
        sb.from("agents").select("id, name, unit, role"),
      ]);
      if (!alive) return;
      const amap = {};
      (agentsRes.data || []).forEach(a => { amap[a.id] = a; });

      const items = [];
      (salesRes.data || []).forEach(s => {
        if (s.amount > 0) {
          items.push({ key: `sale-${s.id}`, kind: "sale", agent_id: s.agent_id,
            note: s.note, amount: s.amount, date: s.date, created_at: s.created_at });
        }
        if (Number(s.recruit_count) > 0) {
          items.push({ key: `rc-${s.id}`, kind: "recruit", agent_id: s.agent_id,
            count: Number(s.recruit_count), date: s.date, created_at: s.created_at });
        }
      });
      (recruitRes.data || []).forEach(a => {
        items.push({ key: `act-${a.id}`, kind: "recruit", agent_id: a.agent_id,
          count: 1, date: a.date, created_at: a.created_at });
      });

      const enriched = items
        .map(it => ({ ...it, agent: amap[it.agent_id] }))
        .filter(it => it.agent && it.agent.role !== "admin")
        .sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
      setFeed(enriched);
    };
    load();
    const timer = window.setInterval(load, 30000);
    return () => { alive = false; window.clearInterval(timer); };
  }, []);

  const loading = feed === null;
  const todayKey = getNow().toLocaleDateString("sv-SE");
  const todayItems = (feed || []).filter(it => it.date === todayKey);
  const todaySales = todayItems.filter(it => it.kind === "sale");
  const todayCount = todaySales.length;
  const todayApe = todaySales.reduce((n, it) => n + Number(it.amount || 0), 0);
  const todayRecruit = todayItems.filter(it => it.kind === "recruit").reduce((n, it) => n + it.count, 0);
  const rows = (feed || []).slice(0, 20);

  return (
    <section className="scene" id="sales-feed">
      <SectionHead
        eyebrow="Live · ผลงานล่าสุด"
        title="ใครเพิ่งขาย & ชวนตัวแทน"
        sub="ยอดขายและการชวนตัวแทนล่าสุด — อัปเดตอัตโนมัติทุก 30 วินาที"
        right={<LiveBadge />}
      />

      <div className="sales-feed-summary">
        <div className="sales-feed-stat">
          <span>วันนี้ขายได้</span>
          <strong>{loading ? "..." : todayCount}</strong>
          <em>รายการ</em>
        </div>
        <div className="sales-feed-stat">
          <span>รวมยอดวันนี้</span>
          <strong>{loading ? "..." : todayApe.toLocaleString("en-US")}</strong>
          <em>APE</em>
        </div>
        <div className="sales-feed-stat">
          <span>Recruit วันนี้</span>
          <strong>{loading ? "..." : todayRecruit}</strong>
          <em>คน</em>
        </div>
      </div>

      {loading ? (
        <div className="loading-msg">กำลังโหลด...</div>
      ) : !rows.length ? (
        <div className="sales-feed-empty">ยังไม่มีผลงานวันนี้ — มาเป็นคนแรกกันเถอะ! 🚀</div>
      ) : (
        <div className="sales-feed-list">
          {rows.map(it => it.kind === "recruit" ? (
            <div key={it.key} className="sales-feed-row recruit">
              <SellerAvatar id={it.agent_id} name={it.agent.name} size={48} />
              <div className="sales-feed-body">
                <div className="sales-feed-line">
                  <strong className="sales-feed-name">{it.agent.name}</strong>
                  {it.agent.unit && <span className="sales-feed-unit">{it.agent.unit}</span>}
                </div>
                <div className="sales-feed-meta">
                  🤝 เพิ่งชวนตัวแทนใหม่ · {timeAgo(it.created_at)}
                </div>
              </div>
              <div className="sales-feed-amount recruit">
                <strong>+{it.count}</strong>
                <em>คน</em>
              </div>
            </div>
          ) : (
            <div key={it.key} className="sales-feed-row">
              <SellerAvatar id={it.agent_id} name={it.agent.name} size={48} />
              <div className="sales-feed-body">
                <div className="sales-feed-line">
                  <strong className="sales-feed-name">{it.agent.name}</strong>
                  {it.agent.unit && <span className="sales-feed-unit">{it.agent.unit}</span>}
                </div>
                <div className="sales-feed-meta">
                  {it.note ? it.note : "ปิดการขายสำเร็จ"} · {timeAgo(it.created_at)}
                </div>
              </div>
              <div className="sales-feed-amount">
                <strong>{Number(it.amount).toLocaleString("en-US")}</strong>
                <em>APE</em>
              </div>
            </div>
          ))}
        </div>
      )}
    </section>
  );
};

// ---- แข่งขัน ทีม vs ทีม (หน้า public landing) — โชว์แมตช์ที่กำลังแข่ง/กำลังจะเริ่ม, ไม่มี = ซ่อนทั้ง section ----
const CompetitionPublicSection = () => {
  const { loading, empty, selected, teams, standings, agentsById, participants } = useActiveTeamCompetition(true);
  if (loading || empty) return null; // กำลังโหลด หรือ ไม่มีแมตช์ → ซ่อนทั้ง section
  return (
    <section className="scene" id="team-competition">
      <SectionHead eyebrow="ศึกชิงเจ้ายอด" title="แข่งขัน ทีม vs ทีม" />
      <CompetitionVsCard competition={selected} teams={teams}
        standings={standings} agentsById={agentsById} participants={participants} />
    </section>
  );
};

// ---- กิจกรรม (หน้า public landing) — 5 รายการล่าสุด, ไม่มี = ซ่อนทั้ง section (port จาก 83G30M) ----
const ActivitiesPublicSection = () => {
  const [activities, setActivities] = React.useState([]);
  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: false, nullsFirst: false })
      .order("created_at", { ascending: false })
      .limit(5)
      // เรียงโชว์: วันที่ใกล้ถึงสุดอยู่บนสุด (ไม่มีวันที่ → ท้ายสุด)
      .then(({ data }) => setActivities((data || []).sort((a, b) => {
        if (!a.activity_date) return b.activity_date ? 1 : 0;
        if (!b.activity_date) return -1;
        return a.activity_date.localeCompare(b.activity_date);
      })));
  }, []);

  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;

  if (!activities.length) return null; // ยังไม่มีกิจกรรม → ซ่อนทั้ง section
  return (
    <section className="scene" id="activities-public">
      <SectionHead eyebrow="กิจกรรม" title="กิจกรรมล่าสุด" sub="กิจกรรมภายนอกของทีม — แตะโปสเตอร์เพื่อดูรูปเต็ม" />
      <div className="activity-list">
        {activities.map((a, i) => {
          const src = posterSrc(a.poster_path);
          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>}
                </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>
          );
        })}
      </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>
  );
};

Object.assign(window, { OverviewSection, ApeGoalSection, TrainingSection, ActivitiesSection, LmsSection, DailyChallengesPublicSection, SalesFeedSection, CompetitionPublicSection, ActivitiesPublicSection });
