/* ============================================================
   page-reports.jsx — Generate reports + admin digest + hours distribution
   ============================================================ */

/* Re-bind parseDetail — set by primitives.jsx */
const parseDetail = window.parseDetail || ((body, status) => (body && (body.detail || body.message)) || `HTTP ${status}`);

/* i18n helpers — Arabic via window.appT, falls back to identity.
   File-unique names (_rT/_rAr): other v2 bundles declare their own top-level
   `_T` in the shared in-browser-Babel global scope, so a bare `const _T` here
   would throw "already declared" and blank the page. */
const _rT = (s) => (window.appT || (x => x))(s);
const _rAr = () => (window.appLang && window.appLang() === 'ar');

/* ── Hours Distribution stacked bar chart (inline SVG) ── */
/* ── Per-user 30-day trends (sparklines + status donut) — #25 ── */
// B7: per-employee violations report with type filter
function PerEmployeeViolationsCard({ refreshTick }) {
  const D = window.STATUS_DATA || {};
  const employees = D.EMPLOYEES || [];
  const [userId, setUserId]   = useState(employees[0]?.id || null);
  const [period, setPeriod]   = useState('month');
  const [type, setType]       = useState('all');
  const [data, setData]       = useState(null);
  const [loading, setLoading] = useState(false);

  const load = async () => {
    if (!userId) return;
    setLoading(true);
    try {
      const r = await fetch(
        `/api/employees/${userId}/violations?period=${period}&type=${type}`,
        { credentials: 'include' }
      );
      if (r.ok) setData(await r.json());
    } catch (_) {}
    finally { setLoading(false); }
  };

  useEffect(() => { load(); }, [userId, period, type, refreshTick]);

  return (
    <div className="card" style={{ marginTop: 16 }}>
      <SectionHeader
        title={_rT("Per-employee violations")}
        tip={_rT("Filter by user + period + violation kind (presence / late / absent / strike / missed_signoff / all). Shows counts and the event timeline so admins can drill into specific compliance issues per person.")}
        sub={_rT("Pick a user, then filter by type. Last update reflects the current period selection.")}
        right={<Refresh onClick={load} loading={loading} />}
      />
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 12, alignItems: 'center' }}>
        <label style={{ fontSize: 12 }}>{_rT("User")}</label>
        <select className="sel" value={userId || ''} onChange={e => setUserId(Number(e.target.value))}
          style={{ minWidth: 160 }}>
          {employees.map(e => <option key={e.id} value={e.id}>{e.display_name}</option>)}
        </select>
        <label style={{ fontSize: 12 }}>{_rT("Period")}</label>
        <select className="sel" value={period} onChange={e => setPeriod(e.target.value)}>
          {['day','week','month','quarter','6_months','year','annual'].map(p =>
            <option key={p} value={p}>{p}</option>
          )}
        </select>
        <label style={{ fontSize: 12 }}>{_rT("Type")}</label>
        <select className="sel" value={type} onChange={e => setType(e.target.value)}>
          <option value="all">{_rT("All")}</option>
          <option value="presence">{_rT("Presence")}</option>
          <option value="late">{_rT("Late")}</option>
          <option value="absent">{_rT("Absent")}</option>
          <option value="missed_signoff">{_rT("Missed sign-off")}</option>
          <option value="strike">{_rT("Format strike")}</option>
        </select>
      </div>
      {!data ? (
        <div className="muted" style={{ padding: 16, fontSize: 13 }}>
          {loading ? _rT('Loading…') : _rT('Pick a user to see violations.')}
        </div>
      ) : (
        <>
          <div style={{ display: 'flex', gap: 12, marginBottom: 12, flexWrap: 'wrap' }}>
            {Object.entries(data.counts || {}).map(([k, v]) => (
              <div key={k} style={{
                padding: '6px 14px', background: 'var(--bg-elev-2)',
                border: '1px solid var(--line)', borderRadius: 8, fontSize: 12,
              }}>
                <span className="muted">{k.replace(/_/g, ' ')}: </span>
                <span className="mono" style={{ fontWeight: 600, color: v > 0 ? 'var(--err)' : 'var(--ok)' }}>{v}</span>
              </div>
            ))}
          </div>
          <table className="t" style={{ fontSize: 12 }}>
            <thead><tr><th>{_rT("When")}</th><th>{_rT("Kind")}</th><th>{_rT("Severity")}</th><th>{_rT("Detail")}</th></tr></thead>
            <tbody>
              {(data.events || []).length === 0 ? (
                <tr><td colSpan={4} className="muted" style={{ textAlign: 'center', padding: 16 }}>{_rT("No events for this filter.")}</td></tr>
              ) : (
                data.events.map((ev, i) => (
                  <tr key={i}>
                    <td className="mono" style={{ fontSize: 11 }}>{ev.when ? new Date(ev.when).toLocaleString() : '—'}</td>
                    <td>{ev.kind}</td>
                    <td><span className={`badge ${ev.severity === 'high' ? 'err' : ev.severity === 'medium' ? 'warn' : 'mute'}`}>{ev.severity}</span></td>
                    <td>{ev.detail}</td>
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </>
      )}
    </div>
  );
}

function PerUserTrendsCard({ refreshTick }) {
  const [rows, setRows] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const reload = React.useCallback(async () => {
    setLoading(true);
    try {
      // Reuse the hours-distribution endpoint (per-user × per-day)
      const r = await fetch('/api/reports/hours-distribution?period=month', { credentials: 'same-origin' });
      if (r.ok) setRows(await r.json());
    } catch(_) {} finally { setLoading(false); }
  }, []);
  React.useEffect(() => { reload(); }, [reload, refreshTick]);

  if (loading) return null;
  if (!rows || rows.length === 0) return null;

  // For each user: extract daily hours array + compute totals
  const enriched = rows.map(u => {
    const days = Object.values(u.hours_by_day || {});
    const total = days.reduce((s, h) => s + (h || 0), 0);
    const present = days.filter(h => (h || 0) >= 1).length;
    const partial = days.filter(h => (h || 0) > 0 && (h || 0) < 1).length;
    const absent  = days.filter(h => (h || 0) === 0).length;
    return { ...u, days, total, present, partial, absent };
  });
  enriched.sort((a, b) => b.total - a.total);

  // Aggregate status mix across all users
  const totalPresent = enriched.reduce((s, u) => s + u.present, 0);
  const totalPartial = enriched.reduce((s, u) => s + u.partial, 0);
  const totalAbsent  = enriched.reduce((s, u) => s + u.absent, 0);
  const totalAll = totalPresent + totalPartial + totalAbsent || 1;
  const slices = [
    { value: totalPresent, label: _rT('Present'), color: 'var(--ok)' },
    { value: totalPartial, label: _rT('Partial'), color: 'var(--warn)' },
    { value: totalAbsent,  label: _rT('Absent'),  color: 'var(--err)' },
  ];

  // Inline donut (small)
  const SIZE = 130, R = SIZE/2 - 12, CX = SIZE/2, CY = SIZE/2;
  const C = 2 * Math.PI * R;
  let off = 0;

  return (
    <div className="card" style={{ marginTop: 16 }}>
      <SectionHeader
        title={_rT("📈 Per-user trends — 30 day")}
          tip={_rT("30-day moving line for each employee — hours / attendance % / progress score. Click a legend item to isolate one user.")}
        sub={_rAr() ? `${enriched.length} موظف · الخط البياني = الساعات اليومية؛ الحلقة = توزيع الحالة الإجمالي` : `${enriched.length} employees · sparkline = daily hours; donut = aggregate status mix`}
      />
      <div style={{ display:'flex', gap:24, padding:'10px 14px 4px', alignItems:'center', flexWrap:'wrap' }}>
        <div style={{ display:'flex', flexDirection:'column', alignItems:'center' }}>
          <svg width={SIZE} height={SIZE}>
            <circle cx={CX} cy={CY} r={R} fill="none" stroke="var(--bg-elev-2)" strokeWidth={14}/>
            {slices.map((s, i) => {
              if (!s.value) return null;
              const len = (s.value / totalAll) * C;
              const node = (
                <circle key={i} cx={CX} cy={CY} r={R} fill="none" stroke={s.color} strokeWidth={14}
                  strokeDasharray={`${len} ${C - len}`} strokeDashoffset={-off}
                  transform={`rotate(-90 ${CX} ${CY})`}>
                  <title>{s.label}: {s.value} {_rT("day-slots")}</title>
                </circle>
              );
              off += len;
              return node;
            })}
            <text x={CX} y={CY-3} textAnchor="middle" fontSize="14" fill="var(--text)" fontWeight="600">
              {Math.round((totalPresent / totalAll) * 100)}%
            </text>
            <text x={CX} y={CY+12} textAnchor="middle" fontSize="9" fill="var(--text-2)">{_rT("present")}</text>
          </svg>
        </div>
        <div style={{ display:'flex', flexDirection:'column', gap:6, fontSize:12 }}>
          {slices.map(s => (
            <div key={s.label} style={{ display:'flex', alignItems:'center', gap:8 }}>
              <span style={{ width:10, height:10, borderRadius:'50%', background: s.color }}/>
              <strong style={{ width:60 }}>{s.label}</strong>
              <span className="num mono">{s.value}</span>
              <span className="muted" style={{ fontSize:11 }}>
                {totalAll > 0 ? Math.round((s.value/totalAll)*100) : 0}%
              </span>
            </div>
          ))}
        </div>
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'1fr auto auto', gap:'4px 14px', padding:'10px 14px', alignItems:'center' }}>
        {enriched.map(u => (
          <React.Fragment key={u.user_id}>
            <div style={{ fontSize:13, display:'flex', flexDirection:'column' }}>
              <strong>{u.name}</strong>
              <span className="muted" style={{ fontSize:10 }}>
                {u.present}p · {u.partial}half · {u.absent}a
              </span>
            </div>
            <div style={{ minWidth:180 }}>
              <Sparkline data={u.days} w={180} h={32} color="var(--brand)" />
            </div>
            <div className="num mono" style={{ fontSize:13, fontWeight:600, textAlign:'right', minWidth:50 }}>
              {u.total.toFixed(1)}h
            </div>
          </React.Fragment>
        ))}
      </div>
    </div>
  );
}

function HoursDistributionChart({ period, refreshTick }) {
  // Independent dropdown so admins can scope the chart without touching
  // the page-wide 'period' that drives the report-generation form.
  const [localPeriod, setLocalPeriod] = useState(period || 'week');
  const [data, setData]     = useState([]);
  const [loading, setLoading] = useState(false);
  const toast = useToast();

  const fetchData = async (p) => {
    setLoading(true);
    try {
      const r = await fetch(`/api/reports/hours-distribution?period=${p}`, { credentials: 'same-origin' });
      if (!r.ok) throw new Error(`${r.status}`);
      const raw = await r.json();
      setData(Array.isArray(raw) ? raw : []);
    } catch(e) {
      toast.push({ msg: (_rAr() ? 'فشل جلب توزيع الساعات: ' : 'Hours distribution fetch failed: ') + e.message, kind: 'err' });
      setData([]);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => { fetchData(localPeriod); }, [localPeriod, refreshTick]);

  // Render a tiny dropdown above the chart so callers can override the period.
  const PERIOD_OPTIONS = [
    { value: 'day',      label: _rT('Day') },
    { value: 'week',     label: _rT('Week') },
    { value: 'month',    label: _rT('Month') },
    { value: 'quarter',  label: _rT('Quarter') },
    { value: '6_months', label: _rT('Semi') },
    { value: 'annual',   label: _rT('Annual') },
  ];
  const periodControl = (
    <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:10 }}>
      <span className="muted" style={{ fontSize:12 }}>{_rT("Range:")}</span>
      <select
        className="inp"
        style={{ width: 140, padding:'4px 8px', fontSize:12 }}
        value={localPeriod}
        onChange={e => setLocalPeriod(e.target.value)}
      >
        {PERIOD_OPTIONS.map(o => (
          <option key={o.value} value={o.value}>{o.label}</option>
        ))}
      </select>
    </div>
  );

  if (loading) return <>{periodControl}<Loading label={_rT("Loading…")} rows={3} /></>;
  if (!data.length) return <>{periodControl}<Empty icon={<Icon name="chart" size={24}/>} title={_rT("No hours data")} subtitle={_rT("No attendance hours logged for this period.")} /></>;

  const total = data.reduce((s, u) => s + (u.total || 0), 0);
  if (total === 0) return <>{periodControl}<Empty icon={<Icon name="chart" size={24}/>} title={_rT("No hours data")} subtitle={_rT("No attendance hours logged for this period.")} /></>;

  // Per-user pie of hours-share. Bigger users = bigger slices.
  const PALETTE = ['#3b82f6','#06b6d4','#8b5cf6','#f59e0b','#10b981','#f43f5e','#a3e635','#ec4899','#14b8a6','#eab308'];
  const SIZE = 220, R = SIZE/2 - 10, CX = SIZE/2, CY = SIZE/2;
  const C = 2 * Math.PI * R;
  let off = 0;
  const slices = data.map((u, i) => {
    const v = u.total || 0;
    if (!v) return null;
    const len = (v / total) * C;
    const node = (
      <circle key={i} cx={CX} cy={CY} r={R} fill="none" stroke={PALETTE[i % PALETTE.length]} strokeWidth={26}
        strokeDasharray={`${len} ${C - len}`} strokeDashoffset={-off}>
        <title>{`${u.name}: ${v.toFixed(1)}h (${Math.round(v/total*100)}%)`}</title>
      </circle>
    );
    off += len;
    return node;
  }).filter(Boolean);

  return (
    <>
      {periodControl}
      <div style={{ display:'flex', gap: 24, alignItems:'center', flexWrap:'wrap', padding:'12px 4px' }}>
      <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={26} />
        {slices}
        <text x={CX} y={CY-2} textAnchor="middle" fill="var(--text)" fontSize={20} fontWeight={700} fontFamily="var(--font-mono)">{total.toFixed(0)}h</text>
        <text x={CX} y={CY+16} textAnchor="middle" fill="var(--text-3)" fontSize={10} fontFamily="var(--font-mono)">{_rT("total")}</text>
      </svg>
      <div style={{ flex: 1, minWidth: 220, display:'flex', flexDirection:'column', gap: 6 }}>
        {/* Sort a COPY for display; color by ORIGINAL index so legend swatches
            match the pie slices (the pie above maps PALETTE[i] over `data`).
            The old code sorted `data` in place during render, desyncing the
            colors and mutating shared state. */}
        {[...data].sort((a,b) => (b.total||0) - (a.total||0)).map((u, i) => {
          const v = u.total || 0;
          const pct = total ? Math.round(v/total*100) : 0;
          const color = PALETTE[data.indexOf(u) % PALETTE.length];
          return (
            <div key={u.user_id || i} style={{ display:'flex', alignItems:'center', gap: 10, fontSize: 12 }}>
              <span style={{ display:'inline-block', width: 10, height: 10, borderRadius: 2, background: color, flexShrink:0 }} />
              <strong style={{ flex:1, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{u.name}</strong>
              <span className="mono" style={{ color:'var(--text-2)', minWidth: 50, textAlign:'right' }}>{v.toFixed(1)}h</span>
              <span className="mono muted" style={{ minWidth: 36, textAlign:'right' }}>{pct}%</span>
            </div>
          );
        })}
      </div>
    </div>
    </>
  );
}

/* ── Admin weekly digest card ── */
function AdminDigestCard({ refreshTick }) {
  const [digest, setDigest]   = useState(null);
  const [loading, setLoading] = useState(false);
  const toast = useToast();

  const fetchDigest = async () => {
    setLoading(true);
    try {
      const r = await fetch('/api/reports/admin-weekly-digest', { credentials: 'same-origin' });
      if (!r.ok) throw new Error(`${r.status}`);
      const data = await r.json();
      setDigest(data);
    } catch(e) {
      toast.push({ msg: (_rAr() ? 'فشل جلب الملخص: ' : 'Digest fetch failed: ') + e.message, kind: 'err' });
      setDigest(null);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => { fetchDigest(); }, [refreshTick]);

  return (
    <div className="card">
      <SectionHeader
        title={_rT("Admin weekly digest")}
          tip={_rT("Manager DM fired Mondays 09:00 local. Per-team summary: attendance, blockers, outliers. Triggered by _job_manager_weekly_digest.")}
        sub={digest ? (_rAr() ? `أسبوع ${digest.week_start} · أُنشئ في ${new Date(digest.generated_at).toLocaleString()}` : `Week of ${digest.week_start} · generated ${new Date(digest.generated_at).toLocaleString()}`) : _rT('Latest summary')}
        right={<Refresh onClick={fetchDigest} loading={loading} label={_rT("Refetch latest")} />}
      />
      {loading && <Loading label={_rT("Loading digest…")} rows={2} />}
      {!loading && !digest && (
        <Empty icon={<Icon name="chart" size={24}/>} title={_rT("No digest available")} subtitle={_rT("Digest generates automatically each week.")} />
      )}
      {!loading && digest && (
        <div>
          <table className="t" style={{ marginTop: 8 }}>
            <thead>
              <tr>
                <th>{_rT("Person")}</th>
                <th>{_rT("Team")}</th>
                <th style={{ textAlign:'right' }}>{_rT("Days present")}</th>
                <th style={{ textAlign:'right' }}>{_rT("Days late")}</th>
                <th style={{ textAlign:'right' }}>{_rT("Total hours")}</th>
                <th style={{ textAlign:'right' }}>{_rT("Violations")}</th>
              </tr>
            </thead>
            <tbody>
              {(digest.users || []).map(u => (
                <tr key={u.user_id}>
                  <td style={{ fontWeight: 500, fontSize: 13 }}>{u.display_name}</td>
                  <td className="muted" style={{ fontSize: 12 }}>{u.team || '—'}</td>
                  <td className="num" style={{ textAlign:'right' }}>{u.days_present}</td>
                  <td className="num" style={{ textAlign:'right', color: u.days_late > 0 ? 'var(--warn)' : undefined }}>{u.days_late}</td>
                  <td className="num" style={{ textAlign:'right' }}>{(u.total_hours || 0).toFixed(1)}h</td>
                  <td className="num" style={{ textAlign:'right', color: u.violations >= 3 ? 'var(--err)' : u.violations > 0 ? 'var(--warn)' : undefined }}>{u.violations}</td>
                </tr>
              ))}
              {(!digest.users || digest.users.length === 0) && (
                <tr><td colSpan={6}><div className="muted" style={{ padding:'8px 0', fontSize: 12 }}>{_rT("No employee data in this digest.")}</div></td></tr>
              )}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

/* ── Generate report form ── */
function GenerateReportForm({ employees, period, setPeriod }) {
  const [selectedUser, setSelectedUser] = useState('all');
  const [fmt, setFmt]   = useState('pdf');
  const [sendDm, setSendDm] = useState(false);
  const [busy, setBusy] = useState(false);
  const [execMonth, setExecMonth] = useState(() => new Date().toISOString().slice(0, 7));
  const [execBusy, setExecBusy] = useState(false);
  const toast = useToast();

  const handleExecSummary = async () => {
    setExecBusy(true);
    try {
      const r = await fetch(`/api/reports/executive-summary?month=${encodeURIComponent(execMonth)}`, { 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 = `executive-summary-${execMonth}.pdf`;
      a.click();
      // Defer revoke — Safari aborts in-progress downloads if revoked synchronously.
      setTimeout(() => URL.revokeObjectURL(link), 60000);
      toast.push({ msg: _rAr() ? 'تم تنزيل الملخص التنفيذي' : 'Executive summary downloaded', kind: 'ok' });
    } catch(e) {
      toast.push({ msg: _rAr() ? `فشل: ${e.message}` : `Failed: ${e.message}`, kind: 'err' });
    } finally {
      setExecBusy(false);
    }
  };

  const handleGenerate = async () => {
    setBusy(true);
    try {
      if (sendDm) {
        // Send via DM
        if (selectedUser === 'all') {
          const r = await fetch(`/api/reports/send-to-all?period=${period}`, {
            method: 'POST', credentials: 'same-origin',
          });
          if (!r.ok) throw new Error(await r.text());
          const data = await r.json();
          toast.push({ msg: _rAr() ? `تم إرسال التقرير إلى ${data.sent_count || 'الكل'} مستخدم عبر الرسائل المباشرة` : `Report sent to ${data.sent_count || 'all'} users via DM`, kind: 'ok' });
        } else {
          const r = await fetch(`/api/reports/send-to-user/${selectedUser}?period=${period}`, {
            method: 'POST', credentials: 'same-origin',
          });
          if (!r.ok) throw new Error(await r.text());
          const emp = employees.find(e => String(e.id) === String(selectedUser));
          toast.push({ msg: _rAr() ? `تم إرسال التقرير إلى ${emp?.display_name || selectedUser}` : `Report DM'd to ${emp?.display_name || selectedUser}`, kind: 'ok' });
        }
      } else {
        // Download
        const userQ = selectedUser === 'all' ? '' : `&user_id=${encodeURIComponent(selectedUser)}`;
        const url = `/api/summary/export?format=${fmt}&period=${period}${userQ}`;
        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;
        const suffix = selectedUser === 'all' ? 'all' : (employees.find(e => String(e.id) === String(selectedUser))?.display_name || selectedUser);
        a.download = `report-${suffix}-${period}.${fmt}`;
        a.click();
        // Defer revoke — Safari aborts in-progress downloads if revoked synchronously.
        setTimeout(() => URL.revokeObjectURL(link), 60000);
        toast.push({ msg: _rAr() ? `تم تنزيل تقرير ${fmt.toUpperCase()}` : `${fmt.toUpperCase()} report downloaded`, kind: 'ok' });
      }
    } catch(e) {
      toast.push({ msg: _rAr() ? `فشل: ${e.message}` : `Failed: ${e.message}`, kind: 'err' });
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="card">
      <SectionHeader
        title={_rT("Generate report")}
        sub={_rT("Download or send via DM")}
        tip={_rT("Builds a PDF report scoped to the selected employee + range. The PDF includes: hours-per-day line chart, attendance donut, PTO breakdown, top contributors bar, per-employee table. 'Send via DM' delivers the PDF to the employee's persistent DM with the bot.")}
      />
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 16, marginTop: 12 }}>
        <div>
          <label className="login-label">{_rT("Employee")}</label>
          <select className="sel" value={selectedUser} onChange={e => setSelectedUser(e.target.value)} style={{ width:'100%' }}>
            <option value="all">{_rT("All employees")}</option>
            {employees.map(e => <option key={e.id} value={e.id}>{e.display_name}</option>)}
          </select>
        </div>
        <div>
          <label className="login-label">{_rT("Period")}</label>
          <select className="sel" value={period} onChange={e => setPeriod(e.target.value)} style={{ width:'100%' }}>
            <option value="day">{_rT("Daily")}</option>
            <option value="week">{_rT("Weekly")}</option>
            <option value="month">{_rT("Monthly")}</option>
            <option value="quarter">{_rT("Quarterly")}</option>
            <option value="6_months">{_rT("Semi-annual (6 months)")}</option>
            <option value="annual">{_rT("Annual")}</option>
          </select>
        </div>
        <div>
          <label className="login-label">{_rT("Format")}</label>
          <Segmented value={fmt} onChange={setFmt} options={[{ value:'pdf', label:'PDF' }, { value:'xlsx', label:'XLSX' }]} />
        </div>
        <div style={{ display:'flex', flexDirection:'column', justifyContent:'flex-end' }}>
          <label className="login-label" style={{ display:'flex', alignItems:'center', gap: 8, cursor:'pointer' }}>
            <input
              type="checkbox"
              checked={sendDm}
              onChange={e => setSendDm(e.target.checked)}
              style={{ accentColor:'var(--brand)' }}
            />
            {_rT("Send via K9 DM instead of download")}
          </label>
        </div>
      </div>
      <div style={{ marginTop: 16 }}>
        <button className="btn btn-pri" onClick={handleGenerate} disabled={busy}>
          <Icon name="download" size={13}/>
          {busy ? _rT('Processing…') : sendDm ? _rT('Send via DM') : _rT('Download report')}
        </button>
      </div>
      <div style={{ marginTop: 18, paddingTop: 14, borderTop: '1px solid var(--line, rgba(128,128,128,.25))' }}>
        <SectionHeader
          title={_rT("Executive summary (monthly)")}
          sub={_rT("1-2 page org roll-up for leadership")}
          tip={_rT("Org-level monthly PDF: average efficiency + band distribution, hours delivered vs expected, strike hotspots, top/bottom performers, salary-impact exposure and recommended actions. Scoped to the employees you can access.")}
        />
        <div style={{ display:'flex', gap: 12, alignItems:'flex-end', marginTop: 10, flexWrap:'wrap' }}>
          <div>
            <label className="login-label">{_rT("Month")}</label>
            <input
              type="month"
              className="sel"
              value={execMonth}
              max={new Date().toISOString().slice(0, 7)}
              onChange={e => setExecMonth(e.target.value)}
            />
          </div>
          <button className="btn" onClick={handleExecSummary} disabled={execBusy || !execMonth}>
            <Icon name="download" size={13}/>
            {execBusy ? _rT('Processing…') : _rT('Download executive summary')}
          </button>
        </div>
      </div>
      <AttendanceDetailSection employees={employees} />
      <div className="muted" style={{ marginTop: 10, fontSize: 12 }}>
        {_rT("💬 Or just ask in chat — DM the bot \"send me the weekly report as xlsx\" and it's generated on the fly.")}
      </div>
    </div>
  );
}

/* ── Attendance detail (day-by-day) ── */
const DETAIL_STRIKE_LABELS = {
  late: 'Late sign-in strike',
  format: 'Standup format strike',
  attendance: 'Missed sign-off strike',
  late_reason_rejected: 'Late reason rejected',
  rescinded: 'Strike rescinded',
};

const DETAIL_STATUS_LABELS = {
  worked: 'worked',
  absent: 'absent',
  off_day: 'off',
  off_day_worked: 'off + work',
  holiday: 'holiday',
  excused: 'leave',
};

function AttendanceDetailSection({ employees }) {
  const [userId, setUserId] = useState('');
  const [month, setMonth] = useState(() => new Date().toISOString().slice(0, 7));
  const [busy, setBusy] = useState(false);
  const [pdfBusy, setPdfBusy] = useState(false);
  const [sendBusy, setSendBusy] = useState(false);
  const [sendTarget, setSendTarget] = useState('selected');
  const [data, setData] = useState(null);
  const toast = useToast();

  const loadDetail = async () => {
    if (!userId || !month) return;
    setBusy(true);
    try {
      const r = await fetch(
        `/api/reports/attendance-detail?user_id=${encodeURIComponent(userId)}&month=${encodeURIComponent(month)}`,
        { credentials: 'same-origin' });
      if (!r.ok) throw new Error(`${r.status}`);
      setData(await r.json());
    } catch (e) {
      toast.push({ msg: (_rAr() ? 'فشل جلب التفاصيل: ' : 'Detail fetch failed: ') + e.message, kind: 'err' });
      setData(null);
    } finally {
      setBusy(false);
    }
  };

  const downloadPdf = async () => {
    if (!userId || !month) return;
    setPdfBusy(true);
    try {
      const r = await fetch(
        `/api/reports/attendance-detail/pdf?user_id=${encodeURIComponent(userId)}&month=${encodeURIComponent(month)}`,
        { 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-detail-${month}.pdf`;
      a.click();
      // Defer revoke — Safari aborts in-progress downloads if revoked synchronously.
      setTimeout(() => URL.revokeObjectURL(link), 60000);
      toast.push({ msg: _rAr() ? 'تم تنزيل التقرير التفصيلي' : 'Detail report downloaded', kind: 'ok' });
    } catch (e) {
      toast.push({ msg: (_rAr() ? 'فشل: ' : 'Failed: ') + e.message, kind: 'err' });
    } finally {
      setPdfBusy(false);
    }
  };

  const teams = [...new Set(employees.map(e => e.team).filter(Boolean))].sort();

  const sendDm = async () => {
    if (!month) return;
    if (sendTarget === 'selected' && !userId) return;
    setSendBusy(true);
    try {
      let okMsg;
      if (sendTarget === 'selected') {
        const emp = employees.find(e => String(e.id) === String(userId));
        const r = await fetch(
          `/api/reports/attendance-detail/send?user_id=${encodeURIComponent(userId)}&month=${encodeURIComponent(month)}`,
          { method: 'POST', credentials: 'same-origin' });
        if (!r.ok) throw new Error(`${r.status}`);
        okMsg = _rAr()
          ? `تم إرسال التقرير التفصيلي إلى ${emp?.display_name || userId}`
          : `Detail report DM'd to ${emp?.display_name || userId}`;
      } else {
        const body = sendTarget === 'all'
          ? { scope: 'all', month }
          : { scope: 'team', team: sendTarget.slice(5), month };
        const r = await fetch('/api/reports/attendance-detail/send-bulk', {
          method: 'POST',
          credentials: 'same-origin',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(body),
        });
        if (!r.ok) throw new Error(`${r.status}`);
        const data = await r.json();
        okMsg = _rAr()
          ? `جارٍ إرسال ${data.scheduled} تقريرًا تفصيليًا عبر الرسائل المباشرة`
          : `Sending ${data.scheduled} detail report(s) via DM`;
      }
      toast.push({ msg: okMsg, kind: 'ok' });
    } catch (e) {
      toast.push({ msg: (_rAr() ? 'فشل الإرسال: ' : 'Send failed: ') + e.message, kind: 'err' });
    } finally {
      setSendBusy(false);
    }
  };

  const dayFlags = (d) => {
    const flags = [];
    if (d.was_late) flags.push(_rT('late'));
    if (d.missed_signoff) {
      flags.push(d.after_close_signoff
        ? `${_rT('no in-day sign-off — message at')} ${d.after_close_signoff}`
        : _rT('no sign-off'));
    }
    if (d.sign_off_note) flags.push(_rT('prev-day after-midnight sign-off'));
    (d.strikes || []).forEach(s => {
      flags.push((DETAIL_STRIKE_LABELS[s.kind] || s.kind) + (s.refunded ? ` (${_rT('refunded')})` : ''));
    });
    if (d.excuse_reason && (d.type === 'excused' || d.type === 'holiday')) flags.push(d.excuse_reason);
    return flags.join(' · ');
  };

  const summary = data?.summary;

  return (
    <div style={{ marginTop: 18, paddingTop: 14, borderTop: '1px solid var(--line, rgba(128,128,128,.25))' }}>
      <SectionHeader
        title={_rT("Attendance detail (day-by-day)")}
        sub={_rT("Every sign-in / sign-out with local times, per-day hours and every strike with its reason")}
        tip={_rT("Dispute-resolution transparency report. Shows each day's sign-in and sign-out in the employee's own timezone, hours vs expected, late flags (vs the employee's learned average start), missed sign-offs including after-midnight sign-off messages, and every strike labeled late / format / missed sign-off with refunds. Employees can pull their own; managers anyone in scope.")}
      />
      <div style={{ display:'flex', gap: 12, alignItems:'flex-end', marginTop: 10, flexWrap:'wrap' }}>
        <div>
          <label className="login-label">{_rT("Employee")}</label>
          <select className="sel" value={userId} onChange={e => { setUserId(e.target.value); setData(null); }}>
            <option value="">{_rT("Select employee…")}</option>
            {employees.map(e => <option key={e.id} value={e.id}>{e.display_name}</option>)}
          </select>
        </div>
        <div>
          <label className="login-label">{_rT("Month")}</label>
          <input
            type="month"
            className="sel"
            value={month}
            max={new Date().toISOString().slice(0, 7)}
            onChange={e => { setMonth(e.target.value); setData(null); }}
          />
        </div>
        <button className="btn" onClick={loadDetail} disabled={busy || !userId || !month}>
          {busy ? _rT('Loading…') : _rT('View detail')}
        </button>
        <button className="btn" onClick={downloadPdf} disabled={pdfBusy || !userId || !month}>
          <Icon name="download" size={13}/>
          {pdfBusy ? _rT('Processing…') : _rT('Download PDF')}
        </button>
        <div>
          <label className="login-label">{_rT("Send to")}</label>
          <select className="sel" value={sendTarget} onChange={e => setSendTarget(e.target.value)}>
            <option value="selected">{_rT("Selected employee")}</option>
            <option value="all">{_rT("All employees")}</option>
            {teams.map(t => <option key={t} value={`team:${t}`}>{_rT("Team")}: {t}</option>)}
          </select>
        </div>
        <button className="btn" onClick={sendDm}
                disabled={sendBusy || !month || (sendTarget === 'selected' && !userId)}
                title={_rT("DMs each recipient their own PDF + headline numbers via the bot (manager+)")}>
          {sendBusy ? _rT('Sending…') : _rT('Send via DM')}
        </button>
      </div>
      {data && summary && (
        <div style={{ marginTop: 14 }}>
          <div className="muted" style={{ fontSize: 12, marginBottom: 8 }}>
            {data.user.display_name} · {data.user.team || '—'} · {_rT("all times")} {data.user.timezone} ·{' '}
            {summary.expected_days} {_rT("expected days")} · {summary.day_hours.toFixed(1)}h /{' '}
            {summary.expected_hours.toFixed(0)}h
            {summary.hours_ratio_pct != null && ` (${summary.hours_ratio_pct.toFixed(0)}%)`} ·{' '}
            {_rT("late")} {summary.late_days} · {_rT("no in-day sign-off")} {summary.missed_signoff_days} ·{' '}
            {_rT("strikes")}: {summary.strikes.late} {_rT("late")}, {summary.strikes.format} {_rT("format")},{' '}
            {summary.strikes.attendance} {_rT("sign-off")}
            {summary.strikes.attendance_refunded > 0 && ` (+${summary.strikes.attendance_refunded} ${_rT("refunded")})`}
          </div>
          <div style={{ overflowX: 'auto' }}>
            <table className="tbl" style={{ width: '100%' }}>
              <thead>
                <tr>
                  <th>{_rT("Date")}</th>
                  <th>{_rT("Day")}</th>
                  <th>{_rT("Status")}</th>
                  <th style={{ textAlign:'right' }}>{_rT("In")}</th>
                  <th style={{ textAlign:'right' }}>{_rT("Out")}</th>
                  <th style={{ textAlign:'right' }}>{_rT("Hours")}</th>
                  <th>{_rT("Flags / notes")}</th>
                </tr>
              </thead>
              <tbody>
                {data.days.map(d => {
                  const quiet = d.type === 'off_day' || d.type === 'holiday' || d.type === 'excused';
                  return (
                    <tr key={d.date} style={quiet ? { opacity: 0.55 } : undefined}>
                      <td className="num" style={{ fontSize: 12 }}>{d.date}</td>
                      <td className="muted" style={{ fontSize: 12 }}>{d.weekday}</td>
                      <td style={{ fontSize: 12, color: d.type === 'absent' ? 'var(--err)' : undefined }}>
                        {_rT(DETAIL_STATUS_LABELS[d.type] || d.type)}
                      </td>
                      <td className="num" style={{ textAlign:'right', color: d.was_late ? 'var(--warn)' : undefined }}>
                        {d.sign_in || '—'}
                      </td>
                      <td className="num" style={{ textAlign:'right' }}>{d.sign_off || '—'}</td>
                      <td className="num" style={{ textAlign:'right' }}>{d.hours ? d.hours.toFixed(1) : '—'}</td>
                      <td style={{ fontSize: 12, color: (d.strikes || []).length || d.was_late || d.missed_signoff ? 'var(--warn)' : undefined }}>
                        {dayFlags(d) || <span className="muted">—</span>}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
          <div className="muted" style={{ marginTop: 8, fontSize: 11 }}>
            {_rT("A day closes at 23:55 local. Sign-offs sent after midnight show on the day they belong to but the day still counts as having no in-day sign-off (hours are estimated from activity). Late is measured against the employee's own learned average start, not a fixed clock.")}
          </div>
        </div>
      )}
    </div>
  );
}

/* ── Leaderboards section ── */
const LEADERBOARD_METRICS = [
  { value: 'progress',    label: 'Progress' },
  { value: 'attendance',  label: 'Attendance' },
  { value: 'blockers',    label: 'Blockers' },
  { value: 'hours_max',   label: 'Most hours' },
  { value: 'hours_min',   label: 'Fewest hours' },
  { value: 'absence',     label: 'Absences' },
];

const MEDALS = ['🥇', '🥈', '🥉'];

function LeaderboardsSection() {
  const [metric, setMetric]   = React.useState('progress');
  const [period, setPeriod]   = React.useState('week');
  const [rows, setRows]       = React.useState([]);
  const [loading, setLoading] = React.useState(false);
  const toast = useToast();

  const load = async (m, p) => {
    setLoading(true);
    try {
      const r = await fetch(`/api/leaderboard?metric=${m}&period=${p}&limit=10`, { credentials: 'same-origin' });
      if (!r.ok) throw new Error(`${r.status}`);
      setRows(await r.json());
    } catch(e) {
      toast.push({ msg: (_rAr() ? 'فشل جلب لوحة المتصدرين: ' : 'Leaderboard fetch failed: ') + e.message, kind: 'err' });
      setRows([]);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => { load(metric, period); }, [metric, period]);

  const isHours = metric === 'hours_max' || metric === 'hours_min';

  const formatValue = (v) => isHours ? `${Number(v).toFixed(1)}h` : String(v);

  return (
    <div className="card" style={{ marginBottom: 16 }}>
      <SectionHeader
        title={_rT("Leaderboards")}
        sub={_rT("Top 10 by metric and period")}
        tip={_rT("Same data as Today's Top 10 widget but with selectable period (week / month / quarter / 6 months / year / annual). Switch metric to compare attendance, hours, blockers, absences, or progress score across employees.")}
      />

      {/* Metric tabs */}
      <div style={{ display:'flex', gap: 6, flexWrap:'wrap', marginTop: 12, marginBottom: 12 }}>
        {LEADERBOARD_METRICS.map(m => (
          <button
            key={m.value}
            className={metric === m.value ? 'btn btn-pri' : 'btn btn-ghost'}
            style={{ padding:'4px 12px', fontSize: 12 }}
            onClick={() => setMetric(m.value)}
          >
            {_rT(m.label)}
          </button>
        ))}
      </div>

      {/* Period radio */}
      <div style={{ display:'flex', gap: 12, marginBottom: 14, alignItems:'center' }}>
        <span className="muted" style={{ fontSize: 12 }}>{_rT("Period:")}</span>
        {[['day','Day'],['week','Week'],['month','Month'],['quarter','Quarter'],['6_months','Semi'],['annual','Annual']].map(([v,l]) => (
          <label key={v} style={{ fontSize: 12, cursor:'pointer', display:'flex', alignItems:'center', gap: 4 }}>
            <input type="radio" name="lb-period" value={v} checked={period === v} onChange={() => setPeriod(v)} />
            {_rT(l)}
          </label>
        ))}
      </div>

      {/* Results */}
      {loading && (
        <div style={{ display:'flex', flexDirection:'column', gap: 8 }}>
          {[...Array(5)].map((_,i) => (
            <div key={i} style={{ height: 36, background:'var(--bg-elev-2)', borderRadius:'var(--r-sm)', opacity: 0.5 + i*0.1 }} />
          ))}
        </div>
      )}
      {!loading && rows.length === 0 && (
        <Empty icon={<Icon name="chart" size={24}/>} title={_rT("No data")} subtitle={_rT("No records found for this metric and period.")} />
      )}
      {!loading && rows.length > 0 && (
        <div style={{ display:'flex', flexDirection:'column', gap: 6 }}>
          {rows.map((row) => {
            const medal = row.rank <= 3 ? MEDALS[row.rank - 1] : null;
            const avatarEl = (window.resolveAvatarUrl && (() => {
              const url = window.resolveAvatarUrl({ avatar_url: row.avatar_url });
              return url
                ? <img src={url} style={{ width:28, height:28, borderRadius:'50%', objectFit:'cover', flexShrink:0 }} onError={e => { e.target.style.display='none'; }} />
                : <div style={{ width:28, height:28, borderRadius:'50%', background:'var(--bg-elev-3)', flexShrink:0, display:'flex', alignItems:'center', justifyContent:'center', fontSize:11, color:'var(--text-3)' }}>{(row.display_name||'?')[0].toUpperCase()}</div>;
            })()) || <div style={{ width:28, height:28, borderRadius:'50%', background:'var(--bg-elev-3)', flexShrink:0 }} />;

            return (
              <div key={row.user_id} style={{ display:'flex', alignItems:'center', gap: 10, padding:'6px 10px', background:'var(--bg-elev-2)', borderRadius:'var(--r-sm)', border:'1px solid var(--line)' }}>
                <span style={{ width: 28, textAlign:'center', fontWeight: 700, fontSize: 13, color: medal ? 'inherit' : 'var(--text-3)', flexShrink:0 }}>
                  {medal || `#${row.rank}`}
                </span>
                {avatarEl}
                <span style={{ flex: 1, fontSize: 13, fontWeight: 500, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                  {row.display_name}
                </span>
                <span className="mono" style={{ fontSize: 13, fontWeight: 700, color:'var(--brand)', flexShrink:0 }}>
                  {formatValue(row.value)}
                </span>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

/* ── Impact / business-value summary card (measured, honest numbers) ── */
function ImpactCard({ refreshTick }) {
  const [data, setData]       = useState(null);
  const [loading, setLoading] = useState(false);
  const toast = useToast();

  const load = React.useCallback(async () => {
    setLoading(true);
    try {
      const r = await fetch('/api/impact/summary', { credentials: 'same-origin' });
      if (!r.ok) throw new Error(parseDetail(await r.json().catch(() => null), r.status));
      setData(await r.json());
    } catch (e) {
      toast.push({ msg: (_rAr() ? 'فشل جلب ملخص الأثر: ' : 'Impact summary fetch failed: ') + e.message, kind: 'err' });
      setData(null);
    } finally {
      setLoading(false);
    }
  }, []);

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

  const productivity = data && data.productivity;
  const syncCalls = data && data.sync_calls;
  const blockers = data && data.blockers;
  const pto = data && data.pto;
  const pctChange = productivity && productivity.pct_change;
  const pctUp = pctChange != null && pctChange >= 0;

  const NotEnoughData = () => (
    <div className="muted" style={{ fontSize: 13, marginTop: 4 }}>{_rT('Not enough data yet')}</div>
  );

  const tileStyle = {
    flex: '1 1 220px', background: 'var(--bg-elev-2)', border: '1px solid var(--line)',
    borderRadius: 'var(--r-sm)', padding: '10px 14px',
  };
  const labelStyle = { fontSize: 10.5, textTransform: 'uppercase', letterSpacing: .4 };

  return (
    <div className="card" style={{ marginTop: 16 }}>
      <SectionHeader
        title={_rT("Impact")}
        tip={_rT("Measured business-value snapshot since bot adoption — productivity change, sync-call hours saved, blocker resolution speed, and PTO handled. Every figure comes straight from recorded activity; anything without enough data shows 'not enough data yet' rather than a fabricated number.")}
        sub={data && data.adoption_week_start
          ? (_rAr() ? `منذ أسبوع الاعتماد ${data.adoption_week_start} · ${data.weeks_measured} أسبوع مقاس` : `Since adoption week of ${data.adoption_week_start} · ${data.weeks_measured} weeks measured`)
          : _rT('Since bot adoption')}
        right={<Refresh onClick={load} loading={loading} />}
      />

      {loading && <Loading label={_rT("Loading…")} rows={2} />}
      {!loading && !data && (
        <Empty icon={<Icon name="chart" size={24}/>} title={_rT("No impact data yet")} subtitle={_rT("Nothing measurable recorded since adoption yet.")} />
      )}

      {!loading && data && (
        <>
          <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginTop: 8 }}>
            {/* Productivity since adoption */}
            <div style={tileStyle}>
              <div className="muted" style={labelStyle}>{_rT('Productivity since adoption')}</div>
              {pctChange == null ? <NotEnoughData /> : (
                <>
                  <div style={{ fontSize: 22, fontWeight: 700, color: pctUp ? 'var(--ok)' : 'var(--err)' }}>
                    {pctUp ? '↑' : '↓'} {pctUp ? '+' : ''}{pctChange.toFixed(1)}%
                  </div>
                  <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>
                    {_rAr()
                      ? `أول 4 أسابيع ${productivity.first4_avg.toFixed(1)}% ← آخر 4 أسابيع ${productivity.last4_avg.toFixed(1)}%`
                      : `first 4 weeks avg ${productivity.first4_avg.toFixed(1)}% → last 4 weeks avg ${productivity.last4_avg.toFixed(1)}%`}
                  </div>
                  {/* Like-for-like both sides (server-computed): the published
                      Efficiency% is not comparable across the migration — see
                      productivity._CMP_WEIGHTS. */}
                  {productivity.before_cutover_avg != null && (
                    <div className="muted" style={{ fontSize: 10.5, marginTop: 2 }}>
                      {_rAr()
                        ? `قبل البوت: ${productivity.before_cutover_avg.toFixed(1)}% · مع البوت: ${productivity.after_cutover_avg != null ? productivity.after_cutover_avg.toFixed(1) + '%' : '—'}${productivity.after_cutover_recent_avg != null ? ` · آخر ٣ أسابيع: ${productivity.after_cutover_recent_avg.toFixed(1)}%` : ''} (مقارنة متكافئة)`
                        : `before bot: ${productivity.before_cutover_avg.toFixed(1)}% · with bot: ${productivity.after_cutover_avg != null ? productivity.after_cutover_avg.toFixed(1) + '%' : '—'}${productivity.after_cutover_recent_avg != null ? ` · last 3 wks: ${productivity.after_cutover_recent_avg.toFixed(1)}%` : ''} (like-for-like)`}
                    </div>
                  )}
                </>
              )}
            </div>

            {/* Sync-call hours saved */}
            <div style={tileStyle}>
              <div className="muted" style={labelStyle}>{_rT('Sync-call hours saved')}</div>
              {!syncCalls || syncCalls.hours_saved == null ? <NotEnoughData /> : (
                <>
                  <div style={{ fontSize: 22, fontWeight: 700 }}>{syncCalls.hours_saved.toFixed(1)}h</div>
                  <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>
                    {_rAr()
                      ? `${syncCalls.standups_recorded} تسجيل يومي × ${syncCalls.call_minutes_per_standup} د`
                      : `${syncCalls.standups_recorded} standups × ${syncCalls.call_minutes_per_standup} min`}
                  </div>
                </>
              )}
            </div>

            {/* Blocker resolution */}
            <div style={tileStyle}>
              <div className="muted" style={labelStyle}>{_rT('Blocker resolution')}</div>
              {!blockers || blockers.avg_resolution_hours == null ? <NotEnoughData /> : (
                <>
                  <div style={{ fontSize: 22, fontWeight: 700 }}>
                    {blockers.avg_resolution_hours.toFixed(1)}h{' '}
                    <span className="muted" style={{ fontSize: 13, fontWeight: 400 }}>
                      {_rT('vs')} {blockers.baseline_days}d {_rT('baseline')}
                    </span>
                  </div>
                  <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>
                    {_rAr()
                      ? `${blockers.resolved_count} تم حله · الوسيط ${blockers.median_resolution_hours != null ? blockers.median_resolution_hours.toFixed(1) + 'h' : '—'}`
                      : `${blockers.resolved_count} resolved · median ${blockers.median_resolution_hours != null ? blockers.median_resolution_hours.toFixed(1) + 'h' : '—'}`}
                  </div>
                </>
              )}
            </div>

            {/* PTO handled */}
            <div style={tileStyle}>
              <div className="muted" style={labelStyle}>{_rT('PTO handled')}</div>
              {!pto ? <NotEnoughData /> : (
                <>
                  <div style={{ fontSize: 22, fontWeight: 700 }}>{pto.requests_handled}</div>
                  <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>
                    {_rAr() ? `${pto.pto_blocks} فترة إجازة` : `${pto.pto_blocks} PTO block${pto.pto_blocks === 1 ? '' : 's'}`}
                  </div>
                </>
              )}
            </div>
          </div>

          <div className="muted" style={{ fontSize: 10.5, marginTop: 12 }}>
            {_rT("All figures measured from recorded activity — see Help → Business value.")}
          </div>
        </>
      )}
    </div>
  );
}

/* ── Main ReportsPage ── */
function ReportsPage() {
  const D = window.STATUS_DATA;
  const toast = useToast ? useToast() : { push: () => {} };
  const [period, setPeriod] = React.useState('week');
  const [refreshTick, setRefreshTick] = React.useState(0);
  const toastTop = useToast ? useToast() : { push: () => {} };

  const employees = D.EMPLOYEES || [];

  const handleRefresh = () => {
    setRefreshTick(t => t + 1);
    try {
      window.dispatchEvent(new CustomEvent('status-data-refresh-requested'));
      if (typeof window.STATUS_REFRESH === 'function') window.STATUS_REFRESH();
    } catch (e) {}
    toastTop.push({ msg: _rT('Refresh complete'), kind: 'ok' });
  };

  return (
    <div className="page">
      <PageHeader
        title={_rT("Reports")}
        meta={_rT("Generate, download, or send reports via DM")}
        right={
          <div style={{ display:'flex', gap: 8, alignItems:'center' }}>
            <Refresh onClick={handleRefresh} loading={false} />
            <Segmented
              value={period}
              onChange={setPeriod}
              options={[
                { value:'day',      label:_rT('Daily') },
                { value:'week',     label:_rT('Weekly') },
                { value:'month',    label:_rT('Monthly') },
                { value:'quarter',  label:_rT('Quarter') },
                { value:'6_months', label:_rT('Semi') },
                { value:'annual',   label:_rT('Annual') },
              ]}
            />
          </div>
        }
      />

      {/* ── Visuals first ────────────────────────────────────────────────
          Charts lead the page; tables, then forms/admin tooling follow.
          Previously PerUserTrends and Hours-distribution were stranded at the
          very bottom, below the report form, digest and two violation tables —
          so the charts people actually open this page for were several screens
          down. Anything that renders a graph now sits above anything that
          renders a list or a form. */}

      {/* Charts */}
      <ChartsSection />

      {/* Productivity trend: company / team / employee weekly Efficiency% */}
      <ProductivityTrendCard refreshTick={refreshTick} />

      {/* Per-user trends (#25) */}
      <PerUserTrendsCard refreshTick={refreshTick} />

      {/* Hours distribution */}
      <div className="card" style={{ marginTop: 16 }}>
        <SectionHeader
          title={_rT("Hours distribution")}
          tip={_rT("Histogram of daily-hours bins across all employees in the range. Spot under/over-work patterns.")}
          sub={_rT("Per employee — stacked by day · use the Range dropdown to scope")}
        />
        <HoursDistributionChart period={period} refreshTick={refreshTick} />
      </div>

      {/* ── Summaries & tables ─────────────────────────────────────────── */}

      {/* Impact / business-value summary — measured, honest numbers */}
      <ImpactCard refreshTick={refreshTick} />

      {/* Leaderboards */}
      <LeaderboardsSection />

      {/* Violations (#25) */}
      <ViolationsSection />

      {/* B7: per-employee violations report with type filter */}
      <PerEmployeeViolationsCard refreshTick={refreshTick} />

      {/* ── Tooling: generate / schedule / digest ──────────────────────── */}

      {/* On-call standings moved to the On-call tab — see page-oncall.jsx.
          The component itself stays defined below so window.OnCallStandingsSection
          (set at the function's bottom) remains importable from the On-call
          page bundle without a duplicate definition. */}

      {/* Generate report form */}
      <GenerateReportForm employees={employees} period={period} setPeriod={setPeriod} />

      {/* Admin digest */}
      <div style={{ marginTop: 16 }}>
        <AdminDigestCard refreshTick={refreshTick} />
      </div>

      {/* Scheduled reports (#18) */}
      <AutoReportSchedule />
    </div>
  );
}

/* ── Per-user picker for a scheduled report row ── */
// Admin chooses individual recipients OR "all employees together" with one
// click. Replaces the old comma-separated user-id text input with a real
// employee checklist (name + matrix_id) sourced from window.STATUS_DATA.
function ScheduledReportUserPicker({ selectedIds, onSave, onCancel }) {
  const D = window.STATUS_DATA || {};
  const employees = (D.EMPLOYEES || []).filter(e => !e.is_test_account);
  const [sel, setSel] = useState(() => new Set((selectedIds || []).map(String)));
  const [q, setQ]     = useState('');

  const toggle = (id) => setSel(prev => {
    const next = new Set(prev);
    const s = String(id);
    if (next.has(s)) next.delete(s); else next.add(s);
    return next;
  });
  const selectAll = () => setSel(new Set(employees.map(e => String(e.id))));
  const clearAll  = () => setSel(new Set());

  const filtered = employees.filter(e => {
    const t = q.trim().toLowerCase();
    if (!t) return true;
    return (e.display_name || '').toLowerCase().includes(t)
        || (e.matrix_id    || '').toLowerCase().includes(t);
  });

  return (
    <div>
      <div style={{ display:'flex', alignItems:'center', gap: 10, marginBottom: 8 }}>
        <input className="inp" placeholder={_rT("Search…")} value={q}
          onChange={e => setQ(e.target.value)}
          style={{ flex: 1, fontSize: 12, padding:'4px 8px' }} />
        <button className="btn btn-ghost" style={{ fontSize: 11, padding:'3px 8px' }}
          onClick={selectAll}>{_rAr() ? `تحديد الكل (${employees.length})` : `Select all (${employees.length})`}</button>
        <button className="btn btn-ghost" style={{ fontSize: 11, padding:'3px 8px' }}
          onClick={clearAll}>{_rT("Clear")}</button>
        <span className="muted" style={{ fontSize: 11 }}>{_rAr() ? `${sel.size} محدد` : `${sel.size} picked`}</span>
      </div>
      <div style={{
        maxHeight: 220, overflowY:'auto',
        border:'1px solid var(--line)', borderRadius:'var(--r-sm)',
        background:'var(--bg)',
        display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(220px, 1fr))',
        padding: 6, gap: 2,
      }}>
        {filtered.length === 0 && (
          <div className="muted" style={{ fontSize: 12, padding: 8 }}>{_rT("No employees match")}</div>
        )}
        {filtered.map(e => {
          const on = sel.has(String(e.id));
          return (
            <label key={e.id} style={{
              display:'flex', alignItems:'center', gap: 8, padding:'4px 8px',
              fontSize: 12.5, cursor:'pointer', borderRadius:'var(--r-xs)',
              background: on ? 'var(--brand-soft)' : 'transparent',
            }}>
              <input type="checkbox" checked={on} onChange={() => toggle(e.id)} />
              <span style={{ flex: 1, color:'var(--text)' }}>{e.display_name}</span>
              <span className="mono" style={{ fontSize: 10.5, color:'var(--text-3)' }}>
                {e.matrix_id}
              </span>
            </label>
          );
        })}
      </div>
      <div style={{ display:'flex', justifyContent:'flex-end', gap: 8, marginTop: 10 }}>
        <button className="btn btn-ghost" onClick={onCancel} style={{ fontSize: 12 }}>
          {_rT("Cancel")}
        </button>
        <button className="btn btn-pri" onClick={() => onSave([...sel])} style={{ fontSize: 12 }}>
          {_rAr() ? `حفظ ${sel.size} مستلم` : `Save ${sel.size} recipient${sel.size === 1 ? '' : 's'}`}
        </button>
      </div>
    </div>
  );
}

/* ── Auto-Report Scheduling panel (Bug 18 — restored + scheduler jobs table) ── */
function AutoReportSchedule() {
  // Q10: full admin-managed scheduled-report surface.
  // GET /api/scheduled-reports  → list items with enabled, recipients_kind,
  //   recipient_ids, channel, last_run_at/status/count, next_run_at.
  // PATCH /api/scheduled-reports/{cadence_key} updates a single row.
  // POST /api/scheduled-reports/{cadence_key}/run-now fires immediately.
  const [items, setItems] = useState([]);
  const [isAdmin, setIsAdmin] = useState(true);
  const [busy, setBusy] = useState(null);
  const [jobs, setJobs] = useState([]);
  const [editing, setEditing] = useState(null);  // cadence_key being edited
  const toast = useToast();

  const reload = async () => {
    try {
      const r = await fetch('/api/scheduled-reports', { credentials: 'same-origin' });
      if (r.status === 403) { setIsAdmin(false); return; }
      if (r.ok) { const d = await r.json(); setItems(d.items || []); setIsAdmin(true); }
    } catch(_) {}
  };

  const reloadJobs = async () => {
    try {
      const r = await fetch('/api/server-health', { credentials: 'same-origin' });
      if (r.ok) {
        const d = await r.json();
        setJobs(Array.isArray(d.scheduler_jobs) ? d.scheduler_jobs : []);
      }
    } catch(_) {}
  };

  useEffect(() => { reload(); reloadJobs(); }, []);

  const patch = async (cadence_key, body) => {
    setBusy(cadence_key);
    try {
      const r = await fetch(`/api/scheduled-reports/${cadence_key}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify(body),
      });
      if (!r.ok) throw new Error(parseDetail(await r.json().catch(() => null), r.status));
      toast.push({ msg: _rAr() ? `تم تحديث ${cadence_key}` : `Updated ${cadence_key}`, kind: 'ok' });
      await reload();
    } catch(e) {
      toast.push({ msg: (_rAr() ? 'فشل التحديث: ' : 'Update failed: ') + e.message, kind: 'err' });
    } finally { setBusy(null); }
  };

  const runNow = async (cadence_key) => {
    setBusy(cadence_key);
    try {
      const r = await fetch(`/api/scheduled-reports/${cadence_key}/run-now`, {
        method: 'POST', credentials: 'same-origin',
      });
      if (!r.ok) throw new Error(parseDetail(await r.json().catch(() => null), r.status));
      toast.push({ msg: _rT('Report fired'), kind: 'ok' });
      await reload();
    } catch(e) {
      toast.push({ msg: (_rAr() ? 'فشل التشغيل: ' : 'Run failed: ') + e.message, kind: 'err' });
    } finally { setBusy(null); }
  };

  const fmtRel = (iso) => {
    if (!iso) return '—';
    try {
      const d = new Date(iso);
      const diff = (d.getTime() - Date.now()) / 1000;
      const abs = Math.abs(diff);
      const ar = _rAr();
      const sign = diff < 0 ? (ar ? 'مضت' : 'ago') : (ar ? 'خلال' : 'in');
      if (abs < 60)      return diff < 0 ? (ar ? 'الآن' : 'just now') : (ar ? 'الآن' : 'now');
      if (abs < 3600)    return ar ? `${sign} ${Math.round(abs/60)} دقيقة` : `${Math.round(abs/60)}m ${sign}`;
      if (abs < 86400)   return ar ? `${sign} ${Math.round(abs/3600)} ساعة` : `${Math.round(abs/3600)}h ${sign}`;
      return d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'});
    } catch (_) { return iso; }
  };

  return (
    <div className="card" style={{ marginTop: 16 }}>
      <SectionHeader
        title={_rT("Scheduled reports")}
        tip={_rT("Admin-managed scheduled report dispatch. Toggle each cadence on/off, pick recipients (employees / admins / specific users / room), pick channel, and see last-run + next-run.")}
        sub={_rT("Each cadence runs on its cron; PATCH updates take effect on the next firing.")}
        right={<Refresh onClick={() => { reload(); reloadJobs(); }} loading={false} label={_rT("Refresh")} />}
      />

      {!isAdmin ? (
        <div className="muted" style={{ fontSize: 13, padding: '8px 0' }}>
          {_rT("Admins only. Ask an admin to configure scheduled reports.")}
        </div>
      ) : (
        <table className="t" style={{ fontSize: 12, marginBottom: 16 }}>
          <thead>
            <tr>
              <th>{_rT("Cadence")}</th><th>{_rT("Cron")}</th><th>{_rT("Enabled")}</th>
              <th>{_rT("Recipients")}</th><th>{_rT("Channel")}</th>
              <th>{_rT("Last run")}</th><th>{_rT("Next run")}</th><th></th>
            </tr>
          </thead>
          <tbody>
            {items.map(it => (
              <React.Fragment key={it.cadence_key}>
                <tr>
                  <td style={{ fontWeight: 600 }}>{it.label}</td>
                  <td className="mono muted" style={{ fontSize: 11 }}>{it.cron}</td>
                  <td>
                    <label style={{ display:'flex', alignItems:'center', gap:6, cursor: busy === it.cadence_key ? 'wait' : 'pointer' }}>
                      <input type="checkbox" checked={!!it.enabled}
                        disabled={busy === it.cadence_key}
                        onChange={e => patch(it.cadence_key, { enabled: e.target.checked })} />
                      <span style={{ fontSize:11, color: it.enabled ? 'var(--ok)' : 'var(--text-3)' }}>
                        {it.enabled ? _rT('ON') : _rT('OFF')}
                      </span>
                    </label>
                  </td>
                  <td>
                    <select className="sel" value={it.recipients_kind}
                      disabled={busy === it.cadence_key}
                      onChange={e => patch(it.cadence_key, { recipients_kind: e.target.value })}
                      style={{ fontSize: 11, padding: '2px 6px' }}>
                      <option value="employees">{_rT("Every employee")}</option>
                      <option value="admins">{_rT("Admins only")}</option>
                      <option value="users">{_rT("Specific users")}</option>
                      <option value="room">{_rT("Matrix room")}</option>
                    </select>
                    {(it.recipients_kind === 'users' || it.recipients_kind === 'room') && (
                      <button className="btn btn-ghost"
                        style={{ marginLeft: 6, padding: '1px 6px', fontSize: 11 }}
                        onClick={() => setEditing(editing === it.cadence_key ? null : it.cadence_key)}>
                        {_rAr() ? `${(it.recipient_ids || []).length} محدد` : `${(it.recipient_ids || []).length} set`}
                      </button>
                    )}
                  </td>
                  <td>
                    <select className="sel" value={it.channel}
                      disabled={busy === it.cadence_key}
                      onChange={e => patch(it.cadence_key, { channel: e.target.value })}
                      style={{ fontSize: 11, padding: '2px 6px' }}>
                      <option value="dm">{_rT("DM")}</option>
                      <option value="room">{_rT("Room post")}</option>
                    </select>
                  </td>
                  <td className="mono" style={{ fontSize: 11 }}>
                    <span title={it.last_run_at || ''}>{fmtRel(it.last_run_at)}</span>
                    {it.last_run_status && (
                      <span style={{
                        marginLeft: 6, fontSize: 10, padding: '1px 5px',
                        borderRadius: 3,
                        background: it.last_run_status === 'ok' ? 'rgba(20,180,80,.15)' :
                                    it.last_run_status === 'partial' ? 'rgba(220,180,30,.18)' : 'rgba(220,80,80,.18)',
                        color: it.last_run_status === 'ok' ? 'var(--ok)' :
                               it.last_run_status === 'partial' ? '#daa30a' : 'var(--err)',
                      }}>{it.last_run_status} · {it.last_run_count}</span>
                    )}
                  </td>
                  <td className="mono muted" style={{ fontSize: 11 }} title={it.next_run_at || ''}>
                    {fmtRel(it.next_run_at)}
                  </td>
                  <td>
                    <button className="btn btn-ghost" style={{ padding:'2px 8px', fontSize:11 }}
                      disabled={busy === it.cadence_key}
                      onClick={() => runNow(it.cadence_key)}>{_rT("Run now")}</button>
                  </td>
                </tr>
                {editing === it.cadence_key && (
                  <tr>
                    <td colSpan={8} style={{ background:'var(--bg-elev-2)', padding:'10px 12px' }}>
                      {it.recipients_kind === 'room' ? (
                        <>
                          <div style={{ fontSize:11, color:'var(--text-3)', marginBottom:4 }}>
                            {_rT("Matrix room ID (e.g. !abc:k9.ms):")}
                          </div>
                          <input className="inp" defaultValue={(it.recipient_ids || [])[0] || ''}
                            style={{ width: '100%', fontSize: 12, fontFamily: 'var(--font-mono)' }}
                            onBlur={e => {
                              const v = e.target.value.trim();
                              patch(it.cadence_key, { recipient_ids: v ? [v] : [] });
                              setEditing(null);
                            }} />
                        </>
                      ) : (
                        // Per-user / all-users picker. Checklist of every
                        // employee — admin ticks the ones who should
                        // receive this cadence. "Select all" + "Clear"
                        // shortcuts make all-users vs per-user trivial.
                        <ScheduledReportUserPicker
                          selectedIds={it.recipient_ids || []}
                          onSave={(ids) => {
                            patch(it.cadence_key, { recipient_ids: ids });
                            setEditing(null);
                          }}
                          onCancel={() => setEditing(null)}
                        />
                      )}
                    </td>
                  </tr>
                )}
              </React.Fragment>
            ))}
          </tbody>
        </table>
      )}

      {jobs.length > 0 && (
        <>
          <div style={{ fontSize:12, fontWeight:600, color:'var(--text-2)', marginBottom:8, marginTop: isAdmin ? 8 : 0 }}>
            {_rT("Active scheduler jobs")}
          </div>
          <table className="t" style={{ fontSize:12 }}>
            <thead>
              <tr>
                <th>{_rT("Job name")}</th>
                <th>{_rT("Cron / trigger")}</th>
                <th>{_rT("Next run")}</th>
              </tr>
            </thead>
            <tbody>
              {jobs.map((j, i) => (
                <tr key={i}>
                  <td style={{ fontWeight:500 }}>{j.name}</td>
                  <td className="mono muted" style={{ fontSize:11 }}>{j.cron || '—'}</td>
                  <td className="mono" style={{ fontSize:11 }}>
                    {j.next ? new Date(j.next).toLocaleString() : '—'}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </>
      )}
    </div>
  );
}

/* ── Charts: period → API range param mapping ── */
const CHART_PERIODS = [
  { value: 'day',   label: 'Day',   range: 'daily'   },
  { value: 'week',  label: 'Week',  range: 'weekly'  },
  { value: 'month', label: 'Month', range: 'monthly' },
];

/* ── Chart 1: Stacked bar — daily attendance breakdown ── */
function AttendanceStackedBar({ range }) {
  const [data, setData]     = useState(null);
  const [loading, setLoading] = useState(false);
  const toast = useToast();

  useEffect(() => {
    if (!range) return;
    setLoading(true);
    Promise.allSettled([
      fetch(`/api/reports/timeseries?range=${range}&metric=present`, { credentials: 'same-origin' }).then(r => r.ok ? r.json() : Promise.reject(r.status)),
      fetch(`/api/reports/timeseries?range=${range}&metric=late`,    { credentials: 'same-origin' }).then(r => r.ok ? r.json() : Promise.reject(r.status)),
    ]).then((results) => {
      const presentData = results[0].status === 'fulfilled' ? results[0].value : { labels: [], datasets: [] };
      const lateData    = results[1].status === 'fulfilled' ? results[1].value : { labels: [], datasets: [] };
      const _ts_anyOk = results[0].status === 'fulfilled' || results[1].status === 'fulfilled';
      if (!_ts_anyOk) { setLoading(false); return; }
      // Continue with whichever metric(s) succeeded.
      void _ts_anyOk;
      const labels = presentData.labels || [];
      const numUsers = presentData.datasets ? presentData.datasets.length : 0;

      // Sum across users per day
      const presentPerDay = labels.map((_, i) =>
        (presentData.datasets || []).reduce((s, ds) => s + (ds.data[i] || 0), 0)
      );
      const latePerDay = labels.map((_, i) =>
        (lateData.datasets || []).reduce((s, ds) => s + (ds.data[i] || 0), 0)
      );
      // on_time = present but not late
      const onTimePerDay = labels.map((_, i) => Math.max(0, presentPerDay[i] - latePerDay[i]));
      const absentPerDay = labels.map((_, i) => Math.max(0, numUsers - presentPerDay[i]));

      setData({ labels, onTimePerDay, latePerDay, absentPerDay, numUsers });
    }).catch(e => {
      toast.push({ msg: (_rAr() ? 'فشل جلب مخطط الحضور: ' : 'Attendance chart fetch failed: ') + e, kind: 'err' });
      setData(null);
    }).finally(() => setLoading(false));
  }, [range]);

  if (loading) return <Loading label={_rT("Loading…")} rows={3} />;
  if (!data || !data.labels.length) return <Empty icon={<Icon name="chart" size={24}/>} title={_rT("No attendance data")} subtitle={_rT("No records for this period.")} />;

  const { labels, onTimePerDay, latePerDay, absentPerDay } = data;
  const maxVal = Math.max(...labels.map((_, i) => onTimePerDay[i] + latePerDay[i] + absentPerDay[i]), 1);

  const SVG_W = 480, SVG_H = 180, PAD_L = 28, PAD_B = 36, PAD_T = 12, PAD_R = 12;
  const chartW = SVG_W - PAD_L - PAD_R;
  const chartH = SVG_H - PAD_B - PAD_T;
  const n = labels.length;
  const barW = Math.max(4, Math.min(30, (chartW / n) * 0.7));
  const gap   = chartW / n;

  const toY  = (v) => PAD_T + chartH - (v / maxVal) * chartH;
  const toH  = (v) => (v / maxVal) * chartH;

  // X-axis labels — show every Nth
  const labelStep = Math.ceil(n / 8);
  const fmtLabel = (iso) => {
    const d = new Date(iso + 'T00:00:00');
    return `${d.getMonth()+1}/${d.getDate()}`;
  };

  const COLORS = { onTime: '#10b981', late: '#f59e0b', absent: '#f43f5e' };

  return (
    <div>
      <svg width="100%" viewBox={`0 0 ${SVG_W} ${SVG_H}`} style={{ overflow:'visible', display:'block' }}>
        {/* Y gridlines */}
        {[0,0.25,0.5,0.75,1].map(f => {
          const y = PAD_T + chartH - f * chartH;
          return (
            <g key={f}>
              <line x1={PAD_L} y1={y} x2={SVG_W - PAD_R} y2={y} stroke="var(--line)" strokeWidth={0.5} />
              <text x={PAD_L - 4} y={y + 4} textAnchor="end" fill="var(--text-3)" fontSize={8} fontFamily="var(--font-mono)">{Math.round(f * maxVal)}</text>
            </g>
          );
        })}
        {/* Bars */}
        {labels.map((label, i) => {
          const cx = PAD_L + i * gap + gap / 2;
          const x  = cx - barW / 2;
          const h1 = toH(onTimePerDay[i]);
          const h2 = toH(latePerDay[i]);
          const h3 = toH(absentPerDay[i]);
          const y1 = PAD_T + chartH - h1;
          const y2 = y1 - h2;
          const y3 = y2 - h3;
          return (
            <g key={label}>
              {onTimePerDay[i] > 0 && <rect x={x} y={y1} width={barW} height={h1} fill={COLORS.onTime} rx={1}>
                <title>{`${fmtLabel(label)}\n${_rT("On time")}: ${onTimePerDay[i]}`}</title>
              </rect>}
              {latePerDay[i] > 0 && <rect x={x} y={y2} width={barW} height={h2} fill={COLORS.late} rx={1}>
                <title>{`${fmtLabel(label)}\n${_rT("Late")}: ${latePerDay[i]}`}</title>
              </rect>}
              {absentPerDay[i] > 0 && <rect x={x} y={y3} width={barW} height={h3} fill={COLORS.absent} rx={1} fillOpacity={0.6}>
                <title>{`${fmtLabel(label)}\n${_rT("Absent")}: ${absentPerDay[i]}`}</title>
              </rect>}
              {i % labelStep === 0 && (
                <text x={cx} y={SVG_H - 4} textAnchor="middle" fill="var(--text-3)" fontSize={8} fontFamily="var(--font-mono)">{fmtLabel(label)}</text>
              )}
            </g>
          );
        })}
        {/* Axes */}
        <line x1={PAD_L} y1={PAD_T} x2={PAD_L} y2={PAD_T + chartH} stroke="var(--line)" strokeWidth={1} />
        <line x1={PAD_L} y1={PAD_T + chartH} x2={SVG_W - PAD_R} y2={PAD_T + chartH} stroke="var(--line)" strokeWidth={1} />
      </svg>
      {/* Legend */}
      <div style={{ display:'flex', gap: 16, marginTop: 8, fontSize: 11, flexWrap:'wrap' }}>
        {[['On time', COLORS.onTime, 1], ['Late', COLORS.late, 1], ['Absent', COLORS.absent, 0.6]].map(([l, c, op]) => (
          <span key={l} style={{ display:'flex', alignItems:'center', gap: 5 }}>
            <span style={{ width:10, height:10, borderRadius:2, background:c, opacity:op, display:'inline-block' }}/>
            <span style={{ color:'var(--text-2)' }}>{_rT(l)}</span>
          </span>
        ))}
      </div>
    </div>
  );
}

/* ── Chart 2: Line chart — total hours per day ── */
function HoursTrendLine({ range }) {
  const [data, setData]     = useState(null);
  const [loading, setLoading] = useState(false);
  const toast = useToast();

  useEffect(() => {
    if (!range) return;
    setLoading(true);
    fetch(`/api/reports/timeseries?range=${range}&metric=hours`, { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(raw => {
        const labels = raw.labels || [];
        const hoursPerDay = labels.map((_, i) =>
          (raw.datasets || []).reduce((s, ds) => s + (ds.data[i] || 0), 0)
        );
        setData({ labels, hoursPerDay });
      })
      .catch(e => {
        toast.push({ msg: (_rAr() ? 'فشل جلب مخطط الساعات: ' : 'Hours chart fetch failed: ') + e, kind: 'err' });
        setData(null);
      })
      .finally(() => setLoading(false));
  }, [range]);

  if (loading) return <Loading label={_rT("Loading…")} rows={3} />;
  if (!data || !data.labels.length) return <Empty icon={<Icon name="chart" size={24}/>} title={_rT("No hours data")} subtitle={_rT("No records for this period.")} />;

  const { labels, hoursPerDay } = data;
  const maxVal = Math.max(...hoursPerDay, 1);
  const minVal = 0;

  const SVG_W = 480, SVG_H = 180, PAD_L = 32, PAD_B = 36, PAD_T = 12, PAD_R = 12;
  const chartW = SVG_W - PAD_L - PAD_R;
  const chartH = SVG_H - PAD_B - PAD_T;
  const n = labels.length;

  const toX = (i) => PAD_L + (i / Math.max(n - 1, 1)) * chartW;
  const toY = (v) => PAD_T + chartH - ((v - minVal) / (maxVal - minVal)) * chartH;

  const labelStep = Math.ceil(n / 8);
  const fmtLabel  = (iso) => { const d = new Date(iso + 'T00:00:00'); return `${d.getMonth()+1}/${d.getDate()}`; };

  const pts = labels.map((_, i) => `${toX(i)},${toY(hoursPerDay[i])}`).join(' ');
  const fillPts = `${PAD_L},${PAD_T + chartH} ${pts} ${toX(n-1)},${PAD_T + chartH}`;

  return (
    <div>
      <svg width="100%" viewBox={`0 0 ${SVG_W} ${SVG_H}`} style={{ overflow:'visible', display:'block' }}>
        <defs>
          <linearGradient id="hoursGrad" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="#0ea5e9" stopOpacity="0.35" />
            <stop offset="100%" stopColor="#0ea5e9" stopOpacity="0.03" />
          </linearGradient>
        </defs>
        {/* Y gridlines */}
        {[0, 0.25, 0.5, 0.75, 1].map(f => {
          const y = PAD_T + chartH - f * chartH;
          const v = Math.round(f * maxVal);
          return (
            <g key={f}>
              <line x1={PAD_L} y1={y} x2={SVG_W - PAD_R} y2={y} stroke="var(--line)" strokeWidth={0.5} strokeDasharray="3,3"/>
              <text x={PAD_L - 4} y={y + 4} textAnchor="end" fill="var(--text-3)" fontSize={8} fontFamily="var(--font-mono)">{v}h</text>
            </g>
          );
        })}
        {/* Fill */}
        {n > 1 && <polygon points={fillPts} fill="url(#hoursGrad)" />}
        {/* Line */}
        {n > 1 && <polyline points={pts} fill="none" stroke="#0ea5e9" strokeWidth={2} strokeLinejoin="round" strokeLinecap="round" />}
        {/* Dots + tooltips */}
        {labels.map((label, i) => (
          <circle key={label} cx={toX(i)} cy={toY(hoursPerDay[i])} r={3} fill="#0ea5e9" stroke="var(--bg)" strokeWidth={1.5}>
            <title>{`${fmtLabel(label)}: ${hoursPerDay[i].toFixed(1)}h`}</title>
          </circle>
        ))}
        {/* X labels */}
        {labels.map((label, i) => i % labelStep === 0 && (
          <text key={label} x={toX(i)} y={SVG_H - 4} textAnchor="middle" fill="var(--text-3)" fontSize={8} fontFamily="var(--font-mono)">{fmtLabel(label)}</text>
        ))}
        {/* Axes */}
        <line x1={PAD_L} y1={PAD_T} x2={PAD_L} y2={PAD_T + chartH} stroke="var(--line)" strokeWidth={1}/>
        <line x1={PAD_L} y1={PAD_T + chartH} x2={SVG_W - PAD_R} y2={PAD_T + chartH} stroke="var(--line)" strokeWidth={1}/>
      </svg>
      <div style={{ fontSize:11, color:'var(--text-3)', marginTop: 6, textAlign:'center' }}>{_rT("Total workforce hours/day")}</div>
    </div>
  );
}

/* ── Chart 3: Donut — status distribution ── */
function StatusDonut({ range, periodLabel }) {
  const [data, setData]     = useState(null);
  const [loading, setLoading] = useState(false);
  const [hovered, setHovered] = useState(null);
  const toast = useToast();

  useEffect(() => {
    if (!range) return;
    setLoading(true);
    fetch(`/api/reports/distribution?range=${range}`, { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(raw => {
        const labels = (raw.attendance && raw.attendance.labels) || [];
        const counts = (raw.attendance && raw.attendance.data)   || [];
        setData({ labels, counts });
      })
      .catch(e => {
        toast.push({ msg: (_rAr() ? 'فشل جلب التوزيع: ' : 'Distribution fetch failed: ') + e, kind: 'err' });
        setData(null);
      })
      .finally(() => setLoading(false));
  }, [range]);

  if (loading) return <Loading label={_rT("Loading…")} rows={3} />;
  if (!data || !data.labels.length) return <Empty icon={<Icon name="chart" size={24}/>} title={_rT("No distribution data")} subtitle={_rT("No records for this period.")} />;

  const { labels, counts } = data;
  const total = counts.reduce((s, v) => s + v, 0) || 1;
  // Color map by label substring
  const labelColor = (l) => {
    const ll = l.toLowerCase();
    if (ll.includes('late'))    return '#f59e0b';
    if (ll.includes('absent'))  return '#f43f5e';
    if (ll.includes('sign'))    return '#8b5cf6';
    if (ll.includes('present')) return '#10b981';
    return '#0ea5e9';
  };

  const SIZE = 180, R = 70, IR = 46, CX = SIZE/2, CY = SIZE/2;
  const C = 2 * Math.PI * R;

  // Build slices
  let cumAngle = -Math.PI / 2; // start at top
  const slices = labels.map((label, i) => {
    const v = counts[i] || 0;
    if (!v) return null;
    const frac  = v / total;
    const angle = frac * 2 * Math.PI;
    const x1 = CX + R * Math.cos(cumAngle);
    const y1 = CY + R * Math.sin(cumAngle);
    cumAngle += angle;
    const x2 = CX + R * Math.cos(cumAngle);
    const y2 = CY + R * Math.sin(cumAngle);
    const ix1 = CX + IR * Math.cos(cumAngle - angle);
    const iy1 = CY + IR * Math.sin(cumAngle - angle);
    const ix2 = CX + IR * Math.cos(cumAngle);
    const iy2 = CY + IR * Math.sin(cumAngle);
    const large = angle > Math.PI ? 1 : 0;
    const d = `M ${ix1} ${iy1} L ${x1} ${y1} A ${R} ${R} 0 ${large} 1 ${x2} ${y2} L ${ix2} ${iy2} A ${IR} ${IR} 0 ${large} 0 ${ix1} ${iy1} Z`;
    const color = labelColor(label);
    const isHov = hovered === i;
    return (
      <path
        key={label}
        d={d}
        fill={color}
        fillOpacity={isHov ? 1 : 0.85}
        stroke="var(--bg)"
        strokeWidth={isHov ? 2 : 1}
        style={{ cursor:'pointer', transition:'fill-opacity 0.15s' }}
        onMouseEnter={() => setHovered(i)}
        onMouseLeave={() => setHovered(null)}
      >
        <title>{`${label}: ${v} (${Math.round(v/total*100)}%)`}</title>
      </path>
    );
  }).filter(Boolean);

  // Center label
  const hovLabel = hovered != null ? labels[hovered] : periodLabel;
  const hovCount = hovered != null ? counts[hovered] : total;
  const hovPct   = hovered != null ? `${Math.round(counts[hovered]/total*100)}%` : _rT('total');

  return (
    <div style={{ display:'flex', gap: 24, alignItems:'center', flexWrap:'wrap' }}>
      <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={1} />
        <circle cx={CX} cy={CY} r={IR} fill="var(--bg-elev)" />
        {slices}
        <text x={CX} y={CY - 8} textAnchor="middle" fill="var(--text)" fontSize={15} fontWeight={700} fontFamily="var(--font-mono)">{hovCount}</text>
        <text x={CX} y={CY + 6} textAnchor="middle" fill="var(--text-3)" fontSize={8}  fontFamily="var(--font-mono)">{hovPct}</text>
        <text x={CX} y={CY + 18} textAnchor="middle" fill="var(--text-3)" fontSize={7.5} fontFamily="var(--font-mono)">{hovLabel}</text>
      </svg>
      <div style={{ flex:1, minWidth:140, display:'flex', flexDirection:'column', gap: 7 }}>
        {labels.map((label, i) => {
          const v = counts[i] || 0;
          const pct = Math.round(v / total * 100);
          const color = labelColor(label);
          return (
            <div key={label} style={{ display:'flex', alignItems:'center', gap: 8, fontSize: 12, cursor:'default' }}
              onMouseEnter={() => setHovered(i)} onMouseLeave={() => setHovered(null)}>
              <span style={{ width:10, height:10, borderRadius:2, background:color, flexShrink:0, opacity: hovered === i ? 1 : 0.85 }}/>
              <span style={{ flex:1, color:'var(--text-2)' }}>{label}</span>
              <span className="mono" style={{ color:'var(--text)', minWidth:28, textAlign:'right', fontWeight:600 }}>{v}</span>
              <span className="mono muted" style={{ minWidth:32, textAlign:'right' }}>{pct}%</span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ── On-call standings section ── */
function OnCallStandingsSection() {
  const [period, setPeriod] = useState('30d');
  const [team, setTeam]     = useState('');
  const [data, setData]     = useState(null);
  const [loading, setLoading] = useState(false);
  const toast = useToast();

  const PERIODS = [
    { value: '7d',  label: _rT('7 days') },
    { value: '30d', label: _rT('30 days') },
    { value: '90d', label: _rT('90 days') },
  ];

  const load = async (p, t) => {
    setLoading(true);
    try {
      const qs = `period=${p}${t ? `&team=${encodeURIComponent(t)}` : ''}`;
      const r = await fetch(`/api/oncall/metrics?${qs}`, { credentials: 'same-origin' });
      if (!r.ok) throw new Error(await r.text());
      setData(await r.json());
    } catch(e) {
      toast.push({ msg: (_rAr() ? 'فشل جلب مقاييس المناوبة: ' : 'On-call metrics fetch failed: ') + e.message, kind: 'err' });
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => { load(period, team); }, [period, team]);

  const cardStyle = {
    flex: '1 1 140px',
    background: 'var(--surface-2)',
    borderRadius: 10,
    padding: '14px 18px',
    textAlign: 'center',
  };

  return (
    <div className="card" style={{ marginTop: 16 }}>
      <SectionHeader
        title={_rT("On-call standings")}
          tip={_rT("Per-user on-call coverage stats — shifts, hours covered, response time avg. Defensive metric for fair-rotation review.")}
        sub={_rT("Shift counts, hours on call, and escalations by period")}
        right={
          <div style={{ display:'flex', gap: 6, alignItems:'center', flexWrap:'wrap' }}>
            {PERIODS.map(p => (
              <button
                key={p.value}
                className={period === p.value ? 'btn btn-pri' : 'btn btn-ghost'}
                style={{ padding:'3px 10px', fontSize: 11 }}
                onClick={() => setPeriod(p.value)}
              >
                {p.label}
              </button>
            ))}
            <input
              className="inp"
              placeholder={_rT("Filter team…")}
              value={team}
              onChange={e => setTeam(e.target.value)}
              style={{ width: 120, padding:'3px 8px', fontSize: 11 }}
            />
          </div>
        }
      />

      {loading && <Loading label={_rT("Loading…")} rows={2} />}

      {!loading && data && (
        <>
          {/* Summary cards */}
          <div style={{ display:'flex', gap: 12, marginTop: 16, flexWrap:'wrap' }}>
            <div style={cardStyle}>
              <div style={{ fontSize: 26, fontWeight: 700, color:'var(--accent)' }}>{data.shifts_total}</div>
              <div style={{ fontSize: 11, color:'var(--text-2)', marginTop: 4 }}>{_rT("Total shifts")}</div>
            </div>
            <div style={cardStyle}>
              <div style={{ fontSize: 26, fontWeight: 700, color:'var(--accent)' }}>{data.hours_total}h</div>
              <div style={{ fontSize: 11, color:'var(--text-2)', marginTop: 4 }}>{_rT("Total hours on call")}</div>
            </div>
            <div style={cardStyle}>
              <div style={{ fontSize: 26, fontWeight: 700, color:'var(--accent)' }}>{data.escalations}</div>
              <div style={{ fontSize: 11, color:'var(--text-2)', marginTop: 4 }}>{_rT("Escalations")}</div>
            </div>
          </div>

          {/* Ranked lists */}
          <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(260px,1fr))', gap: 24, marginTop: 24 }}>
            {/* Top users by hours */}
            <div>
              <div style={{ fontSize: 12, fontWeight: 600, color:'var(--text-2)', marginBottom: 10 }}>
                {_rT("Top users by hours on call")}
              </div>
              {(data.per_user ?? []).length === 0
                ? <div className="muted" style={{ fontSize: 12 }}>{_rT("No data for this period.")}</div>
                : (data.per_user ?? []).slice(0, 10).map((u, i) => (
                    <div key={u.user_id} style={{ display:'flex', alignItems:'center', gap: 8, padding:'6px 0', borderBottom:'1px solid var(--line)' }}>
                      <span style={{ fontSize: 11, color:'var(--text-3)', width: 18, textAlign:'right' }}>#{i+1}</span>
                      <span style={{ flex: 1, fontSize: 13, fontWeight: 500 }}>{u.display_name}</span>
                      <span style={{ fontSize: 12, color:'var(--text-2)' }}>{u.hours}h</span>
                      <span className="chip" style={{ fontSize: 10 }}>{_rAr() ? `${u.shifts} مناوبة` : `${u.shifts} shift${u.shifts !== 1 ? 's' : ''}`}</span>
                    </div>
                  ))
              }
            </div>

            {/* Top teams by shifts */}
            <div>
              <div style={{ fontSize: 12, fontWeight: 600, color:'var(--text-2)', marginBottom: 10 }}>
                {_rT("Top teams by shifts")}
              </div>
              {(data.per_team ?? []).length === 0
                ? <div className="muted" style={{ fontSize: 12 }}>{_rT("No data for this period.")}</div>
                : (data.per_team ?? []).slice(0, 10).map((t, i) => (
                    <div key={t.team_name} style={{ display:'flex', alignItems:'center', gap: 8, padding:'6px 0', borderBottom:'1px solid var(--line)' }}>
                      <span style={{ fontSize: 11, color:'var(--text-3)', width: 18, textAlign:'right' }}>#{i+1}</span>
                      <span style={{ flex: 1, fontSize: 13, fontWeight: 500 }}>{t.team_name}</span>
                      <span style={{ fontSize: 12, color:'var(--text-2)' }}>{t.hours}h</span>
                      <span className="chip" style={{ fontSize: 10 }}>{_rAr() ? `${t.shifts} مناوبة` : `${t.shifts} shift${t.shifts !== 1 ? 's' : ''}`}</span>
                    </div>
                  ))
              }
            </div>
          </div>
        </>
      )}

      {!loading && !data && (
        <div className="muted" style={{ padding: 12, fontSize: 13 }}>{_rT("No on-call data available.")}</div>
      )}
    </div>
  );
}
// Re-exposed so the On-call page bundle can mount the same standings block.
window.OnCallStandingsSection = OnCallStandingsSection;

/* ── Charts section — period picker + 2-col grid ── */

/* ── Violations section (#25) — period filter + table + pie + trend ── */
function ViolationsSection() {
  const [period, setPeriod]   = React.useState('week');
  const [search, setSearch]   = React.useState('');
  const [rows, setRows]       = React.useState([]);
  const [breakdown, setBreakdown] = React.useState({ labels: [], data: [], total: 0 });
  const [timeline, setTimeline]   = React.useState({ labels: [], data: [], total: 0 });
  const [loading, setLoading]     = React.useState(false);
  const toast = useToast();
  const trendRef = React.useRef(null);
  const pieRef   = React.useRef(null);

  const PERIODS = [
    { value: 'day',     label: _rT('Day') },
    { value: 'week',    label: _rT('Week') },
    { value: 'month',   label: _rT('Month') },
    { value: 'quarter', label: _rT('Quarter') },
    { value: 'year',    label: _rT('Year') },
  ];
  const PIE_COLORS = ['#ef4444', '#f59e0b', '#3b82f6', '#a855f7', '#10b981', '#ec4899', '#14b8a6'];

  const load = async (p) => {
    setLoading(true);
    try {
      // Scope all 3 fetches to the same search so donut/trend/table never drift.
      const searchQ = search ? '&search=' + encodeURIComponent(search) : '';
      const [r1, r2, r3] = await Promise.all([
        fetch('/api/summary/violations?period=' + encodeURIComponent(p) + searchQ,
              { credentials: 'same-origin' }).then(r => r.ok ? r.json() : []),
        fetch('/api/violations/breakdown?period=' + encodeURIComponent(p) + searchQ,
              { credentials: 'same-origin' }).then(r => r.ok ? r.json() : { labels: [], data: [], total: 0 }),
        fetch('/api/violations/timeline?period=' + encodeURIComponent(p === 'day' ? 'week' : p) + searchQ,
              { credentials: 'same-origin' }).then(r => r.ok ? r.json() : { labels: [], data: [], total: 0 }),
      ]);
      setRows(Array.isArray(r1) ? r1 : []);
      setBreakdown(r2);
      setTimeline(r3);
    } catch(e) {
      toast.push({ msg: (_rAr() ? 'فشل جلب المخالفات: ' : 'Violations fetch failed: ') + e.message, kind: 'err' });
    } finally {
      setLoading(false);
    }
  };

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

  // Chart.js pie + line
  React.useEffect(() => {
    if (!window.Chart || !pieRef.current || !breakdown.labels.length) return;
    const ctx = pieRef.current.getContext('2d');
    if (pieRef.current._chart) pieRef.current._chart.destroy();
    pieRef.current._chart = new window.Chart(ctx, {
      type: 'doughnut',
      data: {
        labels: breakdown.labels,
        datasets: [{
          data: breakdown.data,
          backgroundColor: breakdown.labels.map((_, i) => PIE_COLORS[i % PIE_COLORS.length]),
          borderWidth: 2,
          borderColor: 'var(--bg-elev-2)',
        }],
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        plugins: {
          legend: { position: 'right', labels: { color: 'var(--text-1)', boxWidth: 12, font:{size:11} } },
          tooltip: {},
        },
      },
    });
    return () => { if (pieRef.current && pieRef.current._chart) pieRef.current._chart.destroy(); };
  }, [breakdown]);

  React.useEffect(() => {
    if (!window.Chart || !trendRef.current || !timeline.labels.length) return;
    const ctx = trendRef.current.getContext('2d');
    if (trendRef.current._chart) trendRef.current._chart.destroy();
    trendRef.current._chart = new window.Chart(ctx, {
      type: 'line',
      data: {
        labels: timeline.labels.map(d => d.slice(5)),
        datasets: [{
          label: _rT('Violations'),
          data: timeline.data,
          borderColor: '#ef4444',
          backgroundColor: 'rgba(239,68,68,0.18)',
          fill: true,
          tension: 0.3,
          pointRadius: 2,
        }],
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        plugins: { legend: { display: false } },
        scales: {
          y: { beginAtZero: true, ticks: { color: 'var(--text-2)', stepSize: 1 } },
          x: { ticks: { color: 'var(--text-2)', maxTicksLimit: 8 } },
        },
      },
    });
    return () => { if (trendRef.current && trendRef.current._chart) trendRef.current._chart.destroy(); };
  }, [timeline]);

  const totalViolations = rows.reduce((s, r) => s + (r.violation_count || 0), 0);

  return (
    <div className="card" style={{ marginTop: 16 }}>
      <SectionHeader
        title={_rT("⚠️ Violations")}
          tip={_rT("Unified violations card (#1, #28). Donut + grid + trend + table read from the same counts+events arrays — so the donut and the count next to it never disagree.")}
        sub={_rAr() ? `${totalViolations} مخالفة هذا الـ${period} · النقاط المتبقية من ${rows[0]?.strike_cap || 3} لكل مستخدم` : `${totalViolations} violation${totalViolations !== 1 ? 's' : ''} this ${period} · strikes left out of ${rows[0]?.strike_cap || 3} per user`}
        right={
          <select className="sel" value={period} onChange={e => setPeriod(e.target.value)}
            style={{ fontSize: 12, padding: '4px 8px' }}>
            {PERIODS.map(p => (<option key={p.value} value={p.value}>{p.label}</option>))}
          </select>
        }
      />

      {/* Charts row */}
      <div className="violations-charts" style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 12, padding:'8px 16px 16px' }}>
        <div style={{ background:'var(--surface-2)', borderRadius: 8, padding: 12 }}>
          <div style={{ fontSize: 12, color:'var(--text-2)', marginBottom: 6 }}>{_rT("By type")}</div>
          <div style={{ height: 180, position:'relative' }}>
            {breakdown.labels.length === 0 && !loading && (
              <div className="muted" style={{ position:'absolute', inset:0, display:'flex', alignItems:'center', justifyContent:'center', fontSize:13 }}>{_rT("No violations in this window 🎉")}</div>
            )}
            <canvas ref={pieRef} style={{ width:'100%', height:'100%' }} />
          </div>
        </div>
        <div style={{ background:'var(--surface-2)', borderRadius: 8, padding: 12 }}>
          <div style={{ fontSize: 12, color:'var(--text-2)', marginBottom: 6 }}>{_rT("Trend")}</div>
          <div style={{ height: 180, position:'relative' }}>
            {timeline.labels.length === 0 && !loading && (
              <div className="muted" style={{ position:'absolute', inset:0, display:'flex', alignItems:'center', justifyContent:'center', fontSize:13 }}>—</div>
            )}
            <canvas ref={trendRef} style={{ width:'100%', height:'100%' }} />
          </div>
        </div>
      </div>

      {/* Search + table — search scopes pie + trend + table together */}
      <div style={{ padding:'0 16px 12px', display:'flex', alignItems:'center', gap:8, marginBottom: 8, flexWrap:'wrap' }}>
        <input
          className="inp"
          placeholder={_rT("Search by name…")}
          value={search}
          onChange={e => setSearch(e.target.value)}
          style={{ maxWidth: 280 }}
        />
        {search && (
          <button className="btn btn-ghost"
            style={{ padding:'4px 10px', fontSize: 11 }}
            onClick={() => setSearch('')}
            title={_rT("Reset to all users")}>
            {_rT("All users")}
          </button>
        )}
        <span className="muted" style={{ fontSize: 11 }}>
          {search ? (_rAr() ? `عرض الحلقة + الاتجاه + الجدول لـ "${search}"` : `Showing donut + trend + table for "${search}"`) : (_rAr() ? `عرض كل المستخدمين · ${rows.length}` : `Showing all users · ${rows.length}`)}
        </span>
      </div>
      <div style={{ padding:'0 16px 12px' }}>
        <div style={{ overflowX: 'auto' }}>
          <table className="t" style={{ width:'100%', fontSize: 12.5 }}>
            <thead>
              <tr>
                <th>{_rT("User")}</th>
                <th>{_rT("Team")}</th>
                <th style={{ textAlign:'right' }} title={_rT("Violations in the selected period")}>{_rT("Period total")}</th>
                <th>{_rT("By type")}</th>
                <th style={{ textAlign:'right' }} title={_rT("Days late in the selected period")}>{_rT("Late")}</th>
                <th style={{ textAlign:'right' }} title={_rT("Days absent in the selected period")}>{_rT("Absent")}</th>
                <th style={{ textAlign:'right' }} title={_rT("Days worked below the expected hours")}>{_rT("Short")}</th>
                <th style={{ textAlign:'right' }} title={_rT("Attendance % over the selected period")}>{_rT("Att %")}</th>
                <th style={{ textAlign:'right' }} title={_rT("Lifetime strikes left out of strike cap (default 3)")}>{_rT("Strikes left (lifetime)")}</th>
                <th>{_rT("Last")}</th>
              </tr>
            </thead>
            <tbody>
              {loading && <LoadingRow colSpan={10} label={_rT("Loading…")} />}
              {!loading && rows.length === 0 && (
                <tr><td colSpan="10" className="muted" style={{padding:12}}>{_rT("No data.")}</td></tr>
              )}
              {rows.map(r => {
                const left = Math.max(0, r.strikes_left || 0);
                const cap  = r.strike_cap || 3;
                const leftColor = left === 0 ? 'var(--err)' : left === 1 ? 'var(--warn)' : 'var(--ok)';
                return (
                  <tr key={r.user_id}>
                    <td>{r.display_name}</td>
                    <td className="muted">{r.team || '—'}</td>
                    <td className="num mono" style={{ textAlign:'right', fontWeight:600 }}>{r.violation_count}</td>
                    <td>
                      {Object.entries(r.by_type || {}).length === 0 ? (
                        <span className="muted">—</span>
                      ) : Object.entries(r.by_type || {}).map(([k, v], i) => (
                        <span key={k} style={{
                          display:'inline-block', padding:'1px 7px', borderRadius:999,
                          background: PIE_COLORS[i % PIE_COLORS.length], color:'#fff',
                          fontSize: 10.5, marginRight:4, marginTop:2,
                        }}>{k.replace('_',' ')}: {v}</span>
                      ))}
                    </td>
                    <td className="num mono" style={{ textAlign:'right', color: (r.days_late||0) ? 'var(--warn)' : 'var(--text-2)' }}>{r.days_late || 0}</td>
                    <td className="num mono" style={{ textAlign:'right', color: (r.days_absent||0) ? 'var(--err)' : 'var(--text-2)' }}>{r.days_absent || 0}</td>
                    <td className="num mono" style={{ textAlign:'right', color:'var(--text-2)' }}>{r.short_days || 0}</td>
                    <td className="num mono" style={{ textAlign:'right', fontWeight:600,
                      color: (r.attendance_pct||0) < 60 ? 'var(--err)' : (r.attendance_pct||0) < 80 ? 'var(--warn)' : 'var(--text-1)' }}>
                      {r.attendance_pct != null ? Math.round(r.attendance_pct) + '%' : '—'}
                    </td>
                    <td className="num mono" style={{ textAlign:'right' }}>
                      <span style={{ color: leftColor, fontWeight:700 }}>{left}</span>
                      <span className="muted" style={{ fontSize:10.5 }}> / {cap}</span>
                    </td>
                    <td className="muted mono" style={{ fontSize: 11 }}>
                      {r.last_violation ? r.last_violation.slice(0, 16).replace('T', ' ') : '—'}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}


/* ── Productivity trend (org / team / employee weekly Efficiency%) ── */
// Categorical line palette for multi-series (teams / employees). Chosen for
// contrast in both light and dark themes.
const TREND_PALETTE = [
  '#0ea5e9', '#f59e0b', '#10b981', '#ef4444', '#8b5cf6', '#ec4899',
  '#14b8a6', '#f97316', '#6366f1', '#84cc16', '#06b6d4', '#e11d48',
  '#a855f7', '#22c55e', '#eab308', '#3b82f6',
];
function _trendHash(s) { let h = 0; for (let i = 0; i < (s || '').length; i++) h = (h * 31 + s.charCodeAt(i)) | 0; return Math.abs(h); }
const _MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

/* ── Small multiples: one mini-chart per employee ──────────────────────────
   Overlaying every employee on one axis is a spaghetti chart. Past ~8 series
   categorical colour stops working — the palette gets cycled, so two people
   silently share a hue — and no single line is traceable anyway; with 24
   staff the migration marker and the trend both vanish into noise.
   Faceting is the fix: each cell is ONE series, so it needs no legend and no
   unique hue. Colour carries TEAM (3 teams — well inside the categorical
   ceiling) and is keyed off the team NAME, so filtering people out never
   repaints the survivors. Sorted by latest value: the roster reads as a
   ranking instead of a tangle. */
function _teamColor(team) {
  return TREND_PALETTE[_trendHash(String(team || 'none')) % TREND_PALETTE.length];
}

function TrendSmallMultiples({ series, labels, axis, cutover, fmtVal, monthly, onPick }) {
  const cells = React.useMemo(() => series.map(s => {
    const vals = s.points.filter(p => p.score != null);
    const latest = vals.length ? vals[vals.length - 1].score : null;
    const bef = [], aft = [];
    s.points.forEach(p => {
      if (p.score == null) return;
      ((monthly ? p.week_start.slice(0, 7) < cutover.slice(0, 7) : p.week_start < cutover) ? bef : aft).push(p.score);
    });
    const avg = a => a.length ? a.reduce((x, y) => x + y, 0) / a.length : null;
    const b = avg(bef), a = avg(aft);
    return { ...s, latest, delta: (b != null && a != null) ? a - b : null };
  }).sort((x, y) => (y.latest ?? -1) - (x.latest ?? -1)), [series, cutover, monthly]);

  const W = 190, H = 42, PAD = 3;
  // Shared y-scale across every cell — per-cell auto-scaling would make a flat
  // 60% and a flat 20% look identical, which is the classic small-multiples trap.
  const allVals = series.flatMap(s => s.points.filter(p => p.score != null).map(p => p.score));
  const maxY = Math.max(1, ...allVals) * 1.1;
  const n = axis.length;
  const toX = i => PAD + (i / Math.max(n - 1, 1)) * (W - PAD * 2);
  const toY = v => H - PAD - (Math.max(0, v) / maxY) * (H - PAD * 2);
  let cutIdx = -1;
  if (cutover) { const cm = monthly ? cutover.slice(0, 7) : cutover; cutIdx = axis.findIndex(a => (monthly ? a.slice(0, 7) : a) >= cm); }
  const cutX = cutIdx > 0 ? toX(cutIdx) - (W - PAD * 2) / Math.max(n - 1, 1) / 2 : -1;

  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: 10 }}>
      {cells.map(c => {
        const runs = []; let run = [];
        c.points.forEach((p, i) => { if (p.score == null) { if (run.length) { runs.push(run); run = []; } } else run.push(i); });
        if (run.length) runs.push(run);
        const col = _teamColor(c.team);
        return (
          <div key={c.key}
               onClick={() => onPick && onPick(c.key)}
               title={_rT('Open this employee')}
               style={{ background: 'var(--bg-2, rgba(127,127,127,.06))', border: '1px solid var(--line)', borderRadius: 8, padding: '7px 9px 5px', cursor: onPick ? 'pointer' : 'default' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 5, marginBottom: 1 }}>
              <span style={{ width: 7, height: 7, borderRadius: 2, background: col, flexShrink: 0 }} />
              <span style={{ fontSize: 11.5, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{c.label}</span>
              <span className="mono" style={{ marginInlineStart: 'auto', fontSize: 11.5, fontWeight: 700 }}>{fmtVal(c.latest)}</span>
            </div>
            <div className="muted" style={{ fontSize: 9.5, marginBottom: 2 }}>
              {c.team || _rT('no team')}
              {c.delta != null && (
                <span style={{ marginInlineStart: 6, color: c.delta >= 0 ? 'var(--ok,#10b981)' : 'var(--err,#f43f5e)' }}>
                  {c.delta >= 0 ? '▲' : '▼'} {Math.abs(c.delta).toFixed(1)}
                </span>
              )}
            </div>
            <svg width="100%" viewBox={`0 0 ${W} ${H}`} style={{ display: 'block', overflow: 'visible' }}>
              {cutX > 0 && <rect x={PAD} y={PAD} width={Math.max(0, cutX - PAD)} height={H - PAD * 2} fill="var(--text-3)" opacity={0.07} />}
              {cutX > 0 && <line x1={cutX} y1={PAD} x2={cutX} y2={H - PAD} stroke="var(--text-2)" strokeWidth={0.7} opacity={0.55} />}
              {runs.map((r, ri) => (
                <polyline key={ri} points={r.map(i => `${toX(i)},${toY(c.points[i].score)}`).join(' ')}
                          fill="none" stroke={col} strokeWidth={1.6} strokeLinejoin="round" strokeLinecap="round" />
              ))}
            </svg>
          </div>
        );
      })}
    </div>
  );
}

const _PROD_ADMIN_ROLES = ['admin', 'moderator', 'superadmin'];
function _isProdAdmin(e) {
  return !!e && (e.is_superadmin ||
    _PROD_ADMIN_ROLES.includes(String(e.org_role || '').toLowerCase()));
}
function ProductivityTrendCard({ refreshTick }) {
  const D = window.STATUS_DATA || {};
  // Admins / moderators / superadmins are leaders, not tracked ICs — keep them
  // out of the employee picker so their line can't be charted, matching the
  // exclude-admins default applied to the org/team means below.
  const employees = React.useMemo(() => (
    (D.EMPLOYEES || []).filter(e => !e.is_test_account && !_isProdAdmin(e))
      .slice().sort((a, b) => (a.display_name || '').localeCompare(b.display_name || ''))
  ), []);
  const teams = React.useMemo(() => (
    (D.TEAMS || []).filter(t => t && t.team_name)
      .slice().sort((a, b) => (a.team_name || '').localeCompare(b.team_name || ''))
  ), []);

  const [level, setLevel]   = useState('org');   // org|team|user|teams|employees
  const [teamId, setTeamId] = useState(teams[0]?.team_name || '');
  const [userId, setUserId] = useState(employees[0]?.id || '');
  const [weeks, setWeeks]   = useState(26);
  const [gran, setGran]     = useState('week');  // week|month
  const [metric, setMetric] = useState('efficiency'); // efficiency|transparency|tasks|hours
  const [exclAdmins, setExclAdmins] = useState(true);
  // The current week is still running, so its point always dips — it is a
  // partial week, not a downturn. Hidden by default; the toggle shows it
  // explicitly labelled rather than letting it read as a real decline.
  const [showPartial, setShowPartial] = useState(false);
  const [helpOpen, setHelpOpen] = useState(false);
  const [data, setData]     = useState(null);
  const [loading, setLoading] = useState(false);
  const [hidden, setHidden] = useState({});      // series key -> true (legend toggle)
  const [hovered, setHovered] = useState(null);  // axis index
  const [forecast, setForecast] = useState(false); // "Projection" toggle — default off
  const toast = useToast();

  const isMulti = level === 'teams' || level === 'employees';
  const scopedId = level === 'team' ? teamId : level === 'user' ? userId : '';
  // Forecast is only supported by the single-series /api/productivity/trend
  // endpoint (org/team/user) — trend-multi has no forecast= param.
  const forecastActive = forecast && !isMulti;

  const load = React.useCallback(async () => {
    if ((level === 'team' && !teamId) || (level === 'user' && !userId)) { setData(null); return; }
    setLoading(true);
    try {
      let url;
      if (isMulti) {
        url = `/api/productivity/trend-multi?mode=${level}&weeks=${weeks}&metric=${metric}`
            + (exclAdmins ? '&exclude_admins=true' : '');
      } else {
        const qs = new URLSearchParams({ level, weeks: String(weeks), metric });
        if (scopedId) qs.set('id', String(scopedId));
        if (exclAdmins) qs.set('exclude_admins', 'true');
        if (forecastActive) qs.set('forecast', 'true');
        url = `/api/productivity/trend?${qs}`;
      }
      const r = await fetch(url, { credentials: 'same-origin' });
      if (!r.ok) throw new Error(parseDetail(await r.json().catch(() => null), r.status));
      setData(await r.json());
    } catch (e) {
      toast.push({ msg: (_rAr() ? 'فشل جلب اتجاه الإنتاجية: ' : 'Productivity trend fetch failed: ') + e.message, kind: 'err' });
      setData(null);
    } finally {
      setLoading(false);
    }
  }, [level, teamId, userId, weeks, scopedId, isMulti, metric, forecastActive, exclAdmins]);

  useEffect(() => { load(); }, [load, refreshTick]);
  useEffect(() => { setHidden({}); setHovered(null); }, [level, weeks, metric]);

  // Unit-aware formatting: efficiency/transparency are %, tasks a count, hours in h.
  const unit = (data && data.unit) || 'pct';
  const isPct = unit === 'pct';
  const fmtVal = (v) => v == null ? '—' : isPct ? v.toFixed(1) + '%' : unit === 'hours' ? v.toFixed(1) + 'h' : String(Math.round(v));
  const fmtDelta = (d) => d == null ? '—' : (d >= 0 ? '↑ ' : '↓ ') + (isPct ? (d > 0 ? '+' : '') + d.toFixed(1) + ' pts' : unit === 'hours' ? (d > 0 ? '+' : '') + d.toFixed(1) + 'h' : (d > 0 ? '+' : '') + Math.round(d));
  // Relative % change — "+38%". Distinct from fmtDelta, which reports a
  // percentage-POINTS gap ("+8.7 pts") for pct metrics.
  const fmtPctChange = (v) => v == null ? '—' : (v >= 0 ? '+' : '') + v.toFixed(1) + '%';
  const METRIC_OPTS = [
    { v: 'efficiency',   l: _rT('Efficiency') },
    { v: 'transparency', l: _rT('Transparency') },
    { v: 'tasks',        l: _rT('Tasks done') },
    { v: 'hours',        l: _rT('Hours') },
  ];
  // What each number actually measures — surfaced behind the "?" so nobody has
  // to guess how e.g. Transparency is derived.
  const METRIC_HELP = {
    efficiency: _rT('Weighted Efficiency% — already a composite: Progress 40%, Presence & hours 25%, Reliability 15%, Integrity 10%, Responsibility 10%. Strikes apply as a capped penalty. A dropped pillar (e.g. no on-call) is removed and its weight redistributed.'),
    transparency: _rT('Visibility% = reporting rate ×50% + sign-off rate ×30% + ping responsiveness ×20%. Reporting rate = days a standup was posted ÷ expected working days. If no pings were sent that week, the ping weight is redistributed over the other two (×62.5 / ×37.5). Measures how openly work is REPORTED — not how much was done.'),
    tasks: _rT('Distinct tasks the standup reported as completed that week. At team/company level this is the mean per employee. Counts what was WRITTEN DOWN, so it rises when reporting improves even if output does not.'),
    hours: _rT('Hours worked that week, derived from sign-in → sign-off spans (mean per employee at team/company level). Days missing either side are undercounted — pre-cutover history is structurally low for this reason.'),
  };
  const METRIC_CAVEAT = _rT('Weeks left of the migration line are reconstructed from the archived room, where the bot did not enforce a standup format. Improvements across that line partly reflect better REPORTING, not only more work. The plotted line is NOT comparable across the migration: before the cutover there were no availability pings, so that pillar does not exist and its weight redistributes, and no attendance strike could be recorded — which flatters the "before" side. Use the like-for-like footnote for any before/after claim.');

  // ---- Normalise single- and multi-series responses to one weekly shape ----
  // norm.axis = [weekIso...]; norm.series = [{key,label,team,color,points:[{week_start,score,n}]}]
  const norm = React.useMemo(() => {
    if (!data) return null;
    if (isMulti) {
      const axis = data.weeks || [];
      const series = (data.series || []).map((s, i) => ({
        key: s.key, label: s.label, team: s.team,
        // Keyed off the entity, never the row index: hiding one series must not
        // repaint the others (a reader who learned "Wael is blue" stays right).
        color: level === 'teams' ? _teamColor(s.team || s.key)
                                 : TREND_PALETTE[_trendHash(s.key) % TREND_PALETTE.length],
        points: s.points || [],
      }));
      return { axis, series, cutover: data.cutover, beforeAfter: null };
    }
    if (!data.series || !data.series.length) return null;
    const axis = data.series.map(p => p.week_start);
    const label = level === 'org' ? _rT('Company') : (scopedId != null ? String(scopedId) : _rT('Series'));
    return { axis, cutover: data.cutover, beforeAfter: data.before_after || null,
             series: [{ key: 'main', label, color: '#0ea5e9', points: data.series }] };
  }, [data, isMulti, level, scopedId]);

  // ---- Month rollup (average of non-null weekly scores per calendar month) --
  // Drop the still-running week unless explicitly asked for. Its point is
  // always low simply because the week isn't over, which reads as a decline —
  // the honest default is to plot complete weeks and offer the partial one as
  // an opt-in (rather than quietly trimming whatever looks bad).
  const trimmed = React.useMemo(() => {
    if (!norm) return null;
    if (showPartial || norm.axis.length < 2) return norm;
    return {
      ...norm,
      axis: norm.axis.slice(0, -1),
      series: norm.series.map(s => ({ ...s, points: s.points.slice(0, -1) })),
    };
  }, [norm, showPartial]);

  const view = React.useMemo(() => {
    const norm = trimmed;
    if (!norm) return null;
    if (gran === 'week') {
      return { axis: norm.axis, labels: norm.axis.map(iso => { const d = new Date(iso + 'T00:00:00'); return `${d.getMonth() + 1}/${d.getDate()}`; }), series: norm.series, cutover: norm.cutover, monthly: false };
    }
    const monthsAxis = [];
    norm.axis.forEach(iso => { const mk = iso.slice(0, 7); if (!monthsAxis.includes(mk)) monthsAxis.push(mk); });
    const rollup = (points) => {
      const byM = {};
      points.forEach(p => { const mk = p.week_start.slice(0, 7); (byM[mk] = byM[mk] || []).push(p); });
      return monthsAxis.map(mk => {
        const rows = (byM[mk] || []).filter(p => p.score != null);
        const vals = rows.map(p => p.score);
        // A rolled-up month only counts as "projected" when every week that
        // fed it was a forecast week — a month mixing real + forecast weeks
        // stays a solid (real) point so client math never blends fake data
        // into an "actual" bucket.
        const allPredicted = rows.length > 0 && rows.every(p => p.predicted);
        return { week_start: mk + '-01', score: vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : null, n: rows.length, predicted: allPredicted };
      });
    };
    return {
      axis: monthsAxis.map(mk => mk + '-01'),
      labels: monthsAxis.map(mk => `${_MONTHS[parseInt(mk.slice(5, 7), 10) - 1]} '${mk.slice(2, 4)}`),
      series: norm.series.map(s => ({ ...s, points: rollup(s.points) })),
      cutover: norm.cutover, monthly: true,
    };
  }, [trimmed, gran]);

  // ---- Before/after cutover averages (from WEEKLY data, excludes the last
  //      in-progress week for fairness) ----
  // "How much better are we?" — the RELATIVE % change across the visible range.
  // That is what "we improved 40%" means, and it is the number the card must
  // prove. The tiles used to show a percentage-POINTS delta (48.6% -> 57.3% =
  // "+8.7 pts"), which is a different and much smaller-looking figure — it was
  // understating a real +38% trend.
  //
  // Both ends are averaged over up to 3 weeks: a single endpoint week swings
  // ~5 points here (45.98 then 50.02 back to back), so a raw first-vs-last
  // headline would be noise-driven and cherry-pickable.
  const progress = React.useMemo(() => {
    if (!norm) return null;
    const lastWeek = norm.axis[norm.axis.length - 1];
    const vis = norm.series.filter(s => !hidden[s.key]);
    const byWeek = new Map();
    vis.forEach(s => s.points.forEach(p => {
      // never average a forecast point or the still-running week
      if (p.score == null || p.predicted || p.week_start === lastWeek) return;
      if (!byWeek.has(p.week_start)) byWeek.set(p.week_start, []);
      byWeek.get(p.week_start).push(p.score);
    }));
    const weeks = [...byWeek.keys()].sort();
    if (weeks.length < 2) return null;
    const mean = (a) => a.reduce((x, y) => x + y, 0) / a.length;
    const wAvg = (w) => mean(byWeek.get(w));
    const ends = Math.max(1, Math.min(3, Math.floor(weeks.length / 2)));
    const start = mean(weeks.slice(0, ends).map(wAvg));
    const now = mean(weeks.slice(-ends).map(wAvg));
    return {
      start, now, ends, span: weeks.length,
      pts: now - start,
      relPct: start ? (now - start) / start * 100 : null,
      firstWeek: weeks[0], lastWeek: weeks[weeks.length - 1],
    };
  }, [norm, hidden]);

  // Migration context (archived-room vs live-room), kept as a one-line footnote
  // rather than three tiles — it answers a different question than "are we
  // improving", and crowding both made the card unreadable.
  // Migration context (archived-room vs live-room), kept as a one-line footnote
  // rather than three tiles — it answers a different question than "are we
  // improving", and crowding both made the card unreadable.
  //
  // ★ Computed SERVER-side on a like-for-like basis and never re-derived here.
  // Averaging the plotted Efficiency% across the migration line compares two
  // different rubrics: before the cutover there are no availability pings, so
  // the integrity pillar does not exist and its weight redistributes onto the
  // pillars archive data scores well on, and no attendance strike can be
  // recorded. That comparison read -0.7% while the like-for-like one read
  // +14%. The server drops everything unavailable before the cutover from BOTH
  // sides; there is no honest way to do it from the plotted points alone.
  const beforeAfter = (norm && norm.beforeAfter) || null;

  const toggleSeries = (k) => setHidden(h => ({ ...h, [k]: !h[k] }));

  return (
    <div className="card" style={{ marginTop: 16 }}>
      <SectionHeader
        title={_rT("Productivity trend")}
        tip={_rT("Weekly Efficiency% over time. Company, one team, one employee, or all teams / all employees as separate lines. The dashed rule marks the migration to the live standup room — weeks left of it are reconstructed from the archived room. The last point is the current (in-progress) week. Test/bot accounts and superadmins are excluded from averages.")}
        sub={_rT("Company · team · employee · all teams · all employees")}
        right={<Refresh onClick={load} loading={loading} />}
      />

      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center', marginBottom: 14 }}>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
          {[
            { value: 'org',       label: _rT('Company') },
            { value: 'team',      label: _rT('Team') },
            { value: 'user',      label: _rT('Employee') },
            { value: 'teams',     label: _rT('All teams') },
            { value: 'employees', label: _rT('All employees') },
          ].map(o => (
            <button
              key={o.value}
              className={level === o.value ? 'btn btn-pri' : 'btn btn-ghost'}
              style={{ padding: '3px 10px', fontSize: 11 }}
              onClick={() => setLevel(o.value)}
            >
              {o.label}
            </button>
          ))}
        </div>
        {level === 'team' && (
          <select className="sel" value={teamId} onChange={e => setTeamId(e.target.value)} style={{ minWidth: 160 }}>
            {teams.map(t => <option key={t.team_name} value={t.team_name}>{t.team_name}</option>)}
          </select>
        )}
        {level === 'user' && (
          <select className="sel" value={userId} onChange={e => setUserId(e.target.value)} style={{ minWidth: 180 }}>
            {employees.map(e => <option key={e.id} value={e.id}>{e.display_name}</option>)}
          </select>
        )}
        <select className="sel" value={weeks} onChange={e => setWeeks(Number(e.target.value))}>
          {[8, 12, 26, 30].map(w => <option key={w} value={w}>{w} {_rT('weeks')}</option>)}
        </select>
        <label
          className={forecast ? 'btn btn-pri' : 'btn btn-ghost'}
          style={{ padding: '3px 10px', fontSize: 11, display: 'inline-flex', alignItems: 'center', gap: 6, cursor: isMulti ? 'not-allowed' : 'pointer', opacity: isMulti ? 0.5 : 1 }}
          title={isMulti ? _rT('Projection is only available for Company / Team / Employee views') : _rT('Project the next 2 weeks from the recent trend')}
        >
          <input type="checkbox" checked={forecast} disabled={isMulti} onChange={e => setForecast(e.target.checked)} style={{ margin: 0 }} />
          {_rT('Projection')}
        </label>
        <div style={{ display: 'flex', gap: 4, marginInlineStart: 'auto' }}>
          {[{ v: 'week', l: _rT('Weekly') }, { v: 'month', l: _rT('Monthly') }].map(o => (
            <button key={o.v} className={gran === o.v ? 'btn btn-pri' : 'btn btn-ghost'} style={{ padding: '3px 10px', fontSize: 11 }} onClick={() => setGran(o.v)}>{o.l}</button>
          ))}
        </div>
      </div>

      {/* Metric selector — what the trend plots */}
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center', marginBottom: 8 }}>
        <span className="muted" style={{ fontSize: 10.5, textTransform: 'uppercase', letterSpacing: .4, marginInlineEnd: 2 }}>{_rT('Metric')}</span>
        {METRIC_OPTS.map(o => (
          <button key={o.v} className={metric === o.v ? 'btn btn-pri' : 'btn btn-ghost'} style={{ padding: '3px 10px', fontSize: 11 }} onClick={() => setMetric(o.v)}>{o.l}</button>
        ))}
        <button
          className="btn btn-ghost"
          style={{ padding: '3px 9px', fontSize: 11, fontWeight: 700, borderRadius: 999 }}
          aria-expanded={helpOpen}
          onClick={() => setHelpOpen(v => !v)}
          title={_rT('How is this measured?')}
        >?</button>
        <label style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 11, cursor: 'pointer', marginInlineStart: 6 }}
               title={_rT('Admin/moderator accounts are tracked loosely and skew a team average.')}>
          <input type="checkbox" checked={exclAdmins} onChange={e => setExclAdmins(e.target.checked)} />
          {_rT('Exclude admins')}
        </label>
        <label style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 11, cursor: 'pointer' }}
               title={_rT('The current week is still running, so its point always dips — that is a partial week, not a decline.')}>
          <input type="checkbox" checked={showPartial} onChange={e => setShowPartial(e.target.checked)} />
          {_rT('Show current (partial) week')}
        </label>
      </div>

      {/* "?" — what this metric actually measures, incl. the reporting caveat */}
      {helpOpen && (
        <div style={{ background: 'var(--bg-2, rgba(127,127,127,.06))', border: '1px solid var(--line)', borderRadius: 8, padding: '10px 12px', marginBottom: 12, fontSize: 12, lineHeight: 1.55 }}>
          <div style={{ fontWeight: 700, marginBottom: 4 }}>
            {(METRIC_OPTS.find(o => o.v === metric) || {}).l}
          </div>
          <div style={{ color: 'var(--text-2)' }}>{METRIC_HELP[metric]}</div>
          <div className="muted" style={{ marginTop: 7, paddingTop: 7, borderTop: '1px solid var(--line)' }}>
            {METRIC_CAVEAT}
          </div>
        </div>
      )}

      {loading && <Loading label={_rT("Loading…")} rows={3} />}

      {!loading && (!view || !view.series.length) && (
        <Empty icon={<Icon name="chart" size={24}/>} title={_rT("No productivity data")} subtitle={_rT("No scored weeks in this range yet.")} />
      )}

      {/* THE headline: how much better are we, as a %. */}
      {!loading && progress && (
        <div style={{ marginBottom: 12 }}>
          <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
            <div style={{ flex: '1 1 110px', background: 'var(--bg-2, rgba(127,127,127,.06))', border: '1px solid var(--line)', borderRadius: 8, padding: '8px 12px' }}>
              <div className="muted" style={{ fontSize: 10.5, textTransform: 'uppercase', letterSpacing: .4 }}>
                {progress.span} {_rT('weeks ago')}
              </div>
              <div style={{ fontSize: 20, fontWeight: 700 }}>{fmtVal(progress.start)}</div>
              <div className="muted" style={{ fontSize: 9.5 }}>{_rT('avg of first')} {progress.ends} {_rT('wks')}</div>
            </div>
            <div style={{ flex: '1 1 110px', background: 'var(--bg-2, rgba(127,127,127,.06))', border: '1px solid var(--line)', borderRadius: 8, padding: '8px 12px' }}>
              <div className="muted" style={{ fontSize: 10.5, textTransform: 'uppercase', letterSpacing: .4 }}>{_rT('Now')}</div>
              <div style={{ fontSize: 20, fontWeight: 700 }}>{fmtVal(progress.now)}</div>
              <div className="muted" style={{ fontSize: 9.5 }}>{_rT('avg of last')} {progress.ends} {_rT('wks')}</div>
            </div>
            <div style={{ flex: '1 1 150px', background: 'var(--bg-2, rgba(127,127,127,.06))', border: '1px solid var(--line)', borderRadius: 8, padding: '8px 12px' }}>
              <div className="muted" style={{ fontSize: 10.5, textTransform: 'uppercase', letterSpacing: .4 }}>{_rT('Improvement')}</div>
              <div style={{ fontSize: 28, fontWeight: 800, lineHeight: 1.1,
                            color: progress.relPct == null ? 'var(--text-2)' : progress.relPct >= 0 ? 'var(--ok,#10b981)' : 'var(--err,#f43f5e)' }}>
                {fmtPctChange(progress.relPct)}
              </div>
              <div className="muted" style={{ fontSize: 9.5 }}>
                {isPct ? <>{fmtDelta(progress.pts)} · {_rT('relative')}</> : _rT('relative change')}
              </div>
            </div>
          </div>
          {beforeAfter && beforeAfter.rel_pct != null && (
            <div className="muted" style={{ fontSize: 10.5, marginTop: 6, lineHeight: 1.55 }}>
              <div>
                {_rT('Across the migration')}, {_rT('like for like')}: {fmtVal(beforeAfter.before)} ({_rT('archived room')}, {beforeAfter.weeks_before} {_rT('wks')}) → {fmtVal(beforeAfter.after)} ({_rT('live room')}, {beforeAfter.weeks_after} {_rT('wks')}) = <b>{fmtPctChange(beforeAfter.rel_pct)}</b>
                {beforeAfter.after_recent != null && (
                  <> · {_rT('last')} {beforeAfter.recent_weeks} {_rT('wks')} {fmtVal(beforeAfter.after_recent)} = <b>{fmtPctChange(beforeAfter.rel_pct_recent)}</b></>
                )}
              </div>
              <div>
                {_rT('Same rubric both sides (progress · presence · reliability) — the pillars that exist without the bot.')}
                {beforeAfter.avg_people_before != null && beforeAfter.avg_people_after != null && (
                  <> {_rT('Visible per week')}: {beforeAfter.avg_people_before} → {beforeAfter.avg_people_after} {_rT('of')} {beforeAfter.roster}.</>
                )}
              </div>
              {/* A zero-filled "before" is RECORDED productivity across the whole
                  roster, not observed productivity. It must never be shown
                  without saying so — the number is otherwise read as evidence
                  that those people did nothing, which the data does not say. */}
              {beforeAfter.zero_filled && (
                <div style={{ color: 'var(--warn, #b45309)' }}>
                  ⚠ {_rT('Pre-migration weeks with no record are counted as 0%')}
                  {beforeAfter.zero_filled_person_weeks > 0 && (
                    <> ({beforeAfter.zero_filled_person_weeks} {_rT('of')} {beforeAfter.person_weeks_before} {_rT('person-weeks')})</>
                  )}
                  . {_rT('That is what we RECORDED, not what was worked — those people were simply not tracked before the bot.')}
                </div>
              )}
            </div>
          )}
        </div>
      )}

      {/* Past ~8 series an overlaid line chart stops working — hues get cycled
          so two people share a colour, and no line is traceable. Facet instead:
          one mini-chart per employee, shared y-scale, click to open them. */}
      {!loading && view && level === 'employees' && view.series.length > 8 && (
        <TrendSmallMultiples
          series={view.series} labels={view.labels} axis={view.axis}
          cutover={view.cutover} fmtVal={fmtVal} monthly={view.monthly}
          onPick={(k) => { setUserId(k); setLevel('user'); }}
        />
      )}

      {!loading && view && view.series.length > 0
        && !(level === 'employees' && view.series.length > 8) && (() => {
        const axis = view.axis, n = axis.length;
        const SVG_W = 520, SVG_H = 200, PAD_L = 32, PAD_B = 40, PAD_T = 12, PAD_R = 12;
        const chartW = SVG_W - PAD_L - PAD_R, chartH = SVG_H - PAD_B - PAD_T;
        const toX = (i) => PAD_L + (i / Math.max(n - 1, 1)) * chartW;
        // y-axis scales to the metric: 0-100 for %, else a "nice" max over the data.
        const visForScale = view.series.filter(s => !hidden[s.key]);
        let maxY = 100;
        if (!isPct) {
          let mx = 0;
          visForScale.forEach(s => s.points.forEach(p => { if (p.score != null && p.score > mx) mx = p.score; }));
          maxY = Math.max(unit === 'hours' ? 10 : 5, Math.ceil(mx / 5) * 5);
        }
        const gridVals = isPct ? [0, 25, 50, 75, 100] : [0, 0.25, 0.5, 0.75, 1].map(f => Math.round(f * maxY));
        const toY = (v) => PAD_T + chartH - (Math.max(0, Math.min(maxY, v)) / maxY) * chartH;
        const stepX = chartW / Math.max(n - 1, 1);

        // Cutover axis position: first index at/after the cutover date. Separator
        // sits just left of it; the reconstructed region is shaded.
        let cutIdx = -1;
        if (view.cutover) { const cm = view.monthly ? view.cutover.slice(0, 7) : view.cutover; cutIdx = axis.findIndex(a => (view.monthly ? a.slice(0, 7) : a) >= cm); }
        const cutX = cutIdx > 0 ? toX(cutIdx) - stepX / 2 : (cutIdx === 0 ? PAD_L : -1);

        const visSeries = view.series.filter(s => !hidden[s.key]);
        // Actual-data runs stop at a predicted point (it continues as the
        // dotted projection below, never as part of the solid line).
        const runsFor = (pts) => { const R = []; let c = []; pts.forEach((p, i) => { if (p.score == null || p.predicted) { if (c.length) { R.push(c); c = []; } } else c.push(i); }); if (c.length) R.push(c); return R; };
        // Dotted continuation: last real point + every predicted point after it.
        const predictedRunFor = (pts) => {
          let lastActual = -1;
          pts.forEach((p, i) => { if (p.score != null && !p.predicted) lastActual = i; });
          const predIdx = [];
          pts.forEach((p, i) => { if (p.predicted && p.score != null) predIdx.push(i); });
          if (!predIdx.length) return [];
          return lastActual >= 0 ? [lastActual, ...predIdx] : predIdx;
        };
        const labelEvery = Math.ceil(n / 12);

        return (
          <div>
            <svg width="100%" viewBox={`0 0 ${SVG_W} ${SVG_H}`} style={{ overflow: 'visible', display: 'block' }}>
              {/* shaded reconstructed (pre-cutover) region */}
              {cutX > PAD_L && (
                <g>
                  <rect x={PAD_L} y={PAD_T} width={Math.max(0, cutX - PAD_L)} height={chartH} fill="var(--text-3)" opacity={0.06} />
                  <line x1={cutX} y1={PAD_T} x2={cutX} y2={PAD_T + chartH} stroke="var(--text-2)" strokeWidth={1} strokeDasharray="4,3" />
                  <text x={cutX} y={PAD_T - 3} textAnchor="middle" fill="var(--text-2)" fontSize={7.5}>{_rT('migration')}</text>
                </g>
              )}
              {gridVals.map(v => {
                const y = toY(v);
                return (<g key={v}>
                  <line x1={PAD_L} y1={y} x2={SVG_W - PAD_R} y2={y} stroke="var(--line)" strokeWidth={0.5} strokeDasharray="3,3" />
                  <text x={PAD_L - 4} y={y + 4} textAnchor="end" fill="var(--text-3)" fontSize={8} fontFamily="var(--font-mono)">{v}</text>
                </g>);
              })}
              {/* one polyline group per visible series */}
              {visSeries.map(s => (
                <g key={s.key}>
                  {runsFor(s.points).map((run, ri) => (
                    <polyline key={ri} points={run.map(i => `${toX(i)},${toY(s.points[i].score)}`).join(' ')} fill="none" stroke={s.color} strokeWidth={isMulti ? 1.6 : 2.2} strokeLinejoin="round" strokeLinecap="round" opacity={0.95} />
                  ))}
                  {/* Projected continuation — dotted, muted color, distinct from the real-data line */}
                  {forecastActive && (() => {
                    const predRun = predictedRunFor(s.points);
                    return predRun.length > 1 ? (
                      <polyline points={predRun.map(i => `${toX(i)},${toY(s.points[i].score)}`).join(' ')} fill="none" stroke="var(--text-3)" strokeWidth={isMulti ? 1.6 : 2.2} strokeDasharray="4,3" strokeLinejoin="round" strokeLinecap="round" opacity={0.85} />
                    ) : null;
                  })()}
                  {(!isMulti) && s.points.map((p, i) => p.score == null ? null : (
                    <circle key={i} cx={toX(i)} cy={toY(p.score)} r={hovered === i ? 5 : 3} fill={p.predicted ? 'var(--text-3)' : s.color} stroke="var(--bg)" strokeWidth={1.5} style={{ cursor: 'pointer' }} onMouseEnter={() => setHovered(i)} onMouseLeave={() => setHovered(null)}>
                      <title>{p.predicted ? `${view.labels[i]}: ${fmtVal(p.score)} (${_rT('projected')})` : `${view.labels[i]}: ${fmtVal(p.score)}`}</title>
                    </circle>
                  ))}
                </g>
              ))}
              {/* x labels (thinned) */}
              {axis.map((a, i) => (i % labelEvery === 0 || i === n - 1) ? (
                <text key={a} x={toX(i)} y={SVG_H - 6} textAnchor="middle" fill="var(--text-3)" fontSize={7.5} fontFamily="var(--font-mono)">{view.labels[i]}</text>
              ) : null)}
              {showPartial && (
                <text x={toX(n - 1)} y={SVG_H - 6 + 9} textAnchor="middle" fill="var(--text-3)" fontSize={6.5}>{_rT('(in progress)')}</text>
              )}
              <line x1={PAD_L} y1={PAD_T} x2={PAD_L} y2={PAD_T + chartH} stroke="var(--line)" strokeWidth={1} />
              <line x1={PAD_L} y1={PAD_T + chartH} x2={SVG_W - PAD_R} y2={PAD_T + chartH} stroke="var(--line)" strokeWidth={1} />
            </svg>

            {/* "projected" legend chip — single-series views with an active forecast */}
            {forecastActive && !isMulti && visSeries.some(s => s.points.some(p => p.predicted)) && (
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 12 }}>
                <span style={{ width: 16, height: 0, borderTop: '2px dashed var(--text-3)', display: 'inline-block' }} />
                <span className="muted" style={{ fontSize: 11 }}>{_rT('projected')}</span>
              </div>
            )}

            {/* Legend (multi-series): click to toggle */}
            {isMulti && (
              <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginTop: 12 }}>
                {view.series.map(s => {
                  const off = !!hidden[s.key];
                  const last = [...s.points].reverse().find(p => p.score != null);
                  return (
                    <button key={s.key} onClick={() => toggleSeries(s.key)} className="btn btn-ghost" style={{ padding: '2px 8px', fontSize: 11, opacity: off ? 0.4 : 1, display: 'inline-flex', alignItems: 'center', gap: 6 }} title={_rT('Toggle series')}>
                      <span style={{ width: 10, height: 10, borderRadius: 2, background: s.color, display: 'inline-block' }} />
                      {s.label}{last ? <span className="muted mono" style={{ fontSize: 10 }}>{fmtVal(last.score)}</span> : null}
                    </button>
                  );
                })}
              </div>
            )}
          </div>
        );
      })()}
    </div>
  );
}

function ChartsSection() {
  const [period, setPeriod] = React.useState('week');
  const selected = CHART_PERIODS.find(p => p.value === period) || CHART_PERIODS[1];

  return (
    <div className="card" style={{ marginTop: 16 }}>
      <SectionHeader
        title={_rT("Charts")}
          tip={_rT("Hours-per-day, attendance %, top contributors. Auto-refresh on filter change. PDF export via the Generate Report card above.")}
        sub={_rT("Attendance breakdown, hours trend, and status distribution")}
        right={
          <div style={{ display:'flex', gap: 6, alignItems:'center' }}>
            {CHART_PERIODS.map(p => (
              <button
                key={p.value}
                className={period === p.value ? 'btn btn-pri' : 'btn btn-ghost'}
                style={{ padding:'3px 10px', fontSize: 11 }}
                onClick={() => setPeriod(p.value)}
              >
                {_rT(p.label)}
              </button>
            ))}
          </div>
        }
      />

      {/* Top row: stacked bar + line chart */}
      <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(300px, 1fr))', gap: 24, marginTop: 20 }}>
        <div>
          <div style={{ fontSize: 12, fontWeight: 600, color:'var(--text-2)', marginBottom: 10 }}>{_rT("Daily attendance breakdown")}</div>
          <AttendanceStackedBar range={selected.range} />
        </div>
        <div>
          <div style={{ fontSize: 12, fontWeight: 600, color:'var(--text-2)', marginBottom: 10 }}>{_rT("Total hours per day")}</div>
          <HoursTrendLine range={selected.range} />
        </div>
      </div>

      {/* Bottom row: donut spans both */}
      <div style={{ marginTop: 28, borderTop:'1px solid var(--line)', paddingTop: 20 }}>
        <div style={{ fontSize: 12, fontWeight: 600, color:'var(--text-2)', marginBottom: 14 }}>{_rT("Status distribution")} — {_rT(selected.label)}</div>
        <StatusDonut range={selected.range} periodLabel={_rT(selected.label)} />
      </div>
    </div>
  );
}

window.ReportsPage = ReportsPage;
