/* ============================================================
   page-today.jsx — the live dashboard
   ============================================================ */

/* Motion primitives — set by primitives.jsx before this script runs */
const AnimatedHeading = window.AnimatedHeading || (({ text, tag: Tag = 'h2', className = '', style = {} }) => <Tag className={className} style={style}>{text}</Tag>);

/* Phase 1 — admin Edit Mode helpers (defined in primitives.jsx). */
const _EditableCell  = window.EditableCell;
const _useEditMode   = window.useEditMode || (() => false);
const _canEditUser   = window.canEditUser || (() => false);
const _showUndoToast = window.showUndoToast || (() => {});

/* Convert an ISO UTC timestamp to a `<input type="datetime-local">` value
   in the BROWSER's local timezone. The PATCH endpoint normalises back to
   UTC server-side so the round-trip is correct. */
/* Active-blockers card shows whatever the user wrote in their sign-in
   message — often a multi-paragraph quote with backticks. This trims it
   down to a single readable sentence we can clamp to 2 lines. */
function summarizeBlocker(text) {
  if (!text) return (window.appT||(s=>s))('Reported a blocker (no details given).');
  let t = String(text);
  // Strip fenced code blocks and inline backticks
  t = t.replace(/```[\s\S]*?```/g, ' ').replace(/`+/g, ' ');
  // Collapse whitespace and stray "None" prefixes from older parsers
  t = t.replace(/\s+/g, ' ').replace(/^\s*none\s+/i, '').trim();
  if (!t) return (window.appT||(s=>s))('Reported a blocker (no details given).');
  // Prefer the first sentence; fall back to a hard char cap
  const m = t.match(/^[^.!?]{12,200}[.!?]/);
  let out = m ? m[0] : t;
  if (out.length > 180) out = out.slice(0, 177).trim() + '…';
  return out;
}

function _isoToLocalInput(iso) {
  if (!iso) return '';
  try {
    const d = new Date(iso);
    if (isNaN(d.getTime())) return '';
    const pad = (n) => String(n).padStart(2, '0');
    return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
  } catch (_) { return ''; }
}
function _localInputToIso(local) {
  if (!local) return null;
  // local is "YYYY-MM-DDTHH:MM" in BROWSER tz; new Date() interprets as local.
  const d = new Date(local);
  return isNaN(d.getTime()) ? null : d.toISOString();
}

/* Save handler factory for the Today page hours-worked cell.
   Phase 4 backend: PATCH /api/daily-attendance/{user_id}/{work_date}/hours
   sets hours_override (NULL clears). Returns a 90s undo_token. */
function _useHoursPatch(employee, onAfter) {
  return async (newValue) => {
    const trimmed = (newValue == null ? '' : String(newValue)).trim();
    let hours;
    if (trimmed === '' || trimmed.toLowerCase() === 'null' || trimmed === '—') {
      hours = null;
    } else {
      const n = parseFloat(trimmed.replace(/h$/i, ''));
      if (isNaN(n) || n < 0 || n > 24) {
        alert((window.appT||(s=>s))('Hours must be a number 0-24, or empty to clear'));
        throw new Error('bad input');
      }
      hours = n;
    }
    // Browser-local YYYY-MM-DD; server interprets per team timezone.
    const today = new Date().toLocaleDateString('en-CA');
    const r = await fetch(
      `/api/daily-attendance/${employee.id}/${today}/hours`,
      {
        method: 'PATCH',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ hours }),
      }
    );
    if (!r.ok) {
      const txt = await r.text();
      alert((window.appLang&&window.appLang()==='ar') ? `فشل التعديل: ${txt}` : `Edit failed: ${txt}`);
      throw new Error(`HTTP ${r.status}`);
    }
    const data = await r.json();
    if (data.undo_token) {
      _showUndoToast({
        token: data.undo_token,
        label: (window.appLang&&window.appLang()==='ar')
          ? `${employee.display_name}: ${hours == null ? 'تم مسح الساعات' : 'تم تجاوز الساعات'}.`
          : `${employee.display_name}: hours ${hours == null ? 'cleared' : 'overridden'}.`,
        onUndone: onAfter,
      });
    }
    if (typeof onAfter === 'function') onAfter();
  };
}

/* Save handler factory for the Today page sign-in / sign-out cells. */
function _useSignTimePatch(employee, kind /* 'in' | 'out' */, onAfter) {
  return async (newLocalValue) => {
    const id = kind === 'in' ? employee.checkin_id : employee.checkout_id;
    if (!id) {
      alert((window.appLang&&window.appLang()==='ar')
        ? `لا يوجد سجل ${kind === 'in' ? 'تسجيل دخول' : 'تسجيل خروج'} بعد — أنشئ واحداً أولاً.`
        : `No ${kind === 'in' ? 'sign-in' : 'sign-out'} row exists yet — create one first.`);
      throw new Error('no row');
    }
    const iso = _localInputToIso(newLocalValue);
    if (!iso) throw new Error('bad input');
    const url = kind === 'in'
      ? `/api/checkins/${id}`
      : `/api/checkouts/${id}`;
    const body = kind === 'in'
      ? { signed_in_at: iso }
      : { signed_off_at: iso };
    const r = await fetch(url, {
      method: 'PATCH',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    });
    if (!r.ok) {
      const txt = await r.text();
      alert((window.appLang&&window.appLang()==='ar') ? `فشل التعديل: ${txt}` : `Edit failed: ${txt}`);
      throw new Error(`HTTP ${r.status}`);
    }
    const data = await r.json();
    if (data.undo_token) {
      _showUndoToast({
        token: data.undo_token,
        label: (window.appLang&&window.appLang()==='ar')
          ? `${employee.display_name}: تم تحديث ${kind === 'in' ? 'تسجيل الدخول' : 'تسجيل الخروج'}.`
          : `${employee.display_name}: ${kind === 'in' ? 'sign-in' : 'sign-out'} updated.`,
        onUndone: onAfter,
      });
    }
    if (typeof onAfter === 'function') onAfter();
  };
}

// Mobile-only daily sign-in card. Posts to /api/me/checkin (mirrors the bot's
// sign-in write). Hidden on desktop via .mobile-checkin-wrap (see styles-extras).
function MobileCheckin() {
  const toast = useToast();
  const [open, setOpen] = useState(false);
  const [yesterday, setYesterday] = useState('');
  const [today, setToday] = useState('');
  const [issues, setIssues] = useState('none');
  const [busy, setBusy] = useState(false);
  // status reflects a sign-in from ANY source (Matrix bot, web, app). Loaded on
  // mount and whenever the app regains focus, so it auto-updates if the user
  // already signed in on the web/another device.
  const [status, setStatus] = useState(null);
  const [nowTick, setNowTick] = useState(Date.now());

  const loadStatus = async () => {
    try {
      const r = await fetch('/api/me/checkin', { credentials: 'include' });
      if (r.ok) setStatus(await r.json());
    } catch (e) { /* offline / ignore */ }
  };

  useEffect(() => {
    loadStatus();
    const onVis = () => { if (document.visibilityState === 'visible') { loadStatus(); setNowTick(Date.now()); } };
    document.addEventListener('visibilitychange', onVis);
    window.addEventListener('focus', onVis);
    const tick = setInterval(() => setNowTick(Date.now()), 60000);
    // Lock Screen widget intent — AppDelegate fires this when the user
    // tapped Sign in / Sign off on the widget. We auto-open the format
    // form so they fill out yesterday / today / blockers instead of
    // posting a blank record.
    const onWidgetIntent = (e) => {
      const kind = e && e.detail && e.detail.kind;
      // Refresh status first so the form picks the right initial mode
      loadStatus();
      if (kind === 'signin' || kind === 'signoff') {
        setOpen(true);
        // Make sure the Today tab is active if the user landed on
        // another page from a previous session.
        try {
          if (window.setActivePage) window.setActivePage('today');
        } catch (_) {}
      }
    };
    window.addEventListener('k9-widget-intent', onWidgetIntent);
    // Cold-launch case: AppDelegate may have already fired the event
    // before this listener attached. Check a one-shot global.
    if (window.__pendingWidgetIntent) {
      const pending = window.__pendingWidgetIntent;
      delete window.__pendingWidgetIntent;
      setTimeout(() => onWidgetIntent({ detail: { kind: pending } }), 50);
    }
    return () => {
      document.removeEventListener('visibilitychange', onVis);
      window.removeEventListener('focus', onVis);
      window.removeEventListener('k9-widget-intent', onWidgetIntent);
      clearInterval(tick);
    };
  }, []);

  // Delegate to the centralized 12-hour formatter from data.jsx so every
  // page renders AM/PM consistently. Accepts optional tz for per-user
  // rendering (Cairo morning vs NY evening on a flex schedule).
  const fmtTime = (iso, tz) => {
    if (!iso) return '';
    if (window.fmtTime) return window.fmtTime(iso, tz) || '';
    try {
      return new Intl.DateTimeFormat('en-US', {
        hour: 'numeric', minute: '2-digit', hour12: true,
        ...(tz ? { timeZone: tz } : {}),
      }).format(new Date(iso));
    } catch (_e) { return ''; }
  };

  const post = async () => {
    if (busy || !today.trim()) return;
    setBusy(true);
    try {
      const r = await fetch('/api/me/checkin', {
        method: 'POST', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ yesterday, today, issues }),
      });
      if (!r.ok) {
        const j = await r.json().catch(() => ({}));
        throw new Error((j && j.detail) || `Failed (${r.status})`);
      }
      const d = await r.json();
      setOpen(false);
      setStatus({ signed_in: true, signed_in_at: d.signed_in_at, work_date: d.work_date, has_blocker: d.has_blocker });
      toast && toast.push && toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم تسجيل الدخول — مُسجّل ليوم ${d.work_date}` : `Signed in — recorded for ${d.work_date}`, kind: 'success' });
    } catch (e) {
      toast && toast.push && toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `فشل تسجيل الدخول: ${e.message || 'خطأ'}` : `Sign-in failed: ${e.message || 'error'}`, kind: 'error' });
    } finally {
      setBusy(false);
    }
  };

  const checkout = async () => {
    if (busy) return;
    setBusy(true);
    try {
      const r = await fetch('/api/me/checkout', { method: 'POST', credentials: 'include' });
      if (!r.ok) {
        const j = await r.json().catch(() => ({}));
        throw new Error((j && j.detail) || `Failed (${r.status})`);
      }
      await loadStatus();
      toast && toast.push && toast.push({ msg: (window.appT||(s=>s))('Signed off — nice work today.'), kind: 'success' });
    } catch (e) {
      toast && toast.push && toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `فشل تسجيل الخروج: ${e.message || 'خطأ'}` : `Sign-off failed: ${e.message || 'error'}`, kind: 'error' });
    } finally {
      setBusy(false);
    }
  };

  const signedIn = status && status.signed_in;
  const signedOff = status && status.signed_off;

  // Elapsed since sign-in (target 8h) for the hero progress bar.
  let elapsedLabel = '', pct = 0;
  if (signedIn && status.signed_in_at) {
    const ms = Math.max(0, nowTick - new Date(status.signed_in_at).getTime());
    const mins = Math.floor(ms / 60000);
    elapsedLabel = (window.appLang&&window.appLang()==='ar')
      ? `مضى ${Math.floor(mins / 60)}س ${mins % 60}د · الهدف 8س`
      : `${Math.floor(mins / 60)}h ${mins % 60}m elapsed · target 8h`;
    pct = Math.min(100, Math.round((ms / (8 * 3600 * 1000)) * 100));
  }

  // Editing form (sign-in / update).
  if (open) {
    return (
      <div className="mobile-checkin-wrap">
        <div className="mobile-checkin-card">
          <div className="mc-form">
            <div className="mc-title">{signedIn ? (window.appT||(s=>s))('Update sign-in') : (window.appT||(s=>s))('Daily sign-in')}</div>
            <label className="mc-label">{(window.appT||(s=>s))('Yesterday')}</label>
            <textarea className="inp" rows={2} value={yesterday} onChange={e => setYesterday(e.target.value)} placeholder={(window.appT||(s=>s))('What you did yesterday')} />
            <label className="mc-label">{(window.appT||(s=>s))('Today')}</label>
            <textarea className="inp" rows={2} value={today} onChange={e => setToday(e.target.value)} placeholder={(window.appT||(s=>s))("What you're working on today")} />
            <label className="mc-label">{(window.appT||(s=>s))('Issues / blockers')}</label>
            <textarea className="inp" rows={1} value={issues} onChange={e => setIssues(e.target.value)} placeholder={(window.appT||(s=>s))("'none' if unblocked")} />
            <div className="mc-actions">
              <button className="btn btn-ghost" onClick={() => setOpen(false)} disabled={busy}>{(window.appT||(s=>s))('Cancel')}</button>
              <button className="btn btn-pri" onClick={post} disabled={busy || !today.trim()}>
                <Icon name="send" size={14} /> {busy ? (window.appT||(s=>s))('Posting…') : (window.appT||(s=>s))('Post sign-in')}
              </button>
            </div>
          </div>
        </div>
      </div>
    );
  }

  // Signed in / signed off → hero card (matches design).
  if (signedIn) {
    return (
      <div className="mobile-checkin-wrap">
        <div className={`mc-hero${signedOff ? ' done' : ''}`}>
          <div className="mc-hero-eyebrow">{signedOff ? (window.appT||(s=>s))('DAY COMPLETE') : (window.appT||(s=>s))('SIGNED IN')}</div>
          <div className="mc-hero-time">{fmtTime(status.signed_in_at)}</div>
          <div className="mc-hero-sub">{signedOff ? (window.appT||(s=>s))('Signed off · see you tomorrow') : elapsedLabel}</div>
          {!signedOff && (
            <div className="mc-hero-bar"><div className="mc-hero-fill" style={{ width: pct + '%' }} /></div>
          )}
          <div className="mc-hero-actions">
            {!signedOff ? (
              <>
                <button className="mc-hero-btn ghost" onClick={() => setOpen(true)} disabled={busy}>{(window.appT||(s=>s))('Update')}</button>
                <button className="mc-hero-btn solid" onClick={checkout} disabled={busy}>{busy ? '…' : (window.appT||(s=>s))('Sign off')}</button>
              </>
            ) : (
              <button className="mc-hero-btn ghost" onClick={() => setOpen(true)} disabled={busy}>{(window.appT||(s=>s))('Edit entry')}</button>
            )}
          </div>
        </div>
      </div>
    );
  }

  // Not signed in → CTA.
  return (
    <div className="mobile-checkin-wrap">
      <div className="mobile-checkin-card">
        <button className="btn btn-pri mc-cta" onClick={() => setOpen(true)}>
          <Icon name="check" size={16} /> {(window.appT||(s=>s))('Sign in for today')}
        </button>
      </div>
    </div>
  );
}

// Embedded progress view for one user (item #6): what they did + whether it
// counts as progress vs their last sign-in. Opens instead of the full profile.
function ProgressSheet({ user, entry, onClose }) {
  const e = entry || {};
  const made = e.score != null && e.score >= 1;
  const toItems = (txt) => (txt || '').split('\n').map(s => s.replace(/^[-•]\s*/, '').trim()).filter(Boolean);
  const today = toItems(e.today_text);
  const yest = toItems(e.yesterday_text);
  return (
    <div className="mc-sheet-backdrop" onClick={onClose}>
      <div className="mc-sheet" onClick={ev => ev.stopPropagation()}>
        <div className="mc-sheet-head">
          <div style={{ fontWeight: 600, fontSize: 15 }}>{user.display_name || user.matrix_id}</div>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={16} /></button>
        </div>
        <div className={`mc-sheet-verdict ${made ? 'ok' : 'no'}`}>
          <Icon name={made ? 'check' : 'alert'} size={14} />
          {e.score == null ? (window.appT||(s=>s))('Not scored yet') : made ? (window.appT||(s=>s))('Made progress') : (window.appT||(s=>s))('No progress')}
          {e.score != null ? (window.appLang&&window.appLang()==='ar' ? ` · النتيجة ${e.score}` : ` · score ${e.score}`) : ''}
        </div>
        <div className="mc-sheet-label">{(window.appT||(s=>s))('What they worked on today')}</div>
        {today.length
          ? <ul className="mc-sheet-list">{today.map((t, i) => <li key={i}>{t}</li>)}</ul>
          : <div className="muted" style={{ fontSize: 12 }}>{(window.appT||(s=>s))('No "Today" items recorded.')}</div>}
        <div className="mc-sheet-label">{(window.appT||(s=>s))('Their last sign-in (for comparison)')}</div>
        {yest.length
          ? <ul className="mc-sheet-list">{yest.map((t, i) => <li key={i}>{t}</li>)}</ul>
          : <div className="muted" style={{ fontSize: 12 }}>{(window.appT||(s=>s))('No previous tasks on record.')}</div>}
        {e.reasoning && (<>
          <div className="mc-sheet-label">{(window.appT||(s=>s))('Why this verdict')}</div>
          <div style={{ fontSize: 12.5, lineHeight: 1.5, color: 'var(--text-2)' }}>{e.reasoning}</div>
        </>)}
      </div>
    </div>
  );
}

// Header meta: clock icon + live time + day, instead of plain date text.
function HeaderDayClock({ team, total }) {
  const [now, setNow] = useState(new Date());
  useEffect(() => {
    const t = setInterval(() => setNow(new Date()), 30000);
    return () => clearInterval(t);
  }, []);
  const day = now.toLocaleDateString([], { weekday: 'long', month: 'long', day: 'numeric' });
  const time = now.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
      <Icon name="clock" size={13} />
      <span className="mono">{time}</span>
      <span>· {day}</span>
      <span>· {team || (window.appT||(s=>s))('Team')} · {(window.appLang&&window.appLang()==='ar') ? `${total} مُتابَع عبر جميع الغرف` : `${total} tracked across all rooms`}</span>
    </span>
  );
}

// (Removed) #38 TodayQuickNav — replaced by TodayJumpSearch (the
// search-bar with Cmd/Ctrl+K). Keeping two separate jump UIs was
// duplicated UI; the search bar covers the same destinations.


function TodayPage({ live, ai }) {
  const D = window.STATUS_DATA;
  const allEmployees = live.employees;

  // Test accounts are filtered out unconditionally. The opt-in toggle was
  // removed because the only test row in the directory is the demo account
  // (intentionally visible) and the rest of the test_account flags are
  // legacy noise — no point exposing a dashboard control for them.
  const showTestAccounts = false;
  // Superadmins (e.g. founders) are not subject to attendance tracking —
  // /api/today/kpis excludes them from signed_in_today and not_signed_in.
  // The page-level employees array must mirror that exclusion or the
  // Absent KPI badge (which reads kpis.not_signed_in) and the absent
  // click-through dropdown (which filters !signed_in on the per-user
  // list) will disagree. Filter once at the top so every downstream
  // count, list, and ratio is consistent.
  const employees = allEmployees.filter(e => !e.is_test_account && !e.is_superadmin);

  const total = employees.length;
  const present = employees.filter(e => e.signed_in && !e.was_late).length;
  const late = employees.filter(e => e.was_late).length;
  // Absent = never signed in AND never signed off (someone who signed off
  // is "done", not absent — even if their sign-in wasn't recorded).
  const absent = employees.filter(e => !e.signed_in && !e.signed_off).length;
  const blockers = employees.filter(e => e.had_blocker);
  const signed_off = employees.filter(e => e.signed_off).length;
  const totalHours = employees.reduce((s, e) => s + (e.hours_worked || 0), 0);
  const [rosterFilter, setRosterFilter] = useState('all');
  const [refreshing, setRefreshing] = useState(false);
  const [exportMenuOpen, setExportMenuOpen] = useState(false);
  const [progressSheet, setProgressSheet] = useState(null); // user obj for the embedded progress view (item #6)
  const [strikes, setStrikes] = useState({});
  const toast = useToast();

  // P3: dismissible late-threshold banner
  const [lateBannerDismissed, setLateBannerDismissed] = useState(
    () => localStorage.getItem('lateBannerDismissed') === 'true'
  );
  const dismissLateBanner = () => {
    setLateBannerDismissed(true);
    localStorage.setItem('lateBannerDismissed', 'true');
  };
  // Show banner if no employee has ever been marked late (threshold likely unset)
  const hasAnyLateData = allEmployees.some(e => e.was_late);

  const hist14 = D.SERIES_14.map(s => s.present);

  // Use real KPI data from /api/today/kpis
  const kpis = D.KPIS || {};
  const weeklyHours = kpis.weekly_hours || 0;
  const strikesThisWeek = kpis.strikes_this_week || 0;
  const pendingRequests = kpis.pending_requests || 0;
  // "Signed in" = CURRENTLY signed in (not yet signed off). Someone who
  // signed in and already signed off is done for the day and must NOT show as
  // still-signed-in. (The backend signed_in_today counts all who signed in at
  // any point, which over-counts for a live "who's in now" view.)
  const currentlyIn = employees.filter(e => e.signed_in && !e.signed_off).length;
  const signedInToday = currentlyIn;

  // Fetch strikes per user
  useEffect(() => {
    const load = async () => {
      try {
        const r = await fetch('/api/employees/strikes', { credentials: 'include' });
        if (r.ok) {
          const data = await r.json();
          const map = {};
          (Array.isArray(data) ? data : []).forEach(s => { map[s.user_id || s.id] = s.strike_count || s.warnings || 0; });
          setStrikes(map);
        }
      } catch (_) {}
    };
    load();
  }, [employees.length]);

  // Fetch live per-user progress flags from /api/today/progress (bot writes
  // progress_score every time a sign-in is parsed). Polled every 12s and on
  // every SSE 'attendance-updated' / 'status-data-ready' event so admins see
  // Yes/No update live the moment a sign-in lands.
  const [progressMap, setProgressMap] = useState({});
  const [progressTick, setProgressTick] = useState(0);
  useEffect(() => {
    let cancelled = false;
    const load = async () => {
      try {
        // Cache-bust to ensure fresh DB read every poll
        const r = await fetch(`/api/today/progress?_t=${Date.now()}`, { credentials: 'include' });
        if (!r.ok || cancelled) return;
        const arr = await r.json();
        const map = {};
        (Array.isArray(arr) ? arr : []).forEach(p => {
          map[p.user_id] = {
            score: p.progress_score,
            badge: p.badge,
            reasoning: p.progress_reasoning,
            today_text: p.today_text,
            yesterday_text: p.yesterday_text,
          };
        });
        if (!cancelled) {
          setProgressMap(map);
          setProgressTick(t => t + 1);
        }
      } catch(_) {}
    };
    load();
    const id = setInterval(load, 12000);
    // Re-fetch progress immediately on:
    // - status-data-ready  (initial bootstrap)
    // - attendance-updated (SSE attendance event from bot)
    const onAttendance = () => { setTimeout(load, 400); };
    window.addEventListener('status-data-ready', load);
    window.addEventListener('attendance-updated', onAttendance);
    return () => {
      cancelled = true;
      clearInterval(id);
      window.removeEventListener('status-data-ready', load);
      window.removeEventListener('attendance-updated', onAttendance);
    };
  }, [employees.length]);

  // Compute "made progress today" splits (item #6)
  const madeProgressUsers = employees.filter(e => {
    const p = progressMap[e.id];
    return p && p.score != null && p.score >= 1;
  });
  const noProgressUsers = employees.filter(e => {
    const p = progressMap[e.id];
    return p && p.score === 0;
  });
  const unscoredSignedIn = employees.filter(e => e.signed_in && (!progressMap[e.id] || progressMap[e.id].score == null));

  const handleRefresh = async () => {
    setRefreshing(true);
    try {
      // Trigger global re-bootstrap (data.jsx listens for this).
      window.dispatchEvent(new CustomEvent('status-data-refresh-requested'));
      // Wait briefly then fall back to direct STATUS_REFRESH() if exposed.
      if (typeof window.STATUS_REFRESH === 'function') {
        await window.STATUS_REFRESH();
      }
      toast.push({ msg: (window.appT||(s=>s))('Refreshed'), kind: 'ok' });
    } catch(e) {
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? ('فشل التحديث: ' + e.message) : ('Refresh failed: ' + e.message), kind: 'err' });
    } finally {
      setRefreshing(false);
    }
  };

  const exportDaily = async (fmt) => {
    try {
      // fmt = 'csv' | 'pdf' | 'xlsx'
      const url = fmt === 'csv'
        ? '/api/reports.csv'
        : `/api/summary/export?format=${fmt}&period=day`;
      const r = await fetch(url, { credentials: 'same-origin' });
      if (!r.ok) throw new Error(`${r.status}`);
      const blob = await r.blob();
      const link = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = link;
      a.download = `attendance-${new Date().toISOString().slice(0,10)}.${fmt}`;
      a.click();
      URL.revokeObjectURL(link);
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم تنزيل ${fmt.toUpperCase()}` : `${fmt.toUpperCase()} downloaded`, kind: 'ok' });
    } catch(e) {
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? ('فشل التصدير: ' + e.message) : ('Export failed: ' + e.message), kind: 'err' });
    }
  };

  // Shared status precedence used by the scoreboard, drawer, and roster.
  // Order: absent → signed-off (day complete) → late (still in) → present.
  // 'late-off' = was late earlier today AND has now signed off. Splitting
  // these out is important — previously a user who signed off was still
  // shown as 'present' (or 'late'), which contradicted the dashboard.
  const computeTodayStatus = (e) => {
    // Signed off (day complete) takes precedence over "absent" — a user
    // who signed off worked today even if their sign-in wasn't recorded.
    if (e.signed_off) return e.was_late ? 'late-off' : 'signed-off';
    if (!e.signed_in) return 'absent';
    return e.was_late ? 'late' : 'present';
  };

  // Filter roster by status. 'late' chip includes late-off (someone who
  // showed up late counts as 'late' for compliance dashboards). 'signed-off'
  // chip includes late-off too.
  const filteredEmployees = employees.filter(e => {
    if (rosterFilter === 'all') return true;
    const status = computeTodayStatus(e);
    if (rosterFilter === 'late')        return status === 'late' || status === 'late-off';
    if (rosterFilter === 'signed-off')  return status === 'signed-off' || status === 'late-off';
    return status === rosterFilter;
  });

  // Progress badge — Yes / No / unscored, sourced from /api/today/progress
  // (bot writes progress_score every time a sign-in is parsed). Replaces the
  // old hours-based heuristic so admins see the bot's verdict directly.
  const progressBadge = (e) => {
    if (!e.signed_in) return <Badge kind="mute">—</Badge>;
    const p = progressMap[e.id];
    if (!p || p.score == null) return <Badge kind="mute" title={(window.appT||(s=>s))('Not yet scored — bot updates after sign-in')}>{(window.appT||(s=>s))('unscored')}</Badge>;
    if (p.score >= 1) return <Badge kind="ok" title={p.reasoning || (window.appT||(s=>s))('Bot detected progress vs yesterday')}>{(window.appT||(s=>s))('Yes')}</Badge>;
    return <Badge kind="err" title={p.reasoning || (window.appT||(s=>s))('Bot detected no progress vs yesterday')}>{(window.appT||(s=>s))('No')}</Badge>;
  };

  // Hours bar inline SVG (0–10 h range)
  const HoursBar = ({ hours }) => {
    const pct = Math.min(100, ((hours || 0) / 10) * 100);
    const color = pct > 60 ? 'var(--ok)' : pct > 30 ? 'var(--warn)' : 'var(--text-3)';
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <div style={{ width: 56, height: 5, background: 'var(--bg-elev-2)', borderRadius: 99, overflow: 'hidden' }}>
          <div style={{ width: `${pct}%`, height: '100%', background: color, borderRadius: 99 }} />
        </div>
        <span className="mono" style={{ fontSize: 11, color: 'var(--text-2)' }}>
          {hours ? `${hours.toFixed(1)}h` : '—'}
        </span>
      </div>
    );
  };

  // Bind fade-in-on-scroll after render
  useEffect(() => {
    const timer = setTimeout(() => { if (window.bindFadeIn) window.bindFadeIn(); }, 80);
    return () => clearTimeout(timer);
  });

  const greeting = (() => { const h = new Date().getHours(); const _t = (window.appT||(s=>s)); return h < 12 ? _t('Good morning') : h < 18 ? _t('Good afternoon') : _t('Good evening'); })();
  const firstName = (D.ME?.display_name || '').split(/\s+/)[0];

  // Jump-to destinations on the long Today page. Replaces the old 8-button
  // strip with a single search input + Cmd/Ctrl+K shortcut.
  const _tm = (window.appT||(s=>s));
  const SCROLL_MATCHERS = [
    { label: _tm('Overview'),    isOverview: true, needles: ['Today',         'Activity report'], hint: _tm('top of page') },
    { label: _tm('Violations'),  needles: ['Violations'],                       hint: _tm('late / no sign-in / format issues') },
    { label: _tm('Top 10'),      needles: ['Top 10'],                           hint: _tm('progress / blockers / absent') },
    { label: _tm('Live'),        needles: ['Live K9 presence', 'Live K9'],      hint: _tm('online / away / offline now') },
    { label: _tm('Attendance'),  needles: ['Attendance report'],                hint: _tm('per-user breakdown') },
    { label: _tm('Scoreboard'),  needles: ["Today's scoreboard", 'scoreboard'], hint: _tm('on-time vs late vs absent') },
    { label: _tm('Activity'),    needles: ['Activity'],                         hint: _tm('recent audit-log events') },
    { label: _tm('Infra'),       needles: ['Infrastructure'],                   hint: _tm('bots / rooms / teams') },
    { label: _tm('Blockers'),    needles: ['Active blockers'],                  hint: _tm('people stuck right now') },
    { label: _tm('My Servers'),  needles: ['My VPSie', 'My Servers'],           hint: _tm('your VPSie nodes') },
  ];
  const jumpTo = (matcher) => {
    if (!matcher) return;
    if (matcher.isOverview) {
      const top = document.querySelector('.page');
      if (top) top.scrollIntoView({ behavior: 'smooth', block: 'start' });
      else window.scrollTo({ top: 0, behavior: 'smooth' });
      return;
    }
    const candidates = document.querySelectorAll(
      '.page .sh-title, .page .sh, .page h1, .page h2, .page h3, .page strong'
    );
    for (const el of candidates) {
      const txt = (el.textContent || '').toLowerCase();
      if (matcher.needles.some(n => txt.includes(n.toLowerCase()))) {
        const card = el.closest('.card') || el;
        card.scrollIntoView({ behavior: 'smooth', block: 'start' });
        return;
      }
    }
  };

  // Search-bar style jump. Click or press Cmd/Ctrl+K to open the popover,
  // type to filter, arrow keys + Enter to select, Esc to close.
  const TodayJumpSearch = () => {
    const [open, setOpen]   = useState(false);
    const [query, setQuery] = useState('');
    const [active, setActive] = useState(0);
    const inputRef = React.useRef(null);

    const filtered = SCROLL_MATCHERS.filter(m => {
      if (!query) return true;
      const q = query.toLowerCase();
      return m.label.toLowerCase().includes(q)
          || m.hint.toLowerCase().includes(q)
          || m.needles.some(n => n.toLowerCase().includes(q));
    });

    // Cmd/Ctrl+K opens, Esc closes. Listener is page-wide so the shortcut
    // works no matter which element has focus.
    useEffect(() => {
      const onKey = (e) => {
        const isModK = (e.metaKey || e.ctrlKey) && (e.key === 'k' || e.key === 'K');
        if (isModK) {
          e.preventDefault();
          setOpen(o => !o);
          setQuery('');
          setActive(0);
        } else if (e.key === 'Escape' && open) {
          setOpen(false);
        }
      };
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, [open]);

    useEffect(() => {
      if (open) setTimeout(() => { try { inputRef.current && inputRef.current.focus(); } catch(_){} }, 30);
    }, [open]);

    const choose = (m) => {
      setOpen(false);
      setQuery('');
      jumpTo(m);
    };

    const onInputKey = (e) => {
      if (e.key === 'ArrowDown') { e.preventDefault(); setActive(a => Math.min(a + 1, filtered.length - 1)); }
      else if (e.key === 'ArrowUp')   { e.preventDefault(); setActive(a => Math.max(a - 1, 0)); }
      else if (e.key === 'Enter')     { e.preventDefault(); choose(filtered[active]); }
      else if (e.key === 'Escape')    { setOpen(false); }
    };

    const isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/i.test(navigator.platform || navigator.userAgent || '');
    const shortcut = isMac ? '⌘K' : 'Ctrl+K';

    return (
      <div style={{ position: 'relative', marginBottom: 14, paddingTop: 2 }}>
        <button
          type="button"
          className="btn btn-ghost"
          onClick={() => { setOpen(true); setQuery(''); setActive(0); }}
          style={{
            width: '100%', display: 'flex', alignItems: 'center', gap: 8,
            padding: '7px 12px', fontSize: 12.5, color: 'var(--text-2)',
            background: 'var(--bg-elev-1)', border: '1px solid var(--line)',
            borderRadius: 'var(--r)', justifyContent: 'flex-start',
            cursor: 'text',
          }}
          title={(window.appT||(s=>s))('Jump to a section on this page')}
        >
          <Icon name="search" size={13} />
          <span style={{ flex: 1, textAlign: 'left' }}>{(window.appT||(s=>s))('Search or jump to a section…')}</span>
          <span className="kbd" style={{
            fontSize: 10.5, padding: '2px 6px', borderRadius: 4,
            border: '1px solid var(--line)', background: 'var(--bg-elev-2)',
            color: 'var(--text-3)', fontFamily: 'ui-monospace, SFMono-Regular, monospace',
          }}>{shortcut}</span>
        </button>

        {open && (
          <>
            <div
              onClick={() => setOpen(false)}
              style={{ position: 'fixed', inset: 0, zIndex: 1499, background: '#0006' }}
            />
            <div
              style={{
                position: 'absolute', left: 0, right: 0, top: 'calc(100% + 6px)',
                background: 'var(--bg-elev-1)', border: '1px solid var(--line-hi)',
                borderRadius: 'var(--r)', boxShadow: '0 12px 40px -10px #000000c0',
                zIndex: 1500, overflow: 'hidden', maxHeight: '60vh', display: 'flex',
                flexDirection: 'column',
              }}
              onClick={e => e.stopPropagation()}
            >
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '10px 12px', borderBottom: '1px solid var(--line)' }}>
                <Icon name="search" size={13} />
                <input
                  ref={inputRef}
                  type="text"
                  value={query}
                  onChange={e => { setQuery(e.target.value); setActive(0); }}
                  onKeyDown={onInputKey}
                  placeholder={(window.appT||(s=>s))('Type to filter sections… (Enter to jump, Esc to close)')}
                  style={{
                    flex: 1, border: 'none', outline: 'none', background: 'transparent',
                    color: 'var(--text-1)', fontSize: 13.5,
                  }}
                />
                <span style={{ fontSize: 10.5, color: 'var(--text-3)' }}>{filtered.length} / {SCROLL_MATCHERS.length}</span>
              </div>
              <div style={{ overflowY: 'auto' }}>
                {filtered.length === 0 ? (
                  <div style={{ padding: '18px 14px', color: 'var(--text-3)', fontSize: 12 }}>{(window.appLang&&window.appLang()==='ar') ? `لا توجد أقسام مطابقة لـ "${query}".` : `No sections match “${query}”.`}</div>
                ) : filtered.map((m, i) => (
                  <button
                    key={m.label}
                    onClick={() => choose(m)}
                    onMouseEnter={() => setActive(i)}
                    style={{
                      width: '100%', display: 'flex', alignItems: 'center', gap: 10,
                      padding: '10px 14px', border: 'none', cursor: 'pointer',
                      textAlign: 'left', fontSize: 13,
                      background: i === active ? 'var(--bg-elev-2)' : 'transparent',
                      color: 'var(--text-1)',
                    }}
                  >
                    <Icon name="arrowR" size={12} />
                    <span style={{ fontWeight: 500 }}>{m.label}</span>
                    <span style={{ color: 'var(--text-3)', fontSize: 11.5, flex: 1 }}>— {m.hint}</span>
                    {i === active && <span style={{ fontSize: 10, color: 'var(--text-3)' }}>↵</span>}
                  </button>
                ))}
              </div>
            </div>
          </>
        )}
      </div>
    );
  };

  return (
    <div className="page">
      <PageHeader
        title={<><AnimatedHeading text={`${greeting}, ${firstName}.`} tag="span" /></>}
        meta={<HeaderDayClock team={D.ME?.team} total={total} />}
        right={
          <div style={{ display:'flex', gap: 8, alignItems:'center', flexWrap:'wrap' }}>
            {/* Show-test-accounts checkbox removed — the only test row is the
                demo account, which stays visible by design. */}
            <Refresh onClick={handleRefresh} loading={refreshing} />
            <div className="hide-on-mobile" style={{ position: 'relative' }}>
              <button
                className="btn btn-ghost"
                onClick={() => setExportMenuOpen(o => !o)}
                title={(window.appT||(s=>s))('Export options')}
              >
                <Icon name="download" size={14} /> {(window.appT||(s=>s))('Export')}
                <span style={{ fontSize: 11, marginLeft: 2, opacity: 0.7 }}>⋯</span>
              </button>
              {exportMenuOpen && (
                <div
                  style={{
                    position: 'absolute', right: 0, top: 'calc(100% + 6px)',
                    background: 'var(--bg-elev-1)', border: '1px solid var(--line-hi)',
                    borderRadius: 'var(--r)', boxShadow: '0 8px 32px -8px #000000a0',
                    minWidth: 160, zIndex: 80, overflow: 'hidden',
                  }}
                  onMouseLeave={() => setExportMenuOpen(false)}
                >
                  {[
                    { label: (window.appT||(s=>s))('Export CSV'),    icon: 'download', action: () => exportDaily('csv') },
                    { label: (window.appT||(s=>s))('Export PDF'),    icon: 'download', action: () => exportDaily('pdf') },
                    { label: (window.appT||(s=>s))('Send report…'), icon: 'msg',      action: () => {
                      toast.push({ msg: (window.appT||(s=>s))('Use the Reports tab to send bulk reports via DM.'), kind: 'info' });
                    }},
                  ].map(item => (
                    <button
                      key={item.label}
                      className="btn btn-ghost"
                      style={{ width: '100%', justifyContent: 'flex-start', borderRadius: 0, padding: '9px 14px', fontSize: 13 }}
                      onClick={() => { item.action(); setExportMenuOpen(false); }}
                    >
                      <Icon name={item.icon} size={13} /> {item.label}
                    </button>
                  ))}
                </div>
              )}
            </div>
          </div>
        }
      />

      <MobileCheckin />

      {progressSheet && (
        <ProgressSheet
          user={progressSheet}
          entry={progressMap[progressSheet.id]}
          onClose={() => setProgressSheet(null)}
        />
      )}

      {/* Searchable jump bar (replaces the old 8-button strip). Cmd/Ctrl+K
          opens it from anywhere on the page. */}
      <TodayJumpSearch />

      <KpiStrip
        signedInToday={signedInToday} total={total} late={late} absent={absent}
        kpis={kpis} blockers={blockers} hist14={hist14} D={D}
        employees={employees}
        madeProgressUsers={madeProgressUsers}
        noProgressUsers={noProgressUsers}
        unscoredSignedIn={unscoredSignedIn}
        progressMap={progressMap}
      />

      {/* 🧠 Neural pulse — live model activity (admins only; self-hides otherwise) */}
      <NeuralPulseCard />

      {/* P8: Made-progress / no-progress callouts — collapsed when both zero */}
      {(madeProgressUsers.length === 0 && noProgressUsers.length === 0) ? (
        <div className="card fade-in-on-scroll" style={{ marginBottom: 16 }}>
          <details>
            <summary style={{ cursor:'pointer', fontSize: 12.5, fontWeight: 600, padding: '2px 0', display:'flex', alignItems:'center', gap: 8 }}>
              <span style={{ color:'var(--text-2)' }}>{(window.appT||(s=>s))("Today's progress")}</span>
              <span className="muted" style={{ fontWeight: 400 }}>
                {unscoredSignedIn.length > 0
                  ? ((window.appLang&&window.appLang()==='ar') ? `${unscoredSignedIn.length} غير مُقيّم — يحدّث البوت بعد تسجيل الدخول` : `${unscoredSignedIn.length} unscored — bot updates after sign-in`)
                  : (window.appT||(s=>s))('No sign-ins scored yet')}
              </span>
            </summary>
            <div className="muted" style={{ fontSize: 11.5, padding: '8px 0 2px' }}>
              {(window.appT||(s=>s))("The progress bot scores each sign-in against yesterday's tasks. Results appear here once at least one person is scored.")}
            </div>
          </details>
        </div>
      ) : (
        <div className="card" style={{ marginBottom: 16 }}>
          <SectionHeader
            title={(window.appT||(s=>s))("Today's progress")}
            sub={(window.appT||(s=>s))('Live — bot scores each sign-in for progress vs yesterday')}
            tip={(window.appT||(s=>s))('Score 2 = made progress (today differs substantially from yesterday + has concrete tasks). Score 1 = partial. Score 0 = no progress (today copies yesterday or is vague). The bot uses jaccard overlap with yesterday + a vague-keyword filter.')}
          />
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 12 }}>
            <div style={{ background:'color-mix(in oklch, var(--ok) 6%, transparent)', border:'1px solid color-mix(in oklch, var(--ok) 30%, var(--line))', borderRadius:'var(--r-md)', padding: 10 }}>
              <div style={{ fontSize: 12, color:'var(--ok)', fontWeight: 600, marginBottom: 6, display:'flex', alignItems:'center', gap: 6 }}>
                <Icon name="check" size={12} style={{ color:'var(--ok)' }}/> {(window.appT||(s=>s))('Made progress')} ({madeProgressUsers.length})
              </div>
              {madeProgressUsers.length === 0 ? (
                <div className="muted" style={{ fontSize: 11.5, padding: 4 }}>{(window.appT||(s=>s))('No users have been scored as making progress yet today.')}</div>
              ) : (
                <div style={{ display:'flex', flexDirection:'column', gap: 8 }}>
                  {madeProgressUsers.map(u => {
                    const reason = progressMap[u.id]?.reasoning;
                    return (
                      <div key={u.id} style={{ fontSize: 12, padding: '6px 4px', borderBottom:'1px solid color-mix(in oklch, var(--ok) 12%, transparent)' }}>
                        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: reason ? 4 : 0 }}>
                          <span><PersonCell person={u} onClick={(ev)=>{ try{ev.stopPropagation()}catch(_){} setProgressSheet(u); }} /></span>
                          <Badge kind="ok">{(window.appT||(s=>s))('Yes')}</Badge>
                        </div>
                        {reason && (
                          <div style={{ fontSize: 11, color:'var(--text-2)', paddingLeft: 30, lineHeight: 1.45, fontStyle:'italic' }}>
                            <span className="muted" style={{ fontStyle:'normal' }}>{(window.appT||(s=>s))('Bot:')} </span>{reason}
                          </div>
                        )}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
            <div style={{ background:'color-mix(in oklch, var(--err) 6%, transparent)', border:'1px solid color-mix(in oklch, var(--err) 30%, var(--line))', borderRadius:'var(--r-md)', padding: 10 }}>
              <div style={{ fontSize: 12, color:'var(--err)', fontWeight: 600, marginBottom: 6, display:'flex', alignItems:'center', gap: 6 }}>
                <Icon name="alert" size={12} style={{ color:'var(--err)' }}/> {(window.appT||(s=>s))('No progress')} ({noProgressUsers.length})
                {unscoredSignedIn.length > 0 && (
                  <span className="muted" style={{ fontWeight: 400, fontSize: 11, marginLeft:'auto' }}>{(window.appLang&&window.appLang()==='ar') ? `+ ${unscoredSignedIn.length} غير مُقيّم` : `+ ${unscoredSignedIn.length} unscored`}</span>
                )}
              </div>
              {noProgressUsers.length === 0 ? (
                <div className="muted" style={{ fontSize: 11.5, padding: 4 }}>{(window.appT||(s=>s))('Nobody flagged as no-progress today.')}</div>
              ) : (
                <div style={{ display:'flex', flexDirection:'column', gap: 8 }}>
                  {noProgressUsers.map(u => {
                    const reason = progressMap[u.id]?.reasoning;
                    return (
                      <div key={u.id} style={{ fontSize: 12, padding: '6px 4px', borderBottom:'1px solid color-mix(in oklch, var(--err) 12%, transparent)' }}>
                        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: reason ? 4 : 0 }}>
                          <span><PersonCell person={u} onClick={(ev)=>{ try{ev.stopPropagation()}catch(_){} setProgressSheet(u); }} /></span>
                          <Badge kind="err">{(window.appT||(s=>s))('No')}</Badge>
                        </div>
                        {reason ? (
                          <div style={{ fontSize: 11, color:'var(--text)', paddingLeft: 30, lineHeight: 1.45 }}>
                            <span className="muted">{(window.appT||(s=>s))('Why:')} </span>
                            <span style={{ fontStyle:'italic' }}>{reason}</span>
                          </div>
                        ) : (
                          <div style={{ fontSize: 11, color:'var(--text-3)', paddingLeft: 30, fontStyle:'italic' }}>
                            <span className="muted" style={{ fontStyle:'normal' }}>{(window.appT||(s=>s))('Why:')} </span>{(window.appT||(s=>s))('Bot scored this user as no-progress but did not record a reason. Likely cause: today\'s plan repeated yesterday\'s verbatim, or the message was missing a "Today:" section.')}
                          </div>
                        )}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          </div>
        </div>
      )}

      <ViolationsSection />
      <TopTenWidget />
      <TeamsBreakdownRow employees={employees} />
      <LivePresenceRow employees={employees} showTestAccounts={showTestAccounts} />
      <AttendanceReportRow employees={employees} showTestAccounts={showTestAccounts} />

      {/* Two-column main grid */}
      <div className="grid-2-3">
        {/* Scoreboard + Roster */}
        <div className="card">
          <SectionHeader
            title={(window.appT||(s=>s))("Today's scoreboard")}
            sub={(window.appT||(s=>s))('Live — updates as people post in #attendance')}
            tip={(window.appT||(s=>s))('One row per active employee. Sign-in time / sign-off time / hours-worked are read live from the Matrix room. The dot to the left of each row is current Matrix presence (green=online, yellow=away/idle, grey=offline).')}
            right={
              <div style={{ display:'flex', gap:6 }}>
                <Segmented
                  value={rosterFilter} onChange={setRosterFilter}
                  options={[
                    { value:'all',     label:(window.appT||(s=>s))('All') },
                    { value:'present',    label:(window.appT||(s=>s))('Present') },
                    { value:'late',       label:(window.appT||(s=>s))('Late') },
                    { value:'signed-off', label:(window.appT||(s=>s))('Signed off') },
                    { value:'absent',     label:(window.appT||(s=>s))('Absent') },
                  ]}
                />
              </div>
            }
          />
          <table className="t" style={{ fontSize: 12 }}>
            <thead>
              <tr>
                <th style={{ width:'22%', padding:'6px 8px' }}>{(window.appT||(s=>s))('Person')}</th>
                <th style={{ padding:'6px 8px' }}>{(window.appT||(s=>s))('Status')}</th>
                <th style={{ padding:'6px 8px' }}>{(window.appT||(s=>s))('Sign-in')}</th>
                <th style={{ width:'28%', padding:'6px 8px' }}>{(window.appT||(s=>s))('Today / Progress')}</th>
                <th style={{ padding:'6px 8px' }}>{(window.appT||(s=>s))('Hours')}</th>
                <th style={{ padding:'6px 8px' }}>{(window.appT||(s=>s))('Strikes')}</th>
              </tr>
            </thead>
            <tbody>
              {filteredEmployees.slice(0, 15).map(e => {
                const status = computeTodayStatus(e);
                const empStrikes = strikes[e.id] ?? null;
                const p = progressMap[e.id];
                // P4: combined Today/Progress cell
                const progressDot = !e.signed_in
                  ? null
                  : !p || p.score == null
                    ? <span title={(window.appT||(s=>s))('Unscored — bot updates after sign-in')} style={{color:'var(--text-3)', fontSize:10, verticalAlign:'middle'}}>— {(window.appT||(s=>s))('unscored')}</span>
                    : p.score >= 1
                      ? <span title={p.reasoning || (window.appT||(s=>s))('Bot detected progress')} style={{color:'var(--ok)', fontSize:10, verticalAlign:'middle', fontWeight:600}}>{(window.appT||(s=>s))('Made progress')}</span>
                      : <span title={p.reasoning || (window.appT||(s=>s))('Bot detected no progress')} style={{color:'var(--err)', fontSize:10, verticalAlign:'middle', fontWeight:600}}>{(window.appT||(s=>s))('No progress')}</span>;
                const oneliner = p?.reasoning ? p.reasoning.split(/[.!?]/)[0].trim() : null;
                return (
                  <tr key={e.id} style={{ lineHeight: 1.3 }}>
                    <td style={{ padding:'5px 8px' }}>
                      <PersonCell person={e} status={status} sub={e.role} />
                    </td>
                    <td style={{ padding:'5px 8px' }}>
                      <StatusBadge status={status} />
                    </td>
                    <td className="mono" style={{ padding:'5px 8px', fontSize: 11.5 }}>
                      {_EditableCell ? (
                        <_EditableCell
                          kind="datetime"
                          value={_isoToLocalInput(e.sign_in_at_iso)}
                          readOnly={
                            !D?.ME ||
                            !_canEditUser(D.ME.org_role, D.ME.team, e.team) ||
                            !e.checkin_id
                          }
                          formatter={() => e.sign_in_at
                            ? e.sign_in_at
                            : '—'}
                          onSave={_useSignTimePatch(e, 'in')}
                        />
                      ) : (
                        e.sign_in_at || <span className="muted">—</span>
                      )}
                    </td>
                    <td style={{ padding:'5px 8px', fontSize: 11.5, maxWidth: 150 }}>
                      <div style={{ display:'flex', flexDirection:'column', gap: 2 }}>
                        <div style={{ color:'var(--text-2)', whiteSpace:'pre-line', wordBreak:'break-word' }}>
                          {e.signed_in ? (e.today || <span className="muted">—</span>) : <span className="muted">—</span>}
                        </div>
                        {progressDot && (
                          <div style={{ display:'flex', alignItems:'center', gap: 4 }}>
                            {progressDot}
                            {oneliner && (
                              <span style={{ color:'var(--text-3)', fontSize:10, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', maxWidth:100 }}
                                title={p?.reasoning}>
                                — {oneliner}
                              </span>
                            )}
                          </div>
                        )}
                      </div>
                    </td>
                    <td style={{ padding:'5px 8px' }}>
                      <HoursBar hours={e.hours_worked} />
                    </td>
                    <td style={{ padding:'5px 8px', textAlign:'center' }}>
                      {empStrikes === null
                        ? <span className="muted">—</span>
                        : empStrikes > 0
                          ? <Badge kind={empStrikes >= 3 ? 'err' : 'warn'}>{empStrikes}</Badge>
                          : <Badge kind="ok">0</Badge>}
                    </td>
                  </tr>
                );
              })}
              {filteredEmployees.length === 0 && (
                <tr><td colSpan={6}><Empty icon="·" title={(window.appLang&&window.appLang()==='ar')
                  ? `لا يوجد ${(window.appT||(s=>s))(rosterFilter === 'all' ? 'employees' : rosterFilter)} اليوم`
                  : `No ${rosterFilter === 'all' ? 'employees' : rosterFilter} today`} /></td></tr>
              )}
            </tbody>
          </table>
        </div>

        {/* Right column — Activity (bigger) + Sign-in histogram */}
        <div style={{ display:'flex', flexDirection:'column', gap: 16 }}>
          {/* Activity feed — organized w/ filters + sorting (item #5) */}
          <ActivityFeedCard activity={live.activity || []} />


          {/* Sign-in distribution */}
          <div className="card">
            <SectionHeader title={(window.appT||(s=>s))('Sign-in times')}
          tip={(window.appT||(s=>s))("Today's sign-in / sign-off / hours per user (editable by admins). If no one has signed in today yet, shows the most recent sign-ins from the last 7 days so the panel is never blank.")} sub={(window.appT||(s=>s))('Today — or recent if none yet')} />
            <table className="t" style={{ fontSize: 12, width: '100%' }}>
              <thead>
                <tr>
                  <th style={{ padding:'6px 8px' }}>{(window.appT||(s=>s))('Person')}</th>
                  <th style={{ padding:'6px 8px' }}>{(window.appT||(s=>s))('Sign-in')}</th>
                  <th style={{ padding:'6px 8px' }}>{(window.appT||(s=>s))('Sign-off')}</th>
                  <th style={{ padding:'6px 8px', textAlign:'right' }}>{(window.appT||(s=>s))('Hours')}</th>
                </tr>
              </thead>
              <tbody>
                {employees.filter(e => e.signed_in).sort((a,b) => (a.sign_in_at||'').localeCompare(b.sign_in_at||'')).map(e => (
                  <tr key={e.id}>
                    <td style={{ padding:'5px 8px' }}>
                      {e.display_name}
                      {e.on_time_streak >= 3 && (
                        <span
                          title={(window.appLang&&window.appLang()==='ar') ? `سلسلة التزام بالمواعيد ${e.on_time_streak} يوم` : `${e.on_time_streak}-day on-time streak`}
                          style={{
                            marginLeft: 6, fontSize: 11, fontWeight: 600,
                            color: e.on_time_streak >= 10 ? 'var(--ok, #16a34a)' : 'var(--warn, #d97706)',
                            whiteSpace: 'nowrap',
                          }}
                        >🔥 {e.on_time_streak}</span>
                      )}
                    </td>
                    <td className="mono" style={{ padding:'5px 8px' }}>
                      {_EditableCell ? (
                        <_EditableCell
                          kind="datetime"
                          value={_isoToLocalInput(e.sign_in_at_iso)}
                          readOnly={!D?.ME || !_canEditUser(D.ME.org_role, D.ME.team, e.team) || !e.checkin_id}
                          formatter={() => e.sign_in_at
                            ? e.sign_in_at
                            : '—'}
                          onSave={_useSignTimePatch(e, 'in')}
                        />
                      ) : (e.sign_in_at || '—')}
                    </td>
                    <td className="mono" style={{ padding:'5px 8px' }}>
                      {_EditableCell ? (
                        <_EditableCell
                          kind="datetime"
                          value={_isoToLocalInput(e.sign_off_at_iso)}
                          readOnly={!D?.ME || !_canEditUser(D.ME.org_role, D.ME.team, e.team) || !e.checkout_id}
                          formatter={() => e.sign_off_at
                            ? e.sign_off_at
                            : '—'}
                          onSave={_useSignTimePatch(e, 'out')}
                        />
                      ) : (e.sign_off_at || <span className="muted">—</span>)}
                    </td>
                    <td className="mono" style={{ padding:'5px 8px', textAlign:'right' }}>
                      {_EditableCell ? (
                        <_EditableCell
                          kind="text"
                          value={e.hours_worked != null ? e.hours_worked.toFixed(1) : ''}
                          readOnly={!D?.ME || !_canEditUser(D.ME.org_role, D.ME.team, e.team)}
                          formatter={() => e.hours_worked ? `${e.hours_worked.toFixed(1)}h` : '—'}
                          onSave={_useHoursPatch(e)}
                          placeholder="0.0"
                        />
                      ) : (e.hours_worked ? `${e.hours_worked.toFixed(1)}h` : '—')}
                    </td>
                  </tr>
                ))}
                {employees.filter(e => e.signed_in).length === 0 && <RecentSignInsRows />}
              </tbody>
            </table>
          </div>
        </div>
      </div>

      {/* Infrastructure summary — Bots, Rooms, Teams (item #2) */}
      <InfrastructureSummary />

      {/* My VPSie Servers widget */}
      <MyServersWidget />

      {/* Blockers strip — concise summary + tap to open the person's profile.
          Dead "DM" / "Resolved" buttons were removed — DM lives in the drawer
          footer, which is opened by tapping the card. */}
      {blockers.length > 0 && (
        <div className="card" style={{ marginTop: 16, borderColor:'color-mix(in oklch, var(--err) 32%, var(--line))' }}>
          <SectionHeader
            title={<><Icon name="alert" size={14} style={{color:'var(--err)'}}/> {(window.appT||(s=>s))('Active blockers')} ({blockers.length})</>}
            sub={(window.appT||(s=>s))('Tap a card to open that person and DM them')}
          />
          <div className="blocker-list">
            {blockers.slice(0, 4).map(e => (
              <div
                /* AttendanceRow exposes the field as `user_id` (not `id`), so
                   `e.id` was undefined — breaking both the React key collision
                   safety AND the dispatched event's user_id payload. Falling
                   back to `e.id` keeps backward-compat with any other shape. */
                key={e.user_id || e.id || e.matrix_id}
                className="blocker-card"
                role="button"
                tabIndex={0}
                onClick={() => {
                  // Bulletproof drawer-open: tab-switch + multi-shot focus
                  // dispatch so Employees catches it whether it mounts in
                  // 50 ms, 500 ms, or 2 s (cold-start, slow Capacitor).
                  // window.__focusUser persists for 30 s as the fallback;
                  // the Employees useEffect picks it up when it sees a
                  // non-empty employees array.
                  const detail = {
                    user_id: e.user_id || e.id,
                    matrix_id: e.matrix_id,
                    ts: Date.now(),
                  };
                  window.__focusUser = detail;
                  // Confirmation toast — proves the click reached us even
                  // if the navigation hiccups later.
                  try { toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `جارٍ فتح ${e.display_name || e.matrix_id || 'الملف الشخصي'}…` : `Opening ${e.display_name || e.matrix_id || 'profile'}…`, kind: 'info' }); } catch(_) {}
                  // Tab switch — prefer the global shim, fall back to the
                  // existing request-open-user event so we have two paths.
                  if (typeof window.__navigate === 'function') {
                    window.__navigate('employees');
                  } else {
                    try { window.dispatchEvent(new CustomEvent('request-open-user', { detail })); } catch(_) {}
                  }
                  // Fire focus-user-now at multiple intervals to outrun
                  // Employees mount + data fetch.
                  [60, 250, 700, 1500].forEach(delay => {
                    setTimeout(() => {
                      try { window.dispatchEvent(new CustomEvent('focus-user-now', { detail })); } catch(_) {}
                    }, delay);
                  });
                }}
                onKeyDown={(ev) => { if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); ev.currentTarget.click(); } }}
                style={{ cursor: 'pointer' }}
                title={e.blocker_text || ''}
              >
                <PersonCell person={e} status="late" sub={e.matrix_id} />
                <div
                  className="blocker-text"
                  style={{ display:'-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient:'vertical', overflow:'hidden', fontSize: 13, lineHeight: 1.4, color: 'var(--text-2)', marginTop: 6 }}>
                  {summarizeBlocker(e.blocker_text)}
                </div>
                <div style={{ display:'flex', justifyContent:'flex-end', marginTop: 8, fontSize: 12, color:'#60a5fa' }}>
                  {(window.appT||(s=>s))('Open profile')} →
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

// ── KPI strip — P5: 3 hero cards + Late/Blockers as collapsible chips ──
function KpiStrip({ signedInToday, total, late, absent, kpis, blockers, hist14, D,
                   employees, madeProgressUsers, noProgressUsers, unscoredSignedIn, progressMap }) {
  const [openKey, setOpenKey] = useState(null);

  // "Signed in" dropdown = currently in (signed in, not yet signed off) so it
  // matches the headline count and excludes people who already signed off.
  const signedInUsers = employees.filter(e => e.signed_in && !e.signed_off);
  // For the progress denominator we want everyone who signed in today
  // (incl. those now signed off — they still made/didn't-make progress today).
  const signedInTodayUsers = employees.filter(e => e.signed_in);
  const lateUsers = employees.filter(e => e.signed_in && e.was_late);
  // "Signed out" = signed in earlier today AND already signed off — done for
  // the day. Separate from "currently in" so admins see who's wrapped up.
  const signedOutUsers = employees.filter(e => e.signed_off);
  // Absent split — IMPORTANT: anyone who has signed OFF is "done for the
  // day" and belongs in Signed out, NOT absent. (A user can have
  // signed_off=True with signed_in=False when they posted a sign-off
  // without a recorded sign-in — they still worked, so they're not
  // absent.) So both absent buckets require !signed_in AND !signed_off.
  //   - CONFIRMED absent = no sign-in, no sign-off, AND the bot already
  //     DM'd them past their adaptive sign-in time (absence_confirmed).
  //   - not-in-yet = no sign-in, no sign-off, bot hasn't nudged yet.
  const confirmedAbsentUsers = employees.filter(e => !e.signed_in && !e.signed_off && e.absence_confirmed);
  const notInYetUsers       = employees.filter(e => !e.signed_in && !e.signed_off && !e.absence_confirmed);
  const absentUsers = confirmedAbsentUsers;  // headline "Absent" = confirmed only
  const blockerUsers = blockers || [];
  const signedOutCount = signedOutUsers.length;
  const confirmedAbsentCount = confirmedAbsentUsers.length;

  const toggle = (k) => setOpenKey(prev => prev === k ? null : k);

  // Late and Blockers as compact clickable chips (below hero row)
  const ChipBtn = ({ label, value, chipKey, accent, icon }) => (
    <button
      onClick={() => toggle(chipKey)}
      style={{
        display:'inline-flex', alignItems:'center', gap:5,
        padding:'5px 12px', borderRadius:99,
        border: openKey === chipKey ? `1.5px solid ${accent}` : '1.5px solid var(--line)',
        background: openKey === chipKey ? `color-mix(in oklch, ${accent} 12%, var(--bg-elev-2))` : 'var(--bg-elev-2)',
        color: openKey === chipKey ? accent : 'var(--text-2)',
        fontSize: 12, fontWeight: 500, cursor:'pointer', transition:'all 120ms',
      }}
      title={(window.appLang&&window.appLang()==='ar') ? `انقر لعرض قائمة ${label}` : `Click to see ${label.toLowerCase()} list`}>
      {icon} {label}: <span style={{fontWeight:700, color: value > 0 ? accent : 'inherit'}}>{value}</span>
    </button>
  );

  return (
    <div style={{ marginBottom: 16, position: 'relative' }}>
      {/* P5: hero KPI cards — Signed in · Signed out · Absent · Made progress */}
      <div className="fade-in-on-scroll" style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(180px, 1fr))', gap: 12, marginBottom: 10 }}>
        <ClickableKpi label={(window.appT||(s=>s))('Signed in')} value={signedInToday} denom={total} accent="var(--ok)"
          trend={{ dir: signedInToday > 0 ? 'up' : 'flat', value: `${signedInToday}/${total}`, label: (window.appT||(s=>s))('today') }}
          sparkData={hist14.length ? hist14 : undefined} color="var(--ok)"
          icon={<Icon name="check" size={14}/>}
          active={openKey === 'signedIn'}
          onClick={() => toggle('signedIn')} />
        <ClickableKpi label={(window.appT||(s=>s))('Signed out')} value={signedOutCount} denom={total} accent="var(--text-3)"
          trend={{ dir: 'flat', value: `${signedOutCount}/${total}`, label: (window.appT||(s=>s))('done today') }}
          color="var(--text-3)"
          icon={<Icon name="check" size={14}/>}
          active={openKey === 'signedOut'}
          onClick={() => toggle('signedOut')} />
        <ClickableKpi label={(window.appT||(s=>s))('Absent')} value={confirmedAbsentCount} denom={total} accent="var(--err)"
          trend={{ dir: confirmedAbsentCount > 0 ? 'up' : 'flat',
                   value: notInYetUsers.length > 0 ? ((window.appLang&&window.appLang()==='ar') ? `+${notInYetUsers.length} لم يحضروا بعد` : `+${notInYetUsers.length} not in yet`) : (window.appT||(s=>s))('confirmed'),
                   label: notInYetUsers.length > 0 ? '' : (window.appT||(s=>s))('no-show') }}
          sparkData={D.SERIES_14.length ? D.SERIES_14.map(s => Math.max(0, (s.total || total) - (s.present || 0))) : undefined}
          color="var(--err)"
          icon={<Icon name="alert" size={14}/>}
          active={openKey === 'absent'}
          onClick={() => toggle('absent')} />
        <ClickableKpi label={(window.appT||(s=>s))('Made progress')} value={madeProgressUsers.length} denom={signedInTodayUsers.length || total} accent="var(--ok)"
          trend={{ dir: madeProgressUsers.length > 0 ? 'up' : 'flat',
                   value: noProgressUsers.length > 0 ? ((window.appLang&&window.appLang()==='ar') ? `${noProgressUsers.length} لا` : `${noProgressUsers.length} no`) : ((window.appLang&&window.appLang()==='ar') ? `${unscoredSignedIn.length} قيد الانتظار` : `${unscoredSignedIn.length} pending`),
                   label: (window.appT||(s=>s))('today') }}
          color="var(--ok)"
          icon={<Icon name="check" size={14}/>}
          active={openKey === 'progress'}
          onClick={() => toggle('progress')} />
      </div>

      {/* P5: Late + Blockers as compact chips */}
      <div style={{ display:'flex', gap:8, alignItems:'center', flexWrap:'wrap', marginBottom: openKey ? 6 : 0 }}>
        <ChipBtn label={(window.appT||(s=>s))('Late')} value={late} chipKey="late" accent="var(--warn)"
          icon={<Icon name="clock" size={11}/>} />
        <ChipBtn label={(window.appT||(s=>s))('Blockers')} value={kpis.active_blockers ?? blockers.length} chipKey="blockers" accent="var(--brand)"
          icon={<Icon name="pulse" size={11}/>} />
      </div>

      {openKey === 'signedIn' && <KpiDropdown kind="signedIn" users={signedInUsers} onClose={() => setOpenKey(null)} />}
      {openKey === 'signedOut' && <KpiDropdown kind="signedOut" users={signedOutUsers} onClose={() => setOpenKey(null)} />}
      {openKey === 'late' && <KpiDropdown kind="late" users={lateUsers} onClose={() => setOpenKey(null)} />}
      {openKey === 'absent' && <KpiDropdown kind="absent"
        users={[...confirmedAbsentUsers.map(u => ({...u, _bucket:'confirmed'})),
                ...notInYetUsers.map(u => ({...u, _bucket:'notyet'}))]}
        onClose={() => setOpenKey(null)} />}
      {openKey === 'blockers' && <KpiDropdown kind="blockers" users={blockerUsers} onClose={() => setOpenKey(null)} />}
      {openKey === 'progress' && <KpiDropdown kind="progress"
        users={[...madeProgressUsers.map(u => ({...u, _bucket:'made'})),
                ...noProgressUsers.map(u => ({...u, _bucket:'no'}))]}
        progressMap={progressMap}
        onClose={() => setOpenKey(null)} />}
    </div>
  );
}

function ClickableKpi(props) {
  const { active, onClick, ...rest } = props;
  return (
    <div onClick={onClick}
      style={{ cursor: 'pointer', borderRadius: 'var(--r-md)',
               outline: active ? '2px solid var(--brand)' : 'none',
               outlineOffset: -2, transition: 'outline-color 120ms' }}
      title={(window.appT||(s=>s))("Click to see who's in this category")}>
      <KPI {...rest} />
    </div>
  );
}

// Dropdown panel listing users in a KPI category, with quick-action buttons.
function KpiDropdown({ kind, users, onClose, progressMap }) {
  const toast = useToast();
  const [busyUid, setBusyUid] = useState(null);

  const _t = (window.appT||(s=>s));
  const TITLES = {
    signedIn: _t('Signed in'), signedOut: _t('Signed out (done today)'),
    late: _t('Late today'), absent: _t('Absent today'),
    blockers: _t('Blockers reported'), progress: _t("Today's progress"),
  };
  const EMPTY = {
    signedIn: _t('Nobody has signed in yet.'),
    signedOut: _t('Nobody has signed off yet.'),
    late: _t('Nobody is late today. 👍'),
    absent: _t('No confirmed absences. 🎉'),
    blockers: _t('No blockers reported.'),
    progress: _t('No progress flags yet today.'),
  };

  // Pre-canned DM templates per category. Admin can edit before sending.
  const TEMPLATES = {
    signedIn:
      _t("Hey! Saw you signed in — hope today's going well. Anything you need from me?"),
    late:
      _t("Hey, noticed you signed in late today. Everything OK? If something came up (sick / appointment / family), let me know — I can mark it as PTO so it doesn't count against you."),
    absent:
      _t("Hey, didn't see a sign-in from you today. Just checking in — is everything alright? If you're out for the day, please reply with 'PTO' or 'sick' so I can log it."),
    blockers:
      _t("Hey, saw your standup mentioned a blocker. Want me to help unblock you? Reply with what's stuck and I'll pull in whoever can help."),
    progress_made:
      _t("Just wanted to say great work today! Saw you made solid progress — keep up the good work 🚀"),
    progress_no:
      _t("Hey, the bot flagged your standup as 'no progress' today (today's plan repeated yesterday's). Anything blocking you that I can help with?"),
  };

  const dmUser = async (matrixId, kindKey, uid) => {
    const tmpl = kindKey === 'progress'
      ? (progressMap?.[uid]?.score >= 1 ? TEMPLATES.progress_made : TEMPLATES.progress_no)
      : TEMPLATES[kindKey];
    const msg = prompt((window.appLang&&window.appLang()==='ar') ? `رسالة خاصة إلى ${matrixId} — عدّلها إن أردت، ثم موافق للإرسال:` : `DM to ${matrixId} — edit if you want, then OK to send:`, tmpl);
    if (!msg || !msg.trim()) return;
    setBusyUid(uid);
    const ctrl = new AbortController();
    const tid = setTimeout(() => ctrl.abort(), 30000);
    try {
      // Backend body shape is {user_id, message} — not {matrix_id, body}.
      const r = await fetch('/api/dm/send', {
        method: 'POST', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        signal: ctrl.signal,
        body: JSON.stringify({ user_id: uid, message: msg.trim() }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        const d = body?.detail;
        let errMsg;
        if (typeof d === 'string') errMsg = d;
        else if (Array.isArray(d)) errMsg = d.map(e => {
          const loc = Array.isArray(e?.loc) ? e.loc.filter(p => p !== 'body').join('.') : '';
          return loc ? `${loc}: ${e.msg}` : e.msg;
        }).filter(Boolean).join('; ');
        else errMsg = `HTTP ${r.status}`;
        throw new Error(errMsg);
      }
      toast.push({ kind: 'ok', msg: (window.appLang&&window.appLang()==='ar') ? `تم إرسال الرسالة إلى ${matrixId}` : `DM sent to ${matrixId}` });
    } catch(e) {
      const msg = e.name === 'AbortError'
        ? (window.appT||(s=>s))('Send timed out — check audit log for delivery status.')
        : ((window.appLang&&window.appLang()==='ar') ? ('فشل إرسال الرسالة: ' + (e.message || e)) : ('DM failed: ' + (e.message || e)));
      toast.push({ kind: 'err', msg });
    } finally {
      clearTimeout(tid);
      setBusyUid(null);
    }
  };

  const markViolation = async (uid, displayName) => {
    const reason = prompt((window.appLang&&window.appLang()==='ar') ? `سبب تسجيل مخالفة على ${displayName}:` : `Reason for marking ${displayName} as violation:`, (window.appT||(s=>s))('Late >3 times this week'));
    if (!reason || !reason.trim()) return;
    setBusyUid(uid);
    try {
      const r = await fetch(`/api/employees/${uid}/violation`, {
        method: 'POST', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ reason: reason.trim(), notify_user: true }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(body?.detail || `${r.status}`);
      }
      const data = await r.json();
      toast.push({ kind: 'ok', msg: (window.appLang&&window.appLang()==='ar') ? `تم تسجيل المخالفة ${data.new_count}/3 على ${displayName}` : `Strike ${data.new_count}/3 recorded for ${displayName}` });
    } catch(e) {
      toast.push({ kind: 'err', msg: (window.appLang&&window.appLang()==='ar') ? ('فشلت المخالفة: ' + (e.message || e)) : ('Violation failed: ' + (e.message || e)) });
    } finally { setBusyUid(null); }
  };

  return (
    <div className="card" style={{ marginTop: 8, position: 'relative', maxHeight: 360, overflowY: 'auto' }}>
      <SectionHeader
        title={TITLES[kind]}
        sub={(window.appLang&&window.appLang()==='ar')
          ? `${users.length} مستخدم — انقر على "رسالة" للمراسلة، عدّل القالب قبل الإرسال`
          : `${users.length} user${users.length === 1 ? '' : 's'} — click DM to message, edit template before send`}
        right={<button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={12}/></button>}
      />
      {users.length === 0 ? (
        <div className="muted" style={{ fontSize: 12, padding: 8 }}>{EMPTY[kind]}</div>
      ) : (
        <div style={{ display:'flex', flexDirection:'column', gap: 6 }}>
          {users.map(u => {
            const isBusy = busyUid === u.id;
            const isMade = kind === 'progress' && u._bucket === 'made';
            const isNo = kind === 'progress' && u._bucket === 'no';
            return (
              <div key={u.id + (u._bucket || '')}
                style={{ display:'flex', alignItems:'center', gap: 8, padding:'6px 4px',
                         borderBottom: '1px solid var(--line)', fontSize: 12 }}>
                <PersonCell person={u} sub={u.team || u.role || ''} />
                <div style={{ flex: 1 }} />
                {kind === 'progress' && (
                  <Badge kind={isMade ? 'ok' : 'err'}>{isMade ? (window.appT||(s=>s))('Yes') : (window.appT||(s=>s))('No')}</Badge>
                )}
                {kind === 'late' && (
                  <button className="btn btn-ghost" disabled={isBusy}
                    style={{ fontSize: 11, padding:'3px 8px' }}
                    onClick={() => markViolation(u.id, u.display_name)}>
                    <Icon name="alert" size={11}/> {(window.appT||(s=>s))('Strike')}
                  </button>
                )}
                <button className="btn btn-pri" disabled={isBusy}
                  style={{ fontSize: 11, padding:'3px 8px' }}
                  onClick={() => dmUser(u.matrix_id, kind, u.id)}>
                  <Icon name="msg" size={11}/> {isBusy ? '…' : (window.appT||(s=>s))('DM')}
                </button>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ── (item #5 ActivityFeedCard inserted below) ──
function ActivityFeedCard({ activity }) {
  const [typeFilter, setTypeFilter] = useState('all');
  const [sortBy, setSortBy] = useState('newest');
  const [groupByUser, setGroupByUser] = useState(false);

  const allTypes = useMemo(() => {
    const s = new Set();
    (activity || []).forEach(a => s.add(a.type || 'other'));
    return Array.from(s).sort();
  }, [activity]);

  const filtered = (activity || []).filter(a =>
    typeFilter === 'all' || (a.type || 'other') === typeFilter
  );
  const sorted = filtered.slice().sort((a, b) => {
    if (sortBy === 'oldest') return (a.at || '').localeCompare(b.at || '');
    if (sortBy === 'who')    return (a.who || '').localeCompare(b.who || '');
    return (b.at || '').localeCompare(a.at || ''); // newest
  });

  // Group by user when requested
  const grouped = useMemo(() => {
    if (!groupByUser) return null;
    const map = {};
    sorted.forEach(a => {
      const key = a.who || '?';
      (map[key] = map[key] || []).push(a);
    });
    return Object.entries(map).sort((a, b) => b[1].length - a[1].length);
  }, [sorted, groupByUser]);

  // Type counts for the filter chips
  const counts = useMemo(() => {
    const c = { all: (activity || []).length };
    (activity || []).forEach(a => {
      const t = a.type || 'other';
      c[t] = (c[t] || 0) + 1;
    });
    return c;
  }, [activity]);

  return (
    <div className="card" style={{ flex: 1 }}>
      <SectionHeader
        title={(window.appT||(s=>s))('Activity')}
        sub={(window.appLang&&window.appLang()==='ar') ? `${(activity || []).length} حدث · مباشر` : `${(activity || []).length} event${(activity || []).length===1?'':'s'} · live`}
        tip={(window.appT||(s=>s))("Live audit feed. Each row = one bot or admin action (sign-in, sign-off, strike, PTO submission, DM sent, on-call assignment, etc.). Toggle 'Bot only' or filter by kind in the dropdown.")}
        right={
          <div style={{ display:'flex', gap: 6, alignItems:'center', flexWrap:'wrap' }}>
            <select className="sel" value={sortBy} onChange={e => setSortBy(e.target.value)}
              style={{ fontSize: 11, padding:'2px 6px', height: 26 }}>
              <option value="newest">{(window.appT||(s=>s))('Newest first')}</option>
              <option value="oldest">{(window.appT||(s=>s))('Oldest first')}</option>
              <option value="who">{(window.appT||(s=>s))('By person')}</option>
            </select>
            <button className="btn btn-ghost" style={{ fontSize: 11, padding:'2px 8px', height: 26 }}
              onClick={() => setGroupByUser(g => !g)}
              title={(window.appT||(s=>s))('Toggle grouping by user')}>
              {groupByUser ? (window.appT||(s=>s))('Flat') : (window.appT||(s=>s))('Group')}
            </button>
          </div>
        }
      />
      {/* Type filter chips */}
      <div style={{ display:'flex', flexWrap:'wrap', gap: 4, marginBottom: 8 }}>
        {[{value:'all', label:'All'}, ...allTypes.map(t => ({value:t, label:t}))].map(t => (
          <button key={t.value}
            onClick={() => setTypeFilter(t.value)}
            className={typeFilter === t.value ? 'btn btn-pri' : 'btn btn-ghost'}
            style={{ fontSize: 10.5, padding:'2px 8px', height: 22, textTransform:'capitalize' }}>
            {(window.appT||(s=>s))(t.label)} <span className="mono" style={{ marginLeft: 4, opacity: 0.7 }}>{counts[t.value] || 0}</span>
          </button>
        ))}
      </div>
      <div className="feed" style={{ maxHeight: 380, overflowY:'auto' }}>
        {grouped ? (
          grouped.length === 0 ? (
            <Empty icon="·" title={(window.appT||(s=>s))('No activity matches')} subtitle={(window.appT||(s=>s))('Adjust the type filter above.')} />
          ) : grouped.map(([who, items]) => (
            <details key={who} open style={{ borderBottom: '1px solid var(--line)', paddingBottom: 4, marginBottom: 4 }}>
              <summary style={{ cursor:'pointer', padding:'5px 4px', fontSize: 12, display:'flex', justifyContent:'space-between' }}>
                <strong>{who}</strong>
                <span className="muted mono" style={{ fontSize: 10.5 }}>{(window.appLang&&window.appLang()==='ar') ? `${items.length} حدث` : `${items.length} event${items.length===1?'':'s'}`}</span>
              </summary>
              {items.map((a, i) => (
                <div key={i} className="feed-row">
                  <span className={`feed-dot feed-${a.type}`} />
                  <div className="feed-line">
                    <span className="muted">{a.meta}</span>
                  </div>
                  <span className="mono feed-time">{a.at}</span>
                </div>
              ))}
            </details>
          ))
        ) : sorted.length === 0 ? (
          <Empty icon="·" title={(window.appT||(s=>s))('No activity yet')} subtitle={(window.appT||(s=>s))('Events appear here as they happen.')} />
        ) : (
          sorted.slice(0, 30).map((a, i) => (
            <div key={i} className="feed-row">
              <span className={`feed-dot feed-${a.type}`} />
              <div className="feed-line">
                <strong>{a.who}</strong> <span className="muted">— {a.meta}</span>
              </div>
              <span className="mono feed-time">{a.at}</span>
            </div>
          ))
        )}
      </div>
    </div>
  );
}


// ── My VPSie Servers widget ──
// Fallback rows for the Sign-in times panel when no one is signed in today:
// shows the most recent real sign-ins (last 7 days) so it's never blank.
function RecentSignInsRows() {
  const [rows, setRows] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    fetch('/api/today/recent-signins?days=7&limit=30', { credentials: 'include' })
      .then(r => r.ok ? r.json() : { rows: [] })
      .then(d => { if (alive) setRows(d.rows || []); })
      .catch(() => { if (alive) setRows([]); });
    return () => { alive = false; };
  }, []);
  if (rows === null) {
    return <tr><td colSpan={4} className="muted" style={{ padding: 10, fontSize: 12 }}>{(window.appT||(s=>s))('Loading recent sign-ins…')}</td></tr>;
  }
  if (rows.length === 0) {
    return <tr><td colSpan={4}><Empty icon="·" title={(window.appT||(s=>s))('No sign-ins recorded in the last 7 days')} /></td></tr>;
  }
  const fmtT = (iso) => { try { return iso ? new Date(iso).toLocaleString([], { month:'short', day:'numeric', hour:'2-digit', minute:'2-digit' }) : '—'; } catch { return '—'; } };
  return (
    <>
      {rows.map((r, i) => (
        <tr key={i}>
          <td style={{ padding:'5px 8px' }}>
            {r.display_name}
            {r.was_late && <span className="badge warn" style={{ marginLeft:6, fontSize:9 }}>{(window.appT||(s=>s))('late')}</span>}
          </td>
          <td className="mono" style={{ padding:'5px 8px' }}>{fmtT(r.sign_in_at)}</td>
          <td className="mono" style={{ padding:'5px 8px' }}>{r.sign_off_at ? fmtT(r.sign_off_at) : <span className="muted">—</span>}</td>
          <td className="mono" style={{ padding:'5px 8px', textAlign:'right' }}>{r.hours_worked ? `${r.hours_worked.toFixed(1)}h` : '—'}</td>
        </tr>
      ))}
    </>
  );
}

function MyServersWidget() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  const load = () => {
    setLoading(true);
    fetch('/api/me/vpsie/servers', { credentials: 'include' })
      .then(r => r.ok ? r.json() : { connected: false, servers: [] })
      .then(d => { setData(d); setLoading(false); })
      .catch(() => { setData({ connected: false, servers: [], error: 'Unreachable' }); setLoading(false); });
  };

  useEffect(() => {
    load();
    const id = setInterval(load, 5 * 60 * 1000); // refresh every 5 min
    return () => clearInterval(id);
  }, []);

  const cardStyle = { background:'var(--bg-elev-2)', border:'1px solid var(--line)', borderRadius:'var(--r-md)', padding: 14, display:'flex', flexDirection:'column', gap: 8 };
  const statusColor = (s) => s === 'on' ? 'var(--ok)' : s === 'off' ? 'var(--text-3)' : 'var(--warn)';

  if (loading) {
    return (
      <div className="card" style={{ marginTop: 8 }}>
        <SectionHeader title={(window.appT||(s=>s))('My Servers')}
          tip={(window.appT||(s=>s))("VPSie virtual machines associated with this user's account. Auto-refreshes every 5 min. 'View all' opens vpsie.com.")} sub={(window.appT||(s=>s))('VPSie virtual machines')} />
        <div className="muted" style={{ fontSize: 12, padding: 8 }}>{(window.appT||(s=>s))('Loading…')}</div>
      </div>
    );
  }

  if (!data || !data.connected) {
    return (
      <div className="card" style={{ marginTop: 8 }}>
        <SectionHeader title={(window.appT||(s=>s))('My Servers')}
          tip={(window.appT||(s=>s))("VPSie virtual machines associated with this user's account. Auto-refreshes every 5 min. 'View all' opens vpsie.com.")} sub={(window.appT||(s=>s))('VPSie virtual machines')} />
        <div style={{ fontSize: 12.5, color: 'var(--text-3)', padding: '4px 0 8px' }}>
          {(window.appT||(s=>s))('No VPSie account connected.')}{' '}
          <span style={{ color: 'var(--text-2)' }}>{(window.appT||(s=>s))('DM the bot:')} </span>
          <code style={{ fontFamily: 'var(--font-mono)', background: 'var(--bg-elev-2)', padding: '1px 4px', borderRadius: 3, fontSize: 11 }}>connect vpsie</code>
        </div>
      </div>
    );
  }

  const servers = data.servers || [];
  const visible = servers;          // show ALL connected VPSie servers (item #12)
  const remaining = 0;

  return (
    <div className="card" style={{ marginTop: 8 }}>
      <SectionHeader
        title={(window.appT||(s=>s))('My Servers')}
          tip={(window.appT||(s=>s))("VPSie virtual machines associated with this user's account. Auto-refreshes every 5 min. 'View all' opens vpsie.com.")}
        sub={(window.appLang&&window.appLang()==='ar') ? `${servers.length} جهاز افتراضي VPSie` : `${servers.length} VPSie VM${servers.length === 1 ? '' : 's'}`}
        action={<button onClick={load} className="btn btn-ghost" style={{ fontSize: 11, padding: '3px 8px' }}>{(window.appT||(s=>s))('Refresh')}</button>}
      />
      {data.error && (
        <div style={{ fontSize: 12, color: 'var(--warn)', marginBottom: 8 }}>{(window.appT||(s=>s))(data.error)}</div>
      )}
      {servers.length === 0 && !data.error && (
        <div className="muted" style={{ fontSize: 12 }}>{(window.appT||(s=>s))('No VMs on your account yet.')}</div>
      )}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {visible.map(s => (
          <div key={s.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', background: 'var(--bg-1)', border: '1px solid var(--line)', borderRadius: 6 }}>
            <span style={{ width: 8, height: 8, borderRadius: '50%', background: statusColor(s.status), flexShrink: 0, display: 'inline-block' }} title={s.status} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 12.5, fontWeight: 600, color: 'var(--text-1)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.hostname || s.id}</div>
              {(s.ip || s.region) && (
                <div style={{ fontSize: 11, color: 'var(--text-3)' }}>
                  {s.ip}{s.ip && s.region ? ' · ' : ''}{s.region}
                </div>
              )}
            </div>
            <span style={{ fontSize: 10.5, fontWeight: 600, color: statusColor(s.status), textTransform: 'uppercase', letterSpacing: 0.3 }}>{s.status || '—'}</span>
          </div>
        ))}
      </div>
      <div style={{ fontSize: 11.5, marginTop: 4 }}>
        <a
          href="https://my.vpsie.com"
          target="_blank"
          rel="noopener noreferrer"
          style={{ color: 'var(--brand)', textDecoration: 'none', fontWeight: 500 }}
        >
          {remaining > 0
            ? ((window.appLang&&window.appLang()==='ar') ? `عرض الكل (${servers.length}) في لوحة تحكم VPSie ↗` : `View all (${servers.length}) in your VPSie dashboard ↗`)
            : (window.appT||(s=>s))('Open VPSie dashboard ↗')}
        </a>
      </div>
    </div>
  );
}

// ── Infrastructure summary: bots / rooms / teams (item #2) ──
function InfrastructureSummary() {
  const [bots, setBots]     = useState(null);
  const [rooms, setRooms]   = useState(null);
  const [teams, setTeams]   = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;
    const load = async () => {
      try {
        const [br, rr, tr] = await Promise.all([
          fetch('/api/bots',  { credentials:'same-origin' }).then(r => r.ok ? r.json() : []).catch(() => []),
          fetch('/api/rooms', { credentials:'same-origin' }).then(r => r.ok ? r.json() : []).catch(() => []),
          fetch('/api/teams', { credentials:'same-origin' }).then(r => r.ok ? r.json() : []).catch(() => []),
        ]);
        if (cancelled) return;
        setBots(Array.isArray(br) ? br : []);
        setRooms(Array.isArray(rr) ? rr : []);
        setTeams(Array.isArray(tr) ? tr : []);
      } finally { if (!cancelled) setLoading(false); }
    };
    load();
    const id = setInterval(load, 60000);
    return () => { cancelled = true; clearInterval(id); };
  }, []);

  if (loading || !bots || !rooms || !teams) {
    return (
      <div className="card" style={{ marginTop: 8 }}>
        <SectionHeader title={(window.appT||(s=>s))('Infrastructure')}
          tip={(window.appT||(s=>s))('Live count of active bots, monitored rooms, configured teams. Click a card to jump to that admin tab.')} sub={(window.appT||(s=>s))('Bots · Rooms · Teams')} />
        <div className="muted" style={{ fontSize: 12, padding: 8 }}>{(window.appT||(s=>s))('Loading…')}</div>
      </div>
    );
  }

  // Bots: aggregate by persona, count active
  const activeBots = bots.filter(b => b.active);
  const botPersonas = {};
  activeBots.forEach(b => {
    const p = b.persona || 'custom';
    botPersonas[p] = (botPersonas[p] || 0) + 1;
  });
  const personaList = Object.entries(botPersonas).sort((a,b) => b[1] - a[1]);

  // Rooms: aggregate by type, count active + bot-assigned
  const activeRooms = rooms.filter(r => r.active);
  const roomTypes = {};
  activeRooms.forEach(r => {
    const t = r.room_type || 'general';
    roomTypes[t] = (roomTypes[t] || 0) + 1;
  });
  const typeList = Object.entries(roomTypes).sort((a,b) => b[1] - a[1]);
  const roomsWithBots = activeRooms.filter(r => r.bot_id).length;

  // Teams: count + members
  const teamList = teams.slice().sort((a,b) => (a.team_name||'').localeCompare(b.team_name||''));
  const totalMembers = teams.reduce((s, t) => s + (t.members ?? t.member_count ?? 0), 0);

  const navTo = (tab) => () => {
    activeTab && (activeTab.value = tab);
    window.dispatchEvent(new CustomEvent('status-nav', { detail: { tab } }));
  };

  const cardStyle = { background:'var(--bg-elev-2)', border:'1px solid var(--line)', borderRadius:'var(--r-md)', padding: 14, display:'flex', flexDirection:'column', gap: 10 };
  const headStyle = { display:'flex', justifyContent:'space-between', alignItems:'baseline' };
  const numStyle  = { fontSize: 24, fontWeight: 700, fontFamily:'var(--font-mono)', color:'var(--text)' };
  const labelStyle = { fontSize: 11, color:'var(--text-3)', textTransform:'uppercase', letterSpacing: 0.5 };

  return (
    <div className="card" style={{ marginTop: 8 }}>
      <SectionHeader
        title={(window.appT||(s=>s))('Infrastructure')}
          tip={(window.appT||(s=>s))('Live count of active bots, monitored rooms, configured teams. Click a card to jump to that admin tab.')}
        sub={(window.appLang&&window.appLang()==='ar')
          ? `${activeBots.length} بوت · ${activeRooms.length} غرفة · ${teams.length} فريق`
          : `${activeBots.length} bot${activeBots.length===1?'':'s'} · ${activeRooms.length} room${activeRooms.length===1?'':'s'} · ${teams.length} team${teams.length===1?'':'s'}`}
      />
      <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(280px, 1fr))', gap: 12 }}>

        {/* Bots */}
        <div style={cardStyle} title={(window.appT||(s=>s))('Click to open Bots tab')} onClick={() => window.dispatchEvent(new CustomEvent('status-nav', { detail: { tab: 'bots' } }))}>
          <div style={headStyle}>
            <div>
              <div style={labelStyle}>{(window.appT||(s=>s))('Bots')}</div>
              <div style={numStyle}>{activeBots.length}<span style={{ fontSize: 12, color:'var(--text-3)', marginLeft: 4 }}>/ {bots.length}</span></div>
            </div>
            <Icon name="ai" size={20} style={{ color: 'var(--brand)' }} />
          </div>
          <div style={{ display:'flex', flexDirection:'column', gap: 4, fontSize: 12 }}>
            {personaList.length === 0 ? (
              <div className="muted" style={{ fontSize: 11.5 }}>{(window.appT||(s=>s))('No active bots')}</div>
            ) : personaList.slice(0, 5).map(([p, n]) => (
              <div key={p} style={{ display:'flex', justifyContent:'space-between' }}>
                <span style={{ textTransform:'capitalize', color:'var(--text-2)' }}>{p.replace(/_/g, ' ')}</span>
                <span className="mono" style={{ color:'var(--text-2)' }}>{n}</span>
              </div>
            ))}
            {personaList.length > 5 && (
              <div className="muted" style={{ fontSize: 11 }}>{(window.appLang&&window.appLang()==='ar') ? `+ ${personaList.length - 5} شخصية إضافية` : `+ ${personaList.length - 5} more persona(s)`}</div>
            )}
          </div>
        </div>

        {/* Rooms */}
        <div style={cardStyle} title={(window.appT||(s=>s))('Click to open Rooms tab')} onClick={() => window.dispatchEvent(new CustomEvent('status-nav', { detail: { tab: 'rooms' } }))}>
          <div style={headStyle}>
            <div>
              <div style={labelStyle}>{(window.appT||(s=>s))('Rooms')}</div>
              <div style={numStyle}>{activeRooms.length}<span style={{ fontSize: 12, color:'var(--text-3)', marginLeft: 4 }}>/ {rooms.length}</span></div>
            </div>
            <Icon name="msg" size={20} style={{ color: 'var(--ok)' }} />
          </div>
          <div style={{ display:'flex', flexDirection:'column', gap: 4, fontSize: 12 }}>
            {typeList.length === 0 ? (
              <div className="muted" style={{ fontSize: 11.5 }}>{(window.appT||(s=>s))('No active rooms')}</div>
            ) : typeList.slice(0, 5).map(([t, n]) => (
              <div key={t} style={{ display:'flex', justifyContent:'space-between' }}>
                <span style={{ textTransform:'capitalize', color:'var(--text-2)' }}>{t}</span>
                <span className="mono" style={{ color:'var(--text-2)' }}>{n}</span>
              </div>
            ))}
            <div style={{ borderTop:'1px solid var(--line)', paddingTop: 5, marginTop: 2, display:'flex', justifyContent:'space-between', fontSize: 11.5 }}>
              <span style={{ color:'var(--text-3)' }}>{(window.appT||(s=>s))('With bot')}</span>
              <span className="mono" style={{ color: roomsWithBots === activeRooms.length ? 'var(--ok)' : 'var(--warn)' }}>{roomsWithBots}/{activeRooms.length}</span>
            </div>
          </div>
        </div>

        {/* Teams */}
        <div style={cardStyle} title={(window.appT||(s=>s))('Click to open Teams tab')} onClick={() => window.dispatchEvent(new CustomEvent('status-nav', { detail: { tab: 'teams' } }))}>
          <div style={headStyle}>
            <div>
              <div style={labelStyle}>{(window.appT||(s=>s))('Teams')}</div>
              <div style={numStyle}>{teams.length}</div>
            </div>
            <Icon name="users" size={20} style={{ color: '#a78bfa' }} />
          </div>
          <div style={{ display:'flex', flexDirection:'column', gap: 4, fontSize: 12 }}>
            {teamList.length === 0 ? (
              <div className="muted" style={{ fontSize: 11.5 }}>{(window.appT||(s=>s))('No teams configured')}</div>
            ) : teamList.slice(0, 5).map(t => (
              <div key={t.id} style={{ display:'flex', justifyContent:'space-between' }}>
                <span style={{ color:'var(--text-2)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', maxWidth: 180 }}>{t.team_name || (window.appT||(s=>s))('(unnamed)')}</span>
                <span className="mono" style={{ color:'var(--text-3)' }}>{(t.timezone || 'UTC').split('/').pop().replace(/_/g, ' ')}</span>
              </div>
            ))}
            <div style={{ borderTop:'1px solid var(--line)', paddingTop: 5, marginTop: 2, display:'flex', justifyContent:'space-between', fontSize: 11.5 }}>
              <span style={{ color:'var(--text-3)' }}>{(window.appT||(s=>s))('Total members')}</span>
              <span className="mono" style={{ color:'var(--text-2)' }}>{totalMembers}</span>
            </div>
          </div>
        </div>

      </div>
    </div>
  );
}

// ── Live presence per-user (donut + breakdown) ──
/* ── Top-10 leaderboard widget (#16) ── */
function TopTenWidget() {
  const [metric, setMetric] = React.useState('progress');
  // Period selector — respects user choice; default Week so the displayed
  // "missed hours" matches what the Violations card above is showing.
  const [period, setPeriod] = React.useState('week');
  const [data, setData]     = React.useState({ rows: [], range_start: '', range_end: '' });
  const [loading, setLoading] = React.useState(false);
  const PERIOD_DAYS = { day: 1, week: 7, month: 30, quarter: 90, '6_months': 180, year: 365, annual: 365 };
  const reload = React.useCallback(async () => {
    setLoading(true);
    try {
      const days = PERIOD_DAYS[period] || 7;
      const r = await fetch('/api/today/top10?metric=' + encodeURIComponent(metric) + '&days=' + days,
        { credentials: 'include' });
      if (r.ok) setData(await r.json());
    } catch(_) {} finally { setLoading(false); }
  }, [metric, period]);
  React.useEffect(() => {
    reload();
    const id = setInterval(reload, 60000);
    return () => clearInterval(id);
  }, [reload]);

  // Issue #2: each metric has a `fmt` that returns a fully-suffixed string
  // (no separate unit span). `tip` documents what the value means.
  const _tm = (window.appT||(s=>s));
  const _ar = window.appLang && window.appLang() === 'ar';
  const METRICS = [
    { key: 'progress',     label: '\u{1F3C6} Progress',
      col: 'progress_avg', fmt: (v) => Number(v||0).toFixed(2),
      tip: _tm('Bot-computed score blending attendance, late days, and blockers (higher is better).') },
    { key: 'blockers',     label: '\u{1F6A7} Blockers',
      col: 'blockers',     fmt: (v) => (_ar ? `${v|0} \u0639\u0627\u0626\u0642` : (v === 1 ? '1 blocker' : `${v|0} blockers`)),
      tip: _tm('Days with a reported blocker.') },
    { key: 'absence',      label: '\u{1F4C5} Absent',
      col: 'absences',     fmt: (v) => (_ar ? `${v|0} \u064A\u0648\u0645` : (v === 1 ? '1 day' : `${v|0} days`)),
      tip: _tm('Working days with no sign-in.') },
    { key: 'attendance',   label: '\u2705 Attendance',
      col: 'checkins',     fmt: (v) => (_ar ? `${v|0} \u064A\u0648\u0645` : (v === 1 ? '1 day' : `${v|0} days`)),
      tip: _tm('Days the user signed in.') },
    { key: 'hours_high',   label: '\u23F1 Hours \u25B2',
      col: 'hours_total',  fmt: (v) => `${Number(v||0).toFixed(1)}h`,
      tip: _tm('Total hours worked in the window (highest first).') },
    { key: 'hours_low',    label: '\u23F1 Hours \u25BC',
      col: 'hours_total',  fmt: (v) => `${Number(v||0).toFixed(1)}h`,
      tip: _tm('Total hours worked in the window (lowest first).') },
    // Issue #2: explicit "missed hours" metric ranking by what each user fell short
    { key: 'missed_hours', label: '\u26A0\uFE0F Missed',
      col: 'missed_hours', fmt: (v) => (_ar ? `${Number(v||0).toFixed(1)}\u0633 \u0645\u0641\u0642\u0648\u062F\u0629` : `${Number(v||0).toFixed(1)}h missed`),
      tip: _tm('Expected hours (team min \u00D7 working days) \u2212 hours worked. Higher = more shortfall.') },
  ];
  const cur = METRICS.find(m => m.key === metric) || METRICS[0];

  return (
    <div className="card" style={{ marginBottom: 8 }}>
      <SectionHeader
        title={'\u{1F3C5} ' + (window.appT||(s=>s))('Top 10') + ' \u00B7 ' + cur.label.replace(/^\W+\s*/, '')}
        sub={(window.appT||(s=>s))('Last 30 days \u00B7 click any metric to re-rank')}
        tip={(window.appT||(s=>s))("Top-10 leaderboard re-rankable by metric: Progress score (avg bot score 0-2), Attendance % (days-present/working-days), Blockers reported, Hours worked (max or min), Absences. The 'value' shown is per-user aggregate over the last 30 days.")}
        right={
          <div style={{ display:'flex', gap: 4, flexWrap:'wrap', alignItems:'center' }}>
            {/* Period selector \u2014 passes days to backend so missed_hours
                is scoped correctly. Default Week aligns with the
                Violations card above. */}
            <select className="sel" value={period} onChange={e => setPeriod(e.target.value)}
              style={{ fontSize: 11.5, padding: '3px 8px', marginRight: 6 }}
              title={(window.appT||(s=>s))('Period \u2014 affects missed-hours math + window')}>
              <option value="day">{(window.appT||(s=>s))('Day')}</option>
              <option value="week">{(window.appT||(s=>s))('Week')}</option>
              <option value="month">{(window.appT||(s=>s))('Month')}</option>
              <option value="quarter">{(window.appT||(s=>s))('Quarter')}</option>
              <option value="6_months">{(window.appT||(s=>s))('6 months')}</option>
              <option value="annual">{(window.appT||(s=>s))('Year')}</option>
            </select>
            <select className="sel" value={metric} onChange={e => setMetric(e.target.value)}
              title={(window.appT||(s=>s))('Rank Top 10 by: Progress / Attendance / Blockers / Absences / Hours / Missed')}
              style={{ fontSize: 12, padding: '4px 8px', maxWidth: 180 }}>
              {METRICS.map(m => (<option key={m.key} value={m.key}>{m.label}</option>))}
            </select>
          </div>
        }
      />
      {loading && <div className="muted" style={{ padding: 12, fontSize: 13 }}>{(window.appT||(s=>s))('Loading\u2026')}</div>}
      {!loading && (data.rows || []).length === 0 && (
        <Empty icon={<Icon name="award" size={24}/>} title={(window.appT||(s=>s))('No data')} subtitle={(window.appLang&&window.appLang()==='ar') ? `\u0644\u0627 \u064a\u0648\u062c\u062f \u0646\u0634\u0627\u0637 \u0645\u0637\u0627\u0628\u0642 \u0641\u064a ${period} \u0627\u0644\u0645\u062d\u062f\u062f.` : `No matching activity in the selected ${period}.`} />
      )}
      {!loading && (data.rows || []).length > 0 && (
        <>
          {/* Working-days context so users know the denominator */}
          {data.working_days_in_window != null && (
            <div className="muted" style={{ fontSize: 11, padding: '0 14px 6px' }}>
              {(window.appLang&&window.appLang()==='ar')
                ? <>الفترة: <strong style={{ color:'var(--text-2)' }}>{period}</strong> · {data.working_days_in_window} يوم عمل في النطاق · المتوقع = الحد الأدنى لساعات الفريق يومياً × أيام العمل · المفقود = المتوقع − المُنجَز · {cur.tip || ''}</>
                : <>Period: <strong style={{ color:'var(--text-2)' }}>{period}</strong> · {data.working_days_in_window} working day{data.working_days_in_window === 1 ? '' : 's'} in window · expected = team min_hours_per_day × working_days · missed = expected − worked · {cur.tip || ''}</>}
            </div>
          )}
          {/* Header row + data — 6 columns: # / Name / Metric / Worked / Missed / Expected */}
          <div className="topten-row-grid" style={{ display:'grid', gridTemplateColumns:'auto 1fr auto auto auto auto', gap:'4px 14px', padding:'4px 14px', alignItems:'baseline', borderBottom:'1px solid var(--line)', fontSize:10.5, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'.06em' }}>
            <div></div>
            <div>{(window.appT||(s=>s))('User')}</div>
            <div style={{ textAlign:'right' }}>{cur.label.replace(/^\W+\s*/, '')}</div>
            <div style={{ textAlign:'right' }} title={(window.appT||(s=>s))('Hours actually worked in window')}>{(window.appT||(s=>s))('Worked')}</div>
            <div style={{ textAlign:'right' }} title={(window.appT||(s=>s))('Hours missed = expected − worked (0 if user met target)')}>{(window.appT||(s=>s))('Missed')}</div>
            <div style={{ textAlign:'right' }} title={(window.appT||(s=>s))('Expected = team min_hours_per_day × working_days_in_window')}>{(window.appT||(s=>s))('Expected')}</div>
          </div>
          <div className="topten-row-grid" style={{ display:'grid', gridTemplateColumns:'auto 1fr auto auto auto auto', gap:'6px 14px', padding:'8px 14px', alignItems:'baseline' }}>
            {(data.rows || []).map((r, i) => {
              const missed = Number(r.missed_hours || 0);
              const worked = Number(r.hours_total || 0);
              const expected = Number(r.expected_hours || 0);
              const missedColor = missed >= 16 ? 'var(--err)' : missed >= 8 ? 'var(--warn)' : 'var(--text-3)';
              return (
                <React.Fragment key={r.user_id}>
                  <div style={{ fontWeight: 600, fontSize:13, color: i === 0 ? 'var(--accent)' : 'var(--text-2)', minWidth: 22 }}>
                    #{i + 1}
                  </div>
                  <div style={{ fontSize:13, display:'flex', alignItems:'center', gap:8 }}>
                    <strong>{r.display_name}</strong>
                    {r.team && <span className="muted mono" style={{ fontSize:10.5 }}>{r.team}</span>}
                  </div>
                  {/* Active metric value */}
                  <div
                    className="num mono"
                    style={{ fontSize:13, fontWeight:600, textAlign:'right' }}
                    title={`${cur.label.replace(/^\W+\s*/, '')}: ${(window.appT||(s=>s))('ranked metric for this row')}`}
                  >
                    {cur.fmt(r[cur.col] != null ? r[cur.col] : 0)}
                  </div>
                  {/* NEW: Worked column — always-on hours worked in window */}
                  <div className="num mono" style={{ fontSize: 12, textAlign: 'right', color: 'var(--text-2)' }}
                    title={(window.appLang&&window.appLang()==='ar') ? `${worked.toFixed(1)} ساعة تسجيل دخول عبر النطاق` : `${worked.toFixed(1)} hours signed-in across the window`}>
                    {worked.toFixed(1)}h
                  </div>
                  {/* NEW: Missed column — always-on shortfall */}
                  <div className="num mono"
                    style={{ fontSize: 12, textAlign: 'right', color: missedColor, fontWeight: missed > 0 ? 600 : 400 }}
                    title={(window.appLang&&window.appLang()==='ar') ? `المفقود = المتوقع (${expected.toFixed(1)}س) − المُنجَز (${worked.toFixed(1)}س)` : `Missed = expected (${expected.toFixed(1)}h) − worked (${worked.toFixed(1)}h)`}>
                    {missed > 0 ? `–${missed.toFixed(1)}h` : '0h'}
                  </div>
                  {/* NEW: Expected column — denominator so admins know the target */}
                  <div className="num mono muted" style={{ fontSize: 11, textAlign: 'right' }}
                    title={(window.appLang&&window.appLang()==='ar') ? `المتوقع = الحد الأدنى لساعات الفريق يومياً × ${data.working_days_in_window || '?'} يوم عمل` : `Expected = team min_hours_per_day × ${data.working_days_in_window || '?'} working days`}>
                    {expected.toFixed(0)}h
                  </div>
                </React.Fragment>
              );
            })}
          </div>
        </>
      )}
    </div>
  );
}

/* ── Teams breakdown row ──────────────────────────────────────────────
 *  One card per active team, listing every member of that team with a
 *  status chip:
 *    🟢 in     — signed in today, not signed off
 *    🟡 off    — signed in then signed off
 *    🔴 absent — never signed in today
 *    🌴 PTO    — on approved leave
 *    ⏰ late   — was marked late
 *  Sorts teams alphabetically; users within a team by status then name.
 *  Reads from the same filtered `employees` array as the rest of Today —
 *  superadmins are already excluded upstream, so totals match the KPIs.
 * ────────────────────────────────────────────────────────────────── */
function TeamsBreakdownRow({ employees }) {
  const grouped = React.useMemo(() => {
    const m = new Map();
    (employees || []).forEach(e => {
      const t = (e.team || '').trim() || '— No team —';
      if (!m.has(t)) m.set(t, []);
      m.get(t).push(e);
    });
    return [...m.entries()]
      .sort((a, b) => a[0].localeCompare(b[0]))
      .map(([team, list]) => ({
        team,
        list: list.slice().sort((a, b) => {
          // Status order: late > absent > in > off, then name.
          // signed_off is checked before !signed_in so a sign-off without
          // a recorded sign-in ranks as "off", not "absent".
          const rank = (e) => {
            if (e.was_late) return 0;
            if (e.signed_off) return 3;
            if (!e.signed_in) return 1;
            return 2;
          };
          const dr = rank(a) - rank(b);
          if (dr) return dr;
          return (a.display_name || '').localeCompare(b.display_name || '');
        }),
      }));
  }, [employees]);

  if (!grouped.length) return null;

  const _t = (window.appT||(s=>s));
  const chip = (e) => {
    if (e.absent_reason === 'vacation' || e.absent_reason === 'pto') {
      return { icon: '🌴', label: _t('PTO'), color: 'var(--info, #60a5fa)' };
    }
    if (e.absent_reason === 'sick') {
      return { icon: '🤒', label: _t('Sick'), color: 'var(--warn)' };
    }
    if (e.was_late) {
      return { icon: '⏰', label: _t('Late'), color: 'var(--warn)' };
    }
    // Signed off (done for the day) is checked BEFORE absent so a user
    // who signed off — even without a recorded sign-in — shows as
    // "Signed off", never "Absent".
    if (e.signed_off) {
      return { icon: '🟡', label: _t('Signed off'), color: 'var(--text-3)' };
    }
    if (!e.signed_in) {
      return { icon: '🔴', label: _t('Absent'), color: 'var(--err)' };
    }
    return { icon: '🟢', label: _t('In'), color: 'var(--ok)' };
  };

  return (
    <div className="card" style={{ marginBottom: 16 }}>
      <SectionHeader
        title={(window.appT||(s=>s))('By team — live')}
        sub={(window.appLang&&window.appLang()==='ar') ? `${grouped.length} فريق · انقر على الاسم لفتح ملفه الشخصي` : `${grouped.length} team${grouped.length !== 1 ? 's' : ''} · click a name to open their profile`}
        tip={(window.appT||(s=>s))("Per-team breakdown of today's attendance. Each member shows current status: 🟢 in / 🟡 signed off / 🔴 absent / 🌴 PTO / ⏰ late. Read from the same data as the KPI cards above so totals match.")}
      />
      <div style={{
        display:'grid',
        gridTemplateColumns:'repeat(auto-fill, minmax(280px, 1fr))',
        gap: 12, padding: 12,
      }}>
        {grouped.map(({ team, list }) => {
          const totals = {
            in:     list.filter(e => e.signed_in && !e.signed_off && !e.was_late).length,
            late:   list.filter(e => e.was_late && !e.signed_off).length,
            off:    list.filter(e => e.signed_off).length,
            absent: list.filter(e => !e.signed_in && !e.signed_off).length,
          };
          return (
            <div key={team} style={{
              border:'1px solid var(--line)', borderRadius:'var(--r-md)',
              padding:'10px 12px', background: 'var(--bg-elev-1)',
            }}>
              <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom: 8 }}>
                <div style={{ fontWeight: 600, fontSize: 13 }}>{team}</div>
                <div className="muted mono" style={{ fontSize: 11 }}>
                  {list.length - totals.absent}/{list.length}
                </div>
              </div>
              <div style={{ display:'flex', gap: 6, flexWrap:'wrap', marginBottom: 8, fontSize: 10 }}>
                {totals.in     > 0 && <span style={{ color:'var(--ok)' }}>🟢 {totals.in} {(window.appT||(s=>s))('in')}</span>}
                {totals.late   > 0 && <span style={{ color:'var(--warn)' }}>⏰ {totals.late} {(window.appT||(s=>s))('late')}</span>}
                {totals.off    > 0 && <span style={{ color:'var(--text-3)' }}>🟡 {totals.off} {(window.appT||(s=>s))('off')}</span>}
                {totals.absent > 0 && <span style={{ color:'var(--err)' }}>🔴 {totals.absent} {(window.appT||(s=>s))('absent')}</span>}
              </div>
              <div style={{ display:'flex', flexDirection:'column', gap: 2 }}>
                {list.map(e => {
                  const c = chip(e);
                  return (
                    <button
                      key={e.user_id}
                      onClick={() => {
                        if (window.openPersonDrawer) window.openPersonDrawer(e.user_id);
                      }}
                      style={{
                        display:'flex', alignItems:'center', gap: 6,
                        background:'transparent', border:'none', padding:'3px 4px',
                        cursor:'pointer', borderRadius: 4, textAlign:'left',
                      }}
                      onMouseEnter={ev => ev.currentTarget.style.background = 'var(--bg-elev-2)'}
                      onMouseLeave={ev => ev.currentTarget.style.background = 'transparent'}
                    >
                      <span style={{ fontSize: 11 }}>{c.icon}</span>
                      <span style={{ fontSize: 12, flex: 1, minWidth: 0, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                        {e.display_name || e.matrix_id}
                      </span>
                      <span style={{ fontSize: 10, color: c.color }}>{c.label}</span>
                    </button>
                  );
                })}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function LivePresenceRow({ employees, showTestAccounts }) {
  const [presence, setPresence] = React.useState({});
  const [tick, setTick]         = React.useState(0);
  const [lastUpdate, setLastUpdate] = React.useState(Date.now());
  const [presLoading, setPresLoading] = React.useState(true);
  const loadRef = React.useRef(null);
  React.useEffect(() => {
    let cancelled = false;
    const load = async () => {
      try {
        // ONE batched request — Synapse dot + DB minute-buckets for every user —
        // instead of 1 + 24 per-card /presence-breakdown fetches. The old
        // 25-request fan-out every 15s was a storm: under load some requests
        // failed → null → the ring showed 0m/grey. Batched shape matches
        // page-employees.jsx; the server caches the buckets (60s) so this is cheap.
        const live = await fetch('/api/presence/live?breakdown=daily', {credentials:'include'})
          .then(r => r.ok ? r.json() : {users:[]}).catch(() => ({users:[]}));
        const map = {};
        (live.users || []).forEach(u => {
          map[u.user_id] = {
            presence: u.presence,
            last_active_ago_ms: u.last_active_ago_ms,
            matrix_id: u.matrix_id,
            breakdown: u.breakdown || null,
          };
        });
        if (!cancelled) {
          // Keep-last-good: never wipe populated cards to 0m/grey on an empty or
          // failed refresh — only replace when the new payload actually has users.
          if (Object.keys(map).length) { setPresence(map); setLastUpdate(Date.now()); }
          setTick(t => t + 1);
          setPresLoading(false);
        }
      } catch(_) { if (!cancelled) setPresLoading(false); }
    };
    loadRef.current = load;
    // P6: sort active-first — online first, then away, then offline/unknown
    const sortByPresence = (emps, presMap) => {
      const rank = (e) => {
        const s = presMap[e.id]?.presence;
        return s === 'online' ? 0 : s === 'away' ? 1 : 2;
      };
      return emps.slice().sort((a, b) => rank(a) - rank(b));
    };
    load();
    const id = setInterval(load, 15000);  // 15s base polling
    // Instant refresh on attendance / presence events from the SSE stream
    const onLive = () => loadRef.current && loadRef.current();
    window.addEventListener('attendance-updated', onLive);
    window.addEventListener('presence-updated', onLive);
    // Instant refresh when tab regains focus or window comes back online
    const onVisible = () => { if (document.visibilityState === 'visible' && loadRef.current) loadRef.current(); };
    document.addEventListener('visibilitychange', onVisible);
    window.addEventListener('focus', onVisible);
    window.addEventListener('online', onVisible);
    return () => {
      cancelled = true;
      clearInterval(id);
      window.removeEventListener('attendance-updated', onLive);
      window.removeEventListener('presence-updated', onLive);
      document.removeEventListener('visibilitychange', onVisible);
      window.removeEventListener('focus', onVisible);
      window.removeEventListener('online', onVisible);
    };
  }, [employees.length]);

  if (!employees.length) return null;
  const ageS = Math.max(0, Math.round((Date.now() - lastUpdate) / 1000));
  const ageStr = (window.appLang&&window.appLang()==='ar')
    ? (ageS < 60 ? `قبل ${ageS} ث` : `قبل ${Math.round(ageS/60)} د`)
    : (ageS < 60 ? `${ageS}s ago` : `${Math.round(ageS/60)}m ago`);
  // P6: sort active-first
  const sorted = (() => {
    const rank = (e) => {
      const s = presence[e.id]?.presence;
      return s === 'online' ? 0 : s === 'away' ? 1 : 2;
    };
    return employees.slice().sort((a, b) => rank(a) - rank(b));
  })();
  return (
    <div className="card" style={{marginBottom: 8}}>
      <SectionHeader
        title={(window.appT||(s=>s))('Live K9 presence — per user')}
          tip={(window.appT||(s=>s))("Live Matrix presence (online/away/offline) for every active user. Pulled fresh from Synapse on every drawer open. Idle re-buckets to 'away' to match Element-web.")}
        sub={(window.appLang&&window.appLang()==='ar') ? `مباشر من Synapse · ${employees.length} مستخدم` : `Live from Synapse · ${employees.length} users`}
        right={
          <div style={{display:'flex', alignItems:'center', gap:8}}>
            <span style={{display:'flex', alignItems:'center', gap:5, fontSize:11, color:'var(--text-2)'}}>
              <span style={{
                display:'inline-block', width:7, height:7, borderRadius:'50%',
                background:'var(--ok)',
                boxShadow:'0 0 0 0 color-mix(in oklab, var(--ok) 60%, transparent)',
                animation:'pulse 2s ease-out infinite',
              }} />
              {(window.appLang&&window.appLang()==='ar') ? `تم التحديث ${ageStr} · يُحدّث كل 15 ث` : `Updated ${ageStr} · refreshes every 15s`}
            </span>
            <button
              className="btn btn-sec btn-sm"
              style={{padding:'2px 8px', fontSize:11}}
              onClick={() => loadRef.current && loadRef.current()}
              title={(window.appT||(s=>s))('Refresh now')}>↻</button>
          </div>
        }
      />
      <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(280px, 1fr))', gap: 10}}>
        {presLoading && employees.slice(0, 6).map((_, i) => (
          <div key={i} style={{display:'flex', gap:10, padding:'10px 12px', background:'var(--bg-elev-2)', border:'1px solid var(--line)', borderRadius:'var(--r-md)'}}>
            {/* avatar circle */}
            <Skel w={72} h={72} br="50%" />
            <div style={{flex:1, display:'flex', flexDirection:'column', justifyContent:'center', gap:6}}>
              <Skel w="55%" h={13} />
              <Skel w="80%" h={10} />
              <Skel w="70%" h={10} />
              <Skel w="60%" h={10} />
            </div>
          </div>
        ))}
        {!presLoading && sorted.map(e => {
          const pres = presence[e.id];
          // P6: fade fully-idle cards (offline/unknown, no online time today)
          const status = pres?.presence;
          const onlineMin = pres?.breakdown?.buckets?.online ?? 0;
          const fullyIdle = (!status || status === 'offline' || status === 'unknown') && onlineMin === 0;
          return (
            <div key={e.id} style={{ opacity: fullyIdle ? 0.5 : 1, transition: 'opacity 200ms' }}>
              <PresenceCard emp={e} pres={pres} />
            </div>
          );
        })}
      </div>
    </div>
  );
}

function PresenceCard({ emp, pres }) {
  // Backend returns shape {buckets:{online,away,offline,in_call}, total_work_minutes, samples}
  const buckets = pres?.breakdown?.buckets || {};
  const online  = buckets.online  ?? 0;
  const away    = buckets.away    ?? 0;
  const offline = buckets.offline ?? 0;
  const total   = pres?.breakdown?.total_work_minutes ?? (online + away + offline);
  const status  = pres?.presence || (online > 0 ? 'online' : away > 0 ? 'away' : 'unknown');
  const fmt = (m) => {
    if (!m && m !== 0) return '—';
    if (m < 60) return `${Math.round(m)}m`;
    return `${(m/60).toFixed(1)}h`;
  };
  return (
    <div
      title={`${emp.display_name} · ${status} · ${fmt(online)} online / ${fmt(away)} away / ${fmt(offline)} offline`}
      style={{display:'flex', gap: 10, padding:'10px 12px', background:'var(--bg-elev-2)', border:'1px solid var(--line)', borderRadius:'var(--r-md)'}}>
      <UserPie online={online} away={away} offline={offline} size={72} />
      <div style={{flex:1, minWidth:0, display:'flex', flexDirection:'column', justifyContent:'center', gap:5}}>
        <div style={{display:'flex', alignItems:'center', gap: 6}}>
          <span style={{
            display:'inline-block', width:9, height:9, borderRadius:'50%',
            background: status==='online'?'var(--ok)':status==='away'?'var(--warn)':'var(--text-3)',
            flexShrink:0
          }} />
          <strong style={{fontSize:13, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>{emp.display_name}</strong>
        </div>
        <div style={{display:'grid', gridTemplateColumns:'auto 1fr', gap:'2px 8px', fontSize:11.5, color:'var(--text-2)'}}>
          <span><span style={{color:'var(--ok)'}}>●</span> {(window.appT||(s=>s))('Online')}</span><span className="num mono">{fmt(online)}</span>
          <span><span style={{color:'var(--warn)'}}>●</span> {(window.appT||(s=>s))('Away')}</span><span className="num mono">{fmt(away)}</span>
          <span><span style={{color:'var(--text-3)'}}>○</span> {(window.appT||(s=>s))('Offline')}</span><span className="num mono">{fmt(offline)}</span>
        </div>
      </div>
    </div>
  );
}

// ── Multi-slice attendance pie (Present / Late / Sick / Vacation / Half / Absent) ──
function AttendancePie({ slices, size = 72 }) {
  const total = slices.reduce((s, x) => s + (x.value || 0), 0) || 1;
  const r = size / 2 - 6;
  const c = 2 * Math.PI * r;
  const cx = size / 2, cy = size / 2;
  let off = 0;
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{flexShrink:0}}>
      <circle cx={cx} cy={cy} r={r} fill="none" stroke="var(--line)" strokeWidth={9} />
      {slices.map((s, i) => {
        if (!s.value) return null;
        const len = (s.value / total) * c;
        const node = (
          <circle key={i} cx={cx} cy={cy} r={r} fill="none" stroke={s.color} strokeWidth={9}
            strokeDasharray={`${len} ${c - len}`} strokeDashoffset={-off}>
            <title>{s.label}: {s.value} {s.unit || 'd'}</title>
          </circle>
        );
        off += len;
        return node;
      })}
    </svg>
  );
}

// ── P9: 7-day week strip component ──
function WeekStrip({ strip }) {
  if (!strip || strip.length === 0) return null;
  const STATUS_COLOR = {
    on_time: 'var(--ok)',
    late:    'var(--warn)',
    pto:     '#06b6d4',
    absent:  'var(--err)',
    future:  'var(--bg-elev-2)',
    weekend: 'transparent',
  };
  const _t = (window.appT||(s=>s));
  const STATUS_LABEL = {
    on_time: _t('On time'), late: _t('Late'), pto: _t('PTO/Leave'),
    absent: _t('Absent'), future: _t('Future'), weekend: _t('Weekend'),
  };
  const workdays = strip.filter(d => d.status !== 'weekend');
  if (!workdays.length) return null;
  return (
    <div style={{ display:'flex', gap:3, marginTop:6 }}>
      {workdays.map((d, i) => (
        <div key={i}
          title={`${d.weekday} ${d.date}: ${STATUS_LABEL[d.status] || d.status}${d.pto_type ? ` (${d.pto_type})` : ''}`}
          style={{
            width: 14, height: 14, borderRadius: 3,
            background: STATUS_COLOR[d.status] || 'var(--line)',
            border: d.status === 'future' ? '1px solid var(--line)' : 'none',
            opacity: d.status === 'future' ? 0.4 : 1,
            flexShrink: 0,
          }}
        />
      ))}
    </div>
  );
}

// ── Per-user FULL attendance report (replaces team-level summary cards) ──
function AttendanceReportRow({ employees, showTestAccounts }) {
  const [rng, setRng]     = React.useState('monthly');
  const [data, setData]   = React.useState({users: [], working_days: 0, range_start: '', range_end: ''});
  const [weekStrip, setWeekStrip] = React.useState({});  // P9: uid → strip[]
  const [loading, setLoading] = React.useState(true);
  const [lastReloadTs, setLastReloadTs] = React.useState(Date.now());
  const reload = React.useCallback(async () => {
    setLoading(true);
    try {
      // P9: fetch attendance-report + week-strip in parallel
      const [r, ws] = await Promise.all([
        fetch(`/api/today/attendance-report?range=${rng}`, {credentials:'include'}),
        fetch('/api/today/week-strip', {credentials:'include'}).then(r2 => r2.ok ? r2.json() : null).catch(() => null),
      ]);
      if (r.ok) {
        const json = await r.json();
        setData(json);
        setLastReloadTs(Date.now());
      }
      if (ws && ws.users) {
        const map = {};
        ws.users.forEach(u => { map[u.user_id] = u.strip; });
        setWeekStrip(map);
      }
    } catch(_) {} finally { setLoading(false); }
  }, [rng]);
  const reloadRef = React.useRef(reload);
  React.useEffect(() => { reloadRef.current = reload; }, [reload]);
  React.useEffect(() => {
    reload();
    const id = setInterval(() => reloadRef.current && reloadRef.current(), 30000);  // 30s base polling
    // Instant refresh on attendance events
    const onLive = () => reloadRef.current && reloadRef.current();
    window.addEventListener('attendance-updated', onLive);
    window.addEventListener('presence-updated', onLive);
    const onVisible = () => { if (document.visibilityState === 'visible' && reloadRef.current) reloadRef.current(); };
    document.addEventListener('visibilitychange', onVisible);
    window.addEventListener('focus', onVisible);
    window.addEventListener('online', onVisible);
    return () => {
      clearInterval(id);
      window.removeEventListener('attendance-updated', onLive);
      window.removeEventListener('presence-updated', onLive);
      document.removeEventListener('visibilitychange', onVisible);
      window.removeEventListener('focus', onVisible);
      window.removeEventListener('online', onVisible);
    };
  }, [reload, employees.length]);

  // P7: promote test-account to real employee
  const toast = useToast();
  const promoteTestAccount = async (uid, displayName) => {
    if (!window.confirm((window.appLang&&window.appLang()==='ar') ? `ترقية "${displayName}" إلى موظف حقيقي؟ يؤدي ذلك إلى إزالة علامة الحساب التجريبي.` : `Promote "${displayName}" to a real employee? This removes the test-account flag.`)) return;
    try {
      const r = await fetch(`/api/users/${uid}/test-flag`, {
        method: 'PUT', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ is_test_account: false }),
      });
      if (!r.ok) throw new Error(`${r.status}`);
      toast.push({ kind: 'ok', msg: (window.appLang&&window.appLang()==='ar') ? `تمت ترقية ${displayName} إلى موظف` : `${displayName} promoted to employee` });
      window.dispatchEvent(new CustomEvent('status-data-refresh-requested'));
      reload();
    } catch(e) { toast.push({ kind: 'err', msg: (window.appLang&&window.appLang()==='ar') ? ('فشلت الترقية: ' + e.message) : ('Promote failed: ' + e.message) }); }
  };
  const deleteUser = async (uid, displayName) => {
    if (!window.confirm((window.appLang&&window.appLang()==='ar') ? `حذف "${displayName}"؟ سيتم إلغاء تنشيطه ولن يظهر في التقارير.` : `Delete "${displayName}"? They will be deactivated and no longer appear in reports.`)) return;
    try {
      const r = await fetch(`/api/users/${uid}`, { method: 'DELETE', credentials: 'include' });
      if (!r.ok) throw new Error(`${r.status}`);
      toast.push({ kind: 'ok', msg: (window.appLang&&window.appLang()==='ar') ? `تم حذف ${displayName}` : `${displayName} deleted` });
      window.dispatchEvent(new CustomEvent('status-data-refresh-requested'));
      reload();
    } catch(e) { toast.push({ kind: 'err', msg: (window.appLang&&window.appLang()==='ar') ? ('فشل الحذف: ' + e.message) : ('Delete failed: ' + e.message) }); }
  };

  const allRows = data.users || [];
  // API now returns is_test_account per row — filter directly from the flag.
  // When showTestAccounts=true, show all; test accounts get a blank card (P7).
  const rows = showTestAccounts ? allRows : allRows.filter(u => !u.is_test_account);
  const wd   = data.working_days || 0;

  // P19: show skeleton cards while initial fetch is pending
  if (loading && !rows.length) {
    return (
      <div className="card" style={{ marginBottom: 8 }}>
        <SectionHeader title={(window.appT||(s=>s))('Attendance report — per user')}
          tip={(window.appT||(s=>s))('Full per-user attendance breakdown for the selected range. Range buttons switch daily/weekly/monthly/6 months/annual without re-fetching.')} sub={(window.appT||(s=>s))('Loading…')} />
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 10, marginTop: 8 }}>
          {Array.from({ length: 4 }).map((_, i) => (
            <div key={i} style={{ display: 'flex', gap: 10, padding: '10px 12px', background: 'var(--bg-elev-2)', border: '1px solid var(--line)', borderRadius: 'var(--r-md)' }}>
              <Skel w={72} h={72} br="50%" />
              <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 6 }}>
                <Skel w="55%" h={13} />
                <Skel w="80%" h={10} />
                <Skel w="70%" h={10} />
                <Skel w="60%" h={10} />
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  }

  if (!rows.length) return null;

  const _t = (window.appT||(s=>s));
  const SLICES = [
    { key: 'on_time_days',  label: _t('On-time'),   color: 'var(--ok)' },
    { key: 'late_days',     label: _t('Late'),      color: 'var(--warn)' },
    { key: 'half_days',     label: _t('Half-day'),  color: '#a78bfa' },
    { key: 'sick_days',     label: _t('Sick'),      color: '#f472b6' },
    { key: 'vacation_days', label: _t('Vacation'),  color: '#06b6d4' },
    { key: 'holiday_days',  label: _t('Holiday'),   color: '#facc15' },
    { key: 'absent_days',   label: _t('Absent'),    color: 'var(--text-3)' },
  ];

  const attendanceAgeS = Math.max(0, Math.round((Date.now() - lastReloadTs) / 1000));
  const attendanceAgeStr = (window.appLang&&window.appLang()==='ar')
    ? (attendanceAgeS < 60 ? `قبل ${attendanceAgeS} ث` : `قبل ${Math.round(attendanceAgeS/60)} د`)
    : (attendanceAgeS < 60 ? `${attendanceAgeS}s ago` : `${Math.round(attendanceAgeS/60)}m ago`);

  return (
    <div className="card" style={{ marginBottom: 8 }}>
      <SectionHeader
        title={(window.appT||(s=>s))('Attendance report — per user')}
          tip={(window.appT||(s=>s))('Full per-user attendance breakdown for the selected range. Range buttons switch daily/weekly/monthly/6 months/annual without re-fetching.')}
        sub={
          <span style={{display:'flex', alignItems:'center', gap:6}}>
            <span>{data.range_start} &rarr; {data.range_end} · {(window.appLang&&window.appLang()==='ar') ? `${wd} يوم عمل` : `${wd} working days`}</span>
            <span style={{display:'flex', alignItems:'center', gap:4, color:'var(--text-3)', fontSize:10.5}}>
              <span style={{
                display:'inline-block', width:6, height:6, borderRadius:'50%',
                background:'var(--ok)',
                boxShadow:'0 0 0 0 color-mix(in oklab, var(--ok) 60%, transparent)',
                animation:'pulse 2s ease-out infinite',
              }} />
              {(window.appLang&&window.appLang()==='ar') ? `تم التحديث ${attendanceAgeStr} · يُحدّث كل 30 ث` : `Updated ${attendanceAgeStr} · refreshes every 30s`}
            </span>
          </span>
        }
        right={
          <div style={{display:'flex', gap:8, alignItems:'center'}}>
            <select className="sel" value={rng} onChange={e => setRng(e.target.value)}
              style={{ fontSize: 12, padding: '4px 8px' }}>
              <option value="daily">{(window.appT||(s=>s))('Day')}</option>
              <option value="weekly">{(window.appT||(s=>s))('Week')}</option>
              <option value="monthly">{(window.appT||(s=>s))('Month')}</option>
              <option value="6month">{(window.appT||(s=>s))('6 mo')}</option>
              <option value="annual">{(window.appT||(s=>s))('Year')}</option>
            </select>
            <button className="btn btn-ghost" onClick={reload} disabled={loading} title={(window.appT||(s=>s))('Refresh')}>↻</button>
          </div>
        }
      />
      {/* Legend */}
      <div style={{display:'flex', flexWrap:'wrap', gap:'4px 12px', fontSize:11, color:'var(--text-2)', marginBottom: 10}}>
        {SLICES.map(s => (
          <span key={s.key}><span style={{color: s.color}}>●</span> {s.label}</span>
        ))}
      </div>
      <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(260px, 1fr))', gap: 10}}>
        {rows.map(u => {
          // P7: test account blank card when shown
          if (u.is_test_account) {
            return (
              <div key={u.user_id}
                style={{display:'flex', flexDirection:'column', gap:8, padding:'10px 12px',
                  background:'var(--bg-elev-2)', border:'1px dashed var(--line)', borderRadius:'var(--r-md)',
                  opacity: 0.55}}>
                <div style={{fontSize:12, color:'var(--text-2)', fontWeight:500}}>
                  {u.display_name}
                </div>
                <div className="muted" style={{fontSize:11}}>{(window.appT||(s=>s))('Test account — no stats tracked')}</div>
                <div style={{display:'flex', gap:6, marginTop:2}}>
                  <button className="btn btn-ghost" style={{fontSize:11, padding:'3px 8px'}}
                    onClick={() => promoteTestAccount(u.user_id, u.display_name)}>
                    + {(window.appT||(s=>s))('Add details')}
                  </button>
                  <button className="btn btn-ghost" style={{fontSize:11, padding:'3px 8px', color:'var(--err)'}}
                    onClick={() => deleteUser(u.user_id, u.display_name)}>
                    {(window.appT||(s=>s))('Delete')}
                  </button>
                </div>
              </div>
            );
          }

          const slices = SLICES.map(s => ({ ...s, value: u[s.key] || 0 }));
          const pct = u.attendance_pct ?? 0;
          const pctColor = pct >= 90 ? 'var(--ok)' : pct >= 70 ? 'var(--warn)' : 'var(--err)';
          const strip = weekStrip[u.user_id] || null;  // P9
          return (
            <div key={u.user_id}
              style={{display:'flex', gap:10, padding:'10px 12px', background:'var(--bg-elev-2)', border:'1px solid var(--line)', borderRadius:'var(--r-md)'}}>
              <div style={{display:'flex', flexDirection:'column', alignItems:'center', gap:4}}>
                <AttendancePie slices={slices} size={72} />
                {/* P9: 7-day status strip under each donut */}
                <WeekStrip strip={strip} />
              </div>
              <div style={{flex:1, minWidth:0, display:'flex', flexDirection:'column', gap:4}}>
                <div style={{display:'flex', alignItems:'center', gap:6, justifyContent:'space-between'}}>
                  <strong style={{fontSize:12.5, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>{u.display_name}</strong>
                  <span className="mono" style={{fontSize:11.5, fontWeight:600, color: pctColor}}>{pct}%</span>
                </div>
                <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:'1px 8px', fontSize:11, color:'var(--text-2)'}}>
                  <span>{(window.appT||(s=>s))('On-time')}</span><span className="mono num">{u.on_time_days}d</span>
                  <span>{(window.appT||(s=>s))('Late')}</span><span className="mono num">{u.late_days}d</span>
                  <span>{(window.appT||(s=>s))('Sick')}</span><span className="mono num">{u.sick_days}d</span>
                  <span>{(window.appT||(s=>s))('Vacation')}</span><span className="mono num">{u.vacation_days}d</span>
                  {u.half_days   > 0 && (<><span>{(window.appT||(s=>s))('Half-day')}</span><span className="mono num">{u.half_days}d</span></>)}
                  {u.holiday_days > 0 && (<><span>{(window.appT||(s=>s))('Holiday')}</span><span className="mono num">{u.holiday_days}d</span></>)}
                  <span>{(window.appT||(s=>s))('Absent')}</span><span className="mono num">{u.absent_days}d</span>
                  <span>{(window.appT||(s=>s))('Hours')}</span><span className="mono num">{u.hours_worked}h</span>
                  <span>{(window.appT||(s=>s))('Blockers')}</span><span className="mono num">{u.blockers_count}</span>
                  <span>{(window.appT||(s=>s))('Strikes')}</span><span className="mono num" style={{color: u.strike_count >= 3 ? 'var(--err)' : u.strike_count > 0 ? 'var(--warn)' : 'inherit'}}>{u.strike_count}</span>
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

window.TodayPage = TodayPage;


/* ============================================================
   NeuralPulseCard — compact live view of the neural layer on
   the Today page. Self-gating: fetch 403s for non-admins and
   the card renders nothing (employees never see risk scores).
   ============================================================ */
const NP_HEADS = ['flag_checkin', 'uphold_strike', 'auto_approve_pto', 'nudge_effective', 'noshow_risk'];
const NP_LABELS = {
  flag_checkin: 'Flags', uphold_strike: 'Strikes', auto_approve_pto: 'PTO',
  nudge_effective: 'Nudges', noshow_risk: 'No-show',
};
const NP_COLORS = {
  flag_checkin: '#3987e5', auto_approve_pto: '#199e70', nudge_effective: '#c98500',
  noshow_risk: '#9085e9', uphold_strike: '#e66767',
};

function NeuralPulseCard() {
  const [pulse, setPulse] = React.useState(null);
  const [hidden, setHidden] = React.useState(false);
  const [slide, setSlide] = React.useState(0);
  const [paused, setPaused] = React.useState(false);
  const donutRef = React.useRef(null);

  React.useEffect(() => {
    let timer;
    const load = () => {
      fetch('/api/ml/pulse', { credentials: 'same-origin' })
        .then(r => { if (!r.ok) throw r.status; return r.json(); })
        .then(setPulse)
        .catch(() => setHidden(true));   // 403 (not admin) or any error → invisible
    };
    load();
    timer = setInterval(load, 120000);   // silent refresh every 2 min
    return () => clearInterval(timer);
  }, []);

  // advancement is driven by the progress-pill fill animation (onAnimationEnd)
  // — perfectly synced with the visible timer, and pausing pauses both.

  const counts = (pulse && pulse.decisions_today) || {};
  const total = NP_HEADS.reduce((a, h) => a + (counts[h] || 0), 0);

  React.useEffect(() => {
    if (!window.Chart || !donutRef.current || !pulse || total === 0) return;
    const ctx = donutRef.current.getContext('2d');
    if (donutRef.current._chart) donutRef.current._chart.destroy();
    donutRef.current._chart = new window.Chart(ctx, {
      type: 'doughnut',
      data: {
        labels: NP_HEADS.map(h => NP_LABELS[h]),
        datasets: [{
          data: NP_HEADS.map(h => counts[h] || 0),
          backgroundColor: NP_HEADS.map(h => NP_COLORS[h]),
          borderColor: 'rgba(0,0,0,0)', borderWidth: 2, hoverOffset: 4,
        }],
      },
      options: {
        responsive: true, maintainAspectRatio: false, cutout: '68%',
        plugins: {
          legend: { display: false },
          tooltip: { callbacks: { label: c => ` ${c.label}: ${c.raw}` } },
        },
      },
    });
    return () => { if (donutRef.current?._chart) donutRef.current._chart.destroy(); };
  }, [JSON.stringify(counts), pulse ? 1 : 0]);

  if (hidden || !pulse) return null;
  const T = window.appT || (s => s);
  const agree = pulse.agreement_7d;
  const risks = pulse.risks || [];
  const sl = pulse.slides || {};

  const SLIDE_ORDER = ['noshow_risk', 'flag_checkin', 'uphold_strike', 'auto_approve_pto', 'nudge_effective'];
  const head = SLIDE_ORDER[slide];
  const pct = (p) => p === null || p === undefined ? '—' : Math.round(p * 100) + '%';
  const pcol = (p) => p === null || p === undefined ? 'rgba(148,163,184,.5)'
    : p >= 0.85 ? '#e66767' : p >= 0.6 ? '#c98500' : '#0ca30c';

  const riskGrid = (
    <div>
      <div className="muted" style={{ fontSize: 10.5, marginBottom: 6 }}>
        {T('Every tracked employee, riskiest first')} — <span style={{ color: '#e66767' }}>■</span> ≥85%
        <span style={{ color: '#c98500' }}> ■</span> 60–84% <span style={{ color: '#0ca30c' }}>■</span> {T('below')} 60%
      </div>
      {risks.length === 0
        ? <div className="muted" style={{ fontSize: 11.5 }}>✅ {T('no tracked employees to score yet')}</div>
        : (
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(185px, 1fr))', gap: '7px 16px' }}>
            {risks.map(r => {
              const p = (r.p === null || r.p === undefined) ? null : r.p;
              return (
                <div key={r.name}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, marginBottom: 1 }}>
                    <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '72%' }}
                          title={r.missed ? `${r.name} — missed ${r.missed} day(s) in the last 14` : r.name}>
                      {r.name}{r.missed ? <span className="muted" style={{ fontSize: 9.5 }}> · {r.missed}✗</span> : null}
                    </span>
                    <span style={{ fontWeight: 600, color: pcol(p) }}>{p !== null ? Math.round(p * 100) + '%' : '—'}</span>
                  </div>
                  <div style={{ height: 4, borderRadius: 3, background: 'rgba(148,163,184,.15)', overflow: 'hidden' }}>
                    <div style={{ height: '100%', width: `${Math.round((p || 0) * 100)}%`, borderRadius: 3, background: pcol(p) }} />
                  </div>
                </div>
              );
            })}
          </div>
        )}
    </div>
  );

  const miniList = (rows, emptyText, extra) => (
    <div>
      {extra}
      {(!rows || rows.length === 0)
        ? <div className="muted" style={{ fontSize: 11.5 }}>{emptyText}</div>
        : rows.map((r, i) => (
          <div key={i} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11.5, padding: '3px 0', borderBottom: '1px solid rgba(148,163,184,.08)' }}>
            <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              {r.name}{r.kind ? <span className="muted" style={{ fontSize: 10 }}> · {r.kind}</span> : null}
            </span>
            <span className="muted" style={{ fontSize: 10.5, whiteSpace: 'nowrap' }}>
              {(r.ts || '').slice(11, 16)} · <b style={{ color: pcol(r.p) }}>{pct(r.p)}</b>
            </span>
          </div>
        ))}
    </div>
  );

  const fl = sl.flag_checkin || {};
  const st = sl.uphold_strike || {};
  const pt = sl.auto_approve_pto || {};
  const nu = sl.nudge_effective || {};

  const SLIDES = {
    noshow_risk: { sub: T('Will anyone miss check-in entirely today?'), body: riskGrid },
    flag_checkin: {
      sub: T('Sign-ins flagged late / early / short today — model % = how flag-worthy it thinks each was'),
      body: miniList(fl.flagged_today, '✅ ' + T('no flags yet today'),
        <div className="muted" style={{ fontSize: 10.5, marginBottom: 6 }}>
          {(fl.flagged_today || []).length} {T('flagged')} · {fl.clean_today || 0} {T('clean sign-ins today')}
        </div>),
    },
    uphold_strike: {
      sub: T('Strikes issued this week — model % = P(an admin would let it stand); never automatic'),
      body: miniList(st.recent, '✅ ' + T('no strikes issued this week')),
    },
    auto_approve_pto: {
      sub: T('Time-off requests this week — model % = P(an admin would approve)'),
      body: miniList(pt.recent, T('no PTO requests this week'),
        pt.pending ? <div style={{ fontSize: 11, marginBottom: 6 }}>⏳ <b>{pt.pending}</b> {T('awaiting approval right now')}</div> : null),
    },
    nudge_effective: {
      sub: T('Absence reminders — did they actually bring people back?'),
      body: (
        <div style={{ fontSize: 12 }}>
          <div style={{ marginBottom: 4 }}>{T('Nudges sent today')}: <b>{nu.sent_today || 0}</b></div>
          <div style={{ marginBottom: 4 }}>{T('Worked within 30 min (7d)')}: <b style={{ color: nu.effectiveness_7d === null || nu.effectiveness_7d === undefined ? 'inherit' : nu.effectiveness_7d >= 0.5 ? '#0ca30c' : '#c98500' }}>{pct(nu.effectiveness_7d)}</b></div>
          <div className="muted" style={{ fontSize: 10.5 }}>{T('The model learns the best moment to remind — it can delay a pointless ping, never past the manager escalation.')}</div>
        </div>
      ),
    },
  };

  return (
    <div className="card fade-in-on-scroll" style={{ marginBottom: 16, padding: 12 }}
         onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}>
      <style>{`
        .np-viewport { overflow: hidden; }
        .np-track { display: flex; width: 500%; transition: transform .65s cubic-bezier(.22,.61,.36,1); will-change: transform; }
        .np-panel { flex: 0 0 20%; min-width: 0; padding-right: 4px; max-height: 190px; overflow-y: auto; scrollbar-width: thin; }
        .np-panel::-webkit-scrollbar { width: 5px; }
        .np-panel::-webkit-scrollbar-thumb { background: rgba(148,163,184,.25); border-radius: 3px; }
        .np-dot { width: 8px; height: 8px; border-radius: 5px; border: none; padding: 0; cursor: pointer; opacity: .35; transition: width .3s, opacity .3s; position: relative; overflow: hidden; }
        .np-dot.active { width: 30px; opacity: 1; }
        @keyframes npFill { from { width: 0; } to { width: 100%; } }
        .np-fill { position: absolute; inset: 0; width: 0; border-radius: 5px; animation: npFill 3s linear forwards; }
        @media (prefers-reduced-motion: reduce) { .np-track { transition: none; } }
      `}</style>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
        <span style={{ fontSize: 13, fontWeight: 600 }}>🧠 {T('Neural pulse')}</span>
        <span className="muted" style={{ fontSize: 11 }}>{T('live — the model observes, rules act')}</span>
        <div style={{ flex: 1 }} />
        <span className="muted" style={{ fontSize: 10.5 }}>
          {T('Agrees with rules (7d)')}: <b style={{ color: agree === null || agree === undefined ? 'inherit' : agree > 0.8 ? '#0ca30c' : '#c98500' }}>{pct(agree)}</b>
          {' · '}{pulse.champion || T('no model')}
        </span>
        <a href="#" className="muted" style={{ fontSize: 11 }}
           onClick={e => { e.preventDefault(); try { window.dispatchEvent(new CustomEvent('status-nav', { detail: { tab: 'ml' } })); } catch (_) {} }}>
          {T('open Neural →')}
        </a>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr', gap: 16, alignItems: 'start' }}>
        {/* fixed left: today's decision mix */}
        <div style={{ width: 108 }}>
          {total === 0
            ? <div className="muted" style={{ fontSize: 10.5, textAlign: 'center', padding: '26px 4px' }}>{T('no decisions yet today')}</div>
            : (
              <div style={{ position: 'relative', height: 92 }}>
                <canvas ref={donutRef} />
                <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }}>
                  <div style={{ fontSize: 16, fontWeight: 700, lineHeight: 1 }}>{total}</div>
                  <div className="muted" style={{ fontSize: 8.5 }}>{T('today')}</div>
                </div>
              </div>
            )}
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, justifyContent: 'center', marginTop: 4 }}>
            {NP_HEADS.filter(h => counts[h] > 0).map(h => (
              <span key={h} className="muted" style={{ fontSize: 8.5, whiteSpace: 'nowrap' }}>
                <span style={{ display: 'inline-block', width: 6, height: 6, borderRadius: 2, background: NP_COLORS[h], marginRight: 2 }} />
                {NP_LABELS[h]} {counts[h]}
              </span>
            ))}
          </div>
        </div>

        {/* right: the decision slideshow */}
        <div style={{ minHeight: 120 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
            <span style={{ display: 'inline-block', width: 9, height: 9, borderRadius: 2, background: NP_COLORS[head], transition: 'background .4s' }} />
            <span style={{ fontSize: 12.5, fontWeight: 700 }}>
              {head === 'noshow_risk' ? T('No-show risk today') : NP_LABELS[head]}
            </span>
            <div style={{ flex: 1 }} />
            <div style={{ display: 'flex', gap: 5, alignItems: 'center' }}>
              {SLIDE_ORDER.map((h, i) => (
                <button key={h} className={'np-dot' + (i === slide ? ' active' : '')}
                        style={{ background: 'rgba(148,163,184,.35)' }}
                        title={h === 'noshow_risk' ? T('No-show risk') : NP_LABELS[h]}
                        onClick={() => setSlide(i)}>
                  {i === slide ? (
                    <span key={'f' + slide} className="np-fill"
                          style={{ background: NP_COLORS[h], animationPlayState: paused ? 'paused' : 'running' }}
                          onAnimationEnd={() => { if (!paused) setSlide(x => (x + 1) % SLIDE_ORDER.length); }} />
                  ) : null}
                </button>
              ))}
            </div>
          </div>
          <div className="muted" style={{ fontSize: 10.5, marginBottom: 6, minHeight: 13 }}>{SLIDES[head].sub}</div>
          <div className="np-viewport">
            <div className="np-track" style={{ transform: `translateX(-${slide * 20}%)` }}>
              {SLIDE_ORDER.map(h => (
                <div key={h} className="np-panel">{SLIDES[h].body}</div>
              ))}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
