// UnearthWorx Payroll — main app component.
// Phase 1: clock in/out, geo-fenced sites, jobs admin, weekly timesheet, CSV export.
// Phase 2: JHA daily gate, document vault, admin tabs (crew/jobs/JHA tpl/audit), audit-logged edits.

const { useState, useEffect, useRef, useMemo, useCallback } = React;

function PayrollApp() {
  const [me, setMe] = useState(undefined); // undefined = loading, null = unauthed
  const [error, setError] = useState(null);
  const [jhaState, setJhaState] = useState(null); // { submitted, template }
  const [clockReloadKey, setClockReloadKey] = useState(0);

  useEffect(() => {
    fetch('/api/me', { credentials: 'same-origin' })
      .then(r => r.status === 401 ? null : r.json())
      .then(body => setMe(body?.user || null))
      .catch(() => setMe(null));
  }, []);

  const loadJha = useCallback(() => {
    fetch('/api/jha/today', { credentials: 'same-origin' })
      .then(r => r.json())
      .then(setJhaState);
  }, []);
  useEffect(() => { if (me) loadJha(); }, [me, loadJha]);

  if (me === undefined) return <div className="loading">Loading…</div>;
  if (me === null) {
    window.location.href = '/login';
    return <div className="loading">Redirecting to sign in…</div>;
  }

  const needsJha = jhaState && !jhaState.submitted && jhaState.template;

  return (
    <div className="app">
      <AppNav me={me} />
      <main className="app-main">
        <div>
          <ClockWidget
            me={me}
            jhaSubmitted={!!jhaState?.submitted}
            onError={setError}
            reloadKey={clockReloadKey}
          />
          <DocumentsPanel me={me} target={me.discord_id} title="My documents" onError={setError} />
          <MyStubsPanel onError={setError} />
          <BankingPanel onError={setError} />
        </div>
        <div>
          {error && <div className="banner error">{error}</div>}
          {needsJha && (
            <JhaGate
              template={jhaState.template}
              onSubmitted={() => { loadJha(); setClockReloadKey(k => k + 1); }}
              onError={setError}
            />
          )}
          <TimesheetPanel me={me} onError={setError} />
          {me.role === 'admin' && <AdminTabs me={me} onError={setError} onChange={() => setClockReloadKey(k => k + 1)} />}
        </div>
      </main>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────
//  NAV
// ───────────────────────────────────────────────────────────────────────

function AppNav({ me }) {
  const logout = async () => {
    await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' });
    window.location.href = '/login';
  };
  return (
    <nav className="app-nav">
      <div className="app-logo">
        UNEARTH<span>WORX</span>
        <small>PAYROLL</small>
      </div>
      <div className="app-user">
        {me.avatar_url && <img className="avatar" src={me.avatar_url} alt="" />}
        <span>{me.display_name}</span>
        <span className="role-badge">{me.role}</span>
        <button className="logout" onClick={logout}>Sign out</button>
      </div>
    </nav>
  );
}

// ───────────────────────────────────────────────────────────────────────
//  CLOCK WIDGET
// ───────────────────────────────────────────────────────────────────────

function ClockWidget({ me, jhaSubmitted, onError, reloadKey }) {
  const [open, setOpen]   = useState(null);   // open shift entry or null
  const [jobs, setJobs]   = useState([]);
  const [jobId, setJobId] = useState('');
  const [busy, setBusy]   = useState(false);
  const [tick, setTick]   = useState(Date.now());

  const loadOpen = useCallback(() => {
    return fetch('/api/clock/current', { credentials: 'same-origin' })
      .then(r => r.json())
      .then(b => setOpen(b.entry || null));
  }, []);
  const loadJobs = useCallback(() => {
    return fetch('/api/jobs?status=active', { credentials: 'same-origin' })
      .then(r => r.json())
      .then(b => setJobs(b.jobs || []));
  }, []);

  useEffect(() => { loadOpen(); loadJobs(); }, [loadOpen, loadJobs, reloadKey]);

  useEffect(() => {
    if (!open) return;
    const t = setInterval(() => setTick(Date.now()), 1000);
    return () => clearInterval(t);
  }, [open]);

  async function getPosition(needed) {
    if (!needed) return { lat: null, lng: null, acc: null };
    if (!navigator.geolocation) throw new Error('Geolocation unavailable in this browser');
    return await new Promise((resolve, reject) => {
      navigator.geolocation.getCurrentPosition(
        pos => resolve({
          lat: pos.coords.latitude,
          lng: pos.coords.longitude,
          acc: pos.coords.accuracy,
        }),
        err => reject(new Error('Location denied or unavailable: ' + err.message)),
        { enableHighAccuracy: true, timeout: 8000, maximumAge: 0 },
      );
    });
  }

  async function clockIn() {
    setBusy(true);
    onError(null);
    try {
      const job = jobs.find(j => j.id === jobId);
      const needGeo = !!(job && job.site_lat != null && job.site_lng != null);
      const pos = await getPosition(needGeo);
      const res = await fetch('/api/clock/in', {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          job_id: jobId || null,
          lat: pos.lat, lng: pos.lng, accuracy_m: pos.acc,
          source: 'ui',
        }),
      });
      const body = await res.json().catch(() => ({}));
      if (!res.ok) {
        if (res.status === 412 && body.reason === 'jha_required') {
          throw new Error('Submit today\'s JHA first.');
        }
        if (res.status === 422 && body.distance_m != null) {
          throw new Error(`You're ${body.distance_m}m from the site (allowed: ${body.allowed_m}m).`);
        }
        throw new Error(body.error || 'Clock-in failed');
      }
      await loadOpen();
    } catch (e) {
      onError(e.message);
    } finally {
      setBusy(false);
    }
  }

  async function clockOut() {
    setBusy(true);
    onError(null);
    try {
      const pos = await getPosition(false).catch(() => ({ lat: null, lng: null }));
      const res = await fetch('/api/clock/out', {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ lat: pos.lat, lng: pos.lng, source: 'ui' }),
      });
      const body = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(body.error || 'Clock-out failed');
      await loadOpen();
    } catch (e) {
      onError(e.message);
    } finally {
      setBusy(false);
    }
  }

  const elapsed = open ? tick - open.clock_in_at : 0;

  return (
    <div className="panel">
      <h3 className="panel-title">
        <span><i className="ph ph-timer"></i> Time clock</span>
      </h3>
      <div className="clock-widget">
        <div className={'clock-state' + (open ? ' on' : '')}>
          <span className="dot" />
          {open ? 'On the clock' : 'Off the clock'}
        </div>
        <div className="clock-time">{open ? formatDuration(elapsed) : '00:00:00'}</div>
        {open && open.job_name && <div className="clock-job">{open.job_name}</div>}

        {!open && (
          <div className="field" style={{ textAlign: 'left' }}>
            <label className="field-label">Job (optional)</label>
            <select
              className="field-select"
              value={jobId}
              onChange={e => setJobId(e.target.value)}
              disabled={busy}
            >
              <option value="">— none —</option>
              {jobs.map(j => (
                <option key={j.id} value={j.id}>
                  {j.name}{j.client ? ` · ${j.client}` : ''}
                  {j.site_lat != null ? ' · GEO' : ''}
                </option>
              ))}
            </select>
          </div>
        )}

        {!open ? (
          <button
            className="clock-action"
            disabled={busy || !jhaSubmitted}
            onClick={clockIn}
            title={!jhaSubmitted ? 'Submit today\'s JHA first' : ''}
          >
            {busy ? 'Working…' : (jhaSubmitted ? 'Clock In' : 'JHA required')}
          </button>
        ) : (
          <button className="clock-action danger" disabled={busy} onClick={clockOut}>
            {busy ? 'Working…' : 'Clock Out'}
          </button>
        )}

        <div className="clock-meta">
          {open
            ? `Started ${new Date(open.clock_in_at).toLocaleTimeString()}`
            : (jhaSubmitted ? 'Pick a job site, then clock in.' : 'Complete your daily JHA →')}
        </div>
      </div>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────
//  TIMESHEET
// ───────────────────────────────────────────────────────────────────────

function TimesheetPanel({ me, onError }) {
  const [range, setRange]   = useState(() => weekRangeOffset(0));
  const [target, setTarget] = useState(me.discord_id);
  const [data, setData]     = useState(null);
  const [users, setUsers]   = useState([]);
  const [editing, setEditing] = useState(null);
  const isAdmin = me.role === 'admin';

  const load = useCallback(() => {
    const u = encodeURIComponent(target || me.discord_id);
    fetch(`/api/timesheet?from=${range.from}&to=${range.to}&user=${u}`,
      { credentials: 'same-origin' })
      .then(r => r.json())
      .then(setData);
  }, [range, target, me.discord_id]);

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

  // Admin: build a list of users seen in the current "all" view, lazily.
  useEffect(() => {
    if (!isAdmin) return;
    fetch(`/api/timesheet?from=${range.from}&to=${range.to}&user=all`,
      { credentials: 'same-origin' })
      .then(r => r.json())
      .then(b => {
        const seen = new Map();
        for (const s of (b.summary || [])) seen.set(s.user_id, s.user_name);
        for (const e of (b.entries || [])) seen.set(e.user_id, e.user_name);
        setUsers(Array.from(seen.entries()).map(([id, name]) => ({ id, name })));
      });
  }, [isAdmin, range]);

  const downloadCsv = () => {
    const u = encodeURIComponent(target);
    window.location.href = `/api/export?from=${range.from}&to=${range.to}&user=${u}`;
  };

  const myTotal = useMemo(() => {
    if (!data?.summary?.length) return null;
    return data.summary.find(s => s.user_id === (target === 'all' ? null : target))
        || data.summary[0];
  }, [data, target]);

  return (
    <div className="panel">
      <h3 className="panel-title">
        <span><i className="ph ph-table"></i> Timesheet</span>
        <button className="btn-sm" onClick={downloadCsv}>
          <i className="ph ph-download-simple"></i> CSV
        </button>
      </h3>

      <div className="range-bar">
        <select
          className="field-select"
          value={rangeKey(range)}
          onChange={e => setRange(weekRangeOffset(Number(e.target.value)))}
        >
          <option value="0">This week (Mon–Sun)</option>
          <option value="-1">Last week</option>
          <option value="-2">2 weeks ago</option>
          <option value="-3">3 weeks ago</option>
        </select>
        {isAdmin && (
          <select
            className="field-select"
            value={target}
            onChange={e => setTarget(e.target.value)}
          >
            <option value={me.discord_id}>{me.display_name} (me)</option>
            <option value="all">All crew</option>
            {users
              .filter(u => u.id !== me.discord_id)
              .map(u => <option key={u.id} value={u.id}>{u.name}</option>)}
          </select>
        )}
      </div>

      {target !== 'all' && myTotal && (
        <div className="sum-row">
          <div className="sum-card">
            <div className="sum-card-label">Total hours</div>
            <div className="sum-card-value">{(myTotal.total_hrs ?? 0).toFixed(2)}</div>
          </div>
          <div className="sum-card">
            <div className="sum-card-label">Regular</div>
            <div className="sum-card-value">{(myTotal.regular_hrs ?? 0).toFixed(2)}</div>
            <div className="sum-card-sub">{centsToUsd(myTotal.regular_pay_cents)}</div>
          </div>
          <div className="sum-card">
            <div className="sum-card-label">Overtime</div>
            <div className="sum-card-value orange">{(myTotal.ot_hrs ?? 0).toFixed(2)}</div>
            <div className="sum-card-sub">{centsToUsd(myTotal.ot_pay_cents)}</div>
          </div>
          <div className="sum-card">
            <div className="sum-card-label">Total pay</div>
            <div className="sum-card-value orange">
              {centsToUsd(myTotal.total_pay_cents)}
            </div>
          </div>
        </div>
      )}

      {target === 'all' && data?.summary && (
        <table className="tbl" style={{ marginBottom: 20 }}>
          <thead>
            <tr>
              <th>Crew</th>
              <th className="num">Hrs</th>
              <th className="num">Reg</th>
              <th className="num">OT</th>
              <th className="num">Pay</th>
            </tr>
          </thead>
          <tbody>
            {data.summary.map(s => (
              <tr key={s.user_id}>
                <td>{s.user_name}</td>
                <td className="num">{(s.total_hrs ?? 0).toFixed(2)}</td>
                <td className="num">{(s.regular_hrs ?? 0).toFixed(2)}</td>
                <td className="num">{(s.ot_hrs ?? 0).toFixed(2)}</td>
                <td className="num">{centsToUsd(s.total_pay_cents)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}

      {data?.entries?.length ? (
        <table className="tbl">
          <thead>
            <tr>
              <th>Day</th>
              {target === 'all' || isAdmin ? <th>Crew</th> : null}
              <th>Job</th>
              <th>In</th>
              <th>Out</th>
              <th className="num">Hrs</th>
              <th>Src</th>
              {isAdmin && <th></th>}
            </tr>
          </thead>
          <tbody>
            {data.entries.map(e => (
              <EntryRow
                key={e.id} e={e}
                showCrew={target === 'all' || isAdmin}
                isAdmin={isAdmin}
                onEdit={() => setEditing(e)}
              />
            ))}
          </tbody>
        </table>
      ) : (
        <div className="loading">No entries in range.</div>
      )}

      {editing && (
        <EntryEditModal
          entry={editing}
          onClose={() => setEditing(null)}
          onSaved={() => { setEditing(null); load(); }}
          onError={onError}
        />
      )}
    </div>
  );
}

function EntryRow({ e, showCrew, isAdmin, onEdit }) {
  const start = new Date(e.clock_in_at);
  const end = e.clock_out_at ? new Date(e.clock_out_at) : null;
  const hrs = end ? (end - start) / 3_600_000 : (Date.now() - start) / 3_600_000;
  return (
    <tr>
      <td>{start.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })}</td>
      {showCrew && <td>{e.user_name}</td>}
      <td>{e.job_name || <span style={{ color: 'var(--text-muted)' }}>—</span>}</td>
      <td>{start.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</td>
      <td>
        {end
          ? end.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
          : <span className="badge orange">open</span>}
      </td>
      <td className="num">{hrs.toFixed(2)}</td>
      <td><span className="badge">{e.source_in}</span></td>
      {isAdmin && (
        <td style={{ textAlign: 'right' }}>
          <button className="btn-sm" onClick={onEdit}>Edit</button>
        </td>
      )}
    </tr>
  );
}

// ───────────────────────────────────────────────────────────────────────
//  JOBS (admin)
// ───────────────────────────────────────────────────────────────────────

function JobsPanel({ onError }) {
  const [jobs, setJobs] = useState([]);
  const [showForm, setShowForm] = useState(false);

  const load = useCallback(() => {
    fetch('/api/jobs?status=all', { credentials: 'same-origin' })
      .then(r => r.json())
      .then(b => setJobs(b.jobs || []));
  }, []);
  useEffect(() => { load(); }, [load]);

  return (
    <div className="panel">
      <h3 className="panel-title">
        <span><i className="ph ph-map-pin"></i> Jobs</span>
        <button className="btn-sm primary" onClick={() => setShowForm(s => !s)}>
          {showForm ? 'Cancel' : '+ New job'}
        </button>
      </h3>

      {showForm && <JobForm onSaved={() => { setShowForm(false); load(); }} onError={onError} />}

      {jobs.length ? (
        <table className="tbl">
          <thead>
            <tr>
              <th>Name</th>
              <th>Client</th>
              <th>Geo</th>
              <th className="num">Radius</th>
              <th>Status</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            {jobs.map(j => (
              <JobRow key={j.id} job={j} onChange={load} onError={onError} />
            ))}
          </tbody>
        </table>
      ) : <div className="loading">No jobs yet.</div>}
    </div>
  );
}

function JobForm({ onSaved, onError, initial }) {
  const [name, setName]     = useState(initial?.name || '');
  const [client, setClient] = useState(initial?.client || '');
  const [lat, setLat]       = useState(initial?.site_lat ?? '');
  const [lng, setLng]       = useState(initial?.site_lng ?? '');
  const [radius, setRadius] = useState(initial?.geofence_radius_m ?? 150);
  const [saving, setSaving] = useState(false);

  const useCurrent = () => {
    if (!navigator.geolocation) return onError('Geolocation unavailable');
    navigator.geolocation.getCurrentPosition(
      p => { setLat(p.coords.latitude); setLng(p.coords.longitude); },
      e => onError('Location: ' + e.message),
      { enableHighAccuracy: true, timeout: 8000 },
    );
  };

  const save = async (e) => {
    e.preventDefault();
    setSaving(true);
    try {
      const url = initial ? `/api/jobs/${initial.id}` : '/api/jobs';
      const method = initial ? 'PUT' : 'POST';
      const res = await fetch(url, {
        method,
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name,
          client: client || null,
          site_lat: lat === '' ? null : Number(lat),
          site_lng: lng === '' ? null : Number(lng),
          geofence_radius_m: Number(radius) || 150,
        }),
      });
      if (!res.ok) {
        const b = await res.json().catch(() => ({}));
        throw new Error(b.error || 'Save failed');
      }
      onSaved();
    } catch (err) {
      onError(err.message);
    } finally {
      setSaving(false);
    }
  };

  return (
    <form onSubmit={save} style={{ marginBottom: 20, padding: 16, background: 'var(--surface-container)' }}>
      <div className="field">
        <label className="field-label">Name</label>
        <input className="field-input" value={name} onChange={e => setName(e.target.value)} required />
      </div>
      <div className="field">
        <label className="field-label">Client</label>
        <input className="field-input" value={client} onChange={e => setClient(e.target.value)} />
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr auto', gap: 8 }}>
        <div className="field">
          <label className="field-label">Lat</label>
          <input className="field-input" value={lat} onChange={e => setLat(e.target.value)} placeholder="optional" />
        </div>
        <div className="field">
          <label className="field-label">Lng</label>
          <input className="field-input" value={lng} onChange={e => setLng(e.target.value)} placeholder="optional" />
        </div>
        <div className="field" style={{ alignSelf: 'end' }}>
          <button type="button" className="btn-sm" onClick={useCurrent}>
            <i className="ph ph-crosshair"></i> Here
          </button>
        </div>
      </div>
      <div className="field">
        <label className="field-label">Geofence radius (m)</label>
        <input className="field-input" type="number" min="20" max="2000"
          value={radius} onChange={e => setRadius(e.target.value)} />
      </div>
      <button className="btn-sm primary" type="submit" disabled={saving}>
        {saving ? 'Saving…' : (initial ? 'Update' : 'Create')}
      </button>
    </form>
  );
}

function JobRow({ job, onChange, onError }) {
  const [editing, setEditing] = useState(false);

  const archive = async () => {
    if (!confirm(`Archive "${job.name}"?`)) return;
    const res = await fetch(`/api/jobs/${job.id}`, {
      method: 'DELETE', credentials: 'same-origin',
    });
    if (!res.ok) {
      const b = await res.json().catch(() => ({}));
      onError(b.error || 'Archive failed');
    } else onChange();
  };

  if (editing) {
    return (
      <tr>
        <td colSpan="6" style={{ padding: 0 }}>
          <JobForm initial={job} onError={onError}
            onSaved={() => { setEditing(false); onChange(); }} />
        </td>
      </tr>
    );
  }

  return (
    <tr>
      <td>{job.name}</td>
      <td>{job.client || '—'}</td>
      <td>
        {job.site_lat != null
          ? <span className="badge orange">{job.site_lat.toFixed(4)}, {job.site_lng.toFixed(4)}</span>
          : <span style={{ color: 'var(--text-muted)' }}>none</span>}
      </td>
      <td className="num">{job.geofence_radius_m}m</td>
      <td><span className={'badge' + (job.status === 'active' ? ' orange' : '')}>{job.status}</span></td>
      <td style={{ textAlign: 'right' }}>
        <button className="btn-sm" onClick={() => setEditing(true)}>Edit</button>{' '}
        {job.status === 'active' && (
          <button className="btn-sm" onClick={archive}>Archive</button>
        )}
      </td>
    </tr>
  );
}

// ───────────────────────────────────────────────────────────────────────
//  JHA GATE (daily safety form)
// ───────────────────────────────────────────────────────────────────────

function JhaGate({ template, onSubmitted, onError }) {
  const [answers, setAnswers] = useState({});
  const [busy, setBusy] = useState(false);

  const setAns = (id, v) => setAnswers(a => ({ ...a, [id]: v }));

  const submit = async (e) => {
    e.preventDefault();
    setBusy(true);
    try {
      const res = await fetch('/api/jha/submit', {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ answers, source: 'ui' }),
      });
      const body = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(body.error || 'Submission failed');
      onSubmitted();
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="panel" style={{ borderLeft: '3px solid var(--orange)' }}>
      <h3 className="panel-title">
        <span><i className="ph ph-shield-check"></i> Daily JHA — required before clock-in</span>
        <span className="badge orange">v{template.version}</span>
      </h3>
      <form onSubmit={submit}>
        {template.questions.map(q => (
          <JhaQuestion key={q.id} q={q} value={answers[q.id]} onChange={v => setAns(q.id, v)} />
        ))}
        <button className="btn-sm primary" type="submit" disabled={busy}>
          {busy ? 'Submitting…' : 'Submit JHA'}
        </button>
      </form>
    </div>
  );
}

function JhaQuestion({ q, value, onChange }) {
  return (
    <div className="field">
      <label className="field-label">
        {q.text}{q.required && <span style={{ color: 'var(--orange)' }}> *</span>}
      </label>
      {q.type === 'yesno' && (
        <div style={{ display: 'flex', gap: 8 }}>
          {['yes', 'no'].map(v => (
            <button
              key={v}
              type="button"
              className={'btn-sm' + (value === v ? ' primary' : '')}
              onClick={() => onChange(v)}
            >{v.toUpperCase()}</button>
          ))}
        </div>
      )}
      {q.type === 'text' && (
        <textarea
          className="field-textarea"
          value={value || ''}
          onChange={e => onChange(e.target.value)}
          required={q.required}
        />
      )}
      {q.type === 'ack' && (
        <label style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 13 }}>
          <input
            type="checkbox"
            checked={!!value}
            onChange={e => onChange(e.target.checked)}
          />
          <span style={{ color: 'var(--on-surface-variant)' }}>I acknowledge</span>
        </label>
      )}
      {q.type === 'choice' && (
        <select
          className="field-select"
          value={value || ''}
          onChange={e => onChange(e.target.value)}
          required={q.required}
        >
          <option value="">—</option>
          {(q.options || []).map(o => <option key={o} value={o}>{o}</option>)}
        </select>
      )}
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────
//  DOCUMENTS PANEL
// ───────────────────────────────────────────────────────────────────────

const DOC_KINDS = ['cdl','osha10','osha30','i9','w4','drug_test','medical','license','other'];
const DOC_KIND_LABELS = {
  cdl: 'CDL', osha10: 'OSHA 10', osha30: 'OSHA 30',
  i9: 'I-9', w4: 'W-4', drug_test: 'Drug test',
  medical: 'Medical', license: 'License', other: 'Other',
};

function DocumentsPanel({ me, target, title, onError }) {
  const [docs, setDocs] = useState([]);
  const [showForm, setShowForm] = useState(false);
  const isAdmin = me.role === 'admin';
  const t = target || me.discord_id;

  const load = useCallback(() => {
    const u = encodeURIComponent(t);
    fetch(`/api/documents?user=${u}`, { credentials: 'same-origin' })
      .then(r => r.json())
      .then(b => setDocs(b.documents || []));
  }, [t]);
  useEffect(() => { load(); }, [load]);

  return (
    <div className="panel">
      <h3 className="panel-title">
        <span><i className="ph ph-file-text"></i> {title || 'Documents'}</span>
        <button className="btn-sm primary" onClick={() => setShowForm(s => !s)}>
          {showForm ? 'Cancel' : '+ Upload'}
        </button>
      </h3>

      {showForm && (
        <DocUploadForm
          targetUser={t}
          isAdmin={isAdmin}
          onSaved={() => { setShowForm(false); load(); }}
          onError={onError}
        />
      )}

      {docs.length ? (
        <table className="tbl">
          <thead>
            <tr>
              <th>Kind</th>
              <th>Label</th>
              {t === 'all' && <th>Crew</th>}
              <th>Expires</th>
              <th>Verified</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            {docs.map(d => (
              <DocRow key={d.id} d={d} isAdmin={isAdmin}
                showCrew={t === 'all'}
                onChange={load} onError={onError} />
            ))}
          </tbody>
        </table>
      ) : <div className="loading">No documents yet.</div>}
    </div>
  );
}

function DocUploadForm({ targetUser, isAdmin, onSaved, onError }) {
  const fileRef = useRef(null);
  const [kind, setKind] = useState('cdl');
  const [label, setLabel] = useState('');
  const [expires, setExpires] = useState('');
  const [busy, setBusy] = useState(false);

  const submit = async (e) => {
    e.preventDefault();
    const file = fileRef.current?.files?.[0];
    if (!file) return onError('Choose a file');
    setBusy(true);
    try {
      const form = new FormData();
      form.append('file', file);
      form.append('kind', kind);
      if (label) form.append('label', label);
      if (expires) form.append('expires_at', expires);
      if (isAdmin && targetUser && targetUser !== 'all') form.append('user_id', targetUser);
      const res = await fetch('/api/documents', {
        method: 'POST', credentials: 'same-origin', body: form,
      });
      if (!res.ok) {
        const b = await res.json().catch(() => ({}));
        throw new Error(b.error || 'Upload failed');
      }
      onSaved();
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <form onSubmit={submit} style={{ marginBottom: 20, padding: 16, background: 'var(--surface-container)' }}>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: 8 }}>
        <div className="field">
          <label className="field-label">Kind</label>
          <select className="field-select" value={kind} onChange={e => setKind(e.target.value)}>
            {DOC_KINDS.map(k => <option key={k} value={k}>{DOC_KIND_LABELS[k]}</option>)}
          </select>
        </div>
        <div className="field">
          <label className="field-label">Label (optional)</label>
          <input className="field-input" value={label} onChange={e => setLabel(e.target.value)} />
        </div>
      </div>
      <div className="field">
        <label className="field-label">Expires (yyyy-mm-dd)</label>
        <input className="field-input" type="date" value={expires} onChange={e => setExpires(e.target.value)} />
      </div>
      <div className="field">
        <label className="field-label">File (PDF / image, max 25 MB)</label>
        <input ref={fileRef} type="file" accept="application/pdf,image/*" required />
      </div>
      <button className="btn-sm primary" type="submit" disabled={busy}>
        {busy ? 'Uploading…' : 'Upload'}
      </button>
    </form>
  );
}

function DocRow({ d, isAdmin, showCrew, onChange, onError }) {
  const [editing, setEditing] = useState(false);
  const expiry = d.expires_at ? new Date(d.expires_at) : null;
  const daysLeft = expiry ? Math.ceil((expiry - Date.now()) / 86400000) : null;
  const expiryClass = daysLeft == null ? '' : daysLeft <= 0 ? 'error' : daysLeft <= 14 ? 'orange' : '';

  const remove = async () => {
    if (!confirm(`Delete "${d.label || d.kind}"?`)) return;
    const res = await fetch(`/api/documents/${d.id}`, { method: 'DELETE', credentials: 'same-origin' });
    if (!res.ok) {
      const b = await res.json().catch(() => ({}));
      onError(b.error || 'Delete failed');
    } else onChange();
  };

  const toggleVerify = async () => {
    const res = await fetch(`/api/documents/${d.id}`, {
      method: 'PUT', credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ verified: !d.verified_at }),
    });
    if (!res.ok) {
      const b = await res.json().catch(() => ({}));
      onError(b.error || 'Verify failed');
    } else onChange();
  };

  if (editing) {
    return (
      <tr>
        <td colSpan={showCrew ? 6 : 5} style={{ padding: 0 }}>
          <DocEditForm d={d} onSaved={() => { setEditing(false); onChange(); }} onError={onError} />
        </td>
      </tr>
    );
  }

  return (
    <tr>
      <td><span className="badge">{DOC_KIND_LABELS[d.kind] || d.kind}</span></td>
      <td>
        <a href={`/api/documents/${d.id}?download=1`} style={{ color: 'var(--orange)' }}>
          {d.label || d.file_name || d.id}
        </a>
      </td>
      {showCrew && <td>{d.user_name}</td>}
      <td>
        {expiry ? (
          <span className={'badge ' + expiryClass} style={
            expiryClass === 'error' ? { background: 'rgba(147,0,10,0.2)', color: 'var(--error-text)' }
            : expiryClass === 'orange' ? {} : { background: 'var(--surface-container-high)' }
          }>
            {expiry.toLocaleDateString()}
            {daysLeft <= 0 ? ` (expired)` : ` (${daysLeft}d)`}
          </span>
        ) : <span style={{ color: 'var(--text-muted)' }}>—</span>}
      </td>
      <td>
        {d.verified_at
          ? <span className="badge orange">✓</span>
          : <span style={{ color: 'var(--text-muted)' }}>no</span>}
      </td>
      <td style={{ textAlign: 'right' }}>
        <button className="btn-sm" onClick={() => setEditing(true)}>Edit</button>{' '}
        {isAdmin && (
          <button className="btn-sm" onClick={toggleVerify}>
            {d.verified_at ? 'Unverify' : 'Verify'}
          </button>
        )}{' '}
        <button className="btn-sm" onClick={remove}>Del</button>
      </td>
    </tr>
  );
}

function DocEditForm({ d, onSaved, onError }) {
  const [kind, setKind] = useState(d.kind);
  const [label, setLabel] = useState(d.label || '');
  const [expires, setExpires] = useState(d.expires_at ? new Date(d.expires_at).toISOString().slice(0, 10) : '');
  const [notes, setNotes] = useState(d.notes || '');
  const [busy, setBusy] = useState(false);

  const save = async (e) => {
    e.preventDefault();
    setBusy(true);
    try {
      const res = await fetch(`/api/documents/${d.id}`, {
        method: 'PUT', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          kind, label: label || null, expires_at: expires || null, notes: notes || null,
        }),
      });
      if (!res.ok) {
        const b = await res.json().catch(() => ({}));
        throw new Error(b.error || 'Save failed');
      }
      onSaved();
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <form onSubmit={save} style={{ padding: 16, background: 'var(--surface-container)' }}>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: 8 }}>
        <div className="field">
          <label className="field-label">Kind</label>
          <select className="field-select" value={kind} onChange={e => setKind(e.target.value)}>
            {DOC_KINDS.map(k => <option key={k} value={k}>{DOC_KIND_LABELS[k]}</option>)}
          </select>
        </div>
        <div className="field">
          <label className="field-label">Label</label>
          <input className="field-input" value={label} onChange={e => setLabel(e.target.value)} />
        </div>
      </div>
      <div className="field">
        <label className="field-label">Expires</label>
        <input className="field-input" type="date" value={expires} onChange={e => setExpires(e.target.value)} />
      </div>
      <div className="field">
        <label className="field-label">Notes</label>
        <textarea className="field-textarea" value={notes} onChange={e => setNotes(e.target.value)} />
      </div>
      <button className="btn-sm primary" type="submit" disabled={busy}>
        {busy ? 'Saving…' : 'Save'}
      </button>
    </form>
  );
}

// ───────────────────────────────────────────────────────────────────────
//  ENTRY EDIT (admin)
// ───────────────────────────────────────────────────────────────────────

function EntryEditModal({ entry, onClose, onSaved, onError }) {
  const [inAt, setInAt]   = useState(toLocalInput(entry.clock_in_at));
  const [outAt, setOutAt] = useState(entry.clock_out_at ? toLocalInput(entry.clock_out_at) : '');
  const [jobId, setJobId] = useState(entry.job_id || '');
  const [notes, setNotes] = useState(entry.notes || '');
  const [reason, setReason] = useState('');
  const [jobs, setJobs] = useState([]);
  const [busy, setBusy] = useState(false);

  useEffect(() => {
    fetch('/api/jobs?status=all', { credentials: 'same-origin' })
      .then(r => r.json()).then(b => setJobs(b.jobs || []));
  }, []);

  const save = async (e) => {
    e.preventDefault();
    if (!reason.trim()) { onError('Reason required'); return; }
    setBusy(true);
    try {
      const res = await fetch(`/api/admin/entries/${entry.id}`, {
        method: 'PUT', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          clock_in_at: fromLocalInput(inAt),
          clock_out_at: outAt ? fromLocalInput(outAt) : null,
          job_id: jobId || null,
          notes: notes || null,
          reason: reason.trim(),
        }),
      });
      if (!res.ok) {
        const b = await res.json().catch(() => ({}));
        throw new Error(b.error || 'Save failed');
      }
      onSaved();
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  const remove = async () => {
    if (!reason.trim()) { onError('Reason required'); return; }
    if (!confirm('Delete this entry permanently?')) return;
    setBusy(true);
    try {
      const res = await fetch(`/api/admin/entries/${entry.id}?reason=${encodeURIComponent(reason.trim())}`, {
        method: 'DELETE', credentials: 'same-origin',
      });
      if (!res.ok) {
        const b = await res.json().catch(() => ({}));
        throw new Error(b.error || 'Delete failed');
      }
      onSaved();
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal-panel" onClick={e => e.stopPropagation()}>
        <h3 className="panel-title">
          <span><i className="ph ph-pencil-simple"></i> Edit entry — {entry.user_name}</span>
          <button className="btn-sm" onClick={onClose}>Close</button>
        </h3>
        <form onSubmit={save}>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
            <div className="field">
              <label className="field-label">Clock in</label>
              <input className="field-input" type="datetime-local" value={inAt}
                onChange={e => setInAt(e.target.value)} required />
            </div>
            <div className="field">
              <label className="field-label">Clock out</label>
              <input className="field-input" type="datetime-local" value={outAt}
                onChange={e => setOutAt(e.target.value)} />
            </div>
          </div>
          <div className="field">
            <label className="field-label">Job</label>
            <select className="field-select" value={jobId} onChange={e => setJobId(e.target.value)}>
              <option value="">— none —</option>
              {jobs.map(j => <option key={j.id} value={j.id}>{j.name}</option>)}
            </select>
          </div>
          <div className="field">
            <label className="field-label">Notes</label>
            <textarea className="field-textarea" value={notes} onChange={e => setNotes(e.target.value)} />
          </div>
          <div className="field">
            <label className="field-label">Reason for change (required)</label>
            <input className="field-input" value={reason} onChange={e => setReason(e.target.value)}
              placeholder="e.g. forgot to clock out, off-by-one shift, …" required />
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn-sm primary" type="submit" disabled={busy}>
              {busy ? 'Saving…' : 'Save'}
            </button>
            <button className="btn-sm" type="button" onClick={remove} disabled={busy}>
              Delete entry
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

function toLocalInput(ms) {
  const d = new Date(ms);
  const pad = n => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
function fromLocalInput(v) { return new Date(v).getTime(); }

// ───────────────────────────────────────────────────────────────────────
//  ADMIN TABS
// ───────────────────────────────────────────────────────────────────────

function AdminTabs({ me, onError, onChange }) {
  const [tab, setTab] = useState('pay');
  const TABS = [
    { id: 'pay',    label: 'Pay',      icon: 'ph-money' },
    { id: 'jobs',   label: 'Jobs',     icon: 'ph-map-pin' },
    { id: 'crew',   label: 'Crew',     icon: 'ph-users-three' },
    { id: 'jha',    label: 'JHA',      icon: 'ph-shield-check' },
    { id: 'docs',   label: 'All docs', icon: 'ph-file-text' },
    { id: 'audit',  label: 'Audit',    icon: 'ph-list-magnifying-glass' },
  ];
  return (
    <div className="panel">
      <h3 className="panel-title">
        <span><i className="ph ph-wrench"></i> Admin</span>
      </h3>
      <div className="tab-bar">
        {TABS.map(t => (
          <button key={t.id}
            className={'tab' + (tab === t.id ? ' active' : '')}
            onClick={() => setTab(t.id)}>
            <i className={'ph ' + t.icon}></i> {t.label}
          </button>
        ))}
      </div>
      <div className="tab-body">
        {tab === 'pay'   && <PayRunsPanel onError={onError} />}
        {tab === 'jobs'  && <JobsPanel onError={onError} />}
        {tab === 'crew'  && <CrewPanel onError={onError} onChange={onChange} />}
        {tab === 'jha'   && <JhaTemplatePanel onError={onError} />}
        {tab === 'docs'  && <DocumentsPanel me={me} target="all" title="All documents" onError={onError} />}
        {tab === 'audit' && <AuditPanel onError={onError} />}
      </div>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────
//  CREW (admin)
// ───────────────────────────────────────────────────────────────────────

function CrewPanel({ onError, onChange }) {
  const [users, setUsers] = useState([]);
  const [editing, setEditing] = useState(null);

  const load = useCallback(() => {
    fetch('/api/admin/users', { credentials: 'same-origin' })
      .then(r => r.json()).then(b => setUsers(b.users || []));
  }, []);
  useEffect(() => { load(); }, [load]);

  return (
    <div>
      <table className="tbl">
        <thead>
          <tr>
            <th>Name</th>
            <th>Role</th>
            <th>Type</th>
            <th className="num">Rate</th>
            <th className="num">OT @</th>
            <th>Status</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          {users.map(u => (
            <tr key={u.discord_id}>
              <td>{u.display_name}</td>
              <td><span className={'badge' + (u.role === 'admin' ? ' orange' : '')}>{u.role}</span></td>
              <td><span className="badge">{u.employment_type}</span></td>
              <td className="num">{centsToUsd(u.hourly_rate_cents)}/h</td>
              <td className="num">{u.ot_threshold_hrs}h × {u.ot_multiplier}</td>
              <td>{u.active ? 'active' : <span style={{ color: 'var(--warm-gray)' }}>inactive</span>}</td>
              <td style={{ textAlign: 'right' }}>
                <button className="btn-sm" onClick={() => setEditing(u)}>Edit</button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
      {editing && (
        <UserEditModal
          user={editing}
          onClose={() => setEditing(null)}
          onSaved={() => { setEditing(null); load(); onChange?.(); }}
          onError={onError}
        />
      )}
    </div>
  );
}

function UserEditModal({ user, onClose, onSaved, onError }) {
  const [rate, setRate]       = useState((user.hourly_rate_cents / 100).toFixed(2));
  const [thr, setThr]         = useState(user.ot_threshold_hrs);
  const [mult, setMult]       = useState(user.ot_multiplier);
  const [type, setType]       = useState(user.employment_type);
  const [role, setRole]       = useState(user.role);
  const [active, setActive]   = useState(!!user.active);
  const [reason, setReason]   = useState('');
  const [busy, setBusy]       = useState(false);

  const save = async (e) => {
    e.preventDefault();
    setBusy(true);
    try {
      const res = await fetch(`/api/admin/users/${user.discord_id}`, {
        method: 'PUT', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          hourly_rate_cents: Math.round(Number(rate) * 100),
          ot_threshold_hrs: Number(thr),
          ot_multiplier: Number(mult),
          employment_type: type,
          role, active,
          reason: reason || null,
        }),
      });
      if (!res.ok) {
        const b = await res.json().catch(() => ({}));
        throw new Error(b.error || 'Save failed');
      }
      onSaved();
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal-panel" onClick={e => e.stopPropagation()}>
        <h3 className="panel-title">
          <span><i className="ph ph-user-circle-gear"></i> {user.display_name}</span>
          <button className="btn-sm" onClick={onClose}>Close</button>
        </h3>
        <form onSubmit={save}>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
            <div className="field">
              <label className="field-label">Hourly rate ($)</label>
              <input className="field-input" type="number" step="0.01" min="0"
                value={rate} onChange={e => setRate(e.target.value)} />
            </div>
            <div className="field">
              <label className="field-label">Employment</label>
              <select className="field-select" value={type} onChange={e => setType(e.target.value)}>
                <option value="w2">W-2</option>
                <option value="1099">1099</option>
              </select>
            </div>
            <div className="field">
              <label className="field-label">OT threshold (hrs/wk)</label>
              <input className="field-input" type="number" step="1" min="0" max="168"
                value={thr} onChange={e => setThr(e.target.value)} />
            </div>
            <div className="field">
              <label className="field-label">OT multiplier</label>
              <input className="field-input" type="number" step="0.1" min="1" max="3"
                value={mult} onChange={e => setMult(e.target.value)} />
            </div>
            <div className="field">
              <label className="field-label">Role</label>
              <select className="field-select" value={role} onChange={e => setRole(e.target.value)}>
                <option value="crew">crew</option>
                <option value="admin">admin</option>
              </select>
            </div>
            <div className="field">
              <label className="field-label">Active</label>
              <select className="field-select" value={active ? '1' : '0'}
                onChange={e => setActive(e.target.value === '1')}>
                <option value="1">active</option>
                <option value="0">inactive</option>
              </select>
            </div>
          </div>
          <div className="field">
            <label className="field-label">Reason (optional, but recommended)</label>
            <input className="field-input" value={reason} onChange={e => setReason(e.target.value)}
              placeholder="e.g. annual raise, role change, departure…" />
          </div>
          <button className="btn-sm primary" type="submit" disabled={busy}>
            {busy ? 'Saving…' : 'Save'}
          </button>
        </form>
      </div>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────
//  JHA TEMPLATE (admin)
// ───────────────────────────────────────────────────────────────────────

function JhaTemplatePanel({ onError }) {
  const [tpl, setTpl] = useState(null);
  const [questions, setQuestions] = useState([]);
  const [name, setName] = useState('Daily JHA');
  const [busy, setBusy] = useState(false);

  const load = useCallback(() => {
    fetch('/api/jha/template', { credentials: 'same-origin' })
      .then(r => r.json())
      .then(b => {
        setTpl(b.template);
        setQuestions(b.template?.questions || []);
        setName(b.template?.name || 'Daily JHA');
      });
  }, []);
  useEffect(() => { load(); }, [load]);

  const update = (i, patch) => setQuestions(qs => qs.map((q, idx) => idx === i ? { ...q, ...patch } : q));
  const remove = (i) => setQuestions(qs => qs.filter((_, idx) => idx !== i));
  const add = () => setQuestions(qs => [...qs, { id: 'q' + Date.now(), text: '', type: 'yesno', required: true }]);
  const move = (i, dir) => {
    setQuestions(qs => {
      const next = qs.slice();
      const j = i + dir;
      if (j < 0 || j >= next.length) return qs;
      [next[i], next[j]] = [next[j], next[i]];
      return next;
    });
  };

  const save = async () => {
    setBusy(true);
    try {
      const res = await fetch('/api/jha/template', {
        method: 'PUT', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name, questions }),
      });
      if (!res.ok) {
        const b = await res.json().catch(() => ({}));
        throw new Error(b.error || 'Save failed');
      }
      load();
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div>
      <div className="banner info">
        Current version: <strong>{tpl?.version ?? '—'}</strong>. Saving creates a new version; past submissions remain tied to their original.
      </div>
      <div className="field">
        <label className="field-label">Name</label>
        <input className="field-input" value={name} onChange={e => setName(e.target.value)} />
      </div>
      {questions.map((q, i) => (
        <div key={q.id} style={{ background: 'var(--surface-container)', padding: 12, marginBottom: 8 }}>
          <div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr auto auto auto', gap: 8, alignItems: 'end' }}>
            <div className="field" style={{ marginBottom: 0 }}>
              <label className="field-label">Type</label>
              <select className="field-select" value={q.type} onChange={e => update(i, { type: e.target.value })}>
                <option value="yesno">Yes/No</option>
                <option value="text">Text</option>
                <option value="ack">Acknowledge</option>
                <option value="choice">Choice</option>
              </select>
            </div>
            <div className="field" style={{ marginBottom: 0 }}>
              <label className="field-label">Question text</label>
              <input className="field-input" value={q.text} onChange={e => update(i, { text: e.target.value })} />
            </div>
            <label style={{ fontSize: 11, color: 'var(--warm-gray)', textTransform: 'uppercase', letterSpacing: '0.06rem' }}>
              <input type="checkbox" checked={q.required}
                onChange={e => update(i, { required: e.target.checked })} /> req
            </label>
            <div style={{ display: 'flex', gap: 4 }}>
              <button className="btn-sm" type="button" onClick={() => move(i, -1)}>↑</button>
              <button className="btn-sm" type="button" onClick={() => move(i, 1)}>↓</button>
            </div>
            <button className="btn-sm" type="button" onClick={() => remove(i)}>×</button>
          </div>
          {q.type === 'choice' && (
            <div className="field" style={{ marginTop: 8, marginBottom: 0 }}>
              <label className="field-label">Options (comma-separated)</label>
              <input className="field-input"
                value={(q.options || []).join(', ')}
                onChange={e => update(i, { options: e.target.value.split(',').map(s => s.trim()).filter(Boolean) })} />
            </div>
          )}
        </div>
      ))}
      <div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
        <button className="btn-sm" onClick={add}>+ Add question</button>
        <button className="btn-sm primary" onClick={save} disabled={busy}>
          {busy ? 'Saving…' : 'Save as new version'}
        </button>
      </div>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────
//  AUDIT (admin)
// ───────────────────────────────────────────────────────────────────────

function AuditPanel() {
  const [entries, setEntries] = useState([]);
  const [actionFilter, setActionFilter] = useState('');
  const [expanded, setExpanded] = useState(null);

  const load = useCallback(() => {
    const q = actionFilter ? '?action=' + encodeURIComponent(actionFilter + '*') : '';
    fetch('/api/audit' + q, { credentials: 'same-origin' })
      .then(r => r.json()).then(b => setEntries(b.entries || []));
  }, [actionFilter]);
  useEffect(() => { load(); }, [load]);

  return (
    <div>
      <div className="range-bar">
        <select className="field-select" value={actionFilter} onChange={e => setActionFilter(e.target.value)}>
          <option value="">All actions</option>
          <option value="entry.">Entry edits</option>
          <option value="user.">User changes</option>
          <option value="job.">Job changes</option>
          <option value="doc.">Document changes</option>
          <option value="jha_template.">JHA template</option>
        </select>
        <button className="btn-sm" onClick={load}>Refresh</button>
      </div>
      <table className="tbl">
        <thead>
          <tr>
            <th>When</th>
            <th>Actor</th>
            <th>Action</th>
            <th>Target</th>
            <th>Reason</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          {entries.map(e => (
            <React.Fragment key={e.id}>
              <tr>
                <td>{new Date(e.at).toLocaleString()}</td>
                <td>{e.actor_name || '—'}</td>
                <td><span className="badge">{e.action}</span></td>
                <td style={{ fontFamily: 'var(--font-label)', fontSize: 11, color: 'var(--warm-gray)' }}>
                  {e.target_table}{e.target_id ? ` · ${e.target_id.slice(0, 8)}` : ''}
                </td>
                <td style={{ maxWidth: 240, overflow: 'hidden', textOverflow: 'ellipsis' }}>{e.reason || ''}</td>
                <td>
                  <button className="btn-sm" onClick={() => setExpanded(expanded === e.id ? null : e.id)}>
                    {expanded === e.id ? 'Hide' : 'Diff'}
                  </button>
                </td>
              </tr>
              {expanded === e.id && (
                <tr>
                  <td colSpan="6" style={{ background: 'var(--surface)' }}>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
                      <pre style={diffStyle}>before:{'\n'}{JSON.stringify(e.before_json, null, 2)}</pre>
                      <pre style={diffStyle}>after:{'\n'}{JSON.stringify(e.after_json, null, 2)}</pre>
                    </div>
                  </td>
                </tr>
              )}
            </React.Fragment>
          ))}
        </tbody>
      </table>
    </div>
  );
}

const diffStyle = {
  fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
  fontSize: 11,
  color: 'var(--on-surface-variant)',
  background: 'var(--surface-container)',
  padding: 10,
  margin: 0,
  whiteSpace: 'pre-wrap',
  maxHeight: 260,
  overflow: 'auto',
};

// ───────────────────────────────────────────────────────────────────────
//  CREW: MY STUBS + BANKING
// ───────────────────────────────────────────────────────────────────────

function MyStubsPanel({ onError }) {
  const [stubs, setStubs] = useState([]);
  useEffect(() => {
    fetch('/api/me/stubs', { credentials: 'same-origin' })
      .then(r => r.json()).then(b => setStubs(b.stubs || []));
  }, []);
  if (!stubs.length) return null;
  return (
    <div className="panel">
      <h3 className="panel-title">
        <span><i className="ph ph-receipt"></i> My pay stubs</span>
      </h3>
      <table className="tbl">
        <thead><tr><th>Period</th><th>Pay date</th><th className="num">Hrs</th><th className="num">Net</th><th></th></tr></thead>
        <tbody>
          {stubs.map(s => (
            <tr key={s.id}>
              <td>{iso(s.period_start)} → {iso(s.period_end - 1)}</td>
              <td>{s.pay_date ? iso(s.pay_date) : '—'}</td>
              <td className="num">{((s.regular_hrs || 0) + (s.ot_hrs || 0)).toFixed(2)}</td>
              <td className="num">{centsToUsd(s.gross_cents)}</td>
              <td><a className="btn-sm" href={`/api/pay/runs/${s.pay_run_id}/stubs/${s.id}`} target="_blank" rel="noopener">Open</a></td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function BankingPanel({ onError }) {
  const [b, setB] = useState(null);
  const [editing, setEditing] = useState(false);
  const load = () => fetch('/api/me/banking', { credentials: 'same-origin' })
    .then(r => r.json()).then(body => setB(body.banking || {}));
  useEffect(() => { load(); }, []);

  return (
    <div className="panel">
      <h3 className="panel-title">
        <span><i className="ph ph-bank"></i> Direct deposit</span>
        <button className="btn-sm" onClick={() => setEditing(e => !e)}>
          {editing ? 'Cancel' : (b?.bank_account_last4 ? 'Update' : '+ Add')}
        </button>
      </h3>
      {editing ? (
        <BankingForm initial={b} onSaved={() => { setEditing(false); load(); }} onError={onError} />
      ) : (
        <div style={{ fontFamily: 'var(--font-label)', fontSize: 12, color: 'var(--on-surface-variant)', textTransform: 'uppercase', letterSpacing: '0.06rem' }}>
          {b?.bank_account_last4 ? (
            <>
              <div>Routing: {b.bank_routing?.slice(0, 4)}…{b.bank_routing?.slice(-2)}</div>
              <div style={{ marginTop: 6 }}>Account: ••••{b.bank_account_last4} ({b.bank_account_type})</div>
            </>
          ) : <div style={{ color: 'var(--warm-gray)' }}>No bank account on file. Without it, you'll be paid by check or CSV-uploaded ACH.</div>}
        </div>
      )}
    </div>
  );
}

function BankingForm({ initial, onSaved, onError }) {
  const [routing, setRouting] = useState(initial?.bank_routing || '');
  const [account, setAccount] = useState('');
  const [confirm, setConfirm] = useState('');
  const [type, setType] = useState(initial?.bank_account_type || 'checking');
  const [busy, setBusy] = useState(false);

  const submit = async (e) => {
    e.preventDefault();
    if (account !== confirm) { onError('Account numbers don\'t match'); return; }
    setBusy(true);
    try {
      const res = await fetch('/api/me/banking', {
        method: 'PUT', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ routing, account, account_type: type }),
      });
      const body = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(body.error || 'Save failed');
      onSaved();
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <form onSubmit={submit}>
      <div className="field">
        <label className="field-label">Routing (9 digits)</label>
        <input className="field-input" value={routing} onChange={e => setRouting(e.target.value)}
          inputMode="numeric" autoComplete="off" required />
      </div>
      <div className="field">
        <label className="field-label">Account number</label>
        <input className="field-input" value={account} onChange={e => setAccount(e.target.value)}
          inputMode="numeric" autoComplete="off" required />
      </div>
      <div className="field">
        <label className="field-label">Confirm account</label>
        <input className="field-input" value={confirm} onChange={e => setConfirm(e.target.value)}
          inputMode="numeric" autoComplete="off" required />
      </div>
      <div className="field">
        <label className="field-label">Account type</label>
        <select className="field-select" value={type} onChange={e => setType(e.target.value)}>
          <option value="checking">Checking</option>
          <option value="savings">Savings</option>
        </select>
      </div>
      <button className="btn-sm primary" type="submit" disabled={busy}>
        {busy ? 'Saving…' : 'Save'}
      </button>
    </form>
  );
}

// ───────────────────────────────────────────────────────────────────────
//  PAY RUNS (admin)
// ───────────────────────────────────────────────────────────────────────

function PayRunsPanel({ onError }) {
  const [runs, setRuns] = useState([]);
  const [openId, setOpenId] = useState(null);
  const [creating, setCreating] = useState(false);

  const load = useCallback(() => {
    fetch('/api/pay/runs', { credentials: 'same-origin' })
      .then(r => r.json()).then(b => setRuns(b.runs || []));
  }, []);
  useEffect(() => { load(); }, [load]);

  if (openId) {
    return (
      <PayRunDetail runId={openId}
        onClose={() => { setOpenId(null); load(); }}
        onError={onError} />
    );
  }

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 12 }}>
        <span style={{ fontFamily: 'var(--font-label)', fontSize: 11, color: 'var(--warm-gray)', textTransform: 'uppercase', letterSpacing: '0.08rem' }}>
          {runs.length} run{runs.length === 1 ? '' : 's'}
        </span>
        <button className="btn-sm primary" onClick={() => setCreating(true)}>+ New pay run</button>
      </div>

      {creating && (
        <PayRunCreateForm
          onCreated={(id) => { setCreating(false); setOpenId(id); }}
          onCancel={() => setCreating(false)}
          onError={onError} />
      )}

      <table className="tbl">
        <thead>
          <tr>
            <th>Period</th>
            <th>Pay date</th>
            <th>Status</th>
            <th>Provider</th>
            <th className="num">Items</th>
            <th className="num">Gross</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          {runs.map(r => (
            <tr key={r.id}>
              <td>{iso(r.period_start)} → {iso(r.period_end - 1)}</td>
              <td>{r.pay_date ? iso(r.pay_date) : '—'}</td>
              <td><span className={'badge' + (r.status === 'paid' ? ' orange' : '')}>{r.status}</span></td>
              <td><span className="badge">{r.provider}</span></td>
              <td className="num">{r.item_count}</td>
              <td className="num">{centsToUsd(r.gross_total_cents)}</td>
              <td><button className="btn-sm" onClick={() => setOpenId(r.id)}>Open</button></td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function PayRunCreateForm({ onCreated, onCancel, onError }) {
  const def = weekRangeOffset(-1);
  const [from, setFrom]   = useState(toDateInput(def.from));
  const [to, setTo]       = useState(toDateInput(def.to - 1));
  const [payDate, setPayDate] = useState(toDateInput(def.to + 5 * 86400000));
  const [provider, setProvider] = useState('manual');
  const [busy, setBusy]   = useState(false);

  const submit = async (e) => {
    e.preventDefault();
    setBusy(true);
    try {
      const res = await fetch('/api/pay/runs', {
        method: 'POST', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          period_start: Date.parse(from + 'T00:00:00Z'),
          period_end:   Date.parse(to + 'T00:00:00Z') + 86400000,
          pay_date:     payDate ? Date.parse(payDate + 'T00:00:00Z') : null,
          provider,
        }),
      });
      const body = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(body.error || 'Create failed');
      onCreated(body.run_id);
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <form onSubmit={submit} style={{ padding: 16, background: 'var(--surface-container)', marginBottom: 16 }}>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8 }}>
        <div className="field"><label className="field-label">Period start</label>
          <input className="field-input" type="date" value={from} onChange={e => setFrom(e.target.value)} required /></div>
        <div className="field"><label className="field-label">Period end (incl.)</label>
          <input className="field-input" type="date" value={to} onChange={e => setTo(e.target.value)} required /></div>
        <div className="field"><label className="field-label">Pay date</label>
          <input className="field-input" type="date" value={payDate} onChange={e => setPayDate(e.target.value)} /></div>
      </div>
      <div className="field">
        <label className="field-label">Provider</label>
        <select className="field-select" value={provider} onChange={e => setProvider(e.target.value)}>
          <option value="manual">Manual (CSV export)</option>
          <option value="nacha">NACHA file (ACH batch upload)</option>
          <option value="check">Check (not yet configured)</option>
        </select>
      </div>
      <div style={{ display: 'flex', gap: 8 }}>
        <button className="btn-sm primary" type="submit" disabled={busy}>
          {busy ? 'Creating…' : 'Create draft'}
        </button>
        <button className="btn-sm" type="button" onClick={onCancel}>Cancel</button>
      </div>
    </form>
  );
}

function PayRunDetail({ runId, onClose, onError }) {
  const [data, setData] = useState(null);
  const [busy, setBusy] = useState(false);

  const load = useCallback(() => {
    fetch(`/api/pay/runs/${runId}`, { credentials: 'same-origin' })
      .then(r => r.json()).then(setData);
  }, [runId]);
  useEffect(() => { load(); }, [load]);

  if (!data) return <div className="loading">Loading…</div>;
  const { run, items } = data;
  const isDraft = run.status === 'draft';
  const grossTotal = items.reduce((s, it) => s + (it.gross_cents || 0), 0);

  const action = async (path, body, confirmMsg) => {
    if (confirmMsg && !confirm(confirmMsg)) return;
    setBusy(true);
    try {
      const res = await fetch(`/api/pay/runs/${runId}/${path}`, {
        method: 'POST', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body || {}),
      });
      const r = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(r.error || 'Action failed');
      load();
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  const discard = async () => {
    if (!confirm('Discard this draft run?')) return;
    setBusy(true);
    try {
      const res = await fetch(`/api/pay/runs/${runId}`, { method: 'DELETE', credentials: 'same-origin' });
      if (!res.ok) {
        const b = await res.json().catch(() => ({}));
        throw new Error(b.error || 'Discard failed');
      }
      onClose();
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
        <button className="btn-sm" onClick={onClose}>← Back</button>
        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          <span className={'badge' + (run.status === 'paid' ? ' orange' : '')}>{run.status}</span>
          <span className="badge">{run.provider}</span>
        </div>
      </div>

      <div className="sum-row">
        <div className="sum-card">
          <div className="sum-card-label">Period</div>
          <div className="sum-card-value" style={{ fontSize: 16 }}>{iso(run.period_start)} → {iso(run.period_end - 1)}</div>
        </div>
        <div className="sum-card">
          <div className="sum-card-label">Pay date</div>
          <div className="sum-card-value" style={{ fontSize: 16 }}>{run.pay_date ? iso(run.pay_date) : '—'}</div>
        </div>
        <div className="sum-card">
          <div className="sum-card-label">Items</div>
          <div className="sum-card-value">{items.length}</div>
        </div>
        <div className="sum-card">
          <div className="sum-card-label">Gross total</div>
          <div className="sum-card-value orange">{centsToUsd(grossTotal)}</div>
        </div>
      </div>

      <table className="tbl">
        <thead>
          <tr>
            <th>Crew</th>
            <th className="num">Reg / OT</th>
            <th className="num">Reg pay</th>
            <th className="num">OT pay</th>
            <th className="num">Bonus</th>
            <th className="num">Deduct</th>
            <th className="num">Reimb</th>
            <th className="num">Gross</th>
            <th>Bank</th>
            <th>Status</th>
            {isDraft && <th></th>}
          </tr>
        </thead>
        <tbody>
          {items.map(it => (
            <PayItemRow key={it.id} it={it} isDraft={isDraft}
              onChange={load} onError={onError} runId={runId} />
          ))}
        </tbody>
      </table>

      <div style={{ display: 'flex', gap: 8, marginTop: 16, flexWrap: 'wrap' }}>
        {isDraft && (
          <>
            <button className="btn-sm primary" disabled={busy}
              onClick={() => action('approve', {}, `Approve ${items.length} items? Hours will be locked.`)}>
              Approve run
            </button>
            <button className="btn-sm" disabled={busy} onClick={discard}>Discard draft</button>
          </>
        )}
        {run.status === 'approved' && (
          <>
            <ExportButtons runId={runId} provider={run.provider} />
            <button className="btn-sm primary" disabled={busy}
              onClick={() => {
                const d = prompt('Mark all items as paid. Pay date (YYYY-MM-DD) — leave blank for today:');
                if (d === null) return;
                const paidAt = d ? Date.parse(d + 'T00:00:00Z') : Date.now();
                action('mark-paid', { paid_at: paidAt });
              }}>
              Mark paid
            </button>
            <button className="btn-sm" disabled={busy}
              onClick={() => {
                const reason = prompt('Reopen to draft — reason?');
                if (!reason) return;
                action('reopen', { reason });
              }}>
              Reopen
            </button>
          </>
        )}
        {run.status === 'paid' && (
          <>
            <ExportButtons runId={runId} provider={run.provider} />
            <button className="btn-sm" disabled={busy}
              onClick={() => {
                const reason = prompt('Void this paid run — reason?');
                if (!reason) return;
                action('void', { reason });
              }}>
              Void run
            </button>
          </>
        )}
      </div>
    </div>
  );
}

function ExportButtons({ runId, provider }) {
  return (
    <>
      <a className="btn-sm" href={`/api/pay/runs/${runId}/export?format=csv`}>↓ CSV</a>
      {provider === 'nacha' && <a className="btn-sm" href={`/api/pay/runs/${runId}/export?format=nacha`}>↓ NACHA</a>}
      {provider === 'check' && <a className="btn-sm" href={`/api/pay/runs/${runId}/export?format=check`}>↓ Check preview</a>}
    </>
  );
}

function PayItemRow({ it, isDraft, runId, onChange, onError }) {
  const [editing, setEditing] = useState(false);

  if (editing && isDraft) {
    return (
      <tr>
        <td colSpan="11" style={{ padding: 0 }}>
          <PayItemEditForm it={it} onSaved={() => { setEditing(false); onChange(); }} onError={onError} />
        </td>
      </tr>
    );
  }

  const banked = it.bank_account_last4 ? `••${it.bank_account_last4}` : '—';
  return (
    <tr>
      <td>{it.user_name}</td>
      <td className="num">{it.regular_hrs.toFixed(2)} / {it.ot_hrs.toFixed(2)}</td>
      <td className="num">{centsToUsd(it.regular_pay_cents)}</td>
      <td className="num">{centsToUsd(it.ot_pay_cents)}</td>
      <td className="num">{centsToUsd(it.bonus_cents)}</td>
      <td className="num">{centsToUsd(it.deduction_cents)}</td>
      <td className="num">{centsToUsd(it.reimbursement_cents)}</td>
      <td className="num"><strong>{centsToUsd(it.gross_cents)}</strong></td>
      <td style={{ fontFamily: 'var(--font-label)', fontSize: 11, color: 'var(--warm-gray)' }}>{banked}</td>
      <td><span className={'badge' + (it.status === 'paid' ? ' orange' : '')}>{it.status}</span></td>
      {isDraft && (
        <td style={{ textAlign: 'right' }}>
          <button className="btn-sm" onClick={() => setEditing(true)}>Edit</button>{' '}
          <a className="btn-sm" href={`/api/pay/runs/${runId}/stubs/${it.id}`} target="_blank" rel="noopener">Stub</a>
        </td>
      )}
    </tr>
  );
}

function PayItemEditForm({ it, onSaved, onError }) {
  const [bonus, setBonus] = useState((it.bonus_cents / 100).toFixed(2));
  const [deduct, setDeduct] = useState((it.deduction_cents / 100).toFixed(2));
  const [reimb, setReimb] = useState((it.reimbursement_cents / 100).toFixed(2));
  const [reason, setReason] = useState(it.adjustment_reason || '');
  const [status, setStatus] = useState(it.status);
  const [busy, setBusy] = useState(false);

  const save = async (e) => {
    e.preventDefault();
    setBusy(true);
    try {
      const res = await fetch(`/api/pay/items/${it.id}`, {
        method: 'PUT', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          bonus_cents:        Math.round(Number(bonus) * 100),
          deduction_cents:    Math.round(Number(deduct) * 100),
          reimbursement_cents: Math.round(Number(reimb) * 100),
          adjustment_reason:  reason || null,
          status,
        }),
      });
      if (!res.ok) {
        const b = await res.json().catch(() => ({}));
        throw new Error(b.error || 'Save failed');
      }
      onSaved();
    } catch (err) {
      onError(err.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <form onSubmit={save} style={{ padding: 16, background: 'var(--surface-container)' }}>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8 }}>
        <div className="field"><label className="field-label">Bonus ($)</label>
          <input className="field-input" type="number" step="0.01" value={bonus} onChange={e => setBonus(e.target.value)} /></div>
        <div className="field"><label className="field-label">Deduction ($)</label>
          <input className="field-input" type="number" step="0.01" value={deduct} onChange={e => setDeduct(e.target.value)} /></div>
        <div className="field"><label className="field-label">Reimbursement ($)</label>
          <input className="field-input" type="number" step="0.01" value={reimb} onChange={e => setReimb(e.target.value)} /></div>
        <div className="field"><label className="field-label">Status</label>
          <select className="field-select" value={status} onChange={e => setStatus(e.target.value)}>
            <option value="pending">pending</option>
            <option value="skipped">skipped (do not pay)</option>
            <option value="void">void</option>
          </select></div>
      </div>
      <div className="field">
        <label className="field-label">Adjustment reason</label>
        <input className="field-input" value={reason} onChange={e => setReason(e.target.value)}
          placeholder="e.g. signing bonus, garnishment, expense reimbursement…" />
      </div>
      <button className="btn-sm primary" type="submit" disabled={busy}>
        {busy ? 'Saving…' : 'Save'}
      </button>
    </form>
  );
}

function toDateInput(ms) {
  const d = new Date(ms);
  return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(d.getUTCDate()).padStart(2, '0')}`;
}
function iso(ms) { return new Date(ms).toISOString().slice(0, 10); }

// ───────────────────────────────────────────────────────────────────────
//  Helpers
// ───────────────────────────────────────────────────────────────────────

function formatDuration(ms) {
  if (ms < 0) ms = 0;
  const s = Math.floor(ms / 1000);
  const hh = String(Math.floor(s / 3600)).padStart(2, '0');
  const mm = String(Math.floor((s % 3600) / 60)).padStart(2, '0');
  const ss = String(s % 60).padStart(2, '0');
  return `${hh}:${mm}:${ss}`;
}

function centsToUsd(c) {
  if (c == null) return '—';
  return '$' + (c / 100).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

function weekRangeOffset(weeksAgo) {
  const now = new Date();
  const day = (now.getDay() + 6) % 7; // Mon=0
  const start = new Date(now);
  start.setHours(0, 0, 0, 0);
  start.setDate(start.getDate() - day + weeksAgo * 7);
  const end = new Date(start);
  end.setDate(end.getDate() + 7);
  return { from: +start, to: +end };
}

function rangeKey(r) {
  const { from } = weekRangeOffset(0);
  const weeks = Math.round((r.from - from) / (7 * 86400 * 1000));
  return String(weeks);
}
