// Shared feature components added per Emma's video feedback.
// Used by both clinician and owner shells.
//
// Sections from the feedback doc:
//  §1 Diary & Appointment Management — DiaryView + AmendApptModal + RequestLeaveModal
//  §2 Online Booking flow — PendingBookingsPanel
//  §3 Client list (alphabetical, search by phone/email, flags) — handled in clinician.jsx
//  §4 Medical Forms — MedicalFormModal
//  §5 Notes — NotesHoverCard (inline preview in diary)
//  §6 Image upload (bulk) — BulkUploadModal
//  §9 Finance & cash-up — CashUpModal
//  §11 Notifications — surfaced in AmendApptModal
//  §12 Dashboard — PendingBookingsPanel + WaitlistPanel
//
// Mock state lives in module-scope React refs since this is a prototype.

// ============================================================
// Diary calendar view — clinician's home (replaces list as default)
// Click a block → AmendApptModal. Drag a block → confirm dialog.
// Click empty cell → ClNewBookingModal (light wrapper around WalkInModal).
// ============================================================
// Lightweight matchMedia hook — re-renders when the query changes.
function useMediaQuery(query) {
  const get = () => typeof window !== 'undefined' && window.matchMedia(query).matches;
  const [match, setMatch] = React.useState(get);
  React.useEffect(() => {
    const mq = window.matchMedia(query);
    const handler = (e) => setMatch(e.matches);
    mq.addEventListener ? mq.addEventListener('change', handler) : mq.addListener(handler);
    return () => mq.removeEventListener ? mq.removeEventListener('change', handler) : mq.removeListener(handler);
  }, [query]);
  return match;
}


// ── DemoNote — the little dashed blue "i" annotations. Deliberately styled
// unlike the rest of the app so it's clear they belong to the demo only. ──
function DemoNote({ title = 'Demo note', text, align = 'left' }) {
  const [open, setOpen] = React.useState(false);
  return (
    <span className="demo-note">
      <button className="demo-note-btn" onClick={(e) => { e.stopPropagation(); setOpen(o => !o); }} aria-label="Demo note" title="Demo note — not part of the finished product">i</button>
      {open && (
        <span className={'demo-note-pop' + (align === 'right' ? ' right' : '')} onClick={(e) => e.stopPropagation()}>
          <b>{title} — demo note</b>
          {text}
          <button className="demo-note-close" onClick={() => setOpen(false)}>close</button>
        </span>
      )}
    </span>
  );
}

function DiaryView({ appointments: initial, onOpenRecord, onCheckOut, mode = 'day', date = todayISO, clinicianFilter = null, defaultClinician = null }) {
  const [appts, setAppts] = React.useState(initial);
  const [drag, setDrag] = React.useState(null);
  const [amend, setAmend] = React.useState(null);
  const [editing, setEditing] = React.useState(null);
  const [hover, setHover] = React.useState(null);
  const justDraggedRef = React.useRef(false); // suppress click immediately after a drag

  // Phone breakpoint — single-column diary with a clinician picker (Emma §10)
  const isPhone = useMediaQuery('(max-width: 720px)');
  // Default to the logged-in clinician (passed in) or the first clinician
  const [phoneClin, setPhoneClin] = React.useState(defaultClinician || CLINICIANS[0].id);

  // sync if parent changes initial
  React.useEffect(() => { setAppts(initial); }, [initial]);

  const minHour = 9, maxHour = 19;
  const hours = Array.from({ length: maxHour - minHour }, (_, i) => minHour + i);
  const slotMins = 30;

  // On phone, force single-column to the picked clinician unless explicit clinicianFilter was passed
  const effectiveFilter = clinicianFilter || (isPhone ? phoneClin : null);
  const cliniciansForView = effectiveFilter
    ? CLINICIANS.filter(c => c.id === effectiveFilter)
    : CLINICIANS;

  const days = mode === 'week'
    ? Array.from({ length: 7 }, (_, i) => offsetDay(i - new Date().getDay() + 1)) // Mon..Sun
    : [date];

  // pixel layout
  // Taller hour rows on phones so a 30-minute booking stays readable
  const HOUR_PX = isPhone ? 92 : 56;
  const HEAD_PX = 36;

  const yFor = (time) => {
    const [h, m] = time.split(':').map(Number);
    return HEAD_PX + ((h - minHour) * 60 + m) * (HOUR_PX / 60);
  };

  const heightFor = (mins) => mins * (HOUR_PX / 60);

  const timeForY = (y) => {
    const totalMins = ((y - HEAD_PX) / HOUR_PX) * 60;
    const snapped = Math.round(totalMins / slotMins) * slotMins;
    const clamped = Math.max(0, Math.min((maxHour - minHour) * 60 - slotMins, snapped));
    const h = minHour + Math.floor(clamped / 60);
    const m = clamped % 60;
    return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
  };

  // Pointer events (rather than mouse-only) so drag/drop works on touch screens
  // too. On iOS Safari we also need to set touch-action: none on the block to
  // stop the page scrolling instead of dragging.
  const onDragStart = (e, appt) => {
    e.preventDefault();
    const startY = e.clientY;
    const startX = e.clientX;
    const startCol = e.target.closest('.diary-col');
    if (!startCol) return;
    const cols = Array.from(startCol.parentElement.querySelectorAll('.diary-col'));
    const startIdx = cols.indexOf(startCol);

    // Capture the pointer so subsequent moves keep firing on this element
    try { e.target.setPointerCapture && e.target.setPointerCapture(e.pointerId); } catch (_) {}

    let movedFlag = false;
    const move = (ev) => {
      const dy = ev.clientY - startY;
      const dx = ev.clientX - startX;
      if (Math.abs(dy) > 4 || Math.abs(dx) > 4) movedFlag = true;
      const baseY = yFor(appt.time);
      const newTime = timeForY(baseY + dy);
      let colIdx = startIdx;
      const targetEl = document.elementFromPoint(ev.clientX, ev.clientY);
      const targetCol = targetEl?.closest?.('.diary-col');
      if (targetCol) colIdx = cols.indexOf(targetCol);
      const targetClin = cliniciansForView[colIdx]?.id || appt.clinicianId;
      setDrag({ id: appt.id, fromTime: appt.time, fromClin: appt.clinicianId, toTime: newTime, toClin: targetClin });
    };
    const up = () => {
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
      window.removeEventListener('pointercancel', up);
      if (movedFlag) justDraggedRef.current = true; // swallow the trailing click
      setDrag(prev => {
        if (prev && movedFlag && (prev.toTime !== prev.fromTime || prev.toClin !== prev.fromClin)) {
          setEditing({ appt, drop: prev });
        }
        return null;
      });
      // clear suppression flag on next tick after click would have fired
      setTimeout(() => { justDraggedRef.current = false; }, 0);
    };
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
    window.addEventListener('pointercancel', up);
  };

  const confirmMove = (sendEmail, resetReminder) => {
    setAppts(prev => prev.map(a =>
      a.id === editing.appt.id ? { ...a, time: editing.drop.toTime, clinicianId: editing.drop.toClin } : a
    ));
    setEditing(null);
    // (real app would fire email + reminder here — mock only)
  };

  const cancelMove = () => setEditing(null);

  const slotIs15 = false; // 30-min ticks for guides

  // Each clinician's shift for today (Mon=0..Sun=6 → JS getDay() Mon=1..Sun=0)
  const dayIndexFor = (iso) => { const d = new Date(iso + 'T12:00:00'); return (d.getDay() + 6) % 7; };

  return (
    <div className="diary-wrap">
      {isPhone && !clinicianFilter && (
        <div className="diary-clin-picker">
          {CLINICIANS.map(c => {
            const dayIdx = dayIndexFor(days[0]);
            const offToday = !STAFF_SHIFTS[c.id]?.[dayIdx];
            const onLeave = LEAVE.find(lv => lv.clinicianId === c.id && days[0] >= lv.from && days[0] <= lv.to);
            return (
              <button key={c.id}
                className={'diary-clin-chip' + (phoneClin === c.id ? ' active' : '') + (offToday || onLeave ? ' off' : '')}
                onClick={() => setPhoneClin(c.id)}
                title={onLeave ? onLeave.kind : (offToday ? 'Off today' : '')}>
                <span className="avatar sm" style={{ background: c.avatarTint, color: c.avatarTint === '#6E7D6B' ? '#fff' : 'var(--ink)' }}>{c.initials}</span>
                <span>{c.name.split(' ')[0]}</span>
                {(offToday || onLeave) && <span className="off-dot"/>}
              </button>
            );
          })}
        </div>
      )}
      <div className="diary-grid" style={{
        gridTemplateColumns: '60px ' + cliniciansForView.map(() => '1fr').join(' '),
        gridTemplateRows: HEAD_PX + 'px 1fr',
      }}>
        {/* clinician header row */}
        <div className="diary-corner"/>
        {cliniciansForView.map(c => {
          const dayIdx = dayIndexFor(days[0]);
          const shift = STAFF_SHIFTS[c.id]?.[dayIdx];
          const onLeave = LEAVE.find(lv => lv.clinicianId === c.id && days[0] >= lv.from && days[0] <= lv.to);
          return (
            <div key={c.id} className="diary-head">
              <span className="avatar sm" style={{ background: c.avatarTint, color: c.avatarTint === '#6E7D6B' ? '#fff' : 'var(--ink)' }}>{c.initials}</span>
              <div className="who"><b>{c.name.split(' ')[0]}</b><span>{onLeave ? onLeave.kind : (shift || 'Off')}</span></div>
            </div>
          );
        })}

        {/* time gutter */}
        <div className="diary-times">
          {hours.map(h => (
            <div key={h} className="diary-hour" style={{ height: HOUR_PX }}>
              <span>{String(h).padStart(2, '0')}:00</span>
            </div>
          ))}
        </div>

        {/* clinician columns */}
        {cliniciansForView.map((c, ci) => {
          const dayIdx = dayIndexFor(days[0]);
          const shift = STAFF_SHIFTS[c.id]?.[dayIdx];
          const onLeave = LEAVE.find(lv => lv.clinicianId === c.id && days[0] >= lv.from && days[0] <= lv.to);
          const colAppts = appts.filter(a => a.date === days[0] && a.clinicianId === c.id);
          return (
            <div key={c.id} className="diary-col" data-col={ci} style={{ height: hours.length * HOUR_PX }}>
              {/* hour grid lines */}
              {hours.map(h => <div key={h} className="diary-row-line" style={{ top: (h - minHour) * HOUR_PX, height: HOUR_PX }}/>)}
              {hours.map(h => <div key={h + 'h'} className="diary-row-half" style={{ top: (h - minHour) * HOUR_PX + HOUR_PX / 2 }}/>)}

              {/* outside-shift overlay */}
              {!onLeave && shift && (() => {
                const [start, end] = shift.split('-');
                const sy = yFor(start);
                const ey = yFor(end);
                return (
                  <>
                    {sy > HEAD_PX && <div className="diary-shade" style={{ top: HEAD_PX, height: sy - HEAD_PX }}/>}
                    {ey < HEAD_PX + hours.length * HOUR_PX && <div className="diary-shade" style={{ top: ey, height: HEAD_PX + hours.length * HOUR_PX - ey }}/>}
                  </>
                );
              })()}
              {!shift && !onLeave && <div className="diary-shade" style={{ top: HEAD_PX, height: hours.length * HOUR_PX }}/>}
              {onLeave && (
                <div className="diary-leave" style={{ top: HEAD_PX, height: hours.length * HOUR_PX }}>
                  <Icon name="sparkle" size={14}/>
                  <div>{onLeave.kind}</div>
                  <div className="meta">{onLeave.note}</div>
                </div>
              )}

              {/* appointment blocks */}
              {colAppts.map(a => {
                const top = yFor(a.time) - HEAD_PX;
                const h = heightFor(a.mins) - 2;
                const cust = customerById(a.customerId);
                const treat = treatmentById(a.treatmentId);
                const isDragging = drag?.id === a.id;
                const fl = flagInfo(cust.flag);
                return (
                  <div key={a.id} className={'diary-block ' + (a.status === 'pending' ? 'pending' : '') + (isDragging ? ' dragging' : '')}
                    style={{
                      top: top + (isDragging ? (yFor(drag.toTime) - yFor(drag.fromTime)) : 0),
                      height: h,
                    }}
                    onPointerDown={(e) => onDragStart(e, a)}
                    onClick={(e) => {
                      if (e.detail === 0) return;
                      if (drag || justDraggedRef.current) return;
                      setAmend(a);
                    }}
                    onMouseEnter={(e) => { const r = e.currentTarget.getBoundingClientRect(); setHover({ id: a.id, x: r.right + 8, y: r.top }); }}
                    onMouseLeave={() => setHover(null)}>
                    {h < 40 ? (
                      /* Short bookings collapse to one readable line instead of three clipped ones */
                      <div className="db-compact"><b>{a.time}</b> {cust.name.split(' ')[0]} · {treat.name}</div>
                    ) : (
                      <>
                        <div className="db-time">{a.time}<span>·</span>{a.mins} min</div>
                        <div className="db-who">{cust.name}{fl && <span className={'db-dot ' + fl.tone}/>}</div>
                        <div className="db-what">{treat.name}{a.note && <span className="db-note"> · {a.note}</span>}</div>
                      </>
                    )}
                  </div>
                );
              })}
            </div>
          );
        })}
      </div>

      {/* Per-block hover preview: notes + alert (§5 — "hover client, see notes inline") */}
      {hover && (() => {
        const a = appts.find(x => x.id === hover.id);
        if (!a) return null;
        const cust = customerById(a.customerId);
        const last = (NOTES[cust.id] || [])[0];
        return (
          <div className="diary-hover" style={{ left: Math.min(hover.x, window.innerWidth - 320), top: Math.max(80, hover.y) }}>
            <div className="dh-head">
              <span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{cust.initials}</span>
              <div><b>{cust.name}</b><span>{cust.phone}</span></div>
              {flagInfo(cust.flag) && <span className={'tag ' + flagInfo(cust.flag).tone}>{flagInfo(cust.flag).label}</span>}
            </div>
            {cust.alert && <div className="dh-alert"><Icon name="info" size={12}/>{cust.alert}</div>}
            {(cust.deposit > 0 || cust.account !== 0) && (
              <div className="dh-row">
                {cust.deposit > 0 && <span className="tag blush">Deposit {fmtMoney(cust.deposit)}</span>}
                {cust.account < 0 && <span className="tag warn">Owes {fmtMoney(Math.abs(cust.account))}</span>}
              </div>
            )}
            {last ? (
              <div className="dh-note">
                <div className="dh-note-when">{last.kind} · {last.when}</div>
                <div className="dh-note-title">{last.title}</div>
                <div className="dh-note-body">{last.body.slice(0, 140)}{last.body.length > 140 ? '…' : ''}</div>
              </div>
            ) : (
              <div className="dh-empty">No notes yet</div>
            )}
            <div className="dh-foot">Click for full record</div>
          </div>
        );
      })()}

      {/* Amend modal — opens on direct click (no drag) */}
      {amend && (
        <AmendApptModal appt={amend} onClose={() => setAmend(null)} onOpenRecord={onOpenRecord} onCheckOut={onCheckOut}
          onSave={(updated) => {
            setAppts(prev => prev.map(a => a.id === updated.id ? updated : a));
            setAmend(null);
          }}/>
      )}

      {/* Drag-drop confirm — Emma's OVATU flow: shows old vs new, email + reset reminder */}
      {editing && (
        <MoveConfirmModal appt={editing.appt} drop={editing.drop} onConfirm={confirmMove} onCancel={cancelMove}/>
      )}
    </div>
  );
}

// ============================================================
// MoveConfirmModal — drag-drop confirmation
// Mirrors the OVATU dialog Emma showed: old vs new time, email customer toggle,
// reset reminder toggle (§1.4 + §11).
// ============================================================
function MoveConfirmModal({ appt, drop, onConfirm, onCancel }) {
  const [email, setEmail] = React.useState(true);
  const [reminder, setReminder] = React.useState(true);
  const cust = customerById(appt.customerId);
  const fromCl = clinicianById(drop.fromClin);
  const toCl = clinicianById(drop.toClin);
  return (
    <div className="modal-scrim" onClick={onCancel}>
      <div className="modal" style={{ width: 'min(460px, 92vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Move appointment</span><h2>Confirm change</h2></div>
          <button className="app-btn ghost icon-only" onClick={onCancel}><Icon name="close" size={16}/></button>
        </div>
        <div className="amend-row">
          <span className="avatar" style={{ background: 'var(--blush-soft)' }}>{cust.initials}</span>
          <div><b>{cust.name}</b><span style={{ fontSize: 12, color: 'var(--fg-3)' }}>{treatmentById(appt.treatmentId).name}</span></div>
        </div>
        <div className="move-compare">
          <div className="move-side">
            <div className="lbl">Original</div>
            <div className="big">{appt.time}</div>
            <div className="sub">with {fromCl.name.split(' ')[0]}</div>
          </div>
          <Icon name="arrow" size={20}/>
          <div className="move-side new">
            <div className="lbl">New</div>
            <div className="big">{drop.toTime}</div>
            <div className="sub">with {toCl.name.split(' ')[0]}</div>
          </div>
        </div>
        <label className="amend-toggle">
          <input type="checkbox" checked={email} onChange={e => setEmail(e.target.checked)}/>
          <div><b>Email {cust.name.split(' ')[0]}</b><span>"Your appointment has been moved to {drop.toTime}"</span></div>
        </label>
        <label className="amend-toggle">
          <input type="checkbox" checked={reminder} onChange={e => setReminder(e.target.checked)}/>
          <div><b>Move the reminders too</b><span>Queued pre-care and reminder emails re-anchor to the new time</span></div>
        </label>
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 20 }}>
          <button className="app-btn ghost" onClick={onCancel}>Keep original</button>
          <button className="app-btn primary" onClick={() => onConfirm(email, reminder)}>Confirm move</button>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// AmendApptModal — direct edit, no cancel/rebook (§1.3, §1.4)
// Edit time, length, treatment, clinician, room, note, status — all in one place.
// ============================================================
function AmendApptModal({ appt, onSave, onClose, onOpenRecord, onCheckOut }) {
  const [draft, setDraft] = React.useState(appt);
  const cust = customerById(appt.customerId);
  const dirty = JSON.stringify(draft) !== JSON.stringify(appt);

  const update = (patch) => setDraft(prev => ({ ...prev, ...patch }));

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(560px, 94vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Appointment</span><h2>Edit details</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>

        <div className="amend-row">
          <span className="avatar lg" style={{ background: 'var(--blush-soft)' }}>{cust.initials}</span>
          <div style={{ flex: 1 }}>
            <b style={{ fontSize: 16, color: 'var(--fg-1)' }}>{cust.name}</b>
            <div style={{ fontSize: 12, color: 'var(--fg-3)', marginTop: 2 }}>{cust.phone} · {cust.email}</div>
            <div style={{ display: 'flex', gap: 6, marginTop: 6 }}>
              {flagInfo(cust.flag) && <span className={'tag ' + flagInfo(cust.flag).tone}>{flagInfo(cust.flag).label}</span>}
              {cust.deposit > 0 && <span className="tag blush">Deposit {fmtMoney(cust.deposit)}</span>}
              {cust.account < 0 && <span className="tag warn">Owes {fmtMoney(Math.abs(cust.account))}</span>}
            </div>
          </div>
          <button className="app-btn small" onClick={() => { onClose(); onOpenRecord && onOpenRecord(cust.id); }}><Icon name="file" size={14}/>Record</button>
        </div>

        {cust.alert && (
          <div className="amend-alert"><Icon name="info" size={14}/>{cust.alert}</div>
        )}

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginTop: 16 }}>
          <div className="field">
            <label>Date</label>
            <input className="stripe-input" type="date" value={draft.date} onChange={e => update({ date: e.target.value })}/>
          </div>
          <div className="field">
            <label>Time</label>
            <input className="stripe-input" type="time" value={draft.time} onChange={e => update({ time: e.target.value })}/>
          </div>
          <div className="field">
            <label>Length</label>
            <select className="stripe-input" value={draft.mins} onChange={e => update({ mins: Number(e.target.value) })}>
              {[15, 30, 45, 60, 75, 90, 105, 120].map(m => <option key={m} value={m}>{m} min</option>)}
            </select>
          </div>
          <div className="field">
            <label>Room</label>
            <select className="stripe-input" value={draft.room || ''} onChange={e => update({ room: e.target.value })}>
              <option value="">No room</option>
              <optgroup label="Treatment rooms">
                {ROOMS.filter(r => r.type === 'treatment').map(r => <option key={r.id} value={r.name}>{r.name}</option>)}
              </optgroup>
              <optgroup label="Consultation spaces">
                {ROOMS.filter(r => r.type === 'consultation').map(r => <option key={r.id} value={r.name}>{r.name}</option>)}
              </optgroup>
            </select>
          </div>
        </div>
        <div className="field">
          <label>Treatment</label>
          <select className="stripe-input" value={draft.treatmentId} onChange={e => update({ treatmentId: e.target.value })}>
            {TREATMENTS.map(t => <option key={t.id} value={t.id}>{t.name} · {t.mins}min · {fmtMoney(t.price)}</option>)}
          </select>
        </div>
        <div className="field">
          <label>Specialist</label>
          <select className="stripe-input" value={draft.clinicianId} onChange={e => update({ clinicianId: e.target.value })}>
            {CLINICIANS.map(c => <option key={c.id} value={c.id}>{c.name} · {c.role}</option>)}
          </select>
        </div>
        <div className="field">
          <label>Booking note</label>
          <input className="stripe-input" placeholder="e.g. Session 3 of 3" value={draft.note || ''} onChange={e => update({ note: e.target.value })}/>
        </div>

        <div className="field">
          <label>Status</label>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {['confirmed', 'pending', 'cancelled', 'no-show'].map(s => (
              <button key={s} className={'pose-chip' + (draft.status === s ? ' captured' : '')}
                onClick={() => update({ status: s })}>{s}</button>
            ))}
          </div>
        </div>

        <div style={{ padding: '12px 14px', background: 'var(--cream)', borderRadius: 4, fontSize: 12, color: 'var(--fg-2)', display: 'flex', alignItems: 'center', gap: 10, marginTop: 8 }}>
          <Icon name="info" size={14}/>
          <span>Changes save in place — no need to cancel and rebook.</span>
        </div>

        <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginTop: 20, flexWrap: 'wrap' }}>
          <button className="app-btn" onClick={() => { onClose(); onCheckOut && onCheckOut(appt); }}><Icon name="pound" size={14}/>Check out</button>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="app-btn ghost" onClick={onClose}>Cancel</button>
            <button className="app-btn primary" disabled={!dirty} onClick={() => onSave(draft)}>Save changes</button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// CashUpModal — end-of-appointment checkout
// Steps: review treatment+price → take payment (amount, method, discount) →
// invoice option → optional rebook.
// ============================================================
function CashUpModal({ appt, onClose, onRebook }) {
  const [step, setStep] = React.useState(0);
  const cust = customerById(appt.customerId);
  const treat = treatmentById(appt.treatmentId);
  const [paid, setPaid] = React.useState(treat.price - (cust.deposit || 0));
  const [method, setMethod] = React.useState('card');
  const [discount, setDiscount] = React.useState(0);
  const [discountReason, setDiscountReason] = React.useState('');
  const [datePaid, setDatePaid] = React.useState(todayISO);
  const [sendInvoice, setSendInvoice] = React.useState(true);
  const [bookNext, setBookNext] = React.useState(false);
  const [sessionNote, setSessionNote] = React.useState('');  // §5.1 — note at checkout
  // Optional tip line (Q23) — never prompted, but available if the customer offers
  const [tip, setTip] = React.useState(0);
  const total = treat.price - discount + tip;
  const balance = total - (cust.deposit || 0) - paid;

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(560px, 94vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Cash-up · {appt.time}</span><h2>Check out</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>

        <div className="amend-row">
          <span className="avatar" style={{ background: 'var(--blush-soft)' }}>{cust.initials}</span>
          <div style={{ flex: 1 }}><b>{cust.name}</b><span style={{ fontSize: 12, color: 'var(--fg-3)' }}>{treat.name} · {appt.mins} min</span></div>
          <div className="cashup-tot">{fmtMoney(treat.price)}</div>
        </div>

        <div className="step-bar">
          {[0, 1, 2].map(i => <div key={i} className={'step ' + (i < step ? 'done' : i === step ? 'active' : '')}/>)}
        </div>

        {step === 0 && (
          <>
            <div style={{ fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--fg-3)', marginBottom: 8 }}>Today's bill</div>
            <div className="cashup-summary">
              <div className="line"><span>{treat.name}</span><span>{fmtMoney(treat.price)}</span></div>
              {cust.deposit > 0 && <div className="line muted"><span>Deposit paid online</span><span>−{fmtMoney(cust.deposit)}</span></div>}
              {discount > 0 && <div className="line muted"><span>Discount{discountReason ? ' · ' + discountReason : ''}</span><span>−{fmtMoney(discount)}</span></div>}
              {tip > 0 && <div className="line muted"><span>Tip</span><span>+{fmtMoney(tip)}</span></div>}
              <div className="line tot"><span>Balance due</span><span>{fmtMoney(total - (cust.deposit || 0))}</span></div>
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              <div className="field">
                <label>Discount</label>
                <input className="stripe-input" inputMode="numeric" placeholder="0"
                  value={discount} onChange={e => setDiscount(Number(e.target.value.replace(/\D/g, '') || 0))}/>
              </div>
              <div className="field">
                <label>Reason</label>
                <select className="stripe-input" value={discountReason} onChange={e => setDiscountReason(e.target.value)}>
                  <option value="">—</option>
                  <option>KG used (loyalty £)</option>
                  <option>Concession</option>
                  <option>Course discount</option>
                  <option>Goodwill</option>
                </select>
              </div>
            </div>

            {/* Optional tip line (Q23) — we never ask, but if the customer offers we can log it. */}
            <div className="field">
              <label>Tip <span style={{ color: 'var(--fg-3)', fontWeight: 400, textTransform: 'none', letterSpacing: 0 }}>· optional · we don't ask, only log if offered</span></label>
              <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
                {[0, 5, 10, 15, 20].map(amt => (
                  <button key={amt} className={'pose-chip' + (tip === amt ? ' captured' : '')} onClick={() => setTip(amt)}>
                    {amt === 0 ? 'No tip' : '£' + amt}
                  </button>
                ))}
                <input className="stripe-input" inputMode="numeric" placeholder="Custom £"
                  style={{ maxWidth: 120 }}
                  value={tip || ''}
                  onChange={e => setTip(Number(e.target.value.replace(/\D/g, '') || 0))}/>
              </div>
            </div>

            {/* §5.1 — note this session before taking payment, saved straight to record */}
            <div className="field">
              <label>Session note (saved to record)</label>
              <textarea className="stripe-input" rows="2" placeholder="e.g. Mild redness post-treatment, settled within 5 min. Recommended SPF50."
                value={sessionNote} onChange={e => setSessionNote(e.target.value)}
                style={{ fontFamily: 'inherit', resize: 'vertical' }}/>
            </div>

            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 8 }}>
              <button className="app-btn ghost" onClick={onClose}>Cancel</button>
              <button className="app-btn primary" onClick={() => setStep(1)}>Take payment<Icon name="arrow" size={14}/></button>
            </div>
          </>
        )}

        {step === 1 && (
          <>
            <div className="field">
              <label>Method</label>
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(110px, 1fr))', gap: 6 }}>
                {[
                  { id: 'card',  label: 'Card · terminal', icon: 'phone' },
                  { id: 'other', label: 'Card other',       icon: 'pound' },
                  { id: 'cash',  label: 'Cash',             icon: 'pound' },
                  { id: 'bank',  label: 'Bank transfer',    icon: 'arrow' },
                ].map(m => (
                  <button key={m.id} className={'pose-chip' + (method === m.id ? ' captured' : '')} onClick={() => setMethod(m.id)}>
                    <Icon name={m.icon} size={12}/>{m.label}
                  </button>
                ))}
              </div>
            </div>

            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              <div className="field">
                <label>Amount</label>
                <input className="stripe-input" inputMode="numeric"
                  value={paid} onChange={e => setPaid(Number(e.target.value.replace(/\D/g, '') || 0))}/>
              </div>
              <div className="field">
                <label>Date paid</label>
                <input className="stripe-input" type="date" value={datePaid} onChange={e => setDatePaid(e.target.value)}/>
              </div>
            </div>

            <div className="cashup-summary">
              <div className="line"><span>Total today</span><span>{fmtMoney(total - (cust.deposit || 0))}</span></div>
              <div className="line"><span>Paid now</span><span>{fmtMoney(paid)}</span></div>
              <div className={'line tot ' + (balance > 0 ? 'warn' : balance < 0 ? 'change' : '')}>
                <span>{balance > 0 ? 'Outstanding' : balance < 0 ? 'Change' : 'Balance'}</span>
                <span>{fmtMoney(Math.abs(balance))}</span>
              </div>
            </div>

            <label className="amend-toggle">
              <input type="checkbox" checked={sendInvoice} onChange={e => setSendInvoice(e.target.checked)}/>
              <div><b>Email invoice</b><span>{cust.email}</span></div>
            </label>

            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginTop: 16 }}>
              <button className="app-btn ghost" onClick={() => setStep(0)}><Icon name="back" size={14}/>Back</button>
              <button className="app-btn primary" onClick={() => setStep(2)}>Charge {fmtMoney(paid)}<Icon name="check" size={14}/></button>
            </div>
          </>
        )}

        {step === 2 && (
          <>
            <div className="confirm-mark"><Icon name="check" size={32}/></div>
            <h3 style={{ textAlign: 'center', margin: '0 0 6px', fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 24 }}>Payment confirmed</h3>
            <p style={{ textAlign: 'center', color: 'var(--fg-3)', fontSize: 13, marginBottom: 16 }}>
              {fmtMoney(paid)} · {method === 'card' ? 'Card terminal' : method === 'other' ? 'Card other' : method === 'cash' ? 'Cash' : 'Bank transfer'}
              {sendInvoice && ' · Invoice sent to ' + cust.email}
            </p>
            <div className="cashup-summary" style={{ marginBottom: 16 }}>
              <div className="line"><span>{treat.name}</span><span>{fmtMoney(treat.price)}</span></div>
              {discount > 0 && <div className="line muted"><span>Discount</span><span>−{fmtMoney(discount)}</span></div>}
              {cust.deposit > 0 && <div className="line muted"><span>Deposit applied</span><span>−{fmtMoney(cust.deposit)}</span></div>}
              <div className="line tot"><span>Total received</span><span>{fmtMoney(paid + (cust.deposit || 0))}</span></div>
            </div>

            <div style={{ padding: 14, background: 'var(--cream)', borderRadius: 4 }}>
              <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--fg-1)', marginBottom: 4 }}>Book {cust.name.split(' ')[0]}'s next visit?</div>
              <div style={{ fontSize: 12, color: 'var(--fg-3)', marginBottom: 10 }}>Skip if they want to book themselves online — we'll send a rebook prompt in 6 weeks.</div>
              <div style={{ display: 'flex', gap: 8 }}>
                <button className="app-btn small" onClick={onClose}>Done</button>
                <button className="app-btn small primary" onClick={() => { onClose(); onRebook && onRebook(appt); }}><Icon name="calendar" size={12}/>Book next</button>
              </div>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ============================================================
// MedicalFormModal — view a completed medical-history form (§4)
// Replaces the bare "Signed" badge: opens with all answers.
// ============================================================
// SignaturePad — canvas + pointer events (works for mouse + touch + stylus).
// Used inside MedicalFormModal for on-screen signing (Q11 — Emma wants the
// simple Ovatu-style sign-on-doc rather than DocuSign).
function SignaturePad({ onSign, initialDataUrl }) {
  const canvasRef = React.useRef(null);
  const lastPointRef = React.useRef(null);
  const [hasInk, setHasInk] = React.useState(!!initialDataUrl);

  // Set the canvas internal pixel size to match its displayed size for crisp
  // strokes on high-density displays.
  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const dpr = window.devicePixelRatio || 1;
    const rect = canvas.getBoundingClientRect();
    canvas.width  = rect.width  * dpr;
    canvas.height = rect.height * dpr;
    const ctx = canvas.getContext('2d');
    ctx.scale(dpr, dpr);
    ctx.lineWidth = 2;
    ctx.lineCap   = 'round';
    ctx.lineJoin  = 'round';
    ctx.strokeStyle = '#1A1A1A';
    if (initialDataUrl) {
      const img = new Image();
      img.onload = () => ctx.drawImage(img, 0, 0, rect.width, rect.height);
      img.src = initialDataUrl;
    }
  }, []);

  const pointFromEvent = (e) => {
    const canvas = canvasRef.current;
    const rect = canvas.getBoundingClientRect();
    return { x: e.clientX - rect.left, y: e.clientY - rect.top };
  };

  const onPointerDown = (e) => {
    e.preventDefault();
    canvasRef.current.setPointerCapture?.(e.pointerId);
    lastPointRef.current = pointFromEvent(e);
  };
  const onPointerMove = (e) => {
    if (!lastPointRef.current) return;
    const p = pointFromEvent(e);
    const ctx = canvasRef.current.getContext('2d');
    ctx.beginPath();
    ctx.moveTo(lastPointRef.current.x, lastPointRef.current.y);
    ctx.lineTo(p.x, p.y);
    ctx.stroke();
    lastPointRef.current = p;
    if (!hasInk) setHasInk(true);
  };
  const onPointerUp = () => { lastPointRef.current = null; };

  const clear = () => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    const rect = canvas.getBoundingClientRect();
    ctx.clearRect(0, 0, rect.width, rect.height);
    setHasInk(false);
  };

  const confirm = () => {
    const dataUrl = canvasRef.current.toDataURL('image/png');
    onSign && onSign(dataUrl);
  };

  return (
    <div className="signature-pad">
      <canvas ref={canvasRef}
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={onPointerUp}
        onPointerCancel={onPointerUp}/>
      <div className="signature-foot">
        <div className="signature-hint">Sign with your finger, stylus or mouse above</div>
        <div className="signature-actions">
          <button className="app-btn ghost small" onClick={clear} disabled={!hasInk}>Clear</button>
          <button className="app-btn primary small" onClick={confirm} disabled={!hasInk}>
            <Icon name="check" size={12}/>Confirm signature
          </button>
        </div>
      </div>
    </div>
  );
}

function MedicalFormModal({ customerId, onClose }) {
  const cust = customerById(customerId);
  const seedForm = MEDICAL_FORMS[customerId];
  // Local runtime signature: stored as data URL once captured. Survives until
  // modal close (matches the rest of the prototype's no-backend model).
  const [signature, setSignature]   = React.useState(null);
  const [showSignPad, setShowSignPad] = React.useState(false);
  const isSigned = !!seedForm || !!signature;
  const signedBy   = seedForm?.signedBy   || cust.name;
  const signedDate = seedForm?.signedDate || new Date().toLocaleDateString('en-GB', { day:'numeric', month:'short', year:'numeric' });

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(640px, 94vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Form · {seedForm?.version || 'v3.2'}</span><h2>Medical history</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>

        {!isSigned ? (
          <>
            <div className="empty"><Icon name="file" size={28}/><div>This form hasn't been signed yet.</div></div>
            {showSignPad ? (
              <SignaturePad onSign={(dataUrl) => { setSignature(dataUrl); setShowSignPad(false); }}/>
            ) : (
              <div style={{ display: 'flex', justifyContent: 'center', gap: 8, marginTop: 12 }}>
                <button className="app-btn" onClick={() => setShowSignPad(true)}><Icon name="edit" size={14}/>Sign on screen</button>
                <button className="app-btn"><Icon name="mail" size={14}/>Send link to customer</button>
              </div>
            )}
          </>
        ) : (
          <>
            <div className="amend-row" style={{ marginBottom: 12 }}>
              <span className="avatar" style={{ background: 'var(--blush-soft)' }}>{cust.initials}</span>
              <div style={{ flex: 1 }}><b>{cust.name}</b><span style={{ fontSize: 12, color: 'var(--fg-3)' }}>Signed {signedDate} · {signedBy}</span></div>
              <span className="tag ok"><Icon name="check" size={10}/>signed</span>
            </div>
            <div className="form-answers">
              {(seedForm?.answers || []).map((qa, i) => (
                <div key={i} className="form-qa">
                  <div className="q">{qa.q}</div>
                  <div className="a">{qa.a}</div>
                </div>
              ))}
              {!seedForm && (
                <div style={{ fontSize: 12, color: 'var(--fg-3)', fontStyle: 'italic', padding: '8px 0' }}>
                  (Form answers will appear here once the customer fills out the body of the form.)
                </div>
              )}
            </div>
            <div className="form-sig">
              <div className="sig-mark">
                {signature ? (
                  <img src={signature} alt="Signature" style={{ maxHeight: 50, maxWidth: '100%', display: 'block' }}/>
                ) : (
                  <svg viewBox="0 0 200 60" style={{ width: '100%', height: 50 }}>
                    <path d="M10,40 Q30,10 50,30 T90,28 Q110,8 130,32 T180,30" stroke="var(--ink)" strokeWidth="1.5" fill="none" strokeLinecap="round"/>
                  </svg>
                )}
              </div>
              <div className="sig-meta">
                <div className="sig-name">{signedBy}</div>
                <div className="sig-when">Signed digitally · {signedDate}</div>
              </div>
            </div>
            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginTop: 20 }}>
              <button className="app-btn"><Icon name="upload" size={14}/>Download PDF</button>
              <div style={{ display: 'flex', gap: 8 }}>
                <button className="app-btn ghost"><Icon name="mail" size={14}/>Request update</button>
                <button className="app-btn primary" onClick={onClose}>Close</button>
              </div>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ============================================================
// BulkUploadModal — image bulk upload (§6)
// Drop or browse 20+ images at a time, tag them by pose, attach to a session.
// ============================================================
function BulkUploadModal({ customerId, onClose, onAttach }) {
  const cust = customerById(customerId);
  const [files, setFiles] = React.useState([]);
  const [defaultPose, setDefaultPose] = React.useState('Front');
  const [session, setSession] = React.useState(todayISO);
  const [drag, setDrag] = React.useState(false);

  const handle = (list) => {
    const arr = Array.from(list).map((f, i) => ({
      id: 'u' + Date.now() + i,
      name: f.name, size: f.size, pose: defaultPose,
      url: URL.createObjectURL(f),
    }));
    setFiles(prev => [...prev, ...arr]);
  };

  const onDrop = (e) => {
    e.preventDefault();
    setDrag(false);
    if (e.dataTransfer.files?.length) handle(e.dataTransfer.files);
  };

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(720px, 94vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Photos · {cust.name}</span><h2>Upload existing images</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
          <div className="field">
            <label>Attach to session</label>
            <input className="stripe-input" type="date" value={session} onChange={e => setSession(e.target.value)}/>
          </div>
          <div className="field">
            <label>Default pose tag</label>
            <select className="stripe-input" value={defaultPose} onChange={e => setDefaultPose(e.target.value)}>
              <option>Front</option><option>Left ¾</option><option>Right ¾</option><option>Left profile</option><option>Right profile</option><option>Chin up</option><option>Other</option>
            </select>
          </div>
        </div>

        <div className={'drop-zone' + (drag ? ' active' : '')}
          onDragEnter={e => { e.preventDefault(); setDrag(true); }}
          onDragOver={e => e.preventDefault()}
          onDragLeave={() => setDrag(false)}
          onDrop={onDrop}>
          <Icon name="upload" size={28}/>
          <div className="dz-title">{drag ? 'Drop to add' : 'Drop images here'}</div>
          <div className="dz-sub">or</div>
          <label className="app-btn" style={{ cursor: 'pointer' }}>
            <Icon name="photo" size={14}/>Browse files
            <input type="file" multiple accept="image/*" onChange={e => handle(e.target.files)} style={{ display: 'none' }}/>
          </label>
          <div className="dz-meta">PNG, JPG, HEIC · up to 50 at a time · max 20 MB each</div>
        </div>

        {files.length > 0 && (
          <>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginTop: 16, marginBottom: 8 }}>
              <div style={{ fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--fg-3)' }}>{files.length} ready to upload</div>
              <button className="app-btn ghost small" onClick={() => setFiles([])}>Clear all</button>
            </div>
            <div className="upload-grid">
              {files.map(f => (
                <div key={f.id} className="upload-thumb">
                  <img src={f.url} alt={f.name}/>
                  <button className="upload-remove" onClick={() => setFiles(prev => prev.filter(x => x.id !== f.id))}><Icon name="close" size={10}/></button>
                  <select value={f.pose} onChange={e => setFiles(prev => prev.map(x => x.id === f.id ? { ...x, pose: e.target.value } : x))}>
                    {['Front','Left ¾','Right ¾','Left profile','Right profile','Chin up','Other'].map(p => <option key={p}>{p}</option>)}
                  </select>
                </div>
              ))}
            </div>
          </>
        )}

        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 20 }}>
          <button className="app-btn ghost" onClick={onClose}>Cancel</button>
          <button className="app-btn primary" disabled={!files.length} onClick={() => { onAttach && onAttach(files, session); onClose(); }}>
            <Icon name="upload" size={14}/>Upload {files.length || ''} {files.length === 1 ? 'image' : 'images'}
          </button>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// RequestLeaveModal — clinician requests a day/range off (§1.7)
// ============================================================
function RequestLeaveModal({ clinicianId, onClose, onRequest }) {
  const [from, setFrom] = React.useState(offsetDay(7));
  const [to, setTo] = React.useState(offsetDay(7));
  const [kind, setKind] = React.useState('Vacation');
  const [note, setNote] = React.useState('');
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(460px, 92vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">My availability</span><h2>Request leave</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
          <div className="field"><label>From</label><input className="stripe-input" type="date" value={from} onChange={e => setFrom(e.target.value)}/></div>
          <div className="field"><label>To</label><input className="stripe-input" type="date" value={to} onChange={e => setTo(e.target.value)}/></div>
        </div>
        <div className="field">
          <label>Type</label>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {['Vacation', 'Sick', 'CPD / training', 'Personal'].map(k => (
              <button key={k} className={'pose-chip' + (kind === k ? ' captured' : '')} onClick={() => setKind(k)}>{k}</button>
            ))}
          </div>
        </div>
        <div className="field">
          <label>Note (optional)</label>
          <input className="stripe-input" placeholder="e.g. Family holiday" value={note} onChange={e => setNote(e.target.value)}/>
        </div>
        <div style={{ padding: 12, background: 'var(--cream)', borderRadius: 4, fontSize: 12, color: 'var(--fg-2)', lineHeight: 1.5, display: 'flex', gap: 10, alignItems: 'flex-start', marginTop: 8 }}>
          <Icon name="info" size={14}/>
          <span>Once approved, your diary will block off automatically and any existing bookings in that window will be flagged for moving.</span>
        </div>
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 }}>
          <button className="app-btn ghost" onClick={onClose}>Cancel</button>
          <button className="app-btn primary" onClick={() => { onRequest && onRequest({ from, to, kind, note }); onClose(); }}>Submit</button>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// PendingBookingsPanel — online bookings awaiting approval (§2 + §12)
// Approve / amend / decline with form-status visible.
// ============================================================
function PendingBookingsPanel({ onOpenForm, onAmend }) {
  const [items, setItems] = React.useState(PENDING_BOOKINGS);
  const [denied, setDenied] = React.useState(null);
  const [sendForm, setSendForm] = React.useState(null);
  const [editing, setEditing] = React.useState(null); // pending booking → AmendApptModal draft

  const remove = (id) => setItems(prev => prev.filter(x => x.id !== id));

  // Convert a pending booking to an appointment-shape so AmendApptModal can edit it
  const pendingToAppt = (pb) => ({
    id: pb.id, customerId: pb.customerId, treatmentId: pb.treatmentId, clinicianId: pb.clinicianId,
    date: pb.wantedDate, time: pb.wantedTime, mins: treatmentById(pb.treatmentId)?.mins || 60,
    status: 'pending', room: '', note: pb.formStatus,
  });

  return (
    <div className="panel" style={{ padding: 0 }}>
      <div className="panel-head" style={{ padding: '20px 24px', marginBottom: 0 }}>
        <h3>Pending approval <span style={{ background: 'var(--blush-deep)', color: '#fff', borderRadius: 999, padding: '2px 8px', fontSize: 11, marginLeft: 8, verticalAlign: 'middle' }}>{items.length}</span><DemoNote title="Online bookings" text="Online bookings arrive as requests, not commitments: the deposit is taken online and the medical form is chased automatically. You confirm with one click — and only then does the confirmation go out, in your wording."/></h3>
        <span className="meta">Online bookings — review then confirm</span>
      </div>
      {items.length === 0 ? (
        <div className="empty"><Icon name="check" size={28}/><div>All caught up — no pending bookings.</div></div>
      ) : items.map(pb => {
        const cust = customerById(pb.customerId);
        const treat = treatmentById(pb.treatmentId);
        const cl = clinicianById(pb.clinicianId);
        return (
          <div key={pb.id} className="pending-row">
            <div className="pr-when">
              <span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{cust.initials}</span>
              <div>
                <b>{cust.name}</b>
                <span>{pb.when}</span>
              </div>
            </div>
            <div className="pr-meta">
              <div className="pr-treat">{treat.name} · {treat.mins} min</div>
              <div className="pr-with">{cl.name.split(' ')[0]} · {fmtDate(pb.wantedDate)} {pb.wantedTime}</div>
            </div>
            <div className="pr-form">
              <span className={'tag ' + (pb.formsComplete ? 'ok' : 'warn')}>
                <Icon name={pb.formsComplete ? 'check' : 'edit'} size={10}/>
                {pb.formsComplete ? 'Forms complete' : 'Forms pending'}
              </span>
              <span style={{ fontSize: 11, color: 'var(--fg-3)' }}>{pb.formStatus}</span>
              <div style={{ display: 'flex', gap: 4 }}>
                <button className="app-btn ghost small" onClick={() => onOpenForm && onOpenForm(pb.customerId)}>View</button>
                {!pb.formsComplete && <button className="app-btn small" onClick={() => setSendForm(pb)}><Icon name="mail" size={10}/>Send</button>}
              </div>
            </div>
            <div className="pr-actions">
              <button className="app-btn small" onClick={() => setEditing(pb)} title="Amend treatment / time / clinician"><Icon name="edit" size={12}/>Amend</button>
              <button className="app-btn small" style={{ background: 'var(--alert)', color: '#fff', borderColor: 'var(--alert)' }}
                onClick={() => setDenied(pb)}>Decline</button>
              <button className="app-btn small primary" disabled={!pb.formsComplete} onClick={() => remove(pb.id)} title={pb.formsComplete ? 'Approve booking' : 'Forms must be complete first'}>
                <Icon name="check" size={12}/>Approve
              </button>
            </div>
          </div>
        );
      })}

      {sendForm && <SendMessageModal customerId={sendForm.customerId} defaultChannel="email" defaultTemplate="consent" onClose={() => setSendForm(null)}/>}

      {editing && (
        <AmendApptModal
          appt={pendingToAppt(editing)}
          onClose={() => setEditing(null)}
          onSave={(updated) => {
            setItems(prev => prev.map(x => x.id === updated.id ? {
              ...x,
              treatmentId: updated.treatmentId,
              clinicianId: updated.clinicianId,
              wantedDate: updated.date,
              wantedTime: updated.time,
            } : x));
            setEditing(null);
          }}
        />
      )}

      {denied && (
        <div className="modal-scrim" onClick={() => setDenied(null)}>
          <div className="modal" style={{ width: 'min(420px, 92vw)' }} onClick={e => e.stopPropagation()}>
            <div className="modal-head">
              <div><span className="eyebrow">Decline booking</span><h2>Let them know?</h2></div>
              <button className="app-btn ghost icon-only" onClick={() => setDenied(null)}><Icon name="close" size={16}/></button>
            </div>
            <p style={{ fontSize: 13, color: 'var(--fg-2)', lineHeight: 1.55, margin: '0 0 14px' }}>
              <b>{customerById(denied.customerId).name}</b> requested {treatmentById(denied.treatmentId).name} on {fmtDate(denied.wantedDate)} {denied.wantedTime}.
            </p>
            <div className="field">
              <label>Reason</label>
              <select className="stripe-input">
                <option>Slot no longer available</option>
                <option>Specialist unavailable</option>
                <option>Outside scope of treatment</option>
                <option>Forms not completed in time</option>
              </select>
            </div>
            <label className="amend-toggle">
              <input type="checkbox" defaultChecked/>
              <div><b>Suggest alternative slots</b><span>Show next 3 available with the same specialist</span></div>
            </label>
            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 }}>
              <button className="app-btn ghost" onClick={() => setDenied(null)}>Cancel</button>
              <button className="app-btn" style={{ background: 'var(--alert)', color: '#fff', borderColor: 'var(--alert)' }}
                onClick={() => { remove(denied.id); setDenied(null); }}>Decline & email</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ============================================================
// WaitlistPanel (§12)
// ============================================================
function WaitlistPanel() {
  const [list, setList] = React.useState(WAITLIST);
  const [offering, setOffering] = React.useState(null);
  const [adding, setAdding] = React.useState(false);
  return (
    <div className="panel">
      <div className="panel-head"><h3>Waitlist</h3><span className="meta">Notify if a slot opens</span></div>
      {list.length === 0 ? (
        <div className="empty"><Icon name="users" size={28}/><div>Nobody on the waitlist.</div></div>
      ) : list.map(w => {
        const cust = customerById(w.customerId);
        const treat = treatmentById(w.treatmentId);
        const cl = clinicianById(w.clinicianId);
        return (
          <div key={w.id} className="waitlist-row">
            <span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{cust.initials}</span>
            <div className="wl-body">
              <b>{cust.name}</b>
              <span>{treat.name} · {cl.name.split(' ')[0]}</span>
              <span className="wl-meta">{w.noticeBefore} · added {w.when}</span>
            </div>
            <button className="app-btn ghost small" onClick={() => setOffering(w)}><Icon name="bell" size={12}/>Offer slot</button>
          </div>
        );
      })}
      <button className="app-btn small" style={{ marginTop: 12 }} onClick={() => setAdding(true)}><Icon name="plus" size={12}/>Add to waitlist</button>

      {offering && <OfferSlotModal entry={offering} onClose={() => setOffering(null)} onSent={(id) => { setList(prev => prev.filter(x => x.id !== id)); setOffering(null); }}/>}
      {adding && <AddToWaitlistModal onClose={() => setAdding(false)} onAdd={(entry) => { setList(prev => [{ id: 'wl-' + Date.now(), when: 'Just now', ...entry }, ...prev]); setAdding(false); }}/>}
    </div>
  );
}

function OfferSlotModal({ entry, onClose, onSent }) {
  const cust = customerById(entry.customerId);
  const treat = treatmentById(entry.treatmentId);
  const cl = clinicianById(entry.clinicianId);
  const [date, setDate] = React.useState(offsetDay(2));
  const [time, setTime] = React.useState('14:30');
  const [channel, setChannel] = React.useState('email');
  const [sending, setSending] = React.useState(false);

  const send = () => {
    setSending(true);
    setTimeout(() => { setSending(false); onSent && onSent(entry.id); }, 600);
  };

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(480px, 92vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Waitlist · slot opened</span><h2>Offer slot</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>
        <div className="amend-row">
          <span className="avatar" style={{ background: 'var(--blush-soft)' }}>{cust.initials}</span>
          <div style={{ flex: 1 }}>
            <b>{cust.name}</b>
            <span style={{ fontSize: 12, color: 'var(--fg-3)' }}>Wants {treat.name} · {cl.name.split(' ')[0]}</span>
          </div>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
          <div className="field"><label>Slot date</label><input className="stripe-input" type="date" value={date} onChange={e => setDate(e.target.value)}/></div>
          <div className="field"><label>Slot time</label><input className="stripe-input" type="time" value={time} onChange={e => setTime(e.target.value)}/></div>
        </div>

        <div style={{ padding: 12, background: 'var(--cream)', borderRadius: 4, fontSize: 12, color: 'var(--fg-2)', lineHeight: 1.5, marginTop: 4 }}>
          <Icon name="info" size={12}/> They'll get an email offering the slot. Once they book, the team clears them from the waitlist — entries whose client has since booked are labelled automatically.
        </div>
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 }}>
          <button className="app-btn ghost" onClick={onClose}>Cancel</button>
          <button className="app-btn primary" disabled={sending} onClick={send}>
            {sending ? 'Sending…' : 'Send offer'}<Icon name="bell" size={14}/>
          </button>
        </div>
      </div>
    </div>
  );
}

function AddToWaitlistModal({ onClose, onAdd }) {
  const [customerId, setCustomerId] = React.useState(CUSTOMERS[0].id);
  const [treatmentId, setTreatmentId] = React.useState(TREATMENTS[0].id);
  const [clinicianId, setClinicianId] = React.useState(CLINICIANS[0].id);
  const [noticeBefore, setNoticeBefore] = React.useState('Anytime');
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(480px, 92vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Waitlist</span><h2>Add to waitlist</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>
        <div className="field">
          <label>Customer</label>
          <select className="stripe-input" value={customerId} onChange={e => setCustomerId(e.target.value)}>
            {CUSTOMERS.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
          </select>
        </div>
        <div className="field">
          <label>Treatment</label>
          <select className="stripe-input" value={treatmentId} onChange={e => setTreatmentId(e.target.value)}>
            {TREATMENTS.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
          </select>
        </div>
        <div className="field">
          <label>With specialist</label>
          <select className="stripe-input" value={clinicianId} onChange={e => setClinicianId(e.target.value)}>
            {CLINICIANS.map(c => <option key={c.id} value={c.id}>{c.name} · {c.role}</option>)}
          </select>
        </div>
        <div className="field">
          <label>Notice preference</label>
          <select className="stripe-input" value={noticeBefore} onChange={e => setNoticeBefore(e.target.value)}>
            <option>Anytime</option>
            <option>Eves & Sat only</option>
            <option>Within 48 hours</option>
            <option>Weekends only</option>
            <option>Mornings only</option>
          </select>
        </div>
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 }}>
          <button className="app-btn ghost" onClick={onClose}>Cancel</button>
          <button className="app-btn primary" onClick={() => onAdd && onAdd({ customerId, treatmentId, clinicianId, noticeBefore })}>
            <Icon name="plus" size={14}/>Add
          </button>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// CustomerFlagBadge — small chip render
// ============================================================
function CustomerFlagBadge({ flag }) {
  const fi = flagInfo(flag);
  if (!fi) return null;
  return <span className={'tag ' + fi.tone}>{fi.label}</span>;
}

// ============================================================
// SendMessageModal — compose email with template picker (§7)
// Variables in templates ({{first}} etc.) are auto-filled from the customer.
// ============================================================
function SendMessageModal({ customerId, onClose, onSend, defaultChannel = 'email', defaultTemplate = null, formsToAttach = [], senderClinicianId = 'grace' }) {
  const cust = customerById(customerId);
  const [channel, setChannel] = React.useState(defaultChannel);
  const [tmplId, setTmplId] = React.useState(defaultTemplate);
  const [subject, setSubject] = React.useState('');
  const [body, setBody] = React.useState('');
  const [sending, setSending] = React.useState(false);
  const [sent, setSent] = React.useState(false);
  // 'team' = "The Team at Demo Clinic", 'clinician' = "{{clinician}} at Demo Clinic" (Q22)
  const [fromMode, setFromMode] = React.useState('team');
  const senderClinician = clinicianById(senderClinicianId);
  const fromLabel = fromMode === 'clinician' && senderClinician
    ? senderClinician.name.split(' ')[0] + ' at Demo Clinic'
    : 'The Team at Demo Clinic';
  // Form templates to attach: starts pre-checked with whatever was passed in
  const [attached, setAttached] = React.useState(formsToAttach);
  const toggleAttach = (name) => setAttached(prev =>
    prev.includes(name) ? prev.filter(n => n !== name) : [...prev, name]
  );

  // Variable expansion from a template body.
  // Supports Emma's the demo CRM-style tags:
  //   {{first}} {{treatment}} {{clinician}} {{date}} {{time}} {{slot}}
  //   {{form}} {{forms}} {{location}} {{month}}
  //   {{precare}}  → per-treatment pre-care snippet from PRE_POST_CARES
  //   {{postcare}} → per-treatment post-care snippet from PRE_POST_CARES
  const expand = (s) => {
    if (!s) return '';
    // For demo purposes, default to HydraFacial care content unless the
    // template's treatment is overridden by the upstream caller.
    const previewTreatment = 'hydra';
    const careEntry = (typeof PRE_POST_CARES !== 'undefined' ? PRE_POST_CARES[previewTreatment] : null) || { pre: '(per-treatment pre-care)', post: '(per-treatment post-care)' };
    const today = new Date();
    const ctx = {
      first: cust.name.split(' ')[0],
      treatment: 'HydraFacial',
      clinician: 'Grace',
      time: '10:30',
      date: today.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' }),
      slot: 'Wed 13 May at 10:30',
      form: 'medical history form',
      // {{forms}} expands to a bullet list of the missing form names
      forms: formsToAttach.length
        ? formsToAttach.map(n => '• ' + n).join('\n')
        : '• your forms',
      location: 'Demo Clinic · 12 High Street',
      month: today.toLocaleDateString('en-GB', { month: 'long' }),
      precare:  careEntry.pre,
      postcare: careEntry.post,
    };
    return s.replace(/\{\{(\w+)\}\}/g, (_, k) => (k in ctx ? ctx[k] : ('{{' + k + '}}')));
  };

  const tmpl = COMM_TEMPLATES.find(t => t.id === tmplId);
  React.useEffect(() => {
    if (tmpl) {
      setChannel(tmpl.channel);
      setSubject(expand(tmpl.subject));
      setBody(expand(tmpl.body));
      // Templates carry a default `from` — apply when chosen, but the user
      // can still toggle it in the modal afterwards.
      if (tmpl.from) setFromMode(tmpl.from);
    }
  }, [tmplId]);

  const send = () => {
    setSending(true);
    setTimeout(() => { setSending(false); setSent(true); onSend && onSend({ channel, subject, body, templateId: tmplId, attachedForms: channel === 'email' ? attached : [], from: fromLabel }); }, 800);
  };

    const tmplsForChannel = COMM_TEMPLATES.filter(t =>
    t.channel === channel
  );

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(640px, 94vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Compose · {cust.name}</span><h2>{sent ? 'Sent' : 'New email'}</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>

        {sent ? (
          <>
            <div className="confirm-mark"><Icon name="check" size={32}/></div>
            <p style={{ textAlign: 'center', fontSize: 14, color: 'var(--fg-1)', margin: '0 0 4px' }}>Email sent</p>
            <p style={{ textAlign: 'center', fontSize: 12, color: 'var(--fg-3)', margin: '0 0 8px' }}>
              {channel === 'email' ? 'From ' + fromLabel + ' · ' : ''}To {cust.email}{tmpl ? ' · template: ' + tmpl.name : ''}
            </p>
            {channel === 'email' && attached.length > 0 && (
              <p style={{ textAlign: 'center', fontSize: 12, color: 'var(--fg-3)', margin: '0 0 20px' }}>
                {attached.length} form template{attached.length === 1 ? '' : 's'} attached: {attached.join(', ')}
              </p>
            )}
            {!(channel === 'email' && attached.length > 0) && <div style={{ marginBottom: 20 }}/>}
            <div style={{ display: 'flex', justifyContent: 'center' }}>
              <button className="app-btn primary" onClick={onClose}>Done</button>
            </div>
          </>
        ) : (
          <>

            {/* To row (read-only) */}
            <div className="amend-row" style={{ marginTop: 8 }}>
              <span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{cust.initials}</span>
              <div style={{ flex: 1 }}>
                <b style={{ fontSize: 14, color: 'var(--fg-1)' }}>{cust.name}</b>
                <div style={{ fontSize: 12, color: 'var(--fg-3)' }}>
                  {channel === 'email' ? cust.email : cust.phone}
                  
                </div>
              </div>
            </div>

            {/* Template picker */}
            <div className="field">
              <label>Use template</label>
              <select className="stripe-input" value={tmplId || ''} onChange={e => setTmplId(e.target.value || null)}>
                <option value="">Start from blank</option>
                {tmplsForChannel.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
              </select>
            </div>

            {/* From — Q22: per-template sender override. Generic "The Team"
                for automations, personalised "Grace at Demo Clinic" for individual
                replies / consultation follow-ups. Email-only. */}
            {channel === 'email' && (
              <div className="field">
                <label>From</label>
                <div className="from-toggle">
                  <button className={fromMode === 'team' ? 'active' : ''} onClick={() => setFromMode('team')}>The Team at Demo Clinic</button>
                  <button className={fromMode === 'clinician' ? 'active' : ''} onClick={() => setFromMode('clinician')}>
                    {senderClinician ? senderClinician.name.split(' ')[0] + ' at Demo Clinic' : 'You at Demo Clinic'}
                  </button>
                </div>
              </div>
            )}

            {channel === 'email' && (
              <div className="field">
                <label>Subject</label>
                <input className="stripe-input" value={subject} onChange={e => setSubject(e.target.value)} placeholder="Subject"/>
              </div>
            )}

            <div className="field">
              <label>{channel === 'email' ? 'Body' : 'Message'}</label>
              <textarea className="stripe-input" rows={channel === 'email' ? 9 : 4}
                value={body} onChange={e => setBody(e.target.value)}
                placeholder="Write your email…"
                style={{ fontFamily: 'inherit', resize: 'vertical' }}/>
            </div>

            {channel === 'email' && formsToAttach.length > 0 && (
              <div className="form-attach">
                <div className="form-attach-head">
                  <Icon name="file" size={12}/>
                  <span>Attach form templates</span>
                </div>
                <div className="form-attach-list">
                  {formsToAttach.map(name => (
                    <label key={name} className="form-attach-item">
                      <input type="checkbox" checked={attached.includes(name)} onChange={() => toggleAttach(name)}/>
                      <span>{name}</span>
                    </label>
                  ))}
                </div>
                {attached.length > 0 && (
                  <div className="form-attach-chips">
                    {attached.map(name => (
                      <span key={name} className="attach-chip">
                        <Icon name="file" size={10}/>{name}.pdf
                        <button onClick={() => toggleAttach(name)} aria-label="Remove"><Icon name="close" size={10}/></button>
                      </span>
                    ))}
                  </div>
                )}
              </div>
            )}

            {channel === 'email' && (
              <div className="comm-foot">
                <Icon name="info" size={12}/>
                <span>Sent from <b>{fromLabel}</b> · via Gmail · info@clientloop.uk · replies land in the clinic inbox</span>
              </div>
            )}

            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginTop: 16 }}>
              <button className="app-btn ghost" onClick={onClose}>Cancel</button>
              <div style={{ display: 'flex', gap: 8 }}>
                <button className="app-btn"><Icon name="file" size={14}/>Save draft</button>
                <button className="app-btn primary" disabled={!body || sending} onClick={send}>
                  {sending ? 'Sending…' : 'Send email'}
                  <Icon name="arrow" size={14}/>
                </button>
              </div>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ============================================================
// CommHistory — message thread per customer (§7)
// Used in the customer record's Messages tab.
// ============================================================
function CommHistory({ customerId, onCompose }) {
  const cust = customerById(customerId);
  const log = (COMM_LOG[customerId] || []).slice().sort((a, b) => b.when.localeCompare(a.when));

  const fmtWhen = (iso) => {
    const d = new Date(iso);
    return d.toLocaleString('en-GB', { weekday: 'short', day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' });
  };

  return (
    <div className="comm-thread">
      <div className="comm-head">
        <div>
          <div className="meta">{log.length} message{log.length === 1 ? '' : 's'} on file</div>
          <div className="contacts">
            <span><Icon name="mail" size={12}/>{cust.email}</span>
            <span><Icon name="phone" size={12}/>{cust.phone}</span>
          </div>
        </div>
        <div className="comm-actions">
          <button className="app-btn primary" onClick={() => onCompose('email')}><Icon name="mail" size={14}/>Email</button>
        </div>
      </div>

      {log.length === 0 ? (
        <div className="empty"><Icon name="mail" size={28}/><div>No messages yet.</div>
          <button className="app-btn small" style={{ marginTop: 12 }} onClick={() => onCompose('email')}>Start a conversation</button>
        </div>
      ) : (
        <div className="comm-list">
          {log.map(m => {
            const inbound = m.direction === 'in';
            return (
              <div key={m.id} className={'comm-msg ' + (inbound ? 'in' : 'out')}>
                <div className="comm-meta">
                  <span className="comm-who">{m.from}</span>
                  <span className="comm-channel-tag">{m.channel}</span>
                  {m.templateId && <span className="tag">tmpl: {COMM_TEMPLATES.find(t => t.id === m.templateId)?.name || m.templateId}</span>}
                  {!inbound && m.opened && <span className="tag ok"><Icon name="check" size={10}/>opened{m.openedAt ? ' ' + m.openedAt.slice(11, 16) : ''}</span>}
                  {!inbound && m.opened === false && <span className="tag ghost">not opened yet</span>}
                  {inbound && !m.read && <span className="tag warn">new</span>}
                  <span className="comm-when">{fmtWhen(m.when)}</span>
                </div>
                {m.subject && <div className="comm-subj">{m.subject}</div>}
                <div className="comm-body">{m.body}</div>
                {inbound && (
                  <div className="comm-reply-row">
                    <button className="app-btn small" onClick={() => onCompose(m.channel)}><Icon name="arrow" size={12}/>Reply</button>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ============================================================
// TasksBoard — Trello-style columns per owner (Q31, Emma explicit preference).
// One column per clinician + "Clinic" (unassigned). Tasks render as cards.
// Drag a card to another column to reassign. Done/snooze controls per card.
// ============================================================
function TasksBoard({ tasks, onToggle, onReassign, onOpenRecord, kindIcon, dueLabel, overdue, currentClinicianId }) {
  // Columns: each clinician + a "Clinic" bucket for unassigned/team tasks
  const ownerIds = ['grace', 'emma', 'sofia', 'daniel'];
  const columns = ownerIds.map(id => ({ id, clinician: clinicianById(id) }));
  columns.push({ id: 'clinic', clinician: { name: 'Clinic', initials: 'NF', avatarTint: 'var(--cream)' } });

  const tasksByOwner = (oid) => tasks.filter(t => t.owner === oid).sort((a, b) =>
    (a.done === b.done ? 0 : a.done ? 1 : -1) || a.due.localeCompare(b.due)
  );

  const [draggingId, setDraggingId] = React.useState(null);
  const [dragOver, setDragOver]   = React.useState(null);

  const onDragStart = (e, id) => {
    setDraggingId(id);
    e.dataTransfer.setData('text/plain', id);
    e.dataTransfer.effectAllowed = 'move';
  };
  const onDropCol = (e, oid) => {
    e.preventDefault();
    const id = e.dataTransfer.getData('text/plain') || draggingId;
    if (id) onReassign && onReassign(id, oid);
    setDraggingId(null);
    setDragOver(null);
  };

  return (
    <div className="tasks-board">
      {columns.map(col => {
        const list = tasksByOwner(col.id);
        return (
          <div key={col.id}
            className={'tasks-col' + (dragOver === col.id ? ' drag-over' : '')}
            onDragOver={(e) => { e.preventDefault(); setDragOver(col.id); }}
            onDragLeave={() => setDragOver(null)}
            onDrop={(e) => onDropCol(e, col.id)}>
            <div className="tasks-col-head">
              <span className="avatar sm" style={{ background: col.clinician.avatarTint, color: col.clinician.avatarTint === '#6E7D6B' ? '#fff' : 'var(--ink)' }}>
                {col.clinician.initials}
              </span>
              <div className="tasks-col-name">
                <b>{col.clinician.name.split(' ')[0]}</b>
                <span>{list.length} {list.length === 1 ? 'task' : 'tasks'}</span>
              </div>
            </div>
            <div className="tasks-col-body">
              {list.length === 0 && <div className="tasks-col-empty">Drop a card here to assign.</div>}
              {list.map(t => {
                const cust = t.customerId ? customerById(t.customerId) : null;
                return (
                  <div key={t.id}
                    className={'tasks-card' + (t.done ? ' done' : '') + (t.priority === 'high' ? ' high' : '')}
                    draggable
                    onDragStart={(e) => onDragStart(e, t.id)}>
                    <div className="tasks-card-head">
                      <button className={'task-check sm' + (t.done ? ' on' : '')} onClick={() => onToggle(t.id)} aria-label="Toggle complete">
                        {t.done && <Icon name="check" size={10}/>}
                      </button>
                      <Icon name={kindIcon(t.kind)} size={11}/>
                      <span className="tasks-card-title">{t.title}</span>
                    </div>
                    <div className="tasks-card-meta">
                      <span className={overdue(t.due) && !t.done ? 'task-due-overdue' : ''}>
                        <Icon name="clock" size={9}/>{dueLabel(t.due)}
                      </span>
                      {t.priority === 'high' && <span className="tag warn" style={{ fontSize: 9, padding: '2px 6px' }}>high</span>}
                    </div>
                    {cust && (
                      <button className="tasks-card-link" onClick={() => onOpenRecord && onOpenRecord(cust.id)}>
                        <Icon name="user" size={9}/>{cust.name}
                      </button>
                    )}
                  </div>
                );
              })}
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ============================================================
// TasksPanel — daily running list (§14)
// Shared across team. Each task has owner, due date, customer link, priority,
// completion state. Filterable by mine/all and pending/done. List or Board view.
// ============================================================
function TasksPanel({ currentClinicianId = 'grace', compact = false, onOpenRecord }) {
  const [tasks, setTasks] = React.useState(TASKS);
  const [filter, setFilter] = React.useState('mine'); // 'mine' | 'all'
  const [showDone, setShowDone] = React.useState(false);
  const [adding, setAdding] = React.useState(false);
  // Q31: Emma prefers Trello-style. List view is the existing dense layout;
  // Board view is column-per-owner with drag/drop. Default to board for the
  // non-compact (full Tasks tab) view since that's what she's asked for.
  const [view, setView] = React.useState(compact ? 'list' : 'board');

  const visible = tasks.filter(t => {
    // The board is column-per-person, so it always shows the whole clinic —
    // otherwise a card dragged to a colleague vanishes behind the "Mine" filter.
    if (view !== 'board' && filter === 'mine' && t.owner !== currentClinicianId && t.owner !== 'clinic') return false;
    if (!showDone && t.done) return false;
    return true;
  });

  const overdue = (iso) => iso < todayISO;
  const dueLabel = (iso) => {
    if (iso === todayISO) return 'Today';
    if (iso < todayISO) return 'Overdue';
    return fmtDate(iso);
  };

  const toggle = (id) => setTasks(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t));
  const reassign = (id, newOwner) => setTasks(prev => prev.map(t => t.id === id ? { ...t, owner: newOwner } : t));

  const kindIcon = (k) => ({
    call: 'phone', msg: 'mail', admin: 'file', review: 'check',
    finance: 'pound', clinical: 'sparkle',
  })[k] || 'check';

  const addTask = (data) => {
    setTasks(prev => [{ id: 'tnew-' + Date.now(), done: false, owner: currentClinicianId, ...data }, ...prev]);
    setAdding(false);
  };

  return (
    <div className="panel tasks-panel">
      <div className="panel-head">
        <h3>{compact ? 'My tasks' : 'Daily running list'}</h3>
        <div className="tasks-controls">
          {!compact && (
            <>
              {view !== 'board' && (
              <div className="seg-toggle">
                <button className={filter === 'mine' ? 'active' : ''} onClick={() => setFilter('mine')}>Mine</button>
                <button className={filter === 'all' ? 'active' : ''} onClick={() => setFilter('all')}>All clinic</button>
              </div>
              )}
              <div className="seg-toggle">
                <button className={view === 'list' ? 'active' : ''} onClick={() => setView('list')}><Icon name="list" size={12}/>List</button>
                <button className={view === 'board' ? 'active' : ''} onClick={() => setView('board')}><Icon name="sparkle" size={12}/>Board</button>
              </div>
            </>
          )}
          <button className="app-btn ghost small" onClick={() => setShowDone(s => !s)}>
            <Icon name={showDone ? 'check' : 'list'} size={12}/>{showDone ? 'Hide done' : 'Show done'}
          </button>
          <button className="app-btn small primary" onClick={() => setAdding(true)}><Icon name="plus" size={12}/>New</button>
        </div>
      </div>

      {/* Board view — Trello-style columns by owner (Q31). Drag a task card
          to another column to reassign it. */}
      {view === 'board' && !compact && (
        <TasksBoard
          tasks={visible}
          onToggle={toggle}
          onReassign={reassign}
          onOpenRecord={onOpenRecord}
          kindIcon={kindIcon}
          dueLabel={dueLabel}
          overdue={overdue}
          currentClinicianId={currentClinicianId}
        />
      )}
      {(view === 'list' || compact) && (visible.length === 0 ? (
        <div className="empty"><Icon name="check" size={28}/><div>{tasks.filter(t => !t.done).length === 0 ? 'All caught up.' : 'Nothing for you today.'}</div></div>
      ) : visible.map(t => {
        const cust = t.customerId ? customerById(t.customerId) : null;
        const owner = t.owner === 'clinic' ? null : clinicianById(t.owner);
        return (
          <div key={t.id} className={'task-row' + (t.done ? ' done' : '')}>
            <button className={'task-check' + (t.done ? ' on' : '')} onClick={() => toggle(t.id)} aria-label="Toggle complete">
              {t.done && <Icon name="check" size={12}/>}
            </button>
            <div className="task-body">
              <div className="task-title">
                <Icon name={kindIcon(t.kind)} size={12}/>
                <span>{t.title}</span>
                {t.priority === 'high' && <span className="tag warn">high</span>}
              </div>
              <div className="task-meta">
                <span className={overdue(t.due) && !t.done ? 'task-due-overdue' : ''}>
                  <Icon name="clock" size={10}/>{dueLabel(t.due)}
                </span>
                {cust && (
                  <button className="task-link" onClick={() => onOpenRecord && onOpenRecord(cust.id)}>
                    <Icon name="user" size={10}/>{cust.name}
                  </button>
                )}
                <span className="task-owner">
                  {owner ? <><span className="avatar sm" style={{ background: owner.avatarTint, color: owner.avatarTint === '#6E7D6B' ? '#fff' : 'var(--ink)', width: 18, height: 18, fontSize: 9 }}>{owner.initials}</span>{owner.name.split(' ')[0]}</> : 'Clinic'}
                </span>
              </div>
            </div>
          </div>
        );
      }))}

      {adding && <NewTaskModal currentClinicianId={currentClinicianId} onClose={() => setAdding(false)} onAdd={addTask}/>}
    </div>
  );
}

function NewTaskModal({ currentClinicianId, onClose, onAdd }) {
  const [title, setTitle] = React.useState('');
  const [owner, setOwner] = React.useState(currentClinicianId);
  const [due, setDue] = React.useState(todayISO);
  const [kind, setKind] = React.useState('admin');
  const [priority, setPriority] = React.useState('normal');
  const [customerId, setCustomerId] = React.useState('');
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(480px, 92vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Daily list</span><h2>New task</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>
        <div className="field"><label>What needs doing?</label><input className="stripe-input" autoFocus value={title} onChange={e => setTitle(e.target.value)} placeholder="e.g. Call Hannah W to schedule patch test"/></div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
          <div className="field">
            <label>Assign to</label>
            <select className="stripe-input" value={owner} onChange={e => setOwner(e.target.value)}>
              <option value="clinic">Clinic (anyone)</option>
              {CLINICIANS.map(c => <option key={c.id} value={c.id}>{c.name.split(' ')[0]}</option>)}
            </select>
          </div>
          <div className="field"><label>Due</label><input className="stripe-input" type="date" value={due} onChange={e => setDue(e.target.value)}/></div>
        </div>
        <div className="field">
          <label>Type</label>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {[['call','Call'],['msg','Message'],['admin','Admin'],['review','Review'],['finance','Finance'],['clinical','Clinical']].map(([k, lbl]) => (
              <button key={k} className={'pose-chip' + (kind === k ? ' captured' : '')} onClick={() => setKind(k)}>{lbl}</button>
            ))}
          </div>
        </div>
        <div className="field">
          <label>Priority</label>
          <div style={{ display: 'flex', gap: 6 }}>
            {['low', 'normal', 'high'].map(p => (
              <button key={p} className={'pose-chip' + (priority === p ? ' captured' : '')} onClick={() => setPriority(p)}>{p}</button>
            ))}
          </div>
        </div>
        <div className="field">
          <label>Linked customer (optional)</label>
          <select className="stripe-input" value={customerId} onChange={e => setCustomerId(e.target.value)}>
            <option value="">— None —</option>
            {CUSTOMERS.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
          </select>
        </div>
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 }}>
          <button className="app-btn ghost" onClick={onClose}>Cancel</button>
          <button className="app-btn primary" disabled={!title} onClick={() => onAdd({ title, owner, due, kind, priority, customerId: customerId || null })}>
            <Icon name="plus" size={14}/>Add task
          </button>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// CheckInButton — inline check-in flow on appointment rows (§1.6)
// Cycles through statuses: confirmed → checked-in → in-treatment → completed.
// Each transition records a timestamp so we can show client flow timing
// ("checked in 09:05, started 09:12, completed 09:48").
// ============================================================
function fmtTimestamp(iso) {
  if (!iso) return '';
  const d = new Date(iso);
  return d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
}

function CheckInButton({ appt, onUpdate, compact = false }) {
  const status = appt.status || 'confirmed';
  const transitions = appt.transitions || {};
  // Pending bookings can't be checked in (need approval first)
  if (status === 'pending' || status === 'cancelled') {
    return <span className={'tag ' + (status === 'pending' ? 'warn' : 'ghost')}>{status}</span>;
  }
  const next = {
    confirmed:    { id: 'checkedIn',  label: compact ? 'In' : 'Check in',  iconName: 'check' },
    checkedIn:    { id: 'inTreat',    label: compact ? 'Start' : 'Start treatment', iconName: 'sparkle' },
    inTreat:      { id: 'completed',  label: compact ? 'Done' : 'Mark complete',    iconName: 'check' },
    completed:    null,
  }[status];

  const stateLabel = {
    confirmed:  'Booked',
    checkedIn:  'Checked in',
    inTreat:    'In treatment',
    completed:  'Completed',
  }[status];

  // Build the running timeline label e.g. "in 09:05 · start 09:12 · done 09:48"
  const timelineParts = [];
  if (transitions.checkedIn) timelineParts.push('in ' + fmtTimestamp(transitions.checkedIn));
  if (transitions.inTreat)   timelineParts.push('start ' + fmtTimestamp(transitions.inTreat));
  if (transitions.completed) timelineParts.push('done ' + fmtTimestamp(transitions.completed));

  const advance = (e) => {
    e.stopPropagation();
    if (!next) return;
    const now = new Date().toISOString();
    onUpdate && onUpdate({
      ...appt,
      status: next.id,
      transitions: { ...transitions, [next.id]: now },
    });
  };

  if (status === 'completed') {
    return (
      <div className="checkin-cluster">
        <span className="tag ok"><Icon name="check" size={10}/>{stateLabel}</span>
        {timelineParts.length > 0 && <span className="checkin-timeline">{timelineParts.join(' · ')}</span>}
      </div>
    );
  }

  return (
    <div className="checkin-cluster">
      <span className={'tag ' + (status === 'inTreat' ? 'blush' : status === 'checkedIn' ? 'ok' : 'ghost')}>{stateLabel}</span>
      {timelineParts.length > 0 && <span className="checkin-timeline">{timelineParts.join(' · ')}</span>}
      {next && (
        <button className="app-btn small primary" onClick={advance}>
          <Icon name={next.iconName} size={10}/>{next.label}
        </button>
      )}
    </div>
  );
}

// ============================================================
// NotificationBell + NotificationPanel (§11)
// Bell icon with badge for unread count; clicking opens the feed.
// ============================================================
function NotificationBell({ onOpen, count }) {
  return (
    <button className="notif-bell" onClick={onOpen} aria-label="Notifications">
      <Icon name="bell" size={16}/>
      {count > 0 && <span className="notif-dot">{count > 9 ? '9+' : count}</span>}
    </button>
  );
}

function NotificationPanel({ onClose, onAction }) {
  const [items, setItems] = React.useState(NOTIFICATIONS);
  const kindMeta = {
    message: { icon: 'mail',     color: 'var(--blush-deep)' },
    booking: { icon: 'calendar', color: 'var(--ink)' },
    form:    { icon: 'check',    color: 'var(--success)' },
    payment: { icon: 'pound',    color: 'var(--success)' },
    cancel:  { icon: 'close',    color: 'var(--alert)' },
    review:  { icon: 'sparkle',  color: 'var(--blush-deep)' },
    noshow:  { icon: 'info',     color: 'var(--alert)' },
  };
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="notif-panel" onClick={e => e.stopPropagation()}>
        <div className="notif-head">
          <div>
            <span className="eyebrow">Recent activity</span>
            <h2>Notifications</h2>
          </div>
          <div style={{ display: 'flex', gap: 6 }}>
            <button className="app-btn ghost small" onClick={() => setItems([])}>Mark all read</button>
            <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
          </div>
        </div>
        {items.length === 0 ? (
          <div className="empty"><Icon name="check" size={28}/><div>You're all caught up.</div></div>
        ) : items.map(n => {
          const meta = kindMeta[n.kind] || kindMeta.message;
          return (
            <div key={n.id} className="notif-row" onClick={() => { onAction && onAction(n); onClose(); }}>
              <span className="notif-icon" style={{ color: meta.color, background: 'var(--cream)' }}>
                <Icon name={meta.icon} size={14}/>
              </span>
              <div className="notif-body">
                <div className="notif-title">{n.title}</div>
                <div className="notif-text">{n.body}</div>
                <div className="notif-when">{n.when}</div>
              </div>
              <Icon name="chevR" size={14}/>
            </div>
          );
        })}
        <div className="notif-foot">
          <span style={{ fontSize: 11, color: 'var(--fg-3)' }}>Custom notification rules in <b>Settings → Workflows</b></span>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// Forms-required gate (§4) — small inline component used in
// pending bookings, customer record etc to indicate which forms
// are missing and offer a one-click "send" button.
// ============================================================
function FormsGate({ customerId, onSend, treatmentId = null }) {
  const cust = customerById(customerId);
  const formData = MEDICAL_FORMS[customerId];

  // Determine required forms from the FORMS catalogue (Q9 — every treatment
  // has its own bespoke medical form plus universal medical-history + photo
  // consent). When a treatmentId is supplied, also include that treatment's
  // form. Otherwise just check the universals.
  const universals  = (typeof FORMS !== 'undefined' ? FORMS : []).filter(f => f.universal);
  const treatForms  = treatmentId && typeof FORMS !== 'undefined'
    ? FORMS.filter(f => !f.universal && (f.treatmentIds || []).includes(treatmentId))
    : [];

  // Mock signed-state per customer (prototype): use existing MEDICAL_FORMS
  // presence + a couple of intentional gaps so we can demo the gate.
  const isSigned = (form) => {
    if (form.universal && form.id === 'form-medical-history') return !!formData;
    if (form.universal && form.id === 'form-photo-consent')
      return cust?.flag !== 'inactive' && customerId !== 'tom' && customerId !== 'priya';
    // Treatment-specific: signed for Sophie and Hannah by default
    return customerId === 'sophie' || customerId === 'hannah';
  };

  const required = [...universals, ...treatForms].map(f => ({
    id: f.id,
    name: f.title,
    signed: isSigned(f),
  }));
  const missing = required.filter(r => !r.signed);
  if (missing.length === 0) return (
    <span className="forms-gate ok"><Icon name="check" size={12}/>All forms signed</span>
  );
  return (
    <div className="forms-gate warn">
      <Icon name="info" size={12}/>
      <div className="fg-body">
        <b>{missing.length} form{missing.length > 1 ? 's' : ''} pending:</b>
        <span> {missing.map(m => m.name).join(', ')}</span>
      </div>
      <button className="app-btn small" onClick={(e) => { e.stopPropagation(); onSend && onSend(missing); }}>
        <Icon name="mail" size={12}/>Send now
      </button>
    </div>
  );
}

// ============================================================
// Reports center (§8) — multiple report tabs in one screen
// Practitioner / Treatment / Inactive / Follow-up / Last visit
// ============================================================
function OwReports({ onOpenRecord }) {
  const [tab, setTab] = React.useState('practitioner');
  const [period, setPeriod] = React.useState('30');
  const [showCompose, setShowCompose] = React.useState(null); // {customerId, channel}

  return (
    <>
      <div className="cl-head">
        <div><span className="eyebrow">Last {period} days</span><h1>Reports<DemoNote title="Reports" text="These run live off the diary and till — nothing to compile at month end. The finished system adds occupancy by staff and room, due-but-not-booked, booking sources, and consultations that didn’t convert."/></h1></div>
        <div className="actions">
          <select className="app-btn" style={{ padding: '8px 14px' }} value={period} onChange={e => setPeriod(e.target.value)}>
            <option value="7">Last 7 days</option>
            <option value="30">Last 30 days</option>
            <option value="90">Quarter to date</option>
            <option value="365">Year to date</option>
          </select>
          <button className="app-btn"><Icon name="upload" size={14}/>Export CSV</button>
        </div>
      </div>

      <div className="record-tabs reports-tabs" style={{ marginBottom: 24 }}>
        {[
          { id: 'practitioner', label: 'Staff' },
          { id: 'treatment',    label: 'Treatments' },
          { id: 'inactive',     label: 'Inactive' },
          { id: 'followup',     label: 'Follow-ups' },
          { id: 'lastvisit',    label: 'Last visit' },
          { id: 'reviews',      label: 'Reviews' },
          { id: 'custom',       label: 'Custom' },
        ].map(t => (
          <button key={t.id} className={tab === t.id ? 'active' : ''} onClick={() => setTab(t.id)}>{t.label}</button>
        ))}
      </div>

      {tab === 'practitioner' && <ReportPractitioner/>}
      {tab === 'treatment' && <ReportTreatment/>}
      {tab === 'inactive' && <ReportInactive onOpenRecord={onOpenRecord} onSendBulk={(ids) => { /* mock */ }} onMessage={(cid) => setShowCompose({ customerId: cid, channel: 'email' })}/>}
      {tab === 'followup' && <ReportFollowUp onOpenRecord={onOpenRecord}/>}
      {tab === 'lastvisit' && <ReportLastVisit onOpenRecord={onOpenRecord}/>}
      {tab === 'reviews' && <ReportReviews/>}
      {tab === 'custom' && <ReportCustom/>}

      {showCompose && <SendMessageModal customerId={showCompose.customerId} defaultChannel={showCompose.channel} defaultTemplate="reengagement" onClose={() => setShowCompose(null)}/>}
    </>
  );
}


function ReportReviews() {
  return (
    <>
      <div className="kpi-grid" style={{ marginBottom: 16 }}>
        <div className="kpi"><div className="lbl">Review requests sent</div><div className="val">41</div><div className="delta">this month · automatic</div></div>
        <div className="kpi"><div className="lbl">Opened</div><div className="val">33</div><div className="delta up">80% open rate</div></div>
        <div className="kpi"><div className="lbl">Marked as reviewed</div><div className="val">14</div><div className="delta up">ticked on the client’s record</div></div>
        <div className="kpi"><div className="lbl">Thank-yous sent</div><div className="val">14</div><div className="delta">nudges stop once ticked</div></div>
      </div>
      <div className="panel" style={{ padding: '18px 24px' }}>
        <div className="panel-head" style={{ padding: 0, marginBottom: 8 }}>
          <h3>How review collection works<DemoNote title="The tick" text="Review sites don’t reliably say which client left a review, so the client’s record carries a simple tick — one click when you spot a new review. The 48-hour follow-up then sends a thank-you instead of a nudge."/></h3>
        </div>
        <div style={{ fontSize: 13, color: 'var(--fg-2)', lineHeight: 1.6 }}>
          The aftercare email carries your Google and Trustpilot links. Two days after a first appointment, clients who haven’t been marked as reviewed get one gentle nudge — those who have get a thank-you. No client is asked twice.
        </div>
      </div>
    </>
  );
}

function ReportPractitioner() {
  const since = offsetDay(-30);
  const stats = CLINICIANS.map(c => {
    const appts = APPOINTMENTS.filter(a => a.clinicianId === c.id && a.date >= since && a.date <= todayISO && a.status !== 'cancelled');
    const revenue = appts.reduce((sum, a) => sum + (treatmentById(a.treatmentId)?.price || 0), 0);
    return {
      ...c,
      appts: appts.length,
      revenue,
      avgTicket: appts.length ? Math.round(revenue / appts.length) : 0,
      utilisation: 78 + Math.floor(Math.random() * 18),
      repeatRate: 60 + Math.floor(Math.random() * 30),
    };
  }).sort((a, b) => b.revenue - a.revenue);
  return (
    <>
      <div className="kpi-grid" style={{ marginBottom: 16 }}>
        <div className="kpi"><div className="lbl">Top biller</div><div className="val">{stats[0].name.split(' ')[0]}</div><div className="delta up">{fmtMoney(stats[0].revenue)}</div></div>
        <div className="kpi"><div className="lbl">Highest utilisation</div><div className="val">{stats.slice().sort((a,b) => b.utilisation - a.utilisation)[0].name.split(' ')[0]}</div><div className="delta">{stats.slice().sort((a,b) => b.utilisation - a.utilisation)[0].utilisation}%</div></div>
        <div className="kpi"><div className="lbl">Best repeat rate</div><div className="val">{stats.slice().sort((a,b) => b.repeatRate - a.repeatRate)[0].name.split(' ')[0]}</div><div className="delta">{stats.slice().sort((a,b) => b.repeatRate - a.repeatRate)[0].repeatRate}%</div></div>
        <div className="kpi"><div className="lbl">Total team revenue</div><div className="val">{fmtMoney(stats.reduce((s, x) => s + x.revenue, 0))}</div><div className="delta up">+8% vs prev</div></div>
      </div>
      <div className="panel" style={{ padding: 0 }}>
        <div className="tbl-scroll">
        <table className="tbl">
          <thead><tr><th>Practitioner</th><th>Appts</th><th>Revenue</th><th>Avg ticket</th><th>Utilisation</th><th>Repeat rate</th></tr></thead>
          <tbody>
            {stats.map(s => (
              <tr key={s.id}>
                <td><div className="cell-cust"><span className="avatar sm" style={{ background: s.avatarTint, color: s.avatarTint === '#6E7D6B' ? '#fff' : 'var(--ink)' }}>{s.initials}</span><b>{s.name}</b></div></td>
                <td>{s.appts}</td>
                <td>{fmtMoney(s.revenue)}</td>
                <td>{fmtMoney(s.avgTicket)}</td>
                <td>{s.utilisation}%</td>
                <td>{s.repeatRate}%</td>
              </tr>
            ))}
          </tbody>
        </table>
        </div>
      </div>
    </>
  );
}

function ReportTreatment() {
  const since = offsetDay(-30);
  const stats = TREATMENTS.map(t => {
    const appts = APPOINTMENTS.filter(a => a.treatmentId === t.id && a.date >= since && a.date <= todayISO && a.status !== 'cancelled');
    const count = appts.length;
    return {
      ...t,
      count,
      revenue: count * t.price,
      avgRebookDays: 30 + Math.floor(Math.random() * 60),
      coursePct: 40 + Math.floor(Math.random() * 50),
    };
  }).sort((a, b) => b.revenue - a.revenue);
  const totalRev = stats.reduce((s, x) => s + x.revenue, 0);
  return (
    <>
      <div className="kpi-grid" style={{ marginBottom: 16 }}>
        <div className="kpi"><div className="lbl">Top treatment</div><div className="val">{stats[0].name.split(' ')[0]}</div><div className="delta up">{fmtMoney(stats[0].revenue)}</div></div>
        <div className="kpi"><div className="lbl">Avg rebook (top)</div><div className="val">{stats[0].avgRebookDays}d</div><div className="delta">days between visits</div></div>
        <div className="kpi"><div className="lbl">Course completion</div><div className="val">{Math.round(stats.reduce((s, x) => s + x.coursePct, 0) / stats.length)}%</div><div className="delta up">+4 pts</div></div>
        <div className="kpi"><div className="lbl">Treatment revenue</div><div className="val">{fmtMoney(totalRev)}</div><div className="delta up">+12%</div></div>
      </div>
      <div className="panel">
        <div className="panel-head"><h3>Revenue by treatment</h3><span className="meta">last {30} days</span></div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {stats.map(t => (
            <div key={t.id}>
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, marginBottom: 6, gap: 10 }}>
                <span style={{ color: 'var(--fg-1)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.name}</span>
                <span style={{ color: 'var(--fg-2)', fontVariantNumeric: 'tabular-nums', flexShrink: 0 }}>
                  <span style={{ color: 'var(--fg-3)', marginRight: 8 }}>{t.count}×</span>{fmtMoney(t.revenue)}
                </span>
              </div>
              <div style={{ height: 4, background: 'var(--cream)', borderRadius: 2, overflow: 'hidden' }}>
                <div style={{ height: '100%', width: ((t.revenue / stats[0].revenue) * 100) + '%', background: 'var(--blush-deep)' }}/>
              </div>
            </div>
          ))}
        </div>
      </div>
    </>
  );
}

function ReportInactive({ onOpenRecord, onSendBulk, onMessage }) {
  const inactive = CUSTOMERS.filter(c => c.flag === 'inactive');
  const all = CUSTOMERS.filter(c => {
    const last = LAST_COMPLETED_VISIT[c.id];
    // Inactive cutoff: 12 months (365 days) per Emma (Q7)
    return c.flag === 'inactive' || (last && last.date < offsetDay(-365));
  });
  const list = inactive.length ? inactive : all;
  const [selected, setSelected] = React.useState(new Set());
  const toggle = (id) => setSelected(prev => { const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next; });
  const allChecked = selected.size === list.length && list.length > 0;
  return (
    <>
      <div className="kpi-grid" style={{ marginBottom: 16 }}>
        <div className="kpi"><div className="lbl">Inactive customers</div><div className="val">{list.length}</div><div className="delta down">need re-engaging</div></div>
        <div className="kpi"><div className="lbl">Avg lifetime spend</div><div className="val">£840</div><div className="delta">value at risk</div></div>
        <div className="kpi"><div className="lbl">Last contacted</div><div className="val">{list.length ? '32d ago' : '—'}</div><div className="delta">avg</div></div>
        <div className="kpi"><div className="lbl">Re-engaged this month</div><div className="val">3</div><div className="delta up">via campaign</div></div>
      </div>
      <div className="panel" style={{ padding: 0 }}>
        <div className="panel-head" style={{ padding: '20px 24px', marginBottom: 0 }}>
          <h3>Customers not seen in 9+ months</h3>
          <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
            <span className="meta">{selected.size} selected</span>
            <button className="app-btn small" disabled={selected.size === 0}><Icon name="upload" size={12}/>Export</button>
            <button className="app-btn small primary" disabled={selected.size === 0} onClick={() => onSendBulk && onSendBulk([...selected])}>
              <Icon name="mail" size={12}/>Send re-engage email
            </button>
          </div>
        </div>
        {list.length === 0 ? (
          <div className="empty"><Icon name="check" size={28}/><div>No inactive customers — everyone's recent.</div></div>
        ) : (
          <div className="tbl-scroll">
          <table className="tbl">
            <thead><tr>
              <th><input type="checkbox" checked={allChecked} onChange={e => setSelected(new Set(e.target.checked ? list.map(c => c.id) : []))}/></th>
              <th>Customer</th><th>Last visit</th><th>Visits</th><th>Concerns</th><th>Last contacted</th><th></th>
            </tr></thead>
            <tbody>
              {list.map(c => {
                const last = LAST_COMPLETED_VISIT[c.id];
                return (
                  <tr key={c.id}>
                    <td><input type="checkbox" checked={selected.has(c.id)} onChange={() => toggle(c.id)}/></td>
                    <td><div className="cell-cust"><span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{c.initials}</span><b style={{ cursor: 'pointer', textDecoration: 'underline' }} onClick={() => onOpenRecord && onOpenRecord(c.id)}>{c.name}</b></div></td>
                    <td>{last ? fmtDate(last.date) : '—'}</td>
                    <td>{Math.floor(Math.random() * 8) + 2}</td>
                    <td>{c.concerns.join(', ')}</td>
                    <td>32 days ago</td>
                    <td><button className="app-btn ghost small" onClick={() => onMessage && onMessage(c.id)}><Icon name="mail" size={12}/></button></td>
                  </tr>
                );
              })}
            </tbody>
          </table>
          </div>
        )}
      </div>
    </>
  );
}

function ReportFollowUp({ onOpenRecord }) {
  // Customers with followUp flag, plus those with course-treatment progress
  const list = CUSTOMERS.filter(c => c.flag === 'followUp');
  const courses = [
    { customerId: 'hannah', course: 'CO2 Laser · 3-session course', progress: '2 of 3 done', nextDue: offsetDay(28), urgent: false },
    { customerId: 'sophie', course: 'Microneedling · 3-session course', progress: 'Complete', nextDue: null, urgent: false },
    { customerId: 'priya',  course: 'Tretinoin patch test review',   progress: 'Awaiting result', nextDue: offsetDay(1),  urgent: true  },
  ];
  return (
    <>
      <div className="kpi-grid" style={{ marginBottom: 16 }}>
        <div className="kpi"><div className="lbl">Awaiting action</div><div className="val">{list.length + courses.filter(c => c.urgent).length}</div><div className="delta down">customers</div></div>
        <div className="kpi"><div className="lbl">Active courses</div><div className="val">{courses.filter(c => c.nextDue).length}</div><div className="delta">in progress</div></div>
        <div className="kpi"><div className="lbl">Patch tests due</div><div className="val">2</div><div className="delta">this week</div></div>
        <div className="kpi"><div className="lbl">Review appts due</div><div className="val">5</div><div className="delta">post-treatment</div></div>
      </div>
      <div className="panel" style={{ marginBottom: 16 }}>
        <div className="panel-head"><h3>Action needed</h3></div>
        {list.map(c => (
          <div key={c.id} className="appt-row" onClick={() => onOpenRecord && onOpenRecord(c.id)}>
            <span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{c.initials}</span>
            <div className="appt-meta">
              <div className="who"><span>{c.name}</span></div>
              <div className="what"><span>{c.alert || 'Follow-up needed'}</span></div>
            </div>
            <span className="tag blush">follow-up</span>
          </div>
        ))}
      </div>
      <div className="panel">
        <div className="panel-head"><h3>Course progress</h3><span className="meta">multi-session treatments</span></div>
        {courses.map((c, i) => {
          const cust = customerById(c.customerId);
          return (
            <div key={i} className="appt-row" onClick={() => onOpenRecord && onOpenRecord(c.customerId)}>
              <span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{cust.initials}</span>
              <div className="appt-meta">
                <div className="who"><span>{cust.name}</span><span style={{ fontSize: 12, color: 'var(--fg-3)' }}>· {c.course}</span></div>
                <div className="what"><span>{c.progress}</span>{c.nextDue && <><span className="dot"/><span>Next: {fmtDate(c.nextDue)}</span></>}</div>
              </div>
              {c.urgent && <span className="tag warn">urgent</span>}
            </div>
          );
        })}
      </div>
    </>
  );
}

function ReportLastVisit({ onOpenRecord }) {
  const rows = CUSTOMERS.map(c => {
    const last = LAST_COMPLETED_VISIT[c.id];
    const lastDate = last ? last.date : null;
    const daysAgo = lastDate ? Math.floor((Date.now() - new Date(lastDate).getTime()) / 86400000) : null;
    return { ...c, lastDate, daysAgo, lastTreatment: last ? treatmentById(last.treatmentId)?.name : null };
  }).sort((a, b) => (b.lastDate || '').localeCompare(a.lastDate || ''));
  const bucket = (d) => d == null ? 'never' : d <= 30 ? 'recent' : d <= 90 ? 'normal' : d <= 180 ? 'lapsing' : 'inactive';
  const tone = { recent: 'ok', normal: 'blush', lapsing: 'warn', inactive: 'warn', never: 'ghost' };
  return (
    <>
      <div className="panel" style={{ padding: 0 }}>
        <div className="panel-head" style={{ padding: '20px 24px', marginBottom: 0 }}>
          <h3>Last visit by customer</h3>
          <span className="meta">sorted most recent first</span>
        </div>
        <div className="tbl-scroll">
        <table className="tbl">
          <thead><tr><th>Customer</th><th>Last visit</th><th>Days ago</th><th>Last treatment</th><th>Status</th></tr></thead>
          <tbody>
            {rows.map(c => {
              const b = bucket(c.daysAgo);
              return (
                <tr key={c.id} onClick={() => onOpenRecord && onOpenRecord(c.id)}>
                  <td><div className="cell-cust"><span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{c.initials}</span><b>{c.name}</b></div></td>
                  <td>{c.lastDate ? fmtDate(c.lastDate) : '—'}</td>
                  <td>{c.daysAgo ?? '—'}</td>
                  <td>{c.lastTreatment || '—'}</td>
                  <td><span className={'tag ' + tone[b]}>{b}</span></td>
                </tr>
              );
            })}
          </tbody>
        </table>
        </div>
      </div>
    </>
  );
}

function ReportCustom() {
  const [metric, setMetric] = React.useState('revenue');
  const [groupBy, setGroupBy] = React.useState('treatment');
  const [period, setPeriod] = React.useState('30');
  const [filter, setFilter] = React.useState('');
  return (
    <>
      <div className="panel" style={{ marginBottom: 16 }}>
        <div className="panel-head"><h3>Build a report</h3><span className="meta">save to share or schedule</span></div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 12 }}>
          <div className="field">
            <label>Metric</label>
            <select className="stripe-input" value={metric} onChange={e => setMetric(e.target.value)}>
              <option value="revenue">Revenue</option>
              <option value="appts">Appointments</option>
              <option value="newcust">New customers</option>
              <option value="reviews">Reviews</option>
              <option value="utilisation">Utilisation %</option>
              <option value="rebook">Rebook rate</option>
              <option value="noshow">No-shows</option>
            </select>
          </div>
          <div className="field">
            <label>Grouped by</label>
            <select className="stripe-input" value={groupBy} onChange={e => setGroupBy(e.target.value)}>
              <option value="treatment">Treatment</option>
              <option value="practitioner">Practitioner</option>
              <option value="day">Day of week</option>
              <option value="hour">Hour of day</option>
              <option value="week">Week</option>
              <option value="month">Month</option>
            </select>
          </div>
          <div className="field">
            <label>Period</label>
            <select className="stripe-input" value={period} onChange={e => setPeriod(e.target.value)}>
              <option value="7">Last 7 days</option>
              <option value="30">Last 30 days</option>
              <option value="90">Last quarter</option>
              <option value="365">Last year</option>
              <option value="all">All time</option>
            </select>
          </div>
          <div className="field">
            <label>Filter (optional)</label>
            <input className="stripe-input" placeholder="e.g. clinician=Emma" value={filter} onChange={e => setFilter(e.target.value)}/>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 12, flexWrap: 'wrap' }}>
          <button className="app-btn ghost"><Icon name="file" size={14}/>Save report</button>
          <button className="app-btn"><Icon name="bell" size={14}/>Schedule weekly</button>
          <button className="app-btn primary"><Icon name="upload" size={14}/>Export CSV</button>
        </div>
      </div>
      <div className="panel">
        <div className="panel-head"><h3>{metric.charAt(0).toUpperCase() + metric.slice(1)} by {groupBy}</h3><span className="meta">last {period === 'all' ? 'all time' : period + ' days'}</span></div>
        <div className="bar-chart" style={{ height: 200 }}>
          {Array.from({ length: 14 }).map((_, i) => {
            const seed = [60, 80, 70, 90, 85, 95, 100, 88, 82, 92, 75, 70, 65, 78][i];
            return <div key={i} className="bar"><span className="b1" style={{ height: seed + '%' }}/></div>;
          })}
        </div>
      </div>
    </>
  );
}

// ============================================================
// Templates editor (§7.3) + Workflows editor (§7.5)
// Both render inside SettingsModal as new tabs.
// ============================================================
function TemplatesEditor() {
  const [tmpls, setTmpls] = React.useState(COMM_TEMPLATES);
  const [editing, setEditing] = React.useState(null);
  const update = (id, patch) => setTmpls(prev => prev.map(t => t.id === id ? { ...t, ...patch } : t));
  const remove = (id) => setTmpls(prev => prev.filter(t => t.id !== id));
  const addNew = () => {
    const t = { id: 'new-' + Date.now(), name: 'Untitled template', channel: 'email', from: 'team', subject: '', body: 'Hi {{first}},\n\n…' };
    setTmpls(prev => [t, ...prev]);
    setEditing(t.id);
  };
  if (editing) {
    const t = tmpls.find(x => x.id === editing);
    if (!t) { setEditing(null); return null; }
    return (
      <>
        <button className="app-btn ghost small" onClick={() => setEditing(null)}><Icon name="back" size={12}/>Back to templates</button>
        <h3 style={{ margin: '12px 0 16px', fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 20 }}>Edit template</h3>
        <div className="field"><label>Name</label><input className="stripe-input" value={t.name} onChange={e => update(t.id, { name: e.target.value })}/></div>

        {t.channel === 'email' && (
          <>
            <div className="field">
              <label>From</label>
              <div className="from-toggle">
                <button className={(t.from || 'team') === 'team' ? 'active' : ''} onClick={() => update(t.id, { from: 'team' })}>The Team at Demo Clinic</button>
                <button className={t.from === 'clinician' ? 'active' : ''} onClick={() => update(t.id, { from: 'clinician' })}>The treating clinician (e.g. Grace at Demo Clinic)</button>
              </div>
              <div style={{ fontSize: 11, color: 'var(--fg-3)', marginTop: 4 }}>
                Generic automations stay as “The Team” · personal replies and consultation emails go from the specialist.
              </div>
            </div>
            <div className="field"><label>Subject</label><input className="stripe-input" value={t.subject} onChange={e => update(t.id, { subject: e.target.value })}/></div>
          </>
        )}
        <div className="field">
          <label>Body</label>
          <textarea className="stripe-input" rows="9" value={t.body} onChange={e => update(t.id, { body: e.target.value })} style={{ fontFamily: 'inherit', resize: 'vertical' }}/>
          <div style={{ fontSize: 11, color: 'var(--fg-3)', marginTop: 4 }}>
            Variables: <code>{'{{first}}'}</code> <code>{'{{treatment}}'}</code> <code>{'{{clinician}}'}</code> <code>{'{{date}}'}</code> <code>{'{{time}}'}</code> <code>{'{{slot}}'}</code> <code>{'{{form}}'}</code>
          </div>
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginTop: 16 }}>
          <button className="app-btn" style={{ background: 'var(--alert)', color: '#fff', borderColor: 'var(--alert)' }} onClick={() => { remove(t.id); setEditing(null); }}>Delete template</button>
          <button className="app-btn primary" onClick={() => setEditing(null)}>Done</button>
        </div>
      </>
    );
  }
  return (
    <>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 12 }}>
        <h3 style={{ margin: 0, fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 20 }}>Email templates<DemoNote title="Your wording, always" text="Every automated message is a template you can edit — subject, wording, sign-off, sender. Nothing goes to a client in words you didn't approve."/></h3>
        <button className="app-btn small primary" onClick={addNew}><Icon name="plus" size={12}/>New template</button>
      </div>
      {tmpls.map(t => (
        <div key={t.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 12, border: '1px solid var(--line)', borderRadius: 'var(--radius-md)', marginBottom: 8, cursor: 'pointer' }}
          onClick={() => setEditing(t.id)}>
          <span className="avatar sm" style={{ background: t.channel === 'email' ? 'var(--blush-soft)' : 'var(--cream)' }}>
            <Icon name={t.channel === 'email' ? 'mail' : 'phone'} size={12}/>
          </span>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 14, color: 'var(--fg-1)', fontWeight: 500 }}>{t.name}</div>
            <div style={{ fontSize: 12, color: 'var(--fg-3)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.subject || t.body}</div>
          </div>
          {t.channel === 'email' && (
            <span className="tag ghost" style={{ fontSize: 10 }}>
              from: {t.from === 'clinician' ? 'clinician' : 'team'}
            </span>
          )}
          <span className="tag">{t.channel}</span>
          <Icon name="chevR" size={14}/>
        </div>
      ))}
    </>
  );
}

function WorkflowsEditor() {
  const [items, setItems] = React.useState(WORKFLOWS);
  const [adding, setAdding] = React.useState(false);
  const toggle = (id) => setItems(prev => prev.map(w => w.id === id ? { ...w, active: !w.active } : w));
  const addRule = (rule) => { setItems(prev => [rule, ...prev]); setAdding(false); };
  const triggerLabel = {
    on_booking:        'On booking confirmed',
    before_appt:       'Before appointment',
    after_appt:        'After appointment',
    after_first:       'After first appointment',
    after_patch:       'After patch test',
    forms_pending:     'Forms pending',
    inactive_days:     'Customer inactive',
    no_show:           'No-show',
    birthday:          'Customer birthday',
    course_done:       'Course completed',
    treatment_followup:'Per-treatment follow-up',
    enquiry_received:  'New enquiry received',
    enquiry_no_reply:  'Enquiry · no reply',
    form_submitted:    'Assessment form submitted',
    advice_sent:       'Personalised advice sent',
  };
  const offsetLabel = (h) => {
    const abs = Math.abs(h);
    if (abs < 24) return abs + 'h';
    if (abs < 168) return Math.round(abs / 24) + ' days';
    if (abs < 720) return Math.round(abs / 168) + ' weeks';
    return Math.round(abs / 720) + ' months';
  };
  return (
    <>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
        <h3 style={{ margin: 0, fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 20 }}>Communication workflows<DemoNote title="Fully customisable" text="Every rule can be switched off, retimed, reworded or scoped to particular treatments — and new rules can be added for how your clinic actually works. The cadence you see here is just a starting point."/></h3>
        <button className="app-btn small primary" onClick={() => setAdding(true)}><Icon name="plus" size={12}/>New rule</button>
      </div>
      <p style={{ fontSize: 13, color: 'var(--fg-3)', marginBottom: 16, lineHeight: 1.5 }}>
        Rules that send messages automatically. Each rule fires a template at a specific point relative to an appointment, form, or inactivity threshold.
      </p>
      {items.map(w => {
        const tmpl = COMM_TEMPLATES.find(t => t.id === w.templateId);
        return (
          <div key={w.id} className="workflow-row">
            <button className="task-check on" style={{ background: w.active ? 'var(--success)' : 'var(--cream)', borderColor: w.active ? 'var(--success)' : 'var(--line)', color: w.active ? '#fff' : 'transparent' }} onClick={() => toggle(w.id)}>
              {w.active && <Icon name="check" size={12}/>}
            </button>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 14, fontWeight: 500, color: 'var(--fg-1)', display: 'flex', alignItems: 'center', gap: 8 }}>
                <span>{w.name}</span>
                {!w.active && <span className="tag ghost">paused</span>}
              </div>
              <div style={{ fontSize: 12, color: 'var(--fg-3)', marginTop: 4, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                <span>{triggerLabel[w.trigger]}{w.offsetHours ? ' · ' + (w.offsetHours < 0 ? '-' : '+') + offsetLabel(w.offsetHours) : ''}</span>
                <span>·</span>
                <span>uses <b style={{ color: 'var(--fg-2)' }}>{tmpl ? tmpl.name : w.templateId}</b></span>
                <span>·</span>
                <span>{w.fired} fired</span>
                {w.conditions && <><span>·</span><span><b>where</b> {w.conditions}</span></>}
              </div>
            </div>
            <button className="app-btn ghost small"><Icon name="edit" size={12}/></button>
          </div>
        );
      })}
      {adding && <NewWorkflowModal onClose={() => setAdding(false)} onAdd={addRule}/>}
    </>
  );
}

// ============================================================
// UpcomingTreatments — small section on customer record showing
// the next 1–3 booked appointments (§5.2b)
// ============================================================
function UpcomingTreatments({ customerId, onOpenAppt }) {
  const upcoming = APPOINTMENTS
    .filter(a => a.customerId === customerId && a.date >= todayISO && a.status !== 'cancelled')
    .sort((a, b) => a.date.localeCompare(b.date) || a.time.localeCompare(b.time))
    .slice(0, 3);
  if (upcoming.length === 0) return null;
  return (
    <div className="upcoming-treatments">
      <div className="ut-head">
        <Icon name="calendar" size={12}/>
        <span>Coming up</span>
      </div>
      <div className="ut-list">
        {upcoming.map(a => {
          const t = treatmentById(a.treatmentId);
          const cl = clinicianById(a.clinicianId);
          return (
            <div key={a.id} className="ut-row" onClick={() => onOpenAppt && onOpenAppt(a)}>
              <span className="ut-when">{fmtDate(a.date)}<small>{a.time}</small></span>
              <span className="ut-treat">{t.name}</span>
              <span className="ut-with">{cl.name.split(' ')[0]}</span>
              {a.note && <span className="ut-note" title={a.note}><Icon name="info" size={10}/></span>}
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ============================================================
// EndOfDayPanel — close-day reconciliation (§9.2)
// Today's totals by payment method, transactions count, "Close day" CTA
// ============================================================
function EndOfDayPanel({ onClose }) {
  // Mock today's payments (in real app this would aggregate from CashUp results)
  const payments = [
    { method: 'card',  label: 'Card terminal',  count: 4, total: 1495 },
    { method: 'other', label: 'Card (Stripe link)', count: 2, total: 295 },
    { method: 'cash',  label: 'Cash',           count: 1, total: 145 },
    { method: 'bank',  label: 'Bank transfer',  count: 1, total: 650 },
  ];
  const grandTotal = payments.reduce((s, p) => s + p.total, 0);
  const grandCount = payments.reduce((s, p) => s + p.count, 0);
  const expected = 2585;
  const variance = grandTotal - expected;
  const [closing, setClosing] = React.useState(false);
  const [closed, setClosed] = React.useState(false);
  const close = () => { setClosing(true); setTimeout(() => { setClosing(false); setClosed(true); }, 800); };
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(560px, 94vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">{new Date().toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' })}</span><h2>Close day</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>

        {closed ? (
          <>
            <div className="confirm-mark"><Icon name="check" size={32}/></div>
            <p style={{ textAlign: 'center', fontSize: 14, color: 'var(--fg-1)', margin: '0 0 4px' }}>Day closed</p>
            <p style={{ textAlign: 'center', fontSize: 12, color: 'var(--fg-3)', margin: '0 0 20px' }}>
              {fmtMoney(grandTotal)} reconciled · {grandCount} transactions · variance {variance === 0 ? 'nil' : (variance > 0 ? '+' : '') + fmtMoney(variance)}
            </p>
            <div style={{ display: 'flex', justifyContent: 'center' }}>
              <button className="app-btn primary" onClick={onClose}>Done</button>
            </div>
          </>
        ) : (
          <>
            <div className="cashup-summary" style={{ marginTop: 8 }}>
              <div className="line muted" style={{ textTransform: 'uppercase', letterSpacing: '0.08em', fontSize: 10 }}><span>Method</span><span>Count · Total</span></div>
              {payments.map(p => (
                <div key={p.method} className="line">
                  <span>{p.label}</span>
                  <span>{p.count} · {fmtMoney(p.total)}</span>
                </div>
              ))}
              <div className="line tot"><span>Total taken today</span><span>{fmtMoney(grandTotal)}</span></div>
              <div className="line muted"><span>Expected (from diary)</span><span>{fmtMoney(expected)}</span></div>
              <div className={'line tot ' + (variance === 0 ? 'change' : variance > 0 ? '' : 'warn')}>
                <span>Variance</span>
                <span>{variance === 0 ? 'Reconciled' : (variance > 0 ? '+' : '') + fmtMoney(variance)}</span>
              </div>
            </div>

            <div style={{ padding: 12, background: 'var(--cream)', borderRadius: 4, fontSize: 12, color: 'var(--fg-2)', lineHeight: 1.5, display: 'flex', gap: 10, alignItems: 'flex-start', marginTop: 8 }}>
              <Icon name="info" size={14}/>
              <span>Closing the day locks today's takings and keeps the daily summary on record for your accountant.</span>
            </div>

            <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, marginTop: 16 }}>
              <button className="app-btn"><Icon name="upload" size={14}/>Print summary</button>
              <button className="app-btn primary" disabled={closing} onClick={close}>
                {closing ? 'Closing day…' : 'Close day & reconcile'}<Icon name="check" size={14}/>
              </button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ============================================================
// NewWorkflowModal — create a custom workflow rule (§11.4)
// ============================================================
function NewWorkflowModal({ onClose, onAdd }) {
  const [name, setName] = React.useState('');
  const [trigger, setTrigger] = React.useState('before_appt');
  const [offsetValue, setOffsetValue] = React.useState(24);
  const [offsetUnit, setOffsetUnit] = React.useState('h');
  const [templateId, setTemplateId] = React.useState(COMM_TEMPLATES[0]?.id || '');
  const [conditions, setConditions] = React.useState('');

  const triggerOptions = [
    { id: 'on_booking',         label: 'On booking confirmed',     sign:  1 },
    { id: 'before_appt',        label: 'Before appointment',       sign: -1 },
    { id: 'after_appt',         label: 'After appointment',        sign:  1 },
    { id: 'after_first',        label: 'After first appointment',  sign:  1 },
    { id: 'after_patch',        label: 'After patch test',         sign:  1 },
    { id: 'forms_pending',      label: 'Forms pending',            sign:  1 },
    { id: 'inactive_days',      label: 'Customer inactive',        sign:  1 },
    { id: 'no_show',            label: 'No-show recorded',         sign:  1 },
    { id: 'birthday',           label: 'Customer birthday',        sign:  1 },
    { id: 'course_done',        label: 'Course completed',         sign:  1 },
    { id: 'treatment_followup', label: 'Per-treatment follow-up',  sign:  1 },
    { id: 'enquiry_received',   label: 'New enquiry received',     sign:  1 },
    { id: 'enquiry_no_reply',   label: 'Enquiry · no reply',       sign:  1 },
    { id: 'form_submitted',     label: 'Assessment form submitted',sign:  1 },
    { id: 'advice_sent',        label: 'Personalised advice sent', sign:  1 },
    { id: 'review_request',     label: 'Review request',           sign:  1 },
  ];
  const trig = triggerOptions.find(t => t.id === trigger);
  const offsetHours = trig.sign * offsetValue * (offsetUnit === 'd' ? 24 : offsetUnit === 'w' ? 168 : 1);

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(540px, 94vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Automation</span><h2>New workflow rule</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>
        <div className="field"><label>Name</label><input className="stripe-input" autoFocus value={name} onChange={e => setName(e.target.value)} placeholder="e.g. Day-of-treatment SPF reminder"/></div>

        <div className="field">
          <label>When</label>
          <select className="stripe-input" value={trigger} onChange={e => setTrigger(e.target.value)}>
            {triggerOptions.map(t => <option key={t.id} value={t.id}>{t.label}</option>)}
          </select>
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
          <div className="field">
            <label>How long {trig.sign < 0 ? 'before' : 'after'}</label>
            <input className="stripe-input" type="number" min="0" value={offsetValue} onChange={e => setOffsetValue(Number(e.target.value || 0))}/>
          </div>
          <div className="field">
            <label>Unit</label>
            <select className="stripe-input" value={offsetUnit} onChange={e => setOffsetUnit(e.target.value)}>
              <option value="h">Hours</option>
              <option value="d">Days</option>
              <option value="w">Weeks</option>
            </select>
          </div>
        </div>

        <div className="field">
          <label>Send template</label>
          <select className="stripe-input" value={templateId} onChange={e => setTemplateId(e.target.value)}>
            {COMM_TEMPLATES.map(t => <option key={t.id} value={t.id}>{t.name} ({t.channel})</option>)}
          </select>
        </div>

        <div className="field">
          <label>Conditions (optional)</label>
          <input className="stripe-input" placeholder="e.g. treatment in: CO2 Laser, HIFU" value={conditions} onChange={e => setConditions(e.target.value)}/>
          <div style={{ fontSize: 11, color: 'var(--fg-3)', marginTop: 4 }}>
            Limit which appointments/customers this rule fires for. Leave blank for all.
          </div>
        </div>

        <div style={{ padding: 12, background: 'var(--cream)', borderRadius: 4, fontSize: 12, color: 'var(--fg-2)', lineHeight: 1.5, marginTop: 8 }}>
          <Icon name="info" size={12}/> <b>Preview</b>: send <b>{COMM_TEMPLATES.find(t => t.id === templateId)?.name}</b> {trig.sign < 0 ? offsetValue + (offsetUnit === 'h' ? ' hours' : offsetUnit === 'd' ? ' days' : ' weeks') + ' before' : offsetValue + (offsetUnit === 'h' ? ' hours' : offsetUnit === 'd' ? ' days' : ' weeks') + ' after'} <b>{trig.label.toLowerCase()}</b>{conditions ? ', when ' + conditions : ''}.
        </div>

        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 }}>
          <button className="app-btn ghost" onClick={onClose}>Cancel</button>
          <button className="app-btn primary" disabled={!name || !templateId} onClick={() => onAdd && onAdd({
            id: 'wf-' + Date.now(), name, trigger, offsetHours, templateId, active: true, fired: 0, conditions,
          })}><Icon name="plus" size={14}/>Create rule</button>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// ComparePhotosModal — drag-to-reveal slider between two photos at the same pose
// ============================================================
function ComparePhotosModal({ customerId, photos, onClose }) {
  const cust = customerById(customerId);
  // Group by pose, only poses with ≥2 photos are comparable
  const byPose = {};
  photos.forEach(p => {
    const pose = p.pose || p.label || '—';
    (byPose[pose] = byPose[pose] || []).push(p);
  });
  const comparablePoses = Object.keys(byPose).filter(k => byPose[k].length >= 2);
  const [pose, setPose] = React.useState(comparablePoses[0] || null);
  const [pos, setPos] = React.useState(50); // 0-100
  const draggingRef = React.useRef(false);
  const stageRef = React.useRef(null);

  const onPointerDown = (e) => {
    draggingRef.current = true;
    stageRef.current?.setPointerCapture?.(e.pointerId);
    movePos(e);
  };
  const onPointerMove = (e) => { if (draggingRef.current) movePos(e); };
  const onPointerUp = () => { draggingRef.current = false; };
  const movePos = (e) => {
    if (!stageRef.current) return;
    const rect = stageRef.current.getBoundingClientRect();
    const x = (e.clientX - rect.left) / rect.width * 100;
    setPos(Math.max(0, Math.min(100, x)));
  };

  const list = pose ? byPose[pose] : [];
  const newer = list[0];
  const older = list[list.length - 1];

  const renderPhoto = (p, className) => p?.dataUrl
    ? <img src={p.dataUrl} className={className} alt=""/>
    : <div className={className + ' ph ' + (p?.tone || 'ph-portrait')}/>;

  const fmtWhen = (p) => p?.when || (p?.ts ? new Date(p.ts).toLocaleDateString('en-GB', { day:'numeric', month:'short' }) : '');

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(900px, 96vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Compare · {cust.name}</span><h2>Side-by-side</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>

        {comparablePoses.length === 0 ? (
          <div className="empty"><Icon name="photo" size={28}/><div>Need at least two photos at the same pose to compare.</div></div>
        ) : (
          <>
            <div style={{ display: 'flex', gap: 6, marginBottom: 16, flexWrap: 'wrap' }}>
              {comparablePoses.map(p => (
                <button key={p} className={'tag ' + (p === pose ? '' : 'ghost')} onClick={() => setPose(p)} style={{ cursor: 'pointer' }}>{p}</button>
              ))}
            </div>

            <div ref={stageRef} className="compare-slider"
              onPointerDown={onPointerDown} onPointerMove={onPointerMove} onPointerUp={onPointerUp} onPointerCancel={onPointerUp}>
              <div className="layer-old">{renderPhoto(older, 'compare-img-fill')}</div>
              <div className="layer-new" style={{ clipPath: `inset(0 ${100 - pos}% 0 0)` }}>{renderPhoto(newer, 'compare-img-fill')}</div>
              <div className="compare-handle" style={{ left: pos + '%' }}/>
              <div className="compare-label tl">After · {fmtWhen(newer)}</div>
              <div className="compare-label tr">Before · {fmtWhen(older)}</div>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ============================================================
// PhotoAnnotateModal — drop numbered pins on a photo and add notes
// ============================================================
function PhotoAnnotateModal({ photo, onSave, onClose }) {
  const isRuntime = photo?.id?.startsWith('rt-');
  const [pins, setPins] = React.useState(photo?.pins || []);
  const [selectedId, setSelectedId] = React.useState(null);
  const stageRef = React.useRef(null);

  const handleStageClick = (e) => {
    if (!isRuntime) return;
    if (e.target.closest('.photo-pin')) return;
    if (!stageRef.current) return;
    const rect = stageRef.current.getBoundingClientRect();
    const x = (e.clientX - rect.left) / rect.width;
    const y = (e.clientY - rect.top) / rect.height;
    const id = 'pin-' + Date.now();
    setPins(prev => [...prev, { id, x, y, note: '' }]);
    setSelectedId(id);
  };

  const updateNote = (id, note) => setPins(prev => prev.map(p => p.id === id ? { ...p, note } : p));
  const deletePin = (id) => { setPins(prev => prev.filter(p => p.id !== id)); setSelectedId(null); };

  const save = () => { onSave && onSave(pins); onClose(); };

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(720px, 96vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Annotate photo</span><h2>{photo?.pose || photo?.label || 'Photo'}</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>

        {!isRuntime && (
          <div style={{ fontSize: 12, color: 'var(--fg-3)', marginBottom: 12 }}>Annotations are only available on captured photos.</div>
        )}

        <div ref={stageRef} className="annotate-stage" onClick={handleStageClick}>
          {photo?.dataUrl
            ? <img src={photo.dataUrl} className="annotate-img" alt=""/>
            : <div className={'annotate-img ph ' + (photo?.tone || 'ph-portrait')}/>}
          {pins.map((p, i) => (
            <button key={p.id}
              className={'photo-pin' + (selectedId === p.id ? ' selected' : '')}
              style={{ left: (p.x * 100) + '%', top: (p.y * 100) + '%' }}
              onClick={(e) => { e.stopPropagation(); setSelectedId(p.id); }}>
              {i + 1}
            </button>
          ))}
        </div>

        {selectedId && (() => {
          const sel = pins.find(p => p.id === selectedId);
          if (!sel) return null;
          return (
            <div style={{ marginTop: 12, display: 'flex', gap: 8, alignItems: 'center' }}>
              <input className="stripe-input" placeholder="Note for this pin (e.g. track this lesion)" value={sel.note}
                onChange={e => updateNote(selectedId, e.target.value)} autoFocus/>
              <button className="app-btn ghost" onClick={() => deletePin(selectedId)}>Delete pin</button>
            </div>
          );
        })()}

        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 20 }}>
          <button className="app-btn ghost" onClick={onClose}>Cancel</button>
          <button className="app-btn primary" disabled={!isRuntime} onClick={save}>Save & close</button>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// PhoneHandoffModal — fake QR code for "scan with iPhone, capture lands here"
// ============================================================
function PhoneHandoffModal({ customerId, onClose }) {
  const cust = customerById(customerId);
  // Deterministic 10x10 dot grid based on customer id
  const seed = (customerId || '').split('').reduce((a, c) => a + c.charCodeAt(0), 0);
  const cells = [];
  for (let i = 0; i < 100; i++) {
    cells.push(((seed * 9301 + i * 49297) % 233280) % 2 === 0);
  }
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width: 'min(440px, 96vw)' }} onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div><span className="eyebrow">Phone handoff · {cust.name}</span><h2>Capture from iPhone</h2></div>
          <button className="app-btn ghost icon-only" onClick={onClose}><Icon name="close" size={16}/></button>
        </div>

        <div style={{ display: 'flex', justifyContent: 'center', marginBottom: 20 }}>
          <div className="qr-mock">
            {cells.map((on, i) => <div key={i} className={on ? 'on' : 'off'}/>)}
          </div>
        </div>

        <div style={{ textAlign: 'center', fontSize: 14, color: 'var(--fg-1)', marginBottom: 8 }}>
          Scan with your iPhone camera.
        </div>
        <div style={{ textAlign: 'center', fontSize: 12, color: 'var(--fg-3)', marginBottom: 16 }}>
          Snap the photo. It lands in this session automatically.
        </div>
        <div style={{ textAlign: 'center', fontSize: 11, color: 'var(--fg-3)', letterSpacing: '0.06em' }}>
          Or send a link: <span style={{ color: 'var(--blush-deep)' }}>clinic.app/c/{customerId}/capture</span>
        </div>

        <div style={{ display: 'flex', justifyContent: 'center', marginTop: 20 }}>
          <button className="app-btn primary" onClick={onClose}>Done</button>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// EnquiriesInbox — the demo CRM help-desk equivalent (Tier 2, video 3).
// Reception/clinician sees all assessment-form submissions, can drill into
// each, draft a reply, and see where each lead sits in the 8-touch advice
// sequence. Sits on its own route — accessible to owner + clinician.
// ============================================================
function EnquiriesInbox({ onOpenLead, defaultStageFilter = 'all' }) {
  const [filter, setFilter] = React.useState(defaultStageFilter);
  const [q, setQ]           = React.useState('');

  const stageBadge = (stageId) => {
    const s = ENQUIRY_STAGES.find(x => x.id === stageId) || { label: stageId, tone: '' };
    return <span className={'tag ' + (s.tone || 'ghost')}>{s.label}</span>;
  };

  // Group "advice-dayN" stages into "advice in progress" for the top-level filter
  const broadStage = (s) => {
    if (s === 'received')                  return 'new';
    if (s === 'replied')                   return 'replied';
    if (s === 'advice-sent' || s.startsWith('advice-day')) return 'advice';
    if (s === 'converted')                 return 'converted';
    if (s === 'closed')                    return 'closed';
    return 'other';
  };

  const list = ENQUIRIES
    .filter(e => filter === 'all' || broadStage(e.stage) === filter)
    .filter(e => {
      if (!q) return true;
      const ql = q.toLowerCase();
      return e.name.toLowerCase().includes(ql)
          || e.email.toLowerCase().includes(ql)
          || e.concern.toLowerCase().includes(ql)
          || (e.treatmentArea || '').toLowerCase().includes(ql);
    })
    .sort((a, b) => (b.submittedAt || '').localeCompare(a.submittedAt || ''));

  const counts = {
    all:       ENQUIRIES.length,
    new:       ENQUIRIES.filter(e => broadStage(e.stage) === 'new').length,
    replied:   ENQUIRIES.filter(e => broadStage(e.stage) === 'replied').length,
    advice:    ENQUIRIES.filter(e => broadStage(e.stage) === 'advice').length,
    converted: ENQUIRIES.filter(e => broadStage(e.stage) === 'converted').length,
    closed:    ENQUIRIES.filter(e => broadStage(e.stage) === 'closed').length,
  };

  return (
    <>
      <div className="cl-head">
        <div>
          <span className="eyebrow">Enquiries</span>
          <h1>Assessment-form inbox</h1>
        </div>
        <div className="actions">
          <span className="tag">{ENQUIRIES.length} total</span>
        </div>
      </div>

      <div className="cust-search-bar" style={{ marginBottom: 12 }}>
        <Icon name="search" size={14}/>
        <input placeholder="Search by name, email, treatment area…" value={q} onChange={e => setQ(e.target.value)}/>
      </div>

      <div className="enq-filter-row">
        {[
          ['all',       'All',       counts.all],
          ['new',       'New',       counts.new],
          ['replied',   'Replied',   counts.replied],
          ['advice',    'Advice sequence', counts.advice],
          ['converted', 'Converted', counts.converted],
          ['closed',    'Closed',    counts.closed],
        ].map(([id, label, n]) => (
          <button key={id} className={'enq-filter' + (filter === id ? ' active' : '')} onClick={() => setFilter(id)}>
            {label}<span className="enq-filter-count">{n}</span>
          </button>
        ))}
      </div>

      <div className="panel" style={{ padding: 0 }}>
        {list.map((e, i) => {
          const submitted = new Date(e.submittedAt);
          return (
            <button key={e.id} className="enq-row" onClick={() => onOpenLead && onOpenLead(e.id)}>
              <div className="enq-row-left">
                <span className="avatar sm" style={{ background: 'var(--blush-soft)' }}>{e.name.split(' ').map(s => s[0]).join('')}</span>
              </div>
              <div className="enq-row-body">
                <div className="enq-row-head">
                  <b>{e.name}</b>
                  <span className="enq-row-meta">{e.treatmentArea}</span>
                </div>
                <div className="enq-row-concern">{e.concern}</div>
                <div className="enq-row-bottom">
                  {stageBadge(e.stage)}
                  <span className="enq-row-when">
                    submitted {submitted.toLocaleString('en-GB', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })}
                  </span>
                  {e.assignedTo && <span className="enq-row-assigned">· assigned {clinicianById(e.assignedTo)?.name.split(' ')[0] || e.assignedTo}</span>}
                </div>
              </div>
              <Icon name="chevR" size={14}/>
            </button>
          );
        })}
        {list.length === 0 && <div className="empty"><Icon name="mail" size={28}/><div>No enquiries match.</div></div>}
      </div>
    </>
  );
}

// ============================================================
// LeadDetail — drill-in for one enquiry. Shows answers, images,
// the advice 8-touch timeline, and actions to draft a reply.
// ============================================================
function LeadDetail({ leadId, onBack, onOpenCompose }) {
  const e = ENQUIRIES.find(x => x.id === leadId);
  if (!e) return null;
  const submitted = new Date(e.submittedAt);
  const adviceDays = [2, 10, 18, 35, 55, 80, 110];
  const advicePings = e.advicePings || adviceDays.map(d => ({ day: d, sentAt: null, opened: null, clicked: null }));

  return (
    <>
      <button className="app-btn ghost small" style={{ marginBottom: 12 }} onClick={onBack}>
        <Icon name="back" size={14}/>Back to enquiries
      </button>
      <div className="cl-head">
        <div>
          <span className="eyebrow">Enquiry · {e.treatmentArea}</span>
          <h1>{e.name}</h1>
          <div className="capture-context" style={{ marginTop: 6 }}>
            <Icon name="calendar" size={11}/>
            <span>Submitted {submitted.toLocaleString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' })} · {e.source}</span>
          </div>
        </div>
        <div className="actions">
          <button className="app-btn" onClick={() => onOpenCompose && onOpenCompose('email')}><Icon name="mail" size={14}/>Reply email</button>
          <button className="app-btn primary"><Icon name="plus" size={14}/>Convert to booking</button>
        </div>
      </div>

      <div className="cl-2col">
        <div>
          <div className="panel" style={{ marginBottom: 16 }}>
            <div className="panel-head"><h3>Concern</h3></div>
            <p style={{ margin: '8px 0 0', lineHeight: 1.55, fontSize: 14, color: 'var(--fg-1)' }}>{e.concern}</p>
          </div>

          <div className="panel" style={{ marginBottom: 16 }}>
            <div className="panel-head"><h3>Form answers</h3><span className="meta">{e.answers.length} questions</span></div>
            <div className="kv-grid" style={{ gridTemplateColumns: '1fr', gap: 12 }}>
              {e.answers.map((a, i) => (
                <div key={i} className="kv">
                  <div className="lbl">{a.q}</div>
                  <div className="val">{a.a}</div>
                </div>
              ))}
            </div>
          </div>

          <div className="panel" style={{ marginBottom: 16 }}>
            <div className="panel-head"><h3>Images uploaded</h3><span className="meta">{e.images.length}</span></div>
            <div className="photo-grid">
              {e.images.map((label, i) => (
                <div key={i} className={'photo-card ph ph-' + (['portrait','skin','room','treat','clinical'][i % 5])}>
                  <div className="label"><span>{label}</span><span className="when">tap to enlarge</span></div>
                </div>
              ))}
            </div>
          </div>

          {e.advice && (
            <div className="panel" style={{ marginBottom: 16 }}>
              <div className="panel-head"><h3>Personalised advice sent</h3><span className="meta">{e.adviceSentAt ? new Date(e.adviceSentAt).toLocaleDateString('en-GB', { day:'numeric', month:'short' }) : ''}</span></div>
              <p style={{ margin: '8px 0 0', lineHeight: 1.55, fontSize: 13, color: 'var(--fg-2)' }}>{e.advice}</p>
            </div>
          )}
        </div>

        <div>
          <div className="panel" style={{ marginBottom: 16 }}>
            <div className="panel-head"><h3>Advice sequence</h3><span className="meta">8-touch · 110 days</span></div>
            <div className="advice-timeline">
              {advicePings.map((p, i) => {
                const sent = !!p.sentAt;
                const stateClass = sent ? (p.opened ? 'opened' : 'sent') : 'pending';
                return (
                  <div key={i} className={'advice-step ' + stateClass}>
                    <div className="advice-dot"/>
                    <div className="advice-body">
                      <div className="advice-day">Day {p.day}</div>
                      <div className="advice-status">
                        {!sent && 'Scheduled'}
                        {sent && !p.opened && 'Sent · not opened'}
                        {sent && p.opened && 'Sent · opened'}
                        
                      </div>
                      {sent && <div className="advice-when">{new Date(p.sentAt).toLocaleDateString('en-GB', { day:'numeric', month:'short' })}</div>}
                    </div>
                  </div>
                );
              })}
            </div>
            <div className="advice-foot">
              <button className="app-btn ghost small">Skip next step</button>
              <button className="app-btn ghost small">Remove from sequence</button>
            </div>
          </div>

          <div className="panel" style={{ marginBottom: 16 }}>
            <div className="panel-head"><h3>Activity</h3></div>
            <div className="history-list">
              {e.history.map((h, i) => (
                <div key={i} className="history-item">
                  <div className="history-when">{new Date(h.when).toLocaleString('en-GB', { day:'numeric', month:'short', hour:'2-digit', minute:'2-digit' })}</div>
                  <div className="history-kind"><span className={'tag ' + (h.kind === 'submitted' ? '' : h.kind === 'replied' || h.kind === 'advice-sent' ? 'ok' : 'ghost')}>{h.kind}</span></div>
                  <div className="history-note">{h.note}</div>
                </div>
              ))}
            </div>
          </div>

          <div className="panel">
            <div className="panel-head"><h3>Contact</h3></div>
            <div className="kv-grid" style={{ gridTemplateColumns: '1fr', gap: 10 }}>
              <div className="kv"><div className="lbl">Email</div><div className="val">{e.email}</div></div>
              <div className="kv"><div className="lbl">Phone</div><div className="val">{e.phone}</div></div>
              <div className="kv"><div className="lbl">Source</div><div className="val">{e.source}</div></div>
              {e.assignedTo && <div className="kv"><div className="lbl">Assigned to</div><div className="val">{clinicianById(e.assignedTo)?.name || e.assignedTo}</div></div>}
            </div>
          </div>
        </div>
      </div>
    </>
  );
}


// ── Message queue (demo) — the automation outbox ─────────────────────────────
// Shows what the workflows have queued, sent, and deliberately skipped.
function OwQueue({ onOpenRecord }) {
  const fmtSend = (iso) => {
    const day = iso.slice(0, 10), time = iso.slice(11, 16);
    if (day === todayISO) return 'Today ' + time;
    return fmtDate(day) + ' ' + time;
  };
  const tmplName = (id) => COMM_TEMPLATES.find(t => t.id === id)?.name || id;
  const queued  = SCHEDULED_MESSAGES.filter(m => m.status === 'queued');
  const sent    = SCHEDULED_MESSAGES.filter(m => m.status === 'sent');
  const skipped = SCHEDULED_MESSAGES.filter(m => m.status === 'skipped');
  const openedCount = sent.filter(m => m.opened).length;

  const Row = ({ m, trail }) => {
    const c = customerById(m.customerId);
    return (
      <div className="comm-msg out" style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px' }}>
        <span className="avatar sm" style={{ background: 'var(--blush-soft)', flexShrink: 0 }}>{c.initials}</span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 14, color: 'var(--fg-1)' }}>
            <b style={{ cursor: onOpenRecord ? 'pointer' : 'default' }} onClick={() => onOpenRecord && onOpenRecord(c.id)}>{c.name}</b>
            {' · '}{tmplName(m.templateId)}
          </div>
          <div style={{ fontSize: 12, color: 'var(--fg-3)', marginTop: 2 }}>{m.context || m.reason}</div>
        </div>
        <span className="comm-channel-tag">{m.channel}</span>
        {trail}
      </div>
    );
  };

  return (
    <>
      <div className="cl-head">
        <div><span className="eyebrow">Automations</span><h1>Message queue<DemoNote title="How the queue thinks" text="Every message is written in your wording and tied to your rules. Before anything sends, the system re-checks the client’s record — already rebooked? replied? opened the advice? If so, it holds back. Clients never get chased twice."/></h1></div>
      </div>
      <div className="kpi-grid" style={{ marginBottom: 20 }}>
        <div className="kpi"><div className="lbl">Waiting to send</div><div className="val">{queued.length}</div><div className="delta">next at {queued[0] ? queued[0].sendAt.slice(11, 16) : '—'}</div></div>
        <div className="kpi"><div className="lbl">Sent today</div><div className="val">{sent.length}</div><div className="delta up">{openedCount} of {sent.length} opened</div></div>
        <div className="kpi"><div className="lbl">Skipped by the rules</div><div className="val">{skipped.length}</div><div className="delta">checked before sending</div></div>
      </div>

      <div style={{ fontSize: 11, color: 'var(--fg-3)', margin: '0 0 8px', letterSpacing: '0.08em', textTransform: 'uppercase' }}>Sending next</div>
      <div className="panel" style={{ padding: 0, marginBottom: 24 }}>
        {queued.map(m => <Row key={m.id} m={m} trail={<span className="tag"><Icon name="clock" size={10}/>{fmtSend(m.sendAt)}</span>}/>)}
      </div>

      <div style={{ fontSize: 11, color: 'var(--fg-3)', margin: '0 0 8px', letterSpacing: '0.08em', textTransform: 'uppercase' }}>Sent earlier today</div>
      <div className="panel" style={{ padding: 0, marginBottom: 24 }}>
        {sent.map(m => <Row key={m.id} m={m} trail={
          m.opened
            ? <span className="tag ok"><Icon name="check" size={10}/>opened {m.openedAt.slice(11, 16)}</span>
            : <span className="tag ghost">not opened yet</span>
        }/>)}
      </div>

      <div style={{ fontSize: 11, color: 'var(--fg-3)', margin: '0 0 8px', letterSpacing: '0.08em', textTransform: 'uppercase' }}>Skipped — the system checked first</div>
      <div className="panel" style={{ padding: 0 }}>
        {skipped.map(m => <Row key={m.id} m={m} trail={<span className="tag warn">skipped</span>}/>)}
      </div>

    </>
  );
}

Object.assign(window, {
  DiaryView, AmendApptModal, MoveConfirmModal, CashUpModal,
  MedicalFormModal, BulkUploadModal, RequestLeaveModal,
  PendingBookingsPanel, WaitlistPanel, CustomerFlagBadge,
  SendMessageModal, CommHistory, TasksPanel, NewTaskModal, CheckInButton,
  NotificationBell, NotificationPanel, FormsGate,
  OwReports, OwQueue, DemoNote, ReportReviews, TemplatesEditor, WorkflowsEditor, NewWorkflowModal,
  UpcomingTreatments, EndOfDayPanel,
  ComparePhotosModal, PhotoAnnotateModal, PhoneHandoffModal,
  EnquiriesInbox, LeadDetail, SignaturePad, TasksBoard,
  useMediaQuery,
});
