// Sub-pages for Dashboard: Week, Patients, Therapists, Reminders, Stats

// ======================= WEEK ============================
/** Montag derselben ISO-Woche wie ymd; Y-M-D = Zivilkalender, unabhängig von Browserlokalzeit (passt zu `weekYmdPractice`). */
function weekMondayFromYmd(ymd) {
  const [y, mo, d] = ymd.split('-').map(Number);
  const dt = new Date(Date.UTC(y, mo - 1, d, 12, 0, 0));
  const dow = dt.getUTCDay();
  const mondayOffset = (dow + 6) % 7;
  dt.setUTCDate(dt.getUTCDate() - mondayOffset);
  return dt;
}

function cinevYmdFromUtcNoonDate(dt) {
  return `${dt.getUTCFullYear()}-${String(dt.getUTCMonth() + 1).padStart(2, '0')}-${String(dt.getUTCDate()).padStart(2, '0')}`;
}

/** Wochenkopf: klares Kalenderdatum aus Y-M-D (sprachabhängig). */
function weekViewColumnDateLabel(ymd, lang) {
  if (!ymd || String(ymd).length < 10) return '';
  const [yY, mM, dD] = String(ymd).split('-');
  if (!yY || !mM || !dD) return '';
  if (lang === 'es') return `${dD}/${mM}/${yY}`;
  return `${dD}.${mM}.${yY}`;
}

function weekYmdPractice(iso) {
  return new Intl.DateTimeFormat('sv-SE', { timeZone: cinevPracticeWallTimezone() }).format(new Date(iso));
}

function weekHourPractice(iso) {
  const parts = new Intl.DateTimeFormat('en-GB', {
    timeZone: cinevPracticeWallTimezone(), hour: '2-digit', minute: '2-digit', hour12: false,
  }).formatToParts(new Date(iso));
  const h = Number(parts.find((p) => p.type === 'hour')?.value);
  const m = Number(parts.find((p) => p.type === 'minute')?.value);
  return { h, m };
}

function weekHourHmFromIso(iso) {
  if (!iso) return '—';
  const { h, m } = weekHourPractice(iso);
  if (!Number.isFinite(h) || !Number.isFinite(m)) return '—';
  return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
}

function weekApptNativeTitleLines(appt, therapistShortFromAppt) {
  const si = appt.session_index != null ? Number(appt.session_index) + 1 : '?';
  const endIso = appt.end_at || appt.start_at;
  return [
    appt.patient_name || '—',
    therapistShortFromAppt(appt),
    `${weekHourHmFromIso(appt.start_at)}–${weekHourHmFromIso(endIso)}`,
    `${si}/12`,
  ].join('\n');
}

function weekTherapistStripLabel(shortName, density) {
  const s = String(shortName || '').trim() || '—';
  if (density >= 6) return s.slice(0, 1).toUpperCase();
  if (density >= 4) return s.length > 3 ? `${s.slice(0, 2)}…` : s;
  if (s.length > 8) return `${s.slice(0, 6)}…`;
  return s;
}

/** Therapeut:in aus vollem Namen — bei wenigen Terminen Kurzform mit Vorname. */
function weekTherapistStripLabelFromAppt(appt, tMap, density) {
  const id = String(appt.therapist_id);
  const full = (tMap && tMap[id] && tMap[id].name) ? tMap[id].name : (appt.therapist_name || '');
  const parts = String(full).trim().split(/\s+/).filter(Boolean);
  if (parts.length === 0) return weekTherapistStripLabel(String(appt.therapist_id || '').slice(0, 6), density);
  const last = parts[parts.length - 1];
  if (density >= 6) return last.slice(0, 1).toUpperCase();
  if (density >= 4) return last.length > 3 ? `${last.slice(0, 2)}…` : last;
  const first = parts[0];
  const fi = first.length ? `${first[0].toUpperCase()}.` : '';
  const ln = last.length > 9 ? `${last.slice(0, 7)}…` : last;
  return `${fi} ${ln}`.trim();
}

function weekPatientStripLabel(patientName, density) {
  const parts = String(patientName || '').trim().split(/\s+/).filter(Boolean);
  if (density >= 6) {
    return parts.map((p) => p[0]).join('').slice(0, 4).toUpperCase() || '?';
  }
  if (density >= 4) {
    const a = parts[0] && parts[0][0] ? parts[0][0] : '';
    const b = parts.length > 1 && parts[parts.length - 1][0] ? parts[parts.length - 1][0] : '';
    return `${a}${b}`.toUpperCase() || '?';
  }
  if (parts.length === 0) return '—';
  const first = parts[0];
  if (parts.length === 1) return first.length > 14 ? `${first.slice(0, 12)}…` : first;
  const last = parts[parts.length - 1];
  const shortLast = last.length > 10 ? `${last.slice(0, 8)}…` : last;
  const pair = `${first} ${shortLast}`;
  if (pair.length <= 18) return pair;
  return `${first.length > 2 ? `${first.slice(0, 1)}.` : first} ${shortLast}`;
}

function weekStripStyle(appt, isToday) {
  const done = appt.status === 'done';
  return {
    background: done ? '#eaf3df' : isToday ? CINEV.teal : CINEV.card,
    color: isToday && !done ? '#fff' : CINEV.ink,
    borderLeft: `3px solid ${done ? CINEV.greenDark : CINEV.tealLight}`,
    border: isToday && !done ? 'none' : `1px solid ${CINEV.line}`,
  };
}

/** Wochenraster: erste Zeile = 07:00, letzte = 22:00 (Praxis bis abends). */
const WEEK_VIEW_FIRST_HOUR = 7;
const WEEK_VIEW_LAST_HOUR = 22;
const WEEK_VIEW_HOUR_LABELS = (() => {
  const a = [];
  for (let h = WEEK_VIEW_FIRST_HOUR; h <= WEEK_VIEW_LAST_HOUR; h += 1) {
    a.push(String(h).padStart(2, '0'));
  }
  return a;
})();

/** Kurze Pause vor dem Termin-Tooltip — weniger Flackern beim Überstreichen vieler Zellen. */
const CINEV_APPOINTMENT_TOOLTIP_DELAY_MS = 500;
/** Phasenplan-Modals: Portal nach `document.body` + hoher z-index — sonst bei Vorfahren mit `transform` (Design-Canvas) unsichtbar / nicht klickbar. */
const CINEV_PHASE_PLAN_MODAL_Z = 100000;

/** `lang` für native date/time: europäische 24h-Darstellung (kein en-US-AM/PM). */
function cinevModalWallLang() {
  return typeof window !== 'undefined' && window.__cinevLang === 'es' ? 'es-ES' : 'de-DE';
}

/** Einheitliche Typografie für Datums-/Zeitfelder in Dialogen (tabular nums, UI-Schrift). */
function cinevModalDatetimeFieldStyle(overrides = {}) {
  return {
    padding: '9px 12px',
    border: `1px solid ${CINEV.line}`,
    borderRadius: 6,
    fontSize: 13,
    lineHeight: 1.35,
    fontFamily: 'inherit, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
    fontVariantNumeric: 'tabular-nums',
    fontFeatureSettings: '"tnum"',
    boxSizing: 'border-box',
    background: CINEV.card,
    color: CINEV.ink,
    minWidth: 0,
    width: '100%',
    maxWidth: '100%',
    ...overrides,
  };
}

/** Outcome-Modal: gleiche Schrift/Padding wie Radio-Zeilen (z. B. Krankheit). */
const OUTCOME_MODAL_UI_BASELINE = {
  padding: '8px 10px',
  borderRadius: 6,
  fontFamily: 'inherit',
  fontSize: 12.5,
  lineHeight: 1.35,
  fontVariantNumeric: 'tabular-nums',
  fontFeatureSettings: '"tnum"',
  boxSizing: 'border-box',
};

/** Gefülltes Feld (Datum/Uhrzeit) — gleiche Basis wie Radio, aber Karten-Hintergrund. */
const CINEV_OUTCOME_DATETIME_FIELD = {
  ...OUTCOME_MODAL_UI_BASELINE,
  border: `1px solid ${CINEV.line}`,
  background: CINEV.card,
  color: CINEV.ink,
  fontWeight: 400,
  minWidth: 0,
};

const CINEV_MODAL_DATETIME_SPLIT_ROW = {
  display: 'flex',
  gap: 8,
  width: '100%',
  minWidth: 0,
  alignItems: 'stretch',
  boxSizing: 'border-box',
};

const CINEV_MODAL_DATETIME_SPLIT_ITEM = {
  flex: '1 1 0',
  width: 0,
};

/** Viertelstunden für Wandzeit-Auswahl (kein natives time-Widget). */
const CINEV_WALL_MINUTES_QUARTER = ['00', '15', '30', '45'];

/** Standard-Sitzungsdauer (Min.) — gleich `backend/lib/appointment-defaults.mjs`. */
const CINEV_DEFAULT_APPOINTMENT_DURATION_MIN = 60;
/** Standard-Start-Stunde für neue Termine / Phasen (volle Stunde mit :00). */
const CINEV_DEFAULT_APPOINTMENT_START_HOUR = 9;

/**
 * Wandzeit HH:mm (oder Präfix) auf 15-Minuten-Schritte runden (…, 00, 15, 30, 45 …); Obergrenze 23:45.
 */
function cinevSnapWallTimeToStep15(hhmm) {
  if (hhmm == null || hhmm === '') return '';
  const m = String(hhmm).trim().match(/^(\d{1,2}):(\d{2})/);
  if (!m) return '00:00';
  let h = Number(m[1]);
  let mi = Number(m[2]);
  if (!Number.isFinite(h) || !Number.isFinite(mi)) return '00:00';
  h = Math.min(23, Math.max(0, h));
  mi = Math.min(59, Math.max(0, mi));
  const total = h * 60 + mi;
  const maxSnap = 23 * 60 + 45;
  let snapped = Math.round(total / 15) * 15;
  if (snapped > maxSnap) snapped = maxSnap;
  if (snapped < 0) snapped = 0;
  const nh = Math.floor(snapped / 60);
  const nmi = snapped % 60;
  return `${String(nh).padStart(2, '0')}:${String(nmi).padStart(2, '0')}`;
}

/** Aus HH:mm → { hour, minute } für Viertelstunden-Selects. */
function cinevWallTimeToQuarterParts(hhmm) {
  const s = cinevSnapWallTimeToStep15(hhmm);
  if (!s) return { hour: String(CINEV_DEFAULT_APPOINTMENT_START_HOUR).padStart(2, '0'), minute: '00' };
  const [h, mi] = s.split(':');
  const minute = CINEV_WALL_MINUTES_QUARTER.includes(mi) ? mi : '00';
  return { hour: String(h).padStart(2, '0'), minute };
}

/** Stunde + Minute (Strings) → HH:mm */
function cinevWallQuarterPartsToHhmm(hour, minute) {
  const h = String(hour || '0').padStart(2, '0');
  const m = CINEV_WALL_MINUTES_QUARTER.includes(String(minute)) ? String(minute) : '00';
  return `${h}:${m}`;
}

/** Zwei Selects: 24h-Stunden + Viertelminuten — kein Browser-time-Widget, kein AM/PM. */
function CinevWallTimeQuarterSelects({ hour, minute, onHourChange, onMinuteChange, variant = 'default' }) {
  useLang();
  const compact = variant === 'compact';
  const modal = variant === 'modal';
  const outcome = variant === 'outcome';
  const selBase = {
    cursor: 'pointer',
    border: `1px solid ${CINEV.line}`,
    borderRadius: 6,
    boxSizing: 'border-box',
    background: CINEV.card,
    color: CINEV.ink,
    fontFamily: 'inherit',
    fontVariantNumeric: 'tabular-nums',
    fontFeatureSettings: '"tnum"',
    textAlign: 'center',
  };
  const selModalShared = cinevModalDatetimeFieldStyle({
    width: 'auto',
    minWidth: 0,
    maxWidth: 'none',
    flex: '0 0 auto',
    cursor: 'pointer',
    textAlign: 'center',
  });
  const selOutcomeShared = {
    ...CINEV_OUTCOME_DATETIME_FIELD,
    width: 'auto',
    maxWidth: 'none',
    flex: '0 0 auto',
    cursor: 'pointer',
    textAlign: 'center',
  };
  const selH = outcome
    ? {
      ...selOutcomeShared,
      minWidth: '3.25rem',
    }
    : modal
    ? {
      ...selModalShared,
      minWidth: '3.5rem',
      padding: '9px 10px',
    }
    : compact
      ? {
        ...selBase,
        width: '4ch',
        minWidth: '4ch',
        maxWidth: '4.5ch',
        padding: '5px 1px',
        fontSize: 12,
        lineHeight: 1.2,
        flex: '0 0 auto',
      }
      : cinevModalDatetimeFieldStyle({
        ...CINEV_MODAL_DATETIME_SPLIT_ITEM,
        flex: '1 1 0',
        cursor: 'pointer',
      });
  const selM = outcome
    ? {
      ...selOutcomeShared,
      minWidth: '3.75rem',
    }
    : modal
    ? {
      ...selModalShared,
      minWidth: '4.25rem',
      padding: '9px 10px',
    }
    : compact
      ? {
        ...selBase,
        width: '4ch',
        minWidth: '4ch',
        maxWidth: '4.5ch',
        padding: '5px 1px',
        fontSize: 12,
        lineHeight: 1.2,
        flex: '0 0 auto',
      }
      : cinevModalDatetimeFieldStyle({
        ...CINEV_MODAL_DATETIME_SPLIT_ITEM,
        flex: '0 0 5.25rem',
        cursor: 'pointer',
      });
  const rowStyle = outcome || modal
    ? { display: 'flex', flexDirection: 'row', flexWrap: 'nowrap', alignItems: 'center', gap: 8, flex: '0 0 auto' }
    : compact
      ? { display: 'flex', flexDirection: 'row', flexWrap: 'nowrap', alignItems: 'center', gap: 4, flex: '0 0 auto' }
      : { ...CINEV_MODAL_DATETIME_SPLIT_ROW, alignItems: 'center' };
  const colonStyle = outcome
    ? { fontSize: 12.5, fontWeight: 500, color: CINEV.inkMute, lineHeight: 1.35, flex: '0 0 auto', userSelect: 'none', padding: '0 1px' }
    : modal || compact
    ? { fontSize: 15, fontWeight: 600, color: CINEV.inkMute, lineHeight: 1, flex: '0 0 auto', userSelect: 'none', padding: '0 1px' }
    : {
      fontSize: 16, fontWeight: 600, color: CINEV.inkMute, lineHeight: 1, flex: '0 0 auto', padding: '0 2px', userSelect: 'none',
    };
  return (
    <div style={rowStyle}>
      <select
        value={hour}
        onChange={(e) => onHourChange(e.target.value)}
        aria-label={t('ui_clock_hour')}
        style={selH}
      >
        {Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')).map((h) => (
          <option key={h} value={h}>{h}</option>
        ))}
      </select>
      <span style={colonStyle}>:</span>
      <select
        value={minute}
        onChange={(e) => onMinuteChange(e.target.value)}
        aria-label={t('ui_clock_quarter')}
        style={selM}
      >
        {CINEV_WALL_MINUTES_QUARTER.map((mm) => (
          <option key={mm} value={mm}>{mm}</option>
        ))}
      </select>
    </div>
  );
}

function WeekView({ onOpenPatient, onOpenTherapist, anchorYmd: anchorControlled, onAnchorYmdChange } = {}) {
  const lang = useLang() || 'de';
  const days = ['wk_mon', 'wk_tue', 'wk_wed', 'wk_thu', 'wk_fri'];
  const hours = WEEK_VIEW_HOUR_LABELS;
  const [wkTip, setWkTip] = React.useState(null);
  const wkTipTimerRef = React.useRef(null);
  const wkTipMoveTsRef = React.useRef(0);
  const live = typeof cinevLiveApi === 'function' && cinevLiveApi();

  React.useEffect(() => () => {
    if (wkTipTimerRef.current) clearTimeout(wkTipTimerRef.current);
  }, []);

  const hideWkTip = React.useCallback(() => {
    if (wkTipTimerRef.current) {
      clearTimeout(wkTipTimerRef.current);
      wkTipTimerRef.current = null;
    }
    setWkTip(null);
  }, []);

  const wkTipHandlers = React.useCallback((text) => ({
    onPointerEnter: (e) => {
      if (!text) return;
      if (wkTipTimerRef.current) clearTimeout(wkTipTimerRef.current);
      wkTipTimerRef.current = setTimeout(() => {
        wkTipTimerRef.current = null;
        setWkTip({ x: e.clientX + 12, y: e.clientY + 12, text });
      }, CINEV_APPOINTMENT_TOOLTIP_DELAY_MS);
    },
    onPointerMove: (e) => {
      const now = Date.now();
      if (now - wkTipMoveTsRef.current < 50) return;
      wkTipMoveTsRef.current = now;
      setWkTip((prev) => (prev ? { ...prev, x: e.clientX + 12, y: e.clientY + 12 } : prev));
    },
    onPointerLeave: hideWkTip,
  }), [hideWkTip]);

  const [weekRowFocus, setWeekRowFocus] = React.useState('patients');
  const [internalAnchorYmd, setInternalAnchorYmd] = React.useState(() => cinevYmdWallToday());
  const anchorControlledMode = anchorControlled != null && typeof onAnchorYmdChange === 'function';
  const anchorYmd = anchorControlledMode ? anchorControlled : internalAnchorYmd;
  const setAnchorYmd = anchorControlledMode ? onAnchorYmdChange : setInternalAnchorYmd;
  const [items, setItems] = React.useState([]);
  const [tMap, setTMap] = React.useState(null);
  const [err, setErr] = React.useState(null);
  const [wkRefresh, setWkRefresh] = React.useState(0);
  const [wkMenu, setWkMenu] = React.useState(null);
  const [wkBusy, setWkBusy] = React.useState(null);
  const [wkActErr, setWkActErr] = React.useState(null);

  React.useEffect(() => {
    if (!live) return undefined;
    let cancelled = false;
    (async () => {
      setErr(null);
      try {
        const [wData, tData] = await Promise.all([
          cinevFetchAnchorWeek(anchorYmd),
          cinevApiGet('/therapists'),
        ]);
        if (cancelled) return;
        setItems(wData.items || []);
        setTMap(cinevTherapistMapFromItems(tData.items || []));
      } catch (e) {
        if (!cancelled) setErr(e.message || String(e));
      }
    })();
    return () => { cancelled = true; };
  }, [live, anchorYmd, wkRefresh]);

  React.useEffect(() => {
    if (!wkMenu) return undefined;
    const fn = (e) => {
      if (e.target.closest && e.target.closest('[data-wk-appt-menu]')) return;
      setWkMenu(null);
    };
    window.addEventListener('mousedown', fn);
    return () => window.removeEventListener('mousedown', fn);
  }, [wkMenu]);

  const newApptOpId = React.useCallback(
    () =>
      (typeof crypto !== 'undefined' && crypto.randomUUID
        ? crypto.randomUUID()
        : `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`),
    [],
  );

  const openApptMenu = React.useCallback((e, appt) => {
    e.preventDefault();
    hideWkTip();
    setWkActErr(null);
    setWkMenu({ appt, x: e.clientX, y: e.clientY });
  }, [hideWkTip]);

  const runWkApptAction = React.useCallback(async (fn) => {
    if (!wkMenu?.appt?.id) return;
    const apptId = wkMenu.appt.id;
    setWkBusy(apptId);
    setWkActErr(null);
    try {
      await fn(wkMenu.appt);
      setWkMenu(null);
      setWkRefresh((n) => n + 1);
    } catch (er) {
      setWkActErr(er.message || String(er));
    } finally {
      setWkBusy(null);
    }
  }, [wkMenu]);

  // Montag–Freitag derselben Woche wie anchorYmd; Y-Strings wie die API (`weekYmdPractice`), nicht Browser-`cinevYmdLocal`.
  const mon = weekMondayFromYmd(anchorYmd);
  const columnYmds = [];
  for (let i = 0; i < 5; i++) {
    const x = new Date(mon.getTime());
    x.setUTCDate(x.getUTCDate() + i);
    columnYmds.push(cinevYmdFromUtcNoonDate(x));
  }
  const todayY = weekYmdPractice(new Date().toISOString());
  const todayIdx = columnYmds.findIndex((y) => y === todayY);

  const apptsForCell = (di, hourStr) => {
    const h = parseInt(hourStr, 10);
    const ymd = columnYmds[di];
    return (items || []).filter((a) => {
      if (a.status === 'cancelled') return false;
      if (weekYmdPractice(a.start_at) !== ymd) return false;
      const { h: ah } = weekHourPractice(a.start_at);
      return ah === h;
    });
  };

  const appointmentCountForDay = (di) => {
    const ymd = columnYmds[di];
    return (items || []).filter((a) => a.status !== 'cancelled' && weekYmdPractice(a.start_at) === ymd).length;
  };

  if (!live) {
    return (
      <div style={{ padding: '20px 32px' }}>
        <div style={{ padding: '10px 12px', borderRadius: 8, background: CINEV.soft, color: CINEV.inkSoft, fontSize: 12.5, border: `1px solid ${CINEV.line}` }}>
          {t('ui_proto_no_live')}
        </div>
      </div>
    );
  }

  return (
    <div style={{ padding: '20px 32px', position: 'relative' }}>
      {wkTip && wkTip.text ? (
        <div
          role="tooltip"
          style={{
            position: 'fixed',
            left: wkTip.x,
            top: wkTip.y,
            zIndex: 50000,
            maxWidth: 280,
            padding: '8px 10px',
            background: CINEV.ink,
            color: '#fff',
            borderRadius: 8,
            fontSize: 11,
            lineHeight: 1.45,
            pointerEvents: 'none',
            boxShadow: '0 8px 24px rgba(0,0,0,0.2)',
            whiteSpace: 'pre-wrap',
            fontWeight: 400,
          }}
        >
          {wkTip.text}
        </div>
      ) : null}
      {wkMenu && wkMenu.appt ? (
        <div
          data-wk-appt-menu
          role="menu"
          style={{
            position: 'fixed',
            left: Math.min(wkMenu.x, (typeof window !== 'undefined' ? window.innerWidth : 800) - 220),
            top: Math.min(wkMenu.y, (typeof window !== 'undefined' ? window.innerHeight : 600) - 240),
            zIndex: 60000,
            minWidth: 200,
            maxWidth: 280,
            background: CINEV.card,
            border: `1px solid ${CINEV.line}`,
            borderRadius: 10,
            boxShadow: '0 12px 40px rgba(0,0,0,0.18)',
            padding: '10px 0',
            fontSize: 12.5,
          }}
          onMouseDown={(e) => e.stopPropagation()}
        >
          <div style={{ padding: '0 12px 8px', fontSize: 10.5, color: CINEV.inkMute, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.4 }}>
            {t('wk_appt_menu_title')}
          </div>
          {wkActErr ? (
            <div style={{ padding: '4px 12px 8px', fontSize: 11.5, color: '#c94343', lineHeight: 1.35 }}>{wkActErr}</div>
          ) : null}
          {(() => {
            const a = wkMenu.appt;
            const st = String(a.status || '');
            const canQuick = st === 'scheduled' || st === 'rescheduled';
            const busyHere = wkBusy === a.id;
            const btnRow = (label, onClick, opts = {}) => (
              <button
                key={label}
                type="button"
                role="menuitem"
                disabled={busyHere || opts.disabled}
                onClick={() => { if (!opts.disabled) onClick(); }}
                style={{
                  display: 'block', width: '100%', textAlign: 'left', padding: '8px 14px', border: 'none', background: 'transparent',
                  fontFamily: 'inherit', fontSize: 12.5, cursor: busyHere || opts.disabled ? 'default' : 'pointer',
                  color: opts.danger ? '#a33a1a' : CINEV.ink, opacity: opts.disabled ? 0.45 : 1,
                }}
              >
                {label}
              </button>
            );
            return (
              <React.Fragment>
                {btnRow(t('wk_appt_open'), () => {
                  if (a.patient_id && onOpenPatient) onOpenPatient(String(a.patient_id));
                  setWkMenu(null);
                })}
                {canQuick ? btnRow(t('wk_appt_done'), () => void runWkApptAction((ap) =>
                  cinevApiPost(`/appointments/${encodeURIComponent(ap.id)}/status`, { status: 'done', operationId: newApptOpId() }))) : null}
                {canQuick ? btnRow(t('wk_appt_no_show'), () => void runWkApptAction((ap) =>
                  cinevApiPost(`/appointments/${encodeURIComponent(ap.id)}/status`, { status: 'no_show', operationId: newApptOpId() }))) : null}
                {canQuick ? btnRow(t('wk_appt_postpone'), () => void runWkApptAction((ap) => {
                  const ms = 86400000;
                  const s = new Date(ap.start_at).getTime();
                  const en = new Date(ap.end_at).getTime();
                  return cinevApiPost(`/appointments/${encodeURIComponent(ap.id)}/reschedule`, {
                    startAt: new Date(s + ms).toISOString(),
                    endAt: new Date(en + ms).toISOString(),
                    operationId: newApptOpId(),
                  });
                })) : null}
                {canQuick ? btnRow(t('wk_appt_cancel'), () => {
                  if (!window.confirm(t('wk_appt_cancel_confirm'))) return;
                  void runWkApptAction((ap) =>
                    cinevApiPost(`/appointments/${encodeURIComponent(ap.id)}/cancel`, {
                      reasonCode: 'other',
                      operationId: newApptOpId(),
                    }));
                }, { danger: true }) : null}
                {busyHere ? (
                  <div style={{ padding: '6px 14px 0', fontSize: 11, color: CINEV.inkMute }}>{t('wk_appt_busy')}</div>
                ) : null}
              </React.Fragment>
            );
          })()}
        </div>
      ) : null}
      {err && (
        <div style={{ marginBottom: 12, padding: '10px 12px', borderRadius: 8, background: CINEV.noShow.bg, color: CINEV.noShow.fg, fontSize: 12.5 }}>{err}</div>
      )}
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16, flexWrap: 'wrap' }}>
        <div style={{ display: 'flex', background: CINEV.bg, borderRadius: 6, padding: 2, border: `1px solid ${CINEV.lineSoft}` }}>
          <button
            type="button"
            onClick={() => setWeekRowFocus('patients')}
            style={{
              border: 'none', padding: '6px 12px', borderRadius: 4, fontSize: 11.5, fontWeight: weekRowFocus === 'patients' ? 600 : 400,
              cursor: 'pointer', fontFamily: 'inherit',
              background: weekRowFocus === 'patients' ? CINEV.teal : 'transparent',
              color: weekRowFocus === 'patients' ? '#fff' : CINEV.inkSoft,
            }}
          >{t('wk_focus_patients')}</button>
          <button
            type="button"
            onClick={() => setWeekRowFocus('therapists')}
            style={{
              border: 'none', padding: '6px 12px', borderRadius: 4, fontSize: 11.5, fontWeight: weekRowFocus === 'therapists' ? 600 : 400,
              cursor: 'pointer', fontFamily: 'inherit',
              background: weekRowFocus === 'therapists' ? CINEV.teal : 'transparent',
              color: weekRowFocus === 'therapists' ? '#fff' : CINEV.inkSoft,
            }}
          >{t('wk_focus_therapists')}</button>
        </div>
        <button type="button" style={wkBtn()} onClick={() => setAnchorYmd((y) => cinevYmdAddDays(y, -7))}>{t('wk_prev')}</button>
        <button type="button" style={{ ...wkBtn(), background: CINEV.teal, color: '#fff', borderColor: CINEV.teal }} onClick={() => setAnchorYmd(cinevYmdWallToday())}>{t('wk_today')}</button>
        <button type="button" style={wkBtn()} onClick={() => setAnchorYmd((y) => cinevYmdAddDays(y, 7))}>{t('wk_next')}</button>
      </div>

      <div style={{ background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`, overflow: 'hidden' }}>
        <div style={{ display: 'grid', gridTemplateColumns: `60px repeat(${days.length}, 1fr)` }}>
          <div style={{ borderBottom: `1px solid ${CINEV.line}`, background: CINEV.bg }}/>
          {days.map((d, i) => {
            const ymdCell = columnYmds[i] || '';
            const apptCount = appointmentCountForDay(i);
            const isToday = todayIdx === i;
            const dateLabel = weekViewColumnDateLabel(ymdCell, lang);
            return (
              <div key={d} style={{ padding: '12px 14px', borderBottom: `1px solid ${CINEV.line}`, borderLeft: `1px solid ${CINEV.lineSoft}`, background: isToday ? CINEV.soft : CINEV.bg }}>
                <div style={{ fontSize: 12, fontWeight: 600, color: isToday ? CINEV.teal : CINEV.ink }}>
                  {dateLabel ? `${t(d)} ${dateLabel}` : t(d)}
                </div>
                <div style={{ fontSize: 10, color: CINEV.inkMute, marginTop: 2 }}>
                  {t('wk_day_appt_count')} · <span style={{ fontWeight: 600, color: CINEV.ink, fontVariantNumeric: 'tabular-nums' }}>{apptCount}</span>
                </div>
              </div>
            );
          })}

          {(() => {
            const therapistShortFromAppt = (a) => {
              const id = String(a.therapist_id);
              const e = tMap && tMap[id];
              if (e && e.name) return e.name.split(/\s+/).pop() || '—';
              const parts = String(a.therapist_name || '').trim().split(/\s+/);
              return parts.length ? parts[parts.length - 1] : '—';
            };
            const therapistLineFromAppt = (a) => {
              const id = String(a.therapist_id);
              const full = (tMap && tMap[id] && tMap[id].name) ? tMap[id].name : (a.therapist_name || '').trim();
              const p = full.split(/\s+/).filter(Boolean);
              if (p.length === 0) return '—';
              if (p.length === 1) return p[0].length > 16 ? `${p[0].slice(0, 14)}…` : p[0];
              const fn = p[0];
              const ln = p[p.length - 1];
              const s = `${fn} ${ln}`;
              return s.length > 20 ? `${fn[0]}. ${ln}` : s;
            };
            return hours.map((h, hi) => (
              <React.Fragment key={h}>
                <div style={{ padding: '6px 8px', fontSize: 10, color: CINEV.inkMute, borderTop: `1px solid ${CINEV.lineSoft}`, textAlign: 'right', background: CINEV.bg, fontVariantNumeric: 'tabular-nums' }}>{h}:00</div>
                {days.map((_, di) => {
                  const list = [...apptsForCell(di, h)].sort((a, b) => String(a.start_at).localeCompare(String(b.start_at)));
                  const isToday = todayIdx === di;
                  const a0 = list[0];
                  const density = list.length;
                  const cellBase = a0 ? {
                    ...weekStripStyle(a0, isToday),
                    borderRadius: 3, padding: '4px 6px', height: '100%', boxSizing: 'border-box',
                    fontSize: 10.5,
                  } : {};
                  const openPatient = (appt) => {
                    if (appt.patient_id && onOpenPatient) onOpenPatient(String(appt.patient_id));
                  };
                  return (
                    <div key={di} style={{
                      borderTop: `1px solid ${CINEV.lineSoft}`, borderLeft: `1px solid ${CINEV.lineSoft}`,
                      minHeight: 52, padding: 4, position: 'relative',
                      background: isToday ? '#faf8f2' : 'transparent',
                    }}>
                      {density > 1 ? (
                        <div style={{
                          display: 'flex', flexDirection: 'row', gap: 2, height: '100%', minHeight: 44, alignItems: 'stretch',
                        }}>
                          {list.map((appt) => {
                            const st = weekStripStyle(appt, isToday);
                            const title = weekApptNativeTitleLines(appt, therapistShortFromAppt);
                            const label = weekRowFocus === 'patients'
                              ? weekPatientStripLabel(appt.patient_name, density)
                              : weekTherapistStripLabelFromAppt(appt, tMap, density);
                            const vert = density >= 5;
                            const fs = density >= 6 ? 7.5 : density >= 4 ? 8.5 : 9.5;
                            return (
                              <button
                                key={appt.id || `${appt.start_at}-${appt.therapist_id}`}
                                type="button"
                                aria-label={title.replace(/\n/g, ', ')}
                                title={t('wk_appt_hint')}
                                {...wkTipHandlers(title)}
                                onContextMenu={(e) => openApptMenu(e, appt)}
                                onClick={() => openPatient(appt)}
                                style={{
                                  flex: 1, minWidth: 0, border: 'none', borderRadius: 3, cursor: appt.patient_id && onOpenPatient ? 'pointer' : 'default',
                                  fontFamily: 'inherit', padding: '2px 1px', display: 'flex', flexDirection: 'column',
                                  alignItems: 'center', justifyContent: 'center', overflow: 'hidden', ...st,
                                  writingMode: vert ? 'vertical-rl' : 'horizontal-tb', textOrientation: 'mixed',
                                  fontSize: fs, fontWeight: 600, color: st.color, lineHeight: vert ? 1.1 : 1.2,
                                }}
                              >
                                {weekRowFocus === 'therapists' && density < 5 ? (
                                  <Avatar therapist={String(appt.therapist_id)} size={Math.max(8, 12 - Math.min(4, density))} therapistApiMap={tMap}/>
                                ) : null}
                                <span style={{
                                  overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '100%',
                                  maxHeight: vert ? '100%' : '2.4em', whiteSpace: vert ? 'normal' : 'nowrap',
                                }}>{label}</span>
                              </button>
                            );
                          })}
                        </div>
                      ) : density === 1 ? (
                        <button
                          type="button"
                          aria-label={weekApptNativeTitleLines(a0, therapistShortFromAppt).replace(/\n/g, ', ')}
                          title={t('wk_appt_hint')}
                          {...wkTipHandlers(weekApptNativeTitleLines(a0, therapistShortFromAppt))}
                          onContextMenu={(e) => openApptMenu(e, a0)}
                          onClick={() => openPatient(a0)}
                          style={{
                            ...cellBase, width: '100%', textAlign: 'left', border: 'none', fontFamily: 'inherit',
                            cursor: a0.patient_id && onOpenPatient ? 'pointer' : 'default', display: 'block',
                          }}
                        >
                          {weekRowFocus === 'patients' ? (
                            <React.Fragment>
                              <div style={{
                                fontWeight: 600, fontSize: 10.5, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                              }}>{a0.patient_name || '—'}</div>
                              <div style={{
                                fontSize: 9, opacity: 0.7, marginTop: 2,
                                overflow: 'hidden', maxHeight: '2.8em', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
                                wordBreak: 'break-word',
                              }}
                              >
                                {therapistLineFromAppt(a0)} · {(a0.session_index != null ? Number(a0.session_index) + 1 : '?')}/12
                              </div>
                            </React.Fragment>
                          ) : (
                            <React.Fragment>
                              <div style={{ display: 'flex', alignItems: 'center', gap: 4, marginBottom: 2, width: '100%' }}>
                                <Avatar therapist={String(a0.therapist_id)} size={14} therapistApiMap={tMap}/>
                                <span style={{ fontSize: 9, opacity: 0.7 }}>{therapistLineFromAppt(a0)}</span>
                              </div>
                              <div style={{ fontWeight: 600, fontSize: 10.5, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                                {(a0.patient_name || '').split(/\s+/)[0] || '—'}
                              </div>
                              <div style={{
                                fontSize: 9, opacity: 0.7, marginTop: 2,
                                overflow: 'hidden', maxHeight: '2.8em', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
                              }}
                              >
                                {(a0.session_index != null ? Number(a0.session_index) + 1 : '?')}/12 · {t('wk_room')} {(hi % 5) + 1}
                              </div>
                            </React.Fragment>
                          )}
                        </button>
                      ) : null}
                    </div>
                  );
                })}
              </React.Fragment>
            ));
          })()}
        </div>
      </div>
    </div>
  );
}

function wkBtn() {
  return { background: CINEV.card, border: `1px solid ${CINEV.line}`, borderRadius: 6,
    padding: '6px 12px', fontSize: 12, color: CINEV.inkSoft, cursor: 'pointer', fontFamily: 'inherit' };
}

// ======================= PATIENTS ============================
function mapApiStatusToUiFilter(st) {
  if (st === 'paused') return 'waiting';
  if (st === 'finished') return 'closed';
  return 'active';
}

function fillI18nPlaceholders(str, map) {
  if (!str) return '';
  return str.replace(/\{(\w+)\}/g, (_, k) => (map[k] != null ? String(map[k]) : ''));
}

/**
 * Anzeige-„Patienten-ID“ wie NIUP: bevorzugt `nube_salud_patient_id` (Ziffern 6–14),
 * sonst lesbarer `external_ref` (ohne rohe UUIDs in der ersten Tabellenspalte).
 */
function patientPracticeIdDisplay(p) {
  const nube = p.nube_salud_patient_id != null && String(p.nube_salud_patient_id).trim();
  const ext = p.external_ref != null && String(p.external_ref).trim();
  for (const cand of [nube, ext]) {
    if (!cand) continue;
    if (/^\d{6,14}$/.test(cand)) return cand;
    if (/^[A-Za-z0-9][A-Za-z0-9._-]{3,40}$/.test(cand)) return cand;
  }
  return '—';
}

function PatientsView({ initialPatientId, onInitialPatientConsumed, navRevealNonce = 0 } = {}) {
  useLang();
  const [ptSearchInput, setPtSearchInput] = React.useState('');
  const [ptSearchQ, setPtSearchQ] = React.useState('');
  const [detail, setDetail] = React.useState(null);
  const [liveRows, setLiveRows] = React.useState(null);
  const [liveTotal, setLiveTotal] = React.useState(0);
  const [liveMap, setLiveMap] = React.useState(null);
  const [liveLoading, setLiveLoading] = React.useState(false);
  const [liveErr, setLiveErr] = React.useState(null);
  const live = typeof cinevLiveApi === 'function' && cinevLiveApi();
  const weekOpenPatientRef = React.useRef(null);
  /** Wird bei Sidebar „Patienten“ erhöht — laufenden Week→Detail-Fetch nicht mehr anwenden. */
  const patientsNavDismissGen = React.useRef(0);

  React.useEffect(() => {
    if (navRevealNonce <= 0) return;
    patientsNavDismissGen.current += 1;
    setDetail(null);
    weekOpenPatientRef.current = null;
  }, [navRevealNonce]);

  React.useEffect(() => {
    if (!initialPatientId) weekOpenPatientRef.current = null;
  }, [initialPatientId]);

  React.useEffect(() => {
    if (!live || !initialPatientId || detail != null) return;
    if (weekOpenPatientRef.current === initialPatientId) return;
    let cancelled = false;
    const weekOpenGenAtStart = patientsNavDismissGen.current;
    (async () => {
      try {
        const data = await cinevApiGet(`/patients/${encodeURIComponent(initialPatientId)}`);
        if (cancelled) return;
        const p = data.patient;
        const sum = await cinevAppointmentSummaryForPatient(initialPatientId);
        const tData = await cinevApiGet('/therapists');
        if (cancelled) return;
        if (patientsNavDismissGen.current !== weekOpenGenAtStart) {
          if (onInitialPatientConsumed) onInitialPatientConsumed();
          return;
        }
        const tMap = cinevTherapistMapFromItems(tData.items || []);
        const name = `${p.first_name || ''} ${p.last_name || ''}`.trim() || '—';
        const gfn = (p.guardian_first_name && String(p.guardian_first_name).trim()) || '';
        const gln = (p.guardian_last_name && String(p.guardian_last_name).trim()) || '';
        const parentDisplay = [gfn, gln].filter(Boolean).join(' ') || (p.email && String(p.email).trim()) || '—';
        const phaseLine =
          p.phase_index != null && p.phase_count != null
            ? fillI18nPlaceholders(t('pt_phase_line'), {
                p1: Number(p.phase_index) + 1,
                pc: p.phase_count,
                d: Number(p.phase_sessions_done ?? 0),
                tgt: Number(p.phase_target_sessions ?? 12),
              })
            : t('pt_phase_none');
        const slotStatuses = sum.slotStatuses && sum.slotStatuses.length === 12 ? sum.slotStatuses : null;
        const mapped = {
          apiId: p.id,
          fromLiveApi: true,
          patientId: String(p.id),
          name,
          age: '—',
          diag: { de: patientPracticeIdDisplay(p), es: patientPracticeIdDisplay(p) },
          externalRef: (p.external_ref && String(p.external_ref).trim()) || null,
          practiceId: patientPracticeIdDisplay(p),
          phaseDisplay: phaseLine,
          parentDisplay,
          slotStatuses,
          tp: p.primary_therapist_id != null ? String(p.primary_therapist_id) : null,
          therapistMap: tMap,
          done: sum.done,
          total: sum.total,
          last: '—',
          next: sum.nextLabel,
          parent: parentDisplay,
          status: mapApiStatusToUiFilter(p.status),
        };
        weekOpenPatientRef.current = initialPatientId;
        setDetail(mapped);
        setLiveMap((prev) => prev || tMap);
        if (onInitialPatientConsumed) onInitialPatientConsumed();
      } catch {
        if (!cancelled && onInitialPatientConsumed) onInitialPatientConsumed();
      }
    })();
    return () => { cancelled = true; };
  }, [initialPatientId, live, detail]);

  React.useEffect(() => {
    const tm = setTimeout(() => setPtSearchQ(ptSearchInput.trim()), 280);
    return () => clearTimeout(tm);
  }, [ptSearchInput]);

  React.useEffect(() => {
    if (!live) {
      setLiveRows(null);
      return;
    }
    let cancelled = false;
    (async () => {
      setLiveLoading(true);
      setLiveErr(null);
      setLiveRows(null);
      setLiveTotal(0);
      try {
        const tData = await cinevApiGet('/therapists');
        if (cancelled) return;
        const tMap = cinevTherapistMapFromItems(tData.items || []);
        setLiveMap(tMap);
        const qs = new URLSearchParams();
        qs.set('limit', '200');
        if (ptSearchQ) qs.set('q', ptSearchQ);
        const pData = await cinevApiGet(`/patients?${qs.toString()}`);
        if (cancelled) return;
        const items = pData.items || [];
        const summaries = await cinevFetchAppointmentSummariesInBatches(
          items.map((p) => p.id),
          6
        );
        if (cancelled) return;
        const lang = window.__cinevLang || 'de';
        const dash = '—';
        const mapped = items.map((p, idx) => {
          const sum = summaries[idx];
          const name = `${p.first_name || ''} ${p.last_name || ''}`.trim() || dash;
          const ref = (p.external_ref && String(p.external_ref).trim()) || null;
          const patientId = p.id != null ? String(p.id) : dash;
          const practiceId = patientPracticeIdDisplay(p);
          const gfn = (p.guardian_first_name && String(p.guardian_first_name).trim()) || '';
          const gln = (p.guardian_last_name && String(p.guardian_last_name).trim()) || '';
          const parentDisplay = [gfn, gln].filter(Boolean).join(' ') || (p.email && String(p.email).trim()) || dash;
          const phaseLine =
            p.phase_index != null && p.phase_count != null
              ? fillI18nPlaceholders(t('pt_phase_line'), {
                  p1: Number(p.phase_index) + 1,
                  pc: p.phase_count,
                  d: Number(p.phase_sessions_done ?? 0),
                  tgt: Number(p.phase_target_sessions ?? 12),
                })
              : t('pt_phase_none');
          const slotStatuses = sum.slotStatuses && sum.slotStatuses.length === 12 ? sum.slotStatuses : null;
          return {
            apiId: p.id,
            fromLiveApi: true,
            patientId,
            name,
            age: dash,
            diag: { de: practiceId, es: practiceId },
            externalRef: ref,
            practiceId,
            phaseDisplay: phaseLine,
            parentDisplay,
            slotStatuses,
            tp: p.primary_therapist_id != null ? String(p.primary_therapist_id) : null,
            therapistMap: tMap,
            done: sum.done,
            total: sum.total,
            last: dash,
            next: sum.nextLabel,
            parent: parentDisplay,
            status: mapApiStatusToUiFilter(p.status),
          };
        });
        setLiveRows(mapped);
        setLiveTotal(typeof pData.total === 'number' ? pData.total : mapped.length);
      } catch (e) {
        if (!cancelled) {
          setLiveErr(e.message || String(e));
          setLiveRows(null);
          setLiveTotal(0);
        }
      } finally {
        if (!cancelled) setLiveLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [live, ptSearchQ]);

  const rows = live ? (Array.isArray(liveRows) ? liveRows : []) : [];

  if (detail) {
    return (
      <div style={{
        flex: 1,
        minHeight: 0,
        display: 'flex',
        flexDirection: 'column',
        overflow: 'hidden',
        alignSelf: 'stretch',
      }}
      >
        <PatientDetail row={detail} />
      </div>
    );
  }

  return (
    <div style={{ padding: '20px 32px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16 }}>
        <label style={{ flex: '1 1 280px', maxWidth: 520, minWidth: 0, fontSize: 11, color: CINEV.inkMute }}>
          {t('pt_search_label')}
          <input
            type="search"
            value={ptSearchInput}
            onChange={(e) => setPtSearchInput(e.target.value)}
            placeholder={t('pt_search')}
            aria-label={t('pt_search_label')}
            autoComplete="off"
            style={{
              display: 'block', width: '100%', marginTop: 6, padding: '10px 12px', borderRadius: 8,
              border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 14, boxSizing: 'border-box',
              background: CINEV.card,
            }}
          />
        </label>
      </div>

      {!live && (
        <div style={{ marginBottom: 12, padding: '10px 12px', borderRadius: 8, background: CINEV.soft, color: CINEV.inkSoft, fontSize: 12.5, border: `1px solid ${CINEV.line}` }}>
          {t('ui_proto_no_live')}
        </div>
      )}
      {live && liveErr && (
        <div style={{ marginBottom: 12, padding: '10px 12px', borderRadius: 8, background: CINEV.noShow.bg, color: CINEV.noShow.fg, fontSize: 12.5 }}>
          {t('pt_api_err')}: {liveErr}
        </div>
      )}
      {live && liveLoading && (
        <div style={{ marginBottom: 12, fontSize: 12.5, color: CINEV.inkMute }}>{t('pt_loading')}</div>
      )}

      <div style={{ background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`, overflow: 'hidden' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
          <thead style={{ background: CINEV.bg }}>
            <tr style={{ color: CINEV.inkMute, fontSize: 10.5, textTransform: 'uppercase', letterSpacing: 0.5 }}>
              <Th>{t('pt_th_patient_id')}</Th>
              <Th>{t('pt_th_patient')}</Th>
              <Th>{t('pt_th_guardian')}</Th>
              <Th>{t('pt_th_therapist')}</Th>
              <Th>{t('pt_th_phase_cycle')}</Th>
              <Th>{t('pt_th_twelve_sessions')}</Th>
            </tr>
          </thead>
          <tbody>
            {rows.map((r, i) => (
              <tr key={r.apiId || i} onClick={() => setDetail(r)} style={{ borderTop: `1px solid ${CINEV.lineSoft}`, cursor: 'pointer' }}
                onMouseEnter={(e) => { e.currentTarget.style.background = CINEV.bg; }}
                onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}>
                <td style={{
                  padding: '11px 10px', color: CINEV.inkSoft, fontSize: 12,
                  fontVariantNumeric: 'tabular-nums',
                }} title={r.externalRef ? `${r.practiceId || '—'} · ${r.externalRef}` : (r.practiceId && r.practiceId !== '—' ? String(r.practiceId) : '')}>{r.practiceId && r.practiceId !== '—' ? r.practiceId : '—'}</td>
                <td style={{ padding: '11px 12px', fontWeight: 500 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <PatientAvatar name={r.name} patientKey={r.apiId || r.name} size={32} />
                    <div>
                      <div>{r.name}</div>
                      {r.age && r.age !== '—' ? (
                        <div style={{ fontSize: 11, color: CINEV.inkMute, fontWeight: 400 }}>{r.age} {t('ta_years')}</div>
                      ) : null}
                    </div>
                  </div>
                </td>
                <td style={{ padding: '11px 10px', color: CINEV.inkSoft, fontSize: 12 }}>{r.parentDisplay || r.parent}</td>
                <td style={{ padding: '11px 8px', color: CINEV.inkSoft }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                    <Avatar therapist={r.tp} size={32} therapistApiMap={r.therapistMap || liveMap} />
                    {therapistDisplayName(r.tp, r.therapistMap || liveMap, '?')}
                  </div>
                </td>
                <td style={{ padding: '11px 8px', color: CINEV.inkSoft, fontVariantNumeric: 'tabular-nums', fontSize: 11.5, whiteSpace: 'nowrap' }}>
                  {r.status === 'waiting' ? <span style={{ fontSize: 11, color: CINEV.warn }}>— {t('pt_status_waiting')}</span> :
                    r.status === 'closed' ? <span style={{ fontSize: 11, color: CINEV.greenDark, fontWeight: 600 }}>{r.total}/{r.total} ✓</span> :
                      (r.phaseDisplay != null && r.phaseDisplay !== ''
                        ? r.phaseDisplay
                        : t('pt_phase_none'))}
                </td>
                <td style={{ padding: '11px 8px' }}>
                  {r.status === 'waiting' || r.status === 'closed' ? (
                    <span style={{ fontSize: 11, color: CINEV.inkMute }}>—</span>
                  ) : r.slotStatuses && r.slotStatuses.length === 12 ? (
                    <SessionDots statuses={r.slotStatuses} total={12} size={9} gap={4} />
                  ) : (
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                      <SessionDots done={Math.min(r.done, 12)} total={Math.min(Math.max(r.total, 1), 12)} size={9} gap={4} />
                      <span style={{ fontSize: 10, color: CINEV.inkMute, fontVariantNumeric: 'tabular-nums' }}>{r.done}/{r.total}</span>
                    </div>
                  )}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      <div style={{ marginTop: 10, fontSize: 11, color: CINEV.inkMute, textAlign: 'right' }}>
        {rows.length} / {live ? (liveErr ? '—' : liveTotal) : '—'}
      </div>
    </div>
  );
}

// ======================= NEW THERAPY MODAL ============================
function NewTherapyModal({ onClose }) {
  useLang();
  const [sessions, setSessions] = React.useState(12);
  const [rhythm, setRhythm] = React.useState('weekly');
  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(20,30,35,0.45)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 100,
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        background: CINEV.card, borderRadius: 12, padding: '22px 26px 24px', width: 440,
        boxShadow: '0 20px 60px rgba(0,0,0,0.25)',
      }}>
        <div style={{ fontSize: 11, color: CINEV.inkMute, letterSpacing: 0.6, textTransform: 'uppercase', fontWeight: 600 }}>{t('new_therapy')}</div>
        <div style={{ fontSize: 20, fontWeight: 600, margin: '4px 0 18px', letterSpacing: -0.3 }}>{t('nt_title')}</div>

        <div style={{ display: 'grid', gap: 12 }}>
          <NtField label={t('nt_patient')} value="Emma Koch"/>
          <NtField label={t('nt_therapist')} value="Markus Beyer"/>
          <NtField label={t('nt_therapy_type')} value={t('ty_behavior')}/>
          <NtField label={t('nt_start')} value="28.04.2026"/>

          <div>
            <div style={{ fontSize: 11, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 500, marginBottom: 6 }}>{t('nt_sessions')}</div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
              <input type="range" min="1" max="20" value={sessions}
                onChange={e => setSessions(Number(e.target.value))}
                style={{ flex: 1, accentColor: CINEV.teal }}/>
              <input type="number" min="1" max="20" value={sessions}
                onChange={e => setSessions(Math.max(1, Math.min(20, Number(e.target.value) || 1)))}
                style={{ width: 60, padding: '6px 8px', border: `1px solid ${CINEV.line}`, borderRadius: 5,
                  fontSize: 14, fontWeight: 600, color: CINEV.teal, textAlign: 'center', fontFamily: 'inherit' }}/>
            </div>
            <div style={{ marginTop: 8 }}><SessionDots done={0} total={sessions}/></div>
            <div style={{ fontSize: 11, color: CINEV.inkMute, marginTop: 6 }}>{t('nt_sessions_hint')}</div>
          </div>

          <div>
            <div style={{ fontSize: 11, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 500, marginBottom: 6 }}>{t('nt_rhythm')}</div>
            <div style={{ display: 'flex', gap: 6 }}>
              {[['weekly', t('nt_rhythm_weekly')], ['biweekly', t('nt_rhythm_biweekly')]].map(([k, lbl]) => (
                <button key={k} onClick={() => setRhythm(k)} style={{
                  flex: 1, padding: '8px 12px', border: rhythm === k ? `1.5px solid ${CINEV.teal}` : `1px solid ${CINEV.line}`,
                  background: rhythm === k ? CINEV.soft : CINEV.card, color: rhythm === k ? CINEV.teal : CINEV.inkSoft,
                  borderRadius: 6, fontSize: 12.5, fontWeight: rhythm === k ? 600 : 400, cursor: 'pointer', fontFamily: 'inherit',
                }}>{lbl}</button>
              ))}
            </div>
          </div>
        </div>

        <div style={{ display: 'flex', gap: 8, marginTop: 22 }}>
          <button onClick={onClose} style={{ flex: 1, background: 'transparent', border: `1px solid ${CINEV.line}`,
            padding: '10px', borderRadius: 6, fontSize: 13, color: CINEV.inkSoft, cursor: 'pointer', fontFamily: 'inherit' }}>{t('fs_cancel')}</button>
          <button onClick={onClose} style={{ flex: 2, background: CINEV.teal, color: '#fff', border: 'none',
            padding: '10px', borderRadius: 6, fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>{t('nt_create')}</button>
        </div>
      </div>
    </div>
  );
}

function NtField({ label, value }) {
  return (
    <div>
      <div style={{ fontSize: 11, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 500, marginBottom: 4 }}>{label}</div>
      <div style={{ padding: '8px 12px', border: `1px solid ${CINEV.line}`, borderRadius: 6, background: CINEV.bg,
        fontSize: 13, color: CINEV.ink, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        {value}<span style={{ color: CINEV.inkMute }}>▾</span>
      </div>
    </div>
  );
}

// ======================= PATIENT DETAIL — Phasenplanung (MVP-UI) ============================
function PdTh({ children }) {
  return (
    <th style={{
      textAlign: 'center',
      verticalAlign: 'middle',
      padding: '8px 6px',
      fontWeight: 500,
      fontSize: 10.5,
      color: CINEV.inkMute,
      textTransform: 'uppercase',
      letterSpacing: 0.5,
    }}>{children}</th>
  );
}

/** Gemeinsame Zellenbasis: Sitzungen- und Meilenstein-Tabelle im Phasenplan (horizontal + vertikal zentriert). */
const phasePlanTableTdBase = {
  padding: '10px 8px',
  verticalAlign: 'middle',
  textAlign: 'center',
};
const phasePlanTherapistCellInner = {
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
  gap: 6,
  flexWrap: 'wrap',
};

const THERAPY_MODALITY_TO_I18N = {
  behavioral: 'ty_behavior',
  speech: 'ty_speech',
  ergo: 'ty_occupational',
  neuro: 'ty_eeg',
  psychomotor: 'ty_psychomotor',
};

function displayTypeKeyFromSessionRow(s) {
  const m = s.therapy_modality;
  if (m && THERAPY_MODALITY_TO_I18N[m]) return THERAPY_MODALITY_TO_I18N[m];
  if (s.session_type === 'test') return 'pd_sess_type_test';
  if (s.session_type === 'assessment') return 'pd_sess_type_assessment';
  return 'pd_session_unassigned';
}

/** Voller Name aus der Therapeut:innen-Map (API); Map-Keys als String(id). */
function therapistDisplayName(id, therapistApiMap, missing = '—') {
  if (therapistApiMap && id != null && id !== '') {
    const e = therapistApiMap[String(id)];
    if (e && e.name) return e.name;
  }
  return missing;
}

function collectCalendarKeys(phases) {
  const keys = new Set();
  phases.forEach((ph) => {
    ph.appointments.forEach((a) => {
      const p = a.dateStr.split('.');
      if (p.length === 3) keys.add(`${p[2]}-${p[1]}-${p[0]}`);
    });
    ph.milestones.forEach((m) => {
      const p = m.dateStr.split('.');
      if (p.length === 3) keys.add(`${p[2]}-${p[1]}-${p[0]}`);
    });
  });
  return keys;
}

function PhaseMonthModal({ onClose, highlightKeys }) {
  useLang();
  const [ym, setYm] = React.useState({ y: 2026, m: 4 });
  const monthDe = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
  const monthEs = ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'];
  const wd = window.__cinevLang === 'es'
    ? ['L', 'M', 'X', 'J', 'V', 'S', 'D']
    : ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
  const label = window.__cinevLang === 'es' ? monthEs[ym.m - 1] : monthDe[ym.m - 1];
  const first = new Date(ym.y, ym.m - 1, 1);
  const lead = (first.getDay() + 6) % 7;
  const dim = new Date(ym.y, ym.m, 0).getDate();
  const cells = [];
  for (let i = 0; i < lead; i++) cells.push(null);
  for (let d = 1; d <= dim; d++) cells.push(d);
  while (cells.length % 7 !== 0) cells.push(null);
  while (cells.length < 42) cells.push(null);

  const shift = (delta) => {
    setYm((prev) => {
      let m = prev.m + delta;
      let y = prev.y;
      if (m > 12) { m = 1; y += 1; }
      if (m < 1) { m = 12; y -= 1; }
      return { y, m };
    });
  };

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(20,30,35,0.45)', zIndex: CINEV_PHASE_PLAN_MODAL_Z,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: CINEV.card, borderRadius: 12, padding: '20px 22px 22px', width: 360,
        boxShadow: '0 20px 60px rgba(0,0,0,0.25)', maxWidth: 'calc(100vw - 32px)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
          <div style={{ fontSize: 16, fontWeight: 600 }}>{t('pd_month_title')}</div>
          <button type="button" onClick={onClose} style={{ ...btn('ghost'), padding: '6px 10px' }}>{t('pd_month_close')}</button>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
          <button type="button" onClick={() => shift(-1)} style={wkBtn()} aria-label={t('pd_month_prev')}>‹</button>
          <div style={{ fontSize: 14, fontWeight: 600, color: CINEV.teal }}>{label} {ym.y}</div>
          <button type="button" onClick={() => shift(1)} style={wkBtn()} aria-label={t('pd_month_next')}>›</button>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 4, textAlign: 'center', fontSize: 10, color: CINEV.inkMute, marginBottom: 6 }}>
          {wd.map((w) => <div key={w} style={{ fontWeight: 600 }}>{w}</div>)}
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 4 }}>
          {cells.map((d, i) => {
            if (d === null) return <div key={`e-${i}`} style={{ height: 36 }} />;
            const k = `${ym.y}-${String(ym.m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
            const hit = highlightKeys.has(k);
            return (
              <div key={i} style={{
                height: 36, borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 12.5, fontWeight: hit ? 700 : 500, fontVariantNumeric: 'tabular-nums',
                background: hit ? CINEV.soft : 'transparent',
                color: hit ? CINEV.teal : CINEV.ink,
                border: hit ? `1px solid ${CINEV.teal}` : `1px solid ${CINEV.lineSoft}`,
              }}>{d}</div>
            );
          })}
        </div>
        <div style={{ marginTop: 12, fontSize: 11, color: CINEV.inkMute, lineHeight: 1.4 }}>{t('pd_month_legend')}</div>
      </div>
    </div>
  );
}

function appointmentStatusPill(st, apptRow) {
  if (st === 'completed') return { bg: CINEV.green + '22', fg: CINEV.greenDark, tx: t('pd_done') };
  if (st === 'now') return { bg: CINEV.teal, fg: '#fff', tx: t('pp_today_badge') };
  if (st === 'no_show') return { bg: '#f9e6e6', fg: '#c94343', tx: t('pd_no_show_label') };
  if (st === 'rescheduled') return { bg: '#fdf6e3', fg: '#b8860b', tx: t('pd_rescheduled_label') };
  if (st === 'cancelled') {
    // Kompaktes Label je nach Initiator/Detail
    const ini = apptRow?.cancellationInitiator;
    const det = apptRow?.cancellationPatientDetail;
    let tx = t('pd_cancelled');
    if (ini === 'patient') {
      if (det === 'not_present_no_reason') tx = t('pd_cancel_dns');
      else if (det === 'illness') tx = t('pd_cancel_illness');
      else tx = t('pd_cancel_by_patient');
    } else if (ini === 'therapist') {
      tx = t('pd_cancel_by_therapist');
    } else if (ini === 'practice') {
      tx = t('pd_cancel_by_practice');
    }
    return { bg: '#f9e6e6', fg: '#c94343', tx };
  }
  return { bg: CINEV.soft, fg: CINEV.teal, tx: t('pd_planned') };
}

// ─── Outcome-Modal für PhasePlanPanel (Absage + optional Datum/Uhrzeit) ───
function AppointmentOutcomeModal({
  appointmentId,
  alreadyCancelled,
  cancellationInitiator: initIni,
  cancellationPatientDetail: initDetail,
  cancellationNoticeTiming: initTiming,
  cancellationNote: initCancelNote,
  appointmentNote,
  allowTimeEdit = false,
  isSpecial = false,
  treatmentNoteEditOnly = false,
  /** Wenn true: bei normalen Behandlungsterminen keine Zeit-Sektion (Zeit nur im Sitzungs-Editor). */
  outcomeTimeSuppressed = false,
  startIso,
  endIso,
  onClose,
  onSaved,
}) {
  useLang();
  const specialUi = Boolean(isSpecial);
  const initApptNote = appointmentNote != null ? String(appointmentNote) : '';
  const showTimeSection = Boolean(
    allowTimeEdit && startIso && endIso && !treatmentNoteEditOnly
    && (specialUi || !outcomeTimeSuppressed),
  );
  const durMin = React.useMemo(() => {
    if (startIso && endIso) {
      const n = Math.round((new Date(endIso).getTime() - new Date(startIso).getTime()) / 60_000);
      if (Number.isFinite(n) && n >= 5 && n <= 480) return n;
    }
    return CINEV_DEFAULT_APPOINTMENT_DURATION_MIN;
  }, [startIso, endIso]);
  const toIso = typeof window.cinevIsoFromWallDatetimeLocal === 'function' ? window.cinevIsoFromWallDatetimeLocal : () => null;
  const [localDate, setLocalDate] = React.useState('');
  const [wallHour, setWallHour] = React.useState(() => String(CINEV_DEFAULT_APPOINTMENT_START_HOUR).padStart(2, '0'));
  const [wallMinute, setWallMinute] = React.useState('00');
  React.useEffect(() => {
    const fromIso = typeof window.cinevWallDatetimeLocalFromIso === 'function' ? window.cinevWallDatetimeLocalFromIso : () => '';
    const defH = String(CINEV_DEFAULT_APPOINTMENT_START_HOUR).padStart(2, '0');
    if (!startIso) {
      setLocalDate('');
      setWallHour(defH);
      setWallMinute('00');
      return;
    }
    const raw = fromIso(startIso);
    if (!raw) {
      setLocalDate('');
      setWallHour(defH);
      setWallMinute('00');
      return;
    }
    const [d, tPart] = raw.includes('T') ? raw.split('T') : [raw, ''];
    setLocalDate(d || '');
    const parts = tPart ? cinevWallTimeToQuarterParts(tPart.slice(0, 5)) : { hour: defH, minute: '00' };
    setWallHour(parts.hour);
    setWallMinute(parts.minute);
  }, [appointmentId, startIso]);
  const wallCombined = localDate ? `${localDate}T${cinevWallQuarterPartsToHhmm(wallHour, wallMinute)}` : '';
  const wallTimeReady = Boolean(localDate && wallHour && CINEV_WALL_MINUTES_QUARTER.includes(wallMinute));
  // Vorbelegen mit gespeicherten Werten; sonst Patient als Standard
  const [initiator, setInitiator] = React.useState(initIni || 'patient');
  const [patientDetail, setPatientDetail] = React.useState(initDetail || '');
  const [noticeTiming, setNoticeTiming] = React.useState(initTiming || '');
  const [note, setNote] = React.useState(
    specialUi || treatmentNoteEditOnly ? initApptNote : (initCancelNote || ''),
  );
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  // Reset auch bei Legacy-Absagen ohne neue Detail-Felder (nur status=cancelled)
  const isExisting = Boolean(initIni) || Boolean(alreadyCancelled);

  React.useEffect(() => {
    if (specialUi || treatmentNoteEditOnly) {
      setNote(appointmentNote != null ? String(appointmentNote) : '');
    } else {
      setInitiator(initIni || 'patient');
      setPatientDetail(initDetail || '');
      setNoticeTiming(initTiming || '');
      setNote(initCancelNote || '');
    }
  }, [appointmentId, specialUi, treatmentNoteEditOnly, appointmentNote, initIni, initDetail, initTiming, initCancelNote]);

  const canSave = () => {
    if (!initiator) return false;
    if (initiator === 'patient') {
      if (!patientDetail || !noticeTiming) return false;
    } else {
      if (!note.trim()) return false;
    }
    return true;
  };

  const handleSave = async () => {
    setBusy(true);
    setErr('');
    try {
      const body = { cancellationInitiator: initiator };
      if (initiator === 'patient') {
        body.cancellationPatientDetail = patientDetail;
        body.cancellationNoticeTiming = noticeTiming;
        if (note.trim()) body.cancellationNote = note.trim();
      } else if (note.trim()) {
        body.cancellationNote = note.trim();
      }
      await cinevApiPost(`/appointments/${encodeURIComponent(appointmentId)}/cancel`, body);
      onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const handleReset = async () => {
    if (!window.confirm(t('pd_outcome_reset_confirm'))) return;
    setBusy(true);
    setErr('');
    try {
      await cinevApiPost(`/appointments/${encodeURIComponent(appointmentId)}/reset-cancellation`, {});
      onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const handleSaveTime = async () => {
    if (!showTimeSection) return;
    if (!wallTimeReady) {
      setErr(t('pd_slot_err_invalid'));
      return;
    }
    setBusy(true);
    setErr('');
    try {
      const s = toIso(wallCombined);
      if (!s) {
        setErr(t('pd_slot_err_invalid'));
        setBusy(false);
        return;
      }
      const end = new Date(new Date(s).getTime() + durMin * 60_000).toISOString();
      const opId = typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `op-${Date.now()}`;
      if (isSpecial) {
        await cinevApiPatch(`/appointments/${encodeURIComponent(appointmentId)}/special`, {
          startAt: s,
          endAt: end,
          operationId: opId,
        });
      } else {
        await cinevApiPost(`/appointments/${encodeURIComponent(appointmentId)}/reschedule`, {
          startAt: s,
          endAt: end,
          operationId: opId,
        });
      }
      onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const pickedIsoForDirty = showTimeSection && wallTimeReady ? toIso(wallCombined) : null;
  const timeDirtySpecial = Boolean(
    pickedIsoForDirty && startIso
    && new Date(pickedIsoForDirty).getTime() !== new Date(startIso).getTime(),
  );
  const noteDirtyAppt = note.trim() !== initApptNote.trim();
  const canSaveSpecial = noteDirtyAppt || timeDirtySpecial;

  const handleSaveTreatmentNote = async () => {
    if (!treatmentNoteEditOnly || !noteDirtyAppt) return;
    setBusy(true);
    setErr('');
    try {
      const opId = typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `op-${Date.now()}`;
      await cinevApiPatch(`/appointments/${encodeURIComponent(appointmentId)}/note`, {
        operationId: opId,
        note: note.trim() || null,
      });
      onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const handleDeleteSpecial = async () => {
    if (!specialUi) return;
    if (!window.confirm(t('pd_special_delete_confirm'))) return;
    setBusy(true);
    setErr('');
    try {
      await cinevApiPost(`/appointments/${encodeURIComponent(appointmentId)}/special/delete`, {});
      onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const handleSaveSpecial = async () => {
    if (!specialUi || !canSaveSpecial) return;
    setBusy(true);
    setErr('');
    try {
      const opId = typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `op-${Date.now()}`;
      const body = { operationId: opId, note: note.trim() || null };
      if (timeDirtySpecial && showTimeSection) {
        const s = toIso(wallCombined);
        if (!s) {
          setErr(t('pd_slot_err_invalid'));
          setBusy(false);
          return;
        }
        body.startAt = s;
        body.endAt = new Date(new Date(s).getTime() + durMin * 60_000).toISOString();
      }
      await cinevApiPatch(`/appointments/${encodeURIComponent(appointmentId)}/special`, body);
      onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const radioStyle = (active) => ({
    flex: 1,
    ...OUTCOME_MODAL_UI_BASELINE,
    border: `1px solid ${active ? CINEV.teal : CINEV.line}`,
    background: active ? CINEV.soft : 'transparent',
    color: active ? CINEV.teal : CINEV.inkSoft,
    fontWeight: active ? 600 : 400,
    cursor: 'pointer',
    textAlign: 'center',
  });

  const fieldLabel = { fontSize: 11, color: CINEV.inkMute, display: 'block', marginBottom: 6 };
  const inputStyle = {
    display: 'block', width: '100%', padding: '8px 10px', borderRadius: 6,
    border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box',
  };

  const dialogShellStyle = {
    background: CINEV.card,
    borderRadius: 12,
    padding: '20px 22px 22px',
    width: 'min(460px, calc(100vw - 32px))',
    maxWidth: '100%',
    boxSizing: 'border-box',
    boxShadow: '0 20px 60px rgba(0,0,0,0.25)',
    border: `1px solid ${CINEV.line}`,
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'stretch',
    minWidth: 0,
  };

  return (
    <div
      onClick={onClose}
      role="presentation"
      style={{ position: 'fixed', inset: 0, background: 'rgba(20,30,35,0.45)', zIndex: CINEV_PHASE_PLAN_MODAL_Z, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16, boxSizing: 'border-box' }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-modal="true"
        aria-labelledby="outcome-modal-title"
        lang={cinevModalWallLang()}
        style={dialogShellStyle}
      >
        <div id="outcome-modal-title" style={{ fontSize: 15, fontWeight: 600, marginBottom: 18, flexShrink: 0 }}>
          {specialUi
            ? t('pd_special_edit_modal_title')
            : treatmentNoteEditOnly
              ? t('pd_done_note_modal_title')
              : t('pd_outcome_modal_title')}
        </div>

        {showTimeSection ? (
          <div style={{
            width: '100%',
            minWidth: 0,
            boxSizing: 'border-box',
            marginBottom: specialUi ? 16 : 20,
            paddingBottom: specialUi ? 16 : 18,
            borderBottom: `1px solid ${CINEV.lineSoft}`,
            flexShrink: 0,
          }}
          >
            <div style={{ fontSize: 12, fontWeight: 600, color: CINEV.teal, marginBottom: 8 }}>{t('pd_outcome_section_time')}</div>
            <div style={{ fontSize: 11.5, color: CINEV.inkMute, marginBottom: 10, lineHeight: 1.45 }}>{t('pd_slot_edit_hint')}</div>
            <label style={{ display: 'block', fontSize: 11, color: CINEV.inkMute, marginBottom: 6 }}>{t('pd_slot_edit_start')}</label>
            <div style={{
              display: 'flex', flexDirection: 'row', flexWrap: 'nowrap', alignItems: 'center', gap: 8,
              width: '100%', minWidth: 0, boxSizing: 'border-box', marginBottom: 8,
            }}
            >
              <CinevDateDmyInput
                valueYmd={localDate}
                onValueYmdChange={setLocalDate}
                style={{
                  ...CINEV_OUTCOME_DATETIME_FIELD,
                  flex: '1 1 auto',
                  width: 'auto',
                  minWidth: '10.5rem',
                  minHeight: 0,
                }}
              />
              <CinevWallTimeQuarterSelects
                variant="outcome"
                hour={wallHour}
                minute={wallMinute}
                onHourChange={setWallHour}
                onMinuteChange={setWallMinute}
              />
            </div>
            <div style={{ fontSize: 11, color: CINEV.inkMute, marginBottom: specialUi ? 0 : 12 }}>
              {t('pd_slot_duration_label')}: {durMin} {t('pd_slot_duration_min')}
            </div>
            {!specialUi ? (
              <button
                type="button"
                onClick={() => void handleSaveTime()}
                disabled={busy || !wallTimeReady}
                style={{
                  marginTop: 4,
                  width: '100%',
                  boxSizing: 'border-box',
                  background: busy || !wallTimeReady ? CINEV.line : CINEV.teal,
                  color: '#fff',
                  border: 'none',
                  padding: '8px 14px',
                  borderRadius: 6,
                  fontFamily: 'inherit',
                  fontWeight: 600,
                  fontSize: 13,
                  cursor: busy || !wallTimeReady ? 'default' : 'pointer',
                }}
              >{busy ? '…' : t('pd_slot_apply_time')}</button>
            ) : null}
          </div>
        ) : null}

        {!specialUi && !treatmentNoteEditOnly ? (
          <>
            {/* Initiator */}
            <div style={{ marginBottom: 14 }}>
              <span style={fieldLabel}>{t('pd_outcome_initiator_label')}</span>
              <div style={{ display: 'flex', gap: 8 }}>
                {[
                  { key: 'patient', label: t('pd_outcome_initiator_patient') },
                  { key: 'therapist', label: t('pd_outcome_initiator_therapist') },
                  { key: 'practice', label: t('pd_outcome_initiator_practice') },
                ].map(({ key, label }) => (
                  <button key={key} type="button" style={radioStyle(initiator === key)} onClick={() => { setInitiator(key); setPatientDetail(''); setNoticeTiming(''); setNote(''); }}>
                    {label}
                  </button>
                ))}
              </div>
            </div>

            {/* Patienten-Felder */}
            {initiator === 'patient' && (
              <>
                <div style={{ marginBottom: 14 }}>
                  <span style={fieldLabel}>{t('pd_outcome_patient_detail_label')}</span>
                  <div style={{ display: 'flex', gap: 8 }}>
                    {[
                      { key: 'not_present_no_reason', label: t('pd_outcome_detail_dns') },
                      { key: 'illness', label: t('pd_outcome_detail_illness') },
                      { key: 'other', label: t('pd_outcome_detail_other') },
                    ].map(({ key, label }) => (
                      <button key={key} type="button" style={radioStyle(patientDetail === key)} onClick={() => { setPatientDetail(key); if (key === 'not_present_no_reason') setNoticeTiming('late'); }}>
                        {label}
                      </button>
                    ))}
                  </div>
                </div>
                <div style={{ marginBottom: 14 }}>
                  <span style={fieldLabel}>{t('pd_outcome_timing_label')}</span>
                  <div style={{ display: 'flex', gap: 8 }}>
                    {[
                      { key: 'on_time', label: t('pd_outcome_timing_on_time') },
                      { key: 'late', label: t('pd_outcome_timing_late') },
                    ].map(({ key, label }) => (
                      <button key={key} type="button" style={radioStyle(noticeTiming === key)} onClick={() => setNoticeTiming(key)}>
                        {label}
                      </button>
                    ))}
                  </div>
                </div>
              </>
            )}

            {/* Notizfeld — immer sichtbar sobald Initiator gewählt */}
            {initiator && (
              <div style={{ marginBottom: 14, width: '100%', minWidth: 0, boxSizing: 'border-box' }}>
                <span style={fieldLabel}>{t('pd_outcome_note_label')}</span>
                <textarea
                  value={note}
                  onChange={(e) => setNote(e.target.value)}
                  placeholder={t('pd_outcome_note_placeholder')}
                  rows={3}
                  style={{ ...inputStyle, resize: 'vertical', width: '100%', minWidth: 0, maxWidth: '100%' }}
                />
              </div>
            )}
          </>
        ) : (
          <div style={{ marginBottom: 14, width: '100%', minWidth: 0, boxSizing: 'border-box' }}>
            <span style={fieldLabel}>{t('pd_outcome_note_label')}</span>
            <textarea
              value={note}
              onChange={(e) => setNote(e.target.value)}
              placeholder={t('pd_outcome_note_placeholder')}
              rows={3}
              style={{ ...inputStyle, resize: 'vertical', width: '100%', minWidth: 0, maxWidth: '100%' }}
            />
          </div>
        )}

        {err && <div style={{ marginBottom: 10, fontSize: 12, color: '#c94343', flexShrink: 0 }}>{err}</div>}

        <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginTop: 4, flexWrap: 'wrap', flexShrink: 0 }}>
          {specialUi && (
            <button
              type="button"
              onClick={() => void handleDeleteSpecial()}
              disabled={busy}
              style={{
                background: 'transparent',
                border: 'none',
                color: '#c94343',
                fontSize: 12.5,
                cursor: busy ? 'default' : 'pointer',
                fontFamily: 'inherit',
                padding: '8px 4px',
                marginRight: 'auto',
              }}
            >{t('pd_special_delete_btn')}</button>
          )}
          {isExisting && !specialUi && !treatmentNoteEditOnly && (
            <button type="button" onClick={() => void handleReset()} disabled={busy}
              style={{ background: 'transparent', border: 'none', color: '#c94343', fontSize: 12.5, cursor: busy ? 'default' : 'pointer', fontFamily: 'inherit', padding: '8px 4px', marginRight: 'auto' }}>
              {t('pd_outcome_reset_btn')}
            </button>
          )}
          <button type="button" onClick={onClose} disabled={busy}
            style={{ background: 'transparent', border: `1px solid ${CINEV.line}`, padding: '8px 14px', borderRadius: 6, fontFamily: 'inherit', cursor: busy ? 'default' : 'pointer', fontSize: 13 }}>
            {t('pd_outcome_close')}
          </button>
          {specialUi ? (
            <button
              type="button"
              onClick={() => void handleSaveSpecial()}
              disabled={busy || !canSaveSpecial}
              style={{
                background: canSaveSpecial && !busy ? CINEV.teal : CINEV.line,
                color: canSaveSpecial && !busy ? '#fff' : CINEV.inkMute,
                border: 'none',
                padding: '8px 14px',
                borderRadius: 6,
                fontFamily: 'inherit',
                fontWeight: 600,
                cursor: busy || !canSaveSpecial ? 'default' : 'pointer',
                fontSize: 13,
              }}
            >{busy ? t('pd_outcome_saving') : t('pd_slot_save')}</button>
          ) : treatmentNoteEditOnly ? (
            <button
              type="button"
              onClick={() => void handleSaveTreatmentNote()}
              disabled={busy || !noteDirtyAppt}
              style={{
                background: noteDirtyAppt && !busy ? CINEV.teal : CINEV.line,
                color: noteDirtyAppt && !busy ? '#fff' : CINEV.inkMute,
                border: 'none',
                padding: '8px 14px',
                borderRadius: 6,
                fontFamily: 'inherit',
                fontWeight: 600,
                cursor: busy || !noteDirtyAppt ? 'default' : 'pointer',
                fontSize: 13,
              }}
            >{busy ? t('pd_outcome_saving') : t('pd_slot_save')}</button>
          ) : (
            <button type="button" onClick={() => void handleSave()} disabled={busy || !canSave()}
              style={{ background: canSave() && !busy ? CINEV.teal : CINEV.line, color: canSave() && !busy ? '#fff' : CINEV.inkMute, border: 'none', padding: '8px 14px', borderRadius: 6, fontFamily: 'inherit', fontWeight: 600, cursor: busy || !canSave() ? 'default' : 'pointer', fontSize: 13 }}>
              {busy ? t('pd_outcome_saving') : t('pd_outcome_save_cancel')}
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

/** Sitzungs-Editor: Zeit, Ort, Therapeut:in, Sitzungsart; optional Bulk auf andere Slots der Phase. */
function PhaseSessionEditorModal({
  isSpecial = false,
  appointmentId,
  currentSlot,
  phaseTargets,
  initialLocationText = '',
  initialTherapistId,
  initialTherapyModality,
  startIso,
  endIso,
  therapistRows = [],
  onClose,
  onSaved,
}) {
  useLang();
  const toIso = typeof window.cinevIsoFromWallDatetimeLocal === 'function' ? window.cinevIsoFromWallDatetimeLocal : () => null;
  const durMin = React.useMemo(() => {
    if (startIso && endIso) {
      const n = Math.round((new Date(endIso).getTime() - new Date(startIso).getTime()) / 60_000);
      if (Number.isFinite(n) && n >= 5 && n <= 480) return n;
    }
    return CINEV_DEFAULT_APPOINTMENT_DURATION_MIN;
  }, [startIso, endIso]);

  const [localDate, setLocalDate] = React.useState('');
  const [wallHour, setWallHour] = React.useState(() => String(CINEV_DEFAULT_APPOINTMENT_START_HOUR).padStart(2, '0'));
  const [wallMinute, setWallMinute] = React.useState('00');
  React.useEffect(() => {
    const fromIso = typeof window.cinevWallDatetimeLocalFromIso === 'function' ? window.cinevWallDatetimeLocalFromIso : () => '';
    const defH = String(CINEV_DEFAULT_APPOINTMENT_START_HOUR).padStart(2, '0');
    if (!startIso) {
      setLocalDate('');
      setWallHour(defH);
      setWallMinute('00');
      return;
    }
    const raw = fromIso(startIso);
    if (!raw) {
      setLocalDate('');
      setWallHour(defH);
      setWallMinute('00');
      return;
    }
    const [d, tPart] = raw.includes('T') ? raw.split('T') : [raw, ''];
    setLocalDate(d || '');
    const parts = tPart ? cinevWallTimeToQuarterParts(tPart.slice(0, 5)) : { hour: defH, minute: '00' };
    setWallHour(parts.hour);
    setWallMinute(parts.minute);
  }, [appointmentId, startIso]);

  const [locationText, setLocationText] = React.useState(initialLocationText || '');
  const [therapistId, setTherapistId] = React.useState(initialTherapistId ?? '');
  const [therapyModality, setTherapyModality] = React.useState(initialTherapyModality ?? '');
  React.useEffect(() => {
    setLocationText(initialLocationText || '');
    setTherapistId(initialTherapistId ?? '');
    setTherapyModality(initialTherapyModality ?? '');
  }, [appointmentId, initialLocationText, initialTherapistId, initialTherapyModality]);

  const wallCombined = localDate ? `${localDate}T${cinevWallQuarterPartsToHhmm(wallHour, wallMinute)}` : '';
  const wallTimeReady = Boolean(localDate && wallHour && CINEV_WALL_MINUTES_QUARTER.includes(wallMinute));

  const selectableSlots = React.useMemo(
    () => (Array.isArray(phaseTargets) ? phaseTargets.filter((p) => p.appointmentId && !p.locked) : []),
    [phaseTargets],
  );
  const [selectedSlots, setSelectedSlots] = React.useState(() => new Set());
  React.useEffect(() => {
    const next = new Set();
    if (currentSlot != null) next.add(Number(currentSlot));
    setSelectedSlots(next);
  }, [appointmentId, currentSlot]);

  const [applyBulkTime, setApplyBulkTime] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');

  const toggleSlot = (slotNum) => {
    const n = Number(slotNum);
    const row = selectableSlots.find((p) => Number(p.slot) === n);
    if (!row) return;
    setSelectedSlots((prev) => {
      const next = new Set(prev);
      if (next.has(n)) {
        if (next.size <= 1) return next;
        next.delete(n);
      } else {
        next.add(n);
      }
      return next;
    });
  };

  const therapistName = (tr) => {
    const fn = tr.first_name || tr.firstName || '';
    const ln = tr.last_name || tr.lastName || '';
    const name = `${fn} ${ln}`.trim();
    return name || '—';
  };

  const timeDirty = () => {
    if (!startIso || !wallTimeReady) return false;
    const s = toIso(wallCombined);
    if (!s) return false;
    return new Date(s).getTime() !== new Date(startIso).getTime();
  };

  const applyWallTimeToTargetIso = (targetStartIso, targetEndIso) => {
    if (!wallTimeReady || !targetStartIso) return null;
    const fromIsoFn = typeof window.cinevWallDatetimeLocalFromIso === 'function' ? window.cinevWallDatetimeLocalFromIso : () => '';
    const targetWall = fromIsoFn(targetStartIso);
    if (!targetWall || !targetWall.includes('T')) return null;
    const [datePart] = targetWall.split('T');
    const sourceWall = wallCombined;
    if (!sourceWall.includes('T')) return null;
    const [, timePart] = sourceWall.split('T');
    const merged = `${datePart}T${timePart}`;
    const newStart = toIso(merged);
    if (!newStart || !targetEndIso) return null;
    const durMs = new Date(targetEndIso).getTime() - new Date(targetStartIso).getTime();
    if (!Number.isFinite(durMs) || durMs <= 0) return null;
    const newEnd = new Date(new Date(newStart).getTime() + durMs).toISOString();
    return { newStart, newEnd };
  };

  const saveTreatment = async () => {
    if (!appointmentId) return;
    if (!wallTimeReady && startIso) {
      setErr(t('pd_slot_err_invalid'));
      return;
    }
    setBusy(true);
    setErr('');
    const newOperationId = () => {
      if (typeof crypto !== 'undefined' && crypto.randomUUID) return crypto.randomUUID();
      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
        const r = Math.random() * 16 | 0;
        const v = c === 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
      });
    };
    try {
      const patchBody = {
        therapistId: therapistId === '' ? null : therapistId,
        locationText: locationText.trim() || null,
        therapyModality: therapyModality === '' ? null : therapyModality,
      };

      let slotsToTouch = Array.from(selectedSlots).filter((sn) => {
        const row = selectableSlots.find((p) => Number(p.slot) === Number(sn));
        return row && row.appointmentId;
      });
      slotsToTouch.sort((a, b) => {
        if (Number(a) === Number(currentSlot)) return -1;
        if (Number(b) === Number(currentSlot)) return 1;
        return Number(a) - Number(b);
      });

      for (let i = 0; i < slotsToTouch.length; i += 1) {
        const sn = slotsToTouch[i];
        const row = selectableSlots.find((p) => Number(p.slot) === Number(sn));
        if (!row.appointmentId) continue;
        const opId = newOperationId();
        const isCurrent = Number(sn) === Number(currentSlot);
        const needTime = (isCurrent && timeDirty())
          || (Boolean(applyBulkTime) && !isCurrent && row.startIso && row.endIso);
        if (needTime) {
          let startAt;
          let endAt;
          if (isCurrent && timeDirty()) {
            const s = toIso(wallCombined);
            if (!s) {
              setErr(t('pd_slot_err_invalid'));
              setBusy(false);
              return;
            }
            startAt = s;
            endAt = new Date(new Date(s).getTime() + durMin * 60_000).toISOString();
          } else {
            const pair = applyWallTimeToTargetIso(row.startIso, row.endIso);
            if (!pair) {
              setErr(t('pd_slot_err_invalid'));
              setBusy(false);
              return;
            }
            startAt = pair.newStart;
            endAt = pair.newEnd;
          }
          if (startAt && endAt) {
            await cinevApiPost(`/appointments/${encodeURIComponent(row.appointmentId)}/reschedule`, {
              startAt,
              endAt,
              operationId: opId,
              planningEdit: true,
            });
          }
        }
        await cinevApiPatch(`/appointments/${encodeURIComponent(row.appointmentId)}/details`, {
          ...patchBody,
          operationId: opId,
        });
      }

      onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const saveSpecial = async () => {
    if (!appointmentId) return;
    if (!wallTimeReady) {
      setErr(t('pd_slot_err_invalid'));
      return;
    }
    setBusy(true);
    setErr('');
    try {
      const opId = typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `op-${Date.now()}`;
      const s = toIso(wallCombined);
      if (!s) {
        setErr(t('pd_slot_err_invalid'));
        setBusy(false);
        return;
      }
      const endAt = new Date(new Date(s).getTime() + durMin * 60_000).toISOString();
      const body = {
        operationId: opId,
        therapistId: therapistId === '' ? null : therapistId,
        locationText: locationText.trim() || null,
      };
      if (timeDirty()) {
        body.startAt = s;
        body.endAt = endAt;
      }
      await cinevApiPatch(`/appointments/${encodeURIComponent(appointmentId)}/special`, body);
      onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const dialogShellStyle = {
    background: CINEV.card,
    borderRadius: 12,
    padding: '20px 22px 22px',
    width: 'min(480px, calc(100vw - 32px))',
    maxWidth: '100%',
    boxSizing: 'border-box',
    boxShadow: '0 20px 60px rgba(0,0,0,0.25)',
    border: `1px solid ${CINEV.line}`,
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'stretch',
    minWidth: 0,
    maxHeight: 'min(90vh, 720px)',
    overflow: 'auto',
  };

  const inputStyle = {
    display: 'block',
    width: '100%',
    padding: '8px 10px',
    borderRadius: 6,
    border: `1px solid ${CINEV.line}`,
    fontFamily: 'inherit',
    fontSize: 13,
    boxSizing: 'border-box',
  };

  const showBulk = !isSpecial && selectableSlots.length > 1;

  return (
    <div
      onClick={onClose}
      role="presentation"
      style={{
        position: 'fixed',
        inset: 0,
        background: 'rgba(20,30,35,0.45)',
        zIndex: CINEV_PHASE_PLAN_MODAL_Z,
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        padding: 16,
        boxSizing: 'border-box',
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-modal="true"
        aria-labelledby="sess-editor-title"
        lang={cinevModalWallLang()}
        style={dialogShellStyle}
      >
        <div id="sess-editor-title" style={{ fontSize: 15, fontWeight: 600, marginBottom: 16, flexShrink: 0 }}>
          {isSpecial ? t('pd_sess_editor_title_special') : t('pd_sess_editor_title')}
        </div>

        <div style={{ marginBottom: 14 }}>
          <span style={{ fontSize: 11, color: CINEV.inkMute, display: 'block', marginBottom: 6 }}>{t('pd_outcome_section_time')}</span>
          <div style={{
            display: 'flex',
            flexDirection: 'row',
            flexWrap: 'wrap',
            alignItems: 'center',
            gap: 8,
            width: '100%',
            minWidth: 0,
            boxSizing: 'border-box',
          }}
          >
            <CinevDateDmyInput
              valueYmd={localDate}
              onValueYmdChange={setLocalDate}
              style={{
                ...CINEV_OUTCOME_DATETIME_FIELD,
                flex: '1 1 auto',
                minWidth: '10.5rem',
                minHeight: 44,
              }}
            />
            <CinevWallTimeQuarterSelects
              variant="outcome"
              hour={wallHour}
              minute={wallMinute}
              onHourChange={setWallHour}
              onMinuteChange={setWallMinute}
            />
          </div>
          <div style={{ fontSize: 11, color: CINEV.inkMute, marginTop: 6 }}>
            {t('pd_slot_duration_label')}: {durMin} {t('pd_slot_duration_min')}
          </div>
        </div>

        <label style={{ fontSize: 11, color: CINEV.inkMute, display: 'block', marginBottom: 6 }}>{t('pd_th_location')}</label>
        <input
          type="text"
          value={locationText}
          onChange={(e) => setLocationText(e.target.value)}
          placeholder={t('pd_sess_location_placeholder')}
          style={{ ...inputStyle, marginBottom: 14, minHeight: 44 }}
        />

        <label style={{ fontSize: 11, color: CINEV.inkMute, display: 'block', marginBottom: 6 }}>{t('pd_th_therapist')}</label>
        <select
          value={therapistId}
          onChange={(e) => setTherapistId(e.target.value)}
          style={{ ...inputStyle, marginBottom: 14, cursor: 'pointer', minHeight: 44 }}
        >
          <option value="">{t('pd_sess_therapist_placeholder')}</option>
          {therapistRows.map((tr) => (
            <option key={String(tr.id)} value={String(tr.id)}>{therapistName(tr)}</option>
          ))}
        </select>

        {!isSpecial ? (
          <>
            <label style={{ fontSize: 11, color: CINEV.inkMute, display: 'block', marginBottom: 6 }}>{t('pd_th_session_kind')}</label>
            <select
              value={therapyModality}
              onChange={(e) => setTherapyModality(e.target.value)}
              style={{ ...inputStyle, marginBottom: 14, cursor: 'pointer', minHeight: 44 }}
            >
              <option value="">{t('pd_sess_modality_none')}</option>
              <option value="behavioral">{t('ty_behavior')}</option>
              <option value="speech">{t('ty_speech')}</option>
              <option value="ergo">{t('ty_occupational')}</option>
              <option value="neuro">{t('ty_eeg')}</option>
              <option value="psychomotor">{t('ty_psychomotor')}</option>
            </select>
          </>
        ) : null}

        {showBulk ? (
          <div style={{
            marginBottom: 14,
            paddingTop: 12,
            borderTop: `1px solid ${CINEV.lineSoft}`,
          }}
          >
            <div style={{ fontSize: 11.5, fontWeight: 600, color: CINEV.teal, marginBottom: 8 }}>{t('pd_bulk_targets_heading')}</div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', marginBottom: 10 }}>
              {selectableSlots.map((p) => {
                const n = Number(p.slot);
                const on = selectedSlots.has(n);
                const isCurrent = Number(n) === Number(currentSlot);
                return (
                  <button
                    key={n}
                    type="button"
                    title={t('pd_bulk_slot_toggle')}
                    onClick={() => toggleSlot(n)}
                    style={{
                      width: 44,
                      height: 44,
                      minWidth: 44,
                      minHeight: 44,
                      borderRadius: '50%',
                      border: `2px solid ${on ? CINEV.teal : CINEV.line}`,
                      background: on ? CINEV.soft : CINEV.card,
                      color: CINEV.teal,
                      fontWeight: 700,
                      fontSize: 13,
                      fontVariantNumeric: 'tabular-nums',
                      cursor: 'pointer',
                      boxSizing: 'border-box',
                      outline: isCurrent ? `2px solid ${CINEV.teal}` : 'none',
                      outlineOffset: 2,
                    }}
                  >{n}</button>
                );
              })}
            </div>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: CINEV.inkSoft, cursor: 'pointer' }}>
              <input
                type="checkbox"
                checked={applyBulkTime}
                onChange={(e) => setApplyBulkTime(e.target.checked)}
              />
              {t('pd_bulk_apply_time')}
            </label>
          </div>
        ) : null}

        {err ? (
          <div style={{ marginBottom: 12, fontSize: 12, color: '#c94343', lineHeight: 1.35 }}>{err}</div>
        ) : null}

        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10, marginTop: 8, flexWrap: 'wrap' }}>
          <button type="button" onClick={onClose} disabled={busy} style={wkBtn()}>{t('pd_sess_editor_cancel')}</button>
          <button
            type="button"
            disabled={busy || !wallTimeReady}
            onClick={() => void (isSpecial ? saveSpecial() : saveTreatment())}
            style={{ ...wkBtn(), background: busy || !wallTimeReady ? CINEV.line : CINEV.teal, color: '#fff', borderColor: CINEV.teal }}
          >{busy ? '…' : t('pd_sess_editor_save')}</button>
        </div>
      </div>
    </div>
  );
}

function pdYmdToDmy(ymd) {
  if (!ymd || typeof ymd !== 'string') return '—';
  const p = ymd.split('-');
  if (p.length !== 3) return '—';
  return `${p[2]}.${p[1]}.${p[0]}`;
}

function pdIsoToDmy(iso) {
  if (!iso) return '—';
  const ymd = weekYmdPractice(iso);
  const [y, m, d] = ymd.split('-');
  return `${d}.${m}.${y}`;
}

/** Freitext `TT.MM.JJJJ` (wie Tabellenspalte) → `YYYY-MM-DD` für Wandzeit-Pipeline; leer wenn ungültig. */
function pdDmyToYmd(dmy) {
  if (!dmy || typeof dmy !== 'string') return '';
  const t = dmy.trim().replace(/,/g, '.');
  const p = t.split('.').map((x) => String(x).trim()).filter((x) => x.length > 0);
  if (p.length !== 3) return '';
  const d = Number(p[0]);
  const m = Number(p[1]);
  let y = Number(p[2]);
  if (!Number.isFinite(d) || !Number.isFinite(m) || !Number.isFinite(y)) return '';
  if (!Number.isInteger(d) || !Number.isInteger(m)) return '';
  if (p[2].length <= 2 && y >= 0 && y < 100) y += 2000;
  if (y < 1900 || y > 2100) return '';
  if (d < 1 || d > 31 || m < 1 || m > 12) return '';
  const ymd = `${String(y).padStart(4, '0')}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
  const [yN, mN, dN] = ymd.split('-').map(Number);
  const test = new Date(Date.UTC(yN, mN - 1, dN, 12, 0, 0));
  if (test.getUTCFullYear() !== yN || test.getUTCMonth() !== mN - 1 || test.getUTCDate() !== dN) return '';
  return ymd;
}

/** Wie `pdYmdToDmy`, aber leerer String statt „—“ — für Eingabefelder. */
function pdYmdToDmyOrEmpty(ymd) {
  if (!ymd || typeof ymd !== 'string') return '';
  const p = ymd.split('-');
  if (p.length !== 3 || !p[0] || p[0].length !== 4) return '';
  return `${p[2]}.${p[1]}.${p[0]}`;
}

/** Datum `TT.MM.JJJJ` (Phasenplan / Praxis-Zeit), gekoppelt an kanonisches `YYYY-MM-DD`. */
function CinevDateDmyInput({ valueYmd, onValueYmdChange, style, disabled }) {
  useLang();
  const [text, setText] = React.useState(() => pdYmdToDmyOrEmpty(valueYmd));
  React.useEffect(() => {
    setText(pdYmdToDmyOrEmpty(valueYmd));
  }, [valueYmd]);
  return (
    <input
      type="text"
      inputMode="numeric"
      autoComplete="off"
      spellCheck={false}
      disabled={disabled}
      placeholder={t('pd_date_dmy_ph')}
      value={text}
      onChange={(e) => {
        const v = e.target.value;
        setText(v);
        if (!String(v).trim()) {
          onValueYmdChange('');
          return;
        }
        const ymd = pdDmyToYmd(v);
        if (ymd) onValueYmdChange(ymd);
      }}
      onBlur={() => {
        const y = pdDmyToYmd(text);
        if (y) setText(pdYmdToDmyOrEmpty(y));
        else setText(pdYmdToDmyOrEmpty(valueYmd));
      }}
      style={style}
    />
  );
}

function pdDotFromSessionRow(s) {
  const mapDot =
    typeof window.mapAppointmentStatusToSessionDot === 'function'
      ? window.mapAppointmentStatusToSessionDot
      : (apSt) =>
          apSt === 'done'
            ? 'completed'
            : apSt === 'no_show'
              ? 'no_show'
              : apSt === 'rescheduled'
                ? 'rescheduled'
                : 'scheduled';
  if (s.appointment_status === 'cancelled') return 'scheduled';
  if (s.appointment_status) return mapDot(s.appointment_status);
  if (s.clinical_status === 'done') return 'completed';
  if (s.clinical_status === 'missed') return 'no_show';
  return 'scheduled';
}

function pdApptPillStatus(s) {
  const st = s.appointment_status;
  if (st === 'done') return 'completed';
  if (st === 'no_show') return 'no_show';
  if (st === 'rescheduled') return 'rescheduled';
  if (st === 'cancelled') return 'cancelled';
  if (s.clinical_status === 'done') return 'completed';
  if (s.clinical_status === 'missed') return 'no_show';
  return 'scheduled';
}

/** i18n-Key für Meilenstein- / Sondertermin-Art (API `kind`). */
function milestoneTitleKey(kind) {
  if (kind === 'parent_talk') return 'pd_milestone_parent';
  if (kind === 'school_talk') return 'pd_milestone_school';
  if (kind === 'closing') return 'pd_milestone_closing';
  if (kind === 'doctor') return 'sk_doctor';
  if (kind === 'diag') return 'sk_diag';
  return 'sk_other';
}

function pdFormatRuleLine(plan, lang) {
  if (!plan || plan.rule_weekday == null) return '';
  const wdDe = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
  const wdEs = ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'];
  const wd = lang === 'es' ? wdEs : wdDe;
  const wn = wd[Number(plan.rule_weekday)] || '';
  const rt = String(plan.rule_time || '').slice(0, 5);
  const n = plan.sessions_per_phase || 12;
  if (lang === 'es') {
    return `Regla semanal: ${wn} · ${rt} · ${n} sesiones por fase`;
  }
  return `Wöchentlicher Regeltermin: ${wn} · ${rt} Uhr · ${n} Sitzungen pro Phase`;
}

function pdFormatChronicleWhen(isoStart, isoEnd, lang) {
  const tz = cinevPracticeWallTimezone();
  const loc = lang === 'es' ? 'es-CO' : 'de-DE';
  const wd = new Intl.DateTimeFormat(loc, { weekday: 'short', timeZone: tz }).format(new Date(isoStart));
  const datePart = new Intl.DateTimeFormat(loc, { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: tz }).format(
    new Date(isoStart),
  );
  const t1 = weekHourHmFromIso(isoStart);
  const t2 = isoEnd ? weekHourHmFromIso(isoEnd) : '';
  return t2 ? `${wd}, ${datePart} · ${t1}–${t2}` : `${wd}, ${datePart} · ${t1}`;
}

function chronNoteForChronicleStatus(chronS) {
  if (chronS === 'completed') return t('pd_done');
  if (chronS === 'now') return t('pp_today_badge');
  if (chronS === 'no_show') return t('pd_no_show_label');
  if (chronS === 'rescheduled') return t('pd_rescheduled_label');
  return t('pd_planned');
}

function buildLiveChronicleFromTimeline(timeline, lang) {
  const sessions = timeline.sessions || [];
  return sessions.map((s) => {
    const chronS = pdDotFromSessionRow(s);
    const n = s.session_index;
    const date = s.start_at ? pdFormatChronicleWhen(s.start_at, s.end_at, lang) : pdYmdToDmy(s.planned_date);
    return { n, s: chronS, date, note: chronNoteForChronicleStatus(chronS) };
  });
}

function buildLivePhaseStructure(timeline, lang, row) {
  const phases = timeline.phases || [];
  const sessions = timeline.sessions || [];
  const locBase = lang === 'es' ? 'Consulta' : 'Praxis';

  return phases.map((ph) => {
    const phSessions = sessions.filter((s) => s.therapy_phase_id === ph.id);
    const slots = phSessions.map((s) => pdDotFromSessionRow(s));
    const appointments = phSessions.map((s) => {
      const st = pdApptPillStatus(s);
      const dateStr = s.start_at ? pdIsoToDmy(s.start_at) : pdYmdToDmy(s.planned_date);
      const timeStr = s.start_at
        ? `${weekHourHmFromIso(s.start_at)}${s.end_at ? `–${weekHourHmFromIso(s.end_at)}` : ''}`
        : '—';
      const therapistId =
        s.therapist_id != null && String(s.therapist_id).trim() !== '' ? String(s.therapist_id) : null;
      const hasTherapist = Boolean(therapistId);
      const locRaw = s.location_text != null && String(s.location_text).trim() !== ''
        ? String(s.location_text).trim()
        : '';
      const location = locRaw || (hasTherapist ? locBase : t('pd_placeholder_dash'));
      const typeKey = displayTypeKeyFromSessionRow(s);
      return {
        slot: s.session_index != null ? Number(s.session_index) + 1 : '?',
        st,
        dateStr,
        timeStr,
        location,
        therapistId,
        typeKey,
        therapyModality: s.therapy_modality ?? null,
        sessionTypeValue: s.session_type ?? 'treatment',
        locationRaw: locRaw,
        slotLocked: st === 'completed',
        // IDs + Absage-Detail für Modal
        appointmentId: s.appointment_id ?? null,
        treatmentSessionId: s.treatment_session_id ?? null,
        outcomeLocked: s.outcome_locked ?? false,
        cancellationInitiator: s.cancellation_initiator ?? null,
        cancellationPatientDetail: s.cancellation_patient_detail ?? null,
        cancellationNoticeTiming: s.cancellation_notice_timing ?? null,
        cancellationNote: s.cancellation_note ?? null,
        appointmentNote: s.note ?? '',
        alreadyCancelled: s.appointment_status === 'cancelled',
        startIso: s.start_at || null,
        endIso: s.end_at || null,
      };
    });
    const phaseSpecials = (timeline.specials || []).filter((s) => s.therapy_phase_id === ph.id);
    const milestones = phaseSpecials.map((s) => {
      const rowLike = { appointment_status: s.appointment_status, clinical_status: 'planned' };
      const st = pdApptPillStatus(rowLike);
      const dateStr = s.start_at ? pdIsoToDmy(s.start_at) : '—';
      const timeStr = s.start_at
        ? `${weekHourHmFromIso(s.start_at)}${s.end_at ? `–${weekHourHmFromIso(s.end_at)}` : ''}`
        : '—';
      const therapistId =
        s.therapist_id != null && String(s.therapist_id).trim() !== '' ? String(s.therapist_id) : null;
      const hasTherapist = Boolean(therapistId);
      const mLocRaw = s.location_text != null && String(s.location_text).trim() !== '' ? String(s.location_text).trim() : '';
      return {
        key: s.id,
        titleKey: milestoneTitleKey(s.kind),
        dateStr,
        timeStr,
        location: mLocRaw || (hasTherapist ? locBase : t('pd_placeholder_dash')),
        locationRaw: mLocRaw,
        therapistId,
        st,
        appointmentId: s.id,
        kind: s.kind,
        outcomeLocked: Boolean(s.outcome_locked),
        cancellationInitiator: s.cancellation_initiator ?? null,
        cancellationPatientDetail: s.cancellation_patient_detail ?? null,
        cancellationNoticeTiming: s.cancellation_notice_timing ?? null,
        cancellationNote: s.cancellation_note ?? null,
        appointmentNote: s.note ?? '',
        alreadyCancelled: s.appointment_status === 'cancelled',
        startIso: s.start_at || null,
        endIso: s.end_at || null,
      };
    });
    const rawStatus = ph.status;
    const status =
      rawStatus === 'active' || rawStatus === 'completed' || rawStatus === 'planned'
        ? rawStatus
        : 'planned';
    return {
      index: Number(ph.phase_index) + 1,
      therapyPhaseId: ph.id,
      status,
      slots,
      appointments,
      milestones,
    };
  });
}

function phaseStatusBadgeLabel(status) {
  if (status === 'active') return t('pd_phase_status_active');
  if (status === 'completed') return t('pd_phase_status_completed');
  return t('pd_phase_status_planned');
}

/** Gleiche Maße/Typo wie „Monatsansicht“: Outline-Kachel für Toolbar + Phasenstatus (nicht klickbar). */
const phasePlanOutlineControlStyle = {
  boxSizing: 'border-box',
  display: 'inline-flex',
  alignItems: 'center',
  justifyContent: 'center',
  background: CINEV.card,
  border: `1px solid ${CINEV.line}`,
  padding: '8px 12px',
  borderRadius: 6,
  fontSize: 12,
  fontWeight: 500,
  fontFamily: 'inherit',
  whiteSpace: 'nowrap',
  lineHeight: 1.2,
};

const phasePlanMonthBtnStyle = {
  ...phasePlanOutlineControlStyle,
  color: CINEV.teal,
  cursor: 'pointer',
};

function phasePlanPhaseBadgeStyle(status) {
  return {
    ...phasePlanOutlineControlStyle,
    color: status === 'active' ? CINEV.teal : CINEV.inkMute,
    cursor: 'default',
  };
}

/** Phasenplan-Tabelle: echte Buttons statt td-onClick — sonst feuern Klicks in manchen Browsern/Scroll-Containern nicht. */
function PhasePlanTableInteractiveTd({
  interactive,
  onActivate,
  style,
  flexInner,
  children,
}) {
  if (!interactive) {
    return <td style={style}>{children}</td>;
  }
  return (
    <td style={{ ...style, padding: 0, verticalAlign: 'middle' }}>
      <button
        type="button"
        onClick={(e) => {
          e.stopPropagation();
          onActivate();
        }}
        onMouseEnter={(e) => {
          e.currentTarget.style.background = CINEV.soft;
        }}
        onMouseLeave={(e) => {
          e.currentTarget.style.background = 'transparent';
        }}
        style={{
          display: flexInner ? 'inline-flex' : 'block',
          alignItems: flexInner ? 'center' : undefined,
          justifyContent: flexInner ? 'center' : undefined,
          flexWrap: flexInner ? 'wrap' : undefined,
          gap: flexInner ? 6 : undefined,
          width: '100%',
          minHeight: 44,
          margin: 0,
          border: 'none',
          background: 'transparent',
          font: 'inherit',
          color: 'inherit',
          cursor: 'pointer',
          textAlign: style.textAlign ?? 'center',
          boxSizing: 'border-box',
          padding: '10px 8px',
          verticalAlign: 'middle',
          fontVariantNumeric: style.fontVariantNumeric,
          fontWeight: style.fontWeight,
          fontSize: style.fontSize,
        }}
      >{children}</button>
    </td>
  );
}

function PhasePlanPanel({ row, therapistApiMap, liveTimeline, onRefresh, onRequestSpecialAppt, specialApptEnabled }) {
  useLang();
  const lang = window.__cinevLang || 'de';
  const phasesRaw = React.useMemo(
    () => (liveTimeline ? buildLivePhaseStructure(liveTimeline, lang, row) : []),
    [liveTimeline, lang, row],
  );
  /** Jüngere Phase (höhere Nummer) zuerst; Sitzungen innerhalb einer Phase weiter 1…12 aufsteigend. */
  const phases = React.useMemo(
    () => [...phasesRaw].sort((a, b) => b.index - a.index),
    [phasesRaw],
  );
  const phasePanelResetKey = `${row?.id ?? ''}|${liveTimeline?.plan?.plan_id ?? ''}`;
  const [sessExp, setSessExp] = React.useState({});
  const [milExp, setMilExp] = React.useState({});

  React.useEffect(() => {
    if (!phases.length) {
      setSessExp({});
      setMilExp({});
      return;
    }
    const nextSess = {};
    const nextMil = {};
    for (const ph of phases) {
      const open = ph.status === 'active';
      nextSess[ph.index] = open;
      nextMil[ph.index] = open;
    }
    setSessExp(nextSess);
    setMilExp(nextMil);
  }, [phasePanelResetKey, phases.length]);
  const [monthOpen, setMonthOpen] = React.useState(false);
  const [addPhaseOpen, setAddPhaseOpen] = React.useState(false);
  const [apStart, setApStart] = React.useState(() => cinevYmdWallToday());
  const [apWd, setApWd] = React.useState(4);
  const [apHour, setApHour] = React.useState(CINEV_DEFAULT_APPOINTMENT_START_HOUR);
  const [apDur, setApDur] = React.useState(CINEV_DEFAULT_APPOINTMENT_DURATION_MIN);
  const [apCount, setApCount] = React.useState(12);
  const [apBusy, setApBusy] = React.useState(false);
  const [apErr, setApErr] = React.useState('');
  const [outcomeModal, setOutcomeModal] = React.useState(null); // { appointmentId, …, allowTimeEdit, isSpecial, startIso, endIso }
  const [sessionEditor, setSessionEditor] = React.useState(null);
  const [therapistRows, setTherapistRows] = React.useState([]);
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const j = await cinevApiGet('/therapists');
        if (!cancelled && j && j.items) setTherapistRows(j.items);
      } catch {
        /* Liste optional */
      }
    })();
    return () => { cancelled = true; };
  }, []);
  const highlightKeys = collectCalendarKeys(phases);
  const isLive = Boolean(liveTimeline);
  const planForAppend = liveTimeline?.plan;
  const planIdForAppend = planForAppend?.plan_id;
  const nextPhaseNum = React.useMemo(() => {
    if (phases.length) return Math.max(...phases.map((p) => p.index)) + 1;
    const raw = liveTimeline?.phases || [];
    if (raw.length) return Math.max(...raw.map((p) => Number(p.phase_index) + 1)) + 1;
    return 1;
  }, [phases, liveTimeline?.phases]);

  const openAddPhaseModal = () => {
    if (!planIdForAppend) return;
    const p = planForAppend;
    const wd = p?.rule_weekday != null ? Number(p.rule_weekday) : 4;
    const rt = String(p?.rule_time || '09:00:00');
    const h = parseInt(rt.slice(0, 2), 10);
    setApErr('');
    setApWd(Number.isFinite(wd) ? wd : 4);
    setApHour(Number.isFinite(h) && h >= 0 && h <= 23 ? h : 9);
    setApDur(CINEV_DEFAULT_APPOINTMENT_DURATION_MIN);
    setApCount(p?.sessions_per_phase != null ? Number(p.sessions_per_phase) : 12);
    setApStart(cinevYmdWallToday());
    setAddPhaseOpen(true);
  };

  const submitAddPhase = async () => {
    if (!planIdForAppend) return;
    setApBusy(true);
    setApErr('');
    try {
      await cinevApiPost(`/plans/${encodeURIComponent(planIdForAppend)}/phases`, {
        startDate: apStart,
        ruleWeekday: Number(apWd),
        ruleTime: `${String(apHour).padStart(2, '0')}:00`,
        durationMinutes: Number(apDur),
        sessionCount: Number(apCount),
      });
      setAddPhaseOpen(false);
      if (onRefresh) onRefresh();
    } catch (e) {
      setApErr(e.message || String(e));
    } finally {
      setApBusy(false);
    }
  };

  const toggle = (setter, idx) => {
    setter((prev) => ({ ...prev, [idx]: !prev[idx] }));
  };

  const phaseAppendInput = {
    width: '100%',
    marginTop: 4,
    padding: '8px 10px',
    borderRadius: 6,
    border: `1px solid ${CINEV.line}`,
    fontFamily: 'inherit',
    fontSize: 13,
    fontVariantNumeric: 'tabular-nums',
    fontFeatureSettings: '"tnum"',
    boxSizing: 'border-box',
  };

  /** Außerhalb von overflow/transform-Vorfahren rendern (Modals sonst nicht sichtbar). */
  const renderPhasePlanPortal = (node) => {
    try {
      const RD = typeof window !== 'undefined' ? window.ReactDOM : null;
      if (typeof document !== 'undefined' && RD && typeof RD.createPortal === 'function') {
        return RD.createPortal(node, document.body);
      }
    } catch {
      /* Fallback: Modal im Baum */
    }
    return node;
  };

  return (
    <div style={{ background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`, padding: '18px 20px', marginBottom: 16 }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12, marginBottom: 8 }}>
        <div>
          <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>{t('pd_phases_title')}</div>
          <div style={{ fontSize: 11.5, color: CINEV.inkMute, lineHeight: 1.45 }}>
            {liveTimeline
              ? pdFormatRuleLine(liveTimeline.plan, lang)
              : (row.fromLiveApi ? t('pd_no_plan_timeline') : t('pd_s4_live_only'))}
          </div>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', flexShrink: 0 }}>
          {planIdForAppend ? (
            <button type="button" onClick={openAddPhaseModal} style={phasePlanMonthBtnStyle}>{t('pd_add_phase')}</button>
          ) : null}
          <button type="button" onClick={() => setMonthOpen(true)} style={phasePlanMonthBtnStyle}>{t('pd_open_month')}</button>
        </div>
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        {phases.map((ph) => {
          const defaultOpen = ph.status === 'active';
          const se = sessExp[ph.index] !== undefined ? sessExp[ph.index] : defaultOpen;
          const me = milExp[ph.index] !== undefined ? milExp[ph.index] : defaultOpen;
          return (
            <div key={ph.index} style={{
              border: `1px solid ${CINEV.lineSoft}`,
              borderRadius: 10,
              background: CINEV.bg,
              padding: '14px 16px',
            }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10, flexWrap: 'wrap', gap: 8 }}>
                <div>
                  <span style={{ fontSize: 12.5, fontWeight: 600 }}>{t('pd_phase_label')} {ph.index}</span>
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                  {isLive && specialApptEnabled && ph.therapyPhaseId && typeof onRequestSpecialAppt === 'function' ? (
                    <button
                      type="button"
                      onClick={() => onRequestSpecialAppt(String(ph.therapyPhaseId))}
                      style={phasePlanMonthBtnStyle}
                    >{t('add_special')}</button>
                  ) : null}
                  <span style={phasePlanPhaseBadgeStyle(ph.status)}>
                    {phaseStatusBadgeLabel(ph.status)}
                  </span>
                </div>
              </div>

              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8, flexWrap: 'wrap', gap: 8 }}>
                <div style={{ fontSize: 10.5, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5 }}>{t('pd_sessions_in_phase')}</div>
                <button type="button" onClick={() => toggle(setSessExp, ph.index)} style={{
                  background: 'transparent', border: 'none', color: CINEV.teal, fontSize: 11.5, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit',
                }}>{se ? t('pd_collapse_sessions') : t('pd_expand_sessions')}</button>
              </div>
              {!se ? (
                <SessionDots total={Math.max(ph.slots.length, 1)} statuses={ph.slots} size={11} gap={5} />
              ) : (
                <div style={{ border: `1px solid ${CINEV.line}`, borderRadius: 8, overflow: 'hidden', background: CINEV.card }}>
                  <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
                    <thead style={{ background: CINEV.bg }}>
                      <tr>
                        <PdTh>#</PdTh>
                        <PdTh>{t('pd_th_date')}</PdTh>
                        <PdTh>{t('pd_th_time')}</PdTh>
                        <PdTh>{t('pd_th_location')}</PdTh>
                        <PdTh>{t('pd_th_therapist')}</PdTh>
                        <PdTh>{t('pd_th_session_kind')}</PdTh>
                        <PdTh>{t('pd_th_status')}</PdTh>
                      </tr>
                    </thead>
                    <tbody>
                      {ph.appointments.map((a) => {
                        const pill = appointmentStatusPill(a.st, a);
                        const canOpenOutcome = isLive && Boolean(a.appointmentId) && a.st !== 'now';
                        const allowTimeEdit = isLive && Boolean(a.appointmentId) && a.startIso && a.endIso
                          && !['completed', 'now', 'cancelled'].includes(a.st);
                        const canOpenSessionEditor = isLive && Boolean(a.appointmentId) && Boolean(a.startIso)
                          && !['completed', 'now', 'cancelled'].includes(a.st);
                        const sessionOutcomeModal = {
                          appointmentId: a.appointmentId,
                          alreadyCancelled: a.alreadyCancelled,
                          cancellationInitiator: a.cancellationInitiator,
                          cancellationPatientDetail: a.cancellationPatientDetail,
                          cancellationNoticeTiming: a.cancellationNoticeTiming,
                          cancellationNote: a.cancellationNote,
                          appointmentNote: a.appointmentNote,
                          treatmentNoteEditOnly: a.st === 'completed',
                          allowTimeEdit,
                          outcomeTimeSuppressed: true,
                          isSpecial: false,
                          startIso: a.startIso,
                          endIso: a.endIso,
                        };
                        const openSessionOutcome = () => setOutcomeModal(sessionOutcomeModal);
                        const phaseTargets = ph.appointments.map((x) => ({
                          slot: x.slot,
                          appointmentId: x.appointmentId,
                          treatmentSessionId: x.treatmentSessionId,
                          startIso: x.startIso,
                          endIso: x.endIso,
                          locked: !x.appointmentId || x.slotLocked,
                        }));
                        const openSessionEditor = () => {
                          if (!canOpenSessionEditor) return;
                          setSessionEditor({
                            isSpecial: false,
                            phaseTargets,
                            currentSlot: a.slot,
                            appointmentId: a.appointmentId,
                            treatmentSessionId: a.treatmentSessionId,
                            initialLocationText: a.locationRaw ?? '',
                            initialTherapistId: a.therapistId,
                            initialTherapyModality: a.therapyModality ?? '',
                            startIso: a.startIso,
                            endIso: a.endIso,
                          });
                        };
                        const statusPillStyle = {
                          display: 'inline-block',
                          fontSize: 10,
                          padding: '3px 8px',
                          borderRadius: 10,
                          fontWeight: 600,
                          background: pill.bg,
                          color: pill.fg,
                        };
                        return (
                          <tr
                            key={a.slot}
                            title={canOpenOutcome && a.outcomeLocked ? t('pd_outcome_locked_hint') : undefined}
                            style={{ borderTop: `1px solid ${CINEV.lineSoft}` }}
                          >
                            <PhasePlanTableInteractiveTd
                              interactive={canOpenSessionEditor}
                              onActivate={openSessionEditor}
                              style={{ ...phasePlanTableTdBase, fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}
                            >{a.slot}</PhasePlanTableInteractiveTd>
                            <PhasePlanTableInteractiveTd
                              interactive={canOpenSessionEditor}
                              onActivate={openSessionEditor}
                              style={{ ...phasePlanTableTdBase, color: CINEV.inkSoft, fontVariantNumeric: 'tabular-nums' }}
                            >{a.dateStr}</PhasePlanTableInteractiveTd>
                            <PhasePlanTableInteractiveTd
                              interactive={canOpenSessionEditor}
                              onActivate={openSessionEditor}
                              style={{ ...phasePlanTableTdBase, fontVariantNumeric: 'tabular-nums' }}
                            >{a.timeStr}</PhasePlanTableInteractiveTd>
                            <PhasePlanTableInteractiveTd
                              interactive={canOpenSessionEditor}
                              onActivate={openSessionEditor}
                              style={{ ...phasePlanTableTdBase, color: CINEV.inkSoft, fontSize: 11.5 }}
                            >{a.location}</PhasePlanTableInteractiveTd>
                            <PhasePlanTableInteractiveTd
                              interactive={canOpenSessionEditor}
                              onActivate={openSessionEditor}
                              flexInner
                              style={{ ...phasePlanTableTdBase }}
                            >
                              <Avatar therapist={a.therapistId} size={20} therapistApiMap={therapistApiMap} />
                              <span style={{ fontSize: 11.5 }}>{therapistDisplayName(a.therapistId, therapistApiMap)}</span>
                            </PhasePlanTableInteractiveTd>
                            <PhasePlanTableInteractiveTd
                              interactive={canOpenSessionEditor}
                              onActivate={openSessionEditor}
                              style={{ ...phasePlanTableTdBase, color: CINEV.inkSoft, fontSize: 11.5 }}
                            >{t(a.typeKey)}</PhasePlanTableInteractiveTd>
                            <PhasePlanTableInteractiveTd
                              interactive={canOpenOutcome}
                              onActivate={openSessionOutcome}
                              style={{ ...phasePlanTableTdBase }}
                            >
                              <span style={statusPillStyle}>{pill.tx}</span>
                            </PhasePlanTableInteractiveTd>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
                </div>
              )}

              {/* Meilensteine — immer sichtbar; Empty State wenn keine Daten */}
              <div style={{ marginTop: 14, paddingTop: 12, borderTop: `1px solid ${CINEV.lineSoft}` }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
                  <div style={{ fontSize: 10.5, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5 }}>{t('pd_milestone_badge')}</div>
                  {ph.milestones && ph.milestones.length > 0 && (
                    <button type="button" onClick={() => toggle(setMilExp, ph.index)} style={{
                      background: 'transparent', border: 'none', color: CINEV.teal, fontSize: 11.5, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit',
                    }}>{me ? t('pd_collapse_milestones') : t('pd_expand_milestones')}</button>
                  )}
                </div>
                {(!ph.milestones || ph.milestones.length === 0) ? (
                  <div style={{ fontSize: 11.5, color: CINEV.inkMute, fontStyle: 'italic' }}>{t('pd_milestones_empty')}</div>
                ) : me ? (
                  <div style={{ border: `1px solid ${CINEV.line}`, borderRadius: 8, overflow: 'hidden', background: CINEV.card }}>
                    <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
                      <thead style={{ background: CINEV.bg }}>
                        <tr>
                          <PdTh>#</PdTh>
                          <PdTh>{t('pd_th_date')}</PdTh>
                          <PdTh>{t('pd_th_time')}</PdTh>
                          <PdTh>{t('pd_th_location')}</PdTh>
                          <PdTh>{t('pd_th_therapist')}</PdTh>
                          <PdTh>{t('pd_th_session_kind')}</PdTh>
                          <PdTh>{t('pd_th_status')}</PdTh>
                        </tr>
                      </thead>
                      <tbody>
                        {ph.milestones.map((m, mi) => {
                          const pill = appointmentStatusPill(m.st, m);
                          const milestoneRowNum = mi + 1;
                          const canOpenOutcome = isLive && Boolean(m.appointmentId) && m.st !== 'now';
                          const allowTimeEdit = isLive && Boolean(m.appointmentId) && m.startIso && m.endIso
                            && !['completed', 'now', 'cancelled'].includes(m.st);
                          const canOpenSessionEditor = isLive && Boolean(m.appointmentId) && Boolean(m.startIso)
                            && !['completed', 'now', 'cancelled'].includes(m.st);
                          const milestoneOutcomeModal = {
                            appointmentId: m.appointmentId,
                            alreadyCancelled: m.alreadyCancelled,
                            cancellationInitiator: m.cancellationInitiator,
                            cancellationPatientDetail: m.cancellationPatientDetail,
                            cancellationNoticeTiming: m.cancellationNoticeTiming,
                            cancellationNote: m.cancellationNote,
                            appointmentNote: m.appointmentNote,
                            allowTimeEdit,
                            isSpecial: true,
                            startIso: m.startIso,
                            endIso: m.endIso,
                          };
                          const openMilestoneOutcome = () => setOutcomeModal(milestoneOutcomeModal);
                          const openMilestoneEditor = () => {
                            if (!canOpenSessionEditor) return;
                            setSessionEditor({
                              isSpecial: true,
                              phaseTargets: [{
                                slot: 1,
                                appointmentId: m.appointmentId,
                                treatmentSessionId: null,
                                startIso: m.startIso,
                                endIso: m.endIso,
                                locked: false,
                              }],
                              currentSlot: 1,
                              appointmentId: m.appointmentId,
                              treatmentSessionId: null,
                              initialLocationText: m.locationRaw ?? '',
                              initialTherapistId: m.therapistId,
                              initialTherapyModality: '',
                              startIso: m.startIso,
                              endIso: m.endIso,
                            });
                          };
                          const msPillStyle = {
                            display: 'inline-block',
                            fontSize: 10,
                            padding: '3px 8px',
                            borderRadius: 10,
                            fontWeight: 600,
                            background: pill.bg,
                            color: pill.fg,
                          };
                          return (
                            <tr
                              key={m.key}
                              title={canOpenOutcome && m.outcomeLocked ? t('pd_outcome_locked_hint') : undefined}
                              style={{ borderTop: `1px solid ${CINEV.lineSoft}` }}
                            >
                              <PhasePlanTableInteractiveTd
                                interactive={canOpenSessionEditor}
                                onActivate={openMilestoneEditor}
                                style={{ ...phasePlanTableTdBase, fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}
                              >{milestoneRowNum}</PhasePlanTableInteractiveTd>
                              <PhasePlanTableInteractiveTd
                                interactive={canOpenSessionEditor}
                                onActivate={openMilestoneEditor}
                                style={{ ...phasePlanTableTdBase, color: CINEV.inkSoft, fontVariantNumeric: 'tabular-nums' }}
                              >{m.dateStr}</PhasePlanTableInteractiveTd>
                              <PhasePlanTableInteractiveTd
                                interactive={canOpenSessionEditor}
                                onActivate={openMilestoneEditor}
                                style={{ ...phasePlanTableTdBase, fontVariantNumeric: 'tabular-nums' }}
                              >{m.timeStr}</PhasePlanTableInteractiveTd>
                              <PhasePlanTableInteractiveTd
                                interactive={canOpenSessionEditor}
                                onActivate={openMilestoneEditor}
                                style={{ ...phasePlanTableTdBase, color: CINEV.inkSoft, fontSize: 11.5 }}
                              >{m.location}</PhasePlanTableInteractiveTd>
                              <PhasePlanTableInteractiveTd
                                interactive={canOpenSessionEditor}
                                onActivate={openMilestoneEditor}
                                flexInner
                                style={{ ...phasePlanTableTdBase }}
                              >
                                <Avatar therapist={m.therapistId} size={20} therapistApiMap={therapistApiMap} />
                                <span style={{ fontSize: 11.5 }}>{therapistDisplayName(m.therapistId, therapistApiMap)}</span>
                              </PhasePlanTableInteractiveTd>
                              <PhasePlanTableInteractiveTd
                                interactive={canOpenSessionEditor}
                                onActivate={openMilestoneEditor}
                                style={{ ...phasePlanTableTdBase, color: CINEV.inkSoft, fontSize: 11.5 }}
                              >{t(m.titleKey)}</PhasePlanTableInteractiveTd>
                              <PhasePlanTableInteractiveTd
                                interactive={canOpenOutcome}
                                onActivate={openMilestoneOutcome}
                                style={{ ...phasePlanTableTdBase }}
                              >
                                <span style={msPillStyle}>{pill.tx}</span>
                              </PhasePlanTableInteractiveTd>
                            </tr>
                          );
                        })}
                      </tbody>
                    </table>
                  </div>
                ) : (
                  <div style={{ fontSize: 11, color: CINEV.inkMute }}>{ph.milestones.length} {t('pd_milestone_badge')}</div>
                )}
              </div>
            </div>
          );
        })}
      </div>
      {addPhaseOpen && renderPhasePlanPortal(
        <div
          onClick={() => !apBusy && setAddPhaseOpen(false)}
          style={{
            position: 'fixed', inset: 0, background: 'rgba(20,30,35,0.45)', zIndex: CINEV_PHASE_PLAN_MODAL_Z,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}
        >
          <div
            onClick={(e) => e.stopPropagation()}
            style={{
              background: CINEV.card, borderRadius: 12, padding: '20px 22px', width: 420,
              maxWidth: 'calc(100vw - 32px)', boxShadow: '0 20px 60px rgba(0,0,0,0.25)', border: `1px solid ${CINEV.line}`,
            }}
          >
            <div style={{ fontSize: 16, fontWeight: 600, marginBottom: 8 }}>
              {t('pd_add_phase_modal_title').replace('{n}', String(nextPhaseNum))}
            </div>
            <div style={{ fontSize: 11.5, color: CINEV.inkMute, lineHeight: 1.45, marginBottom: 16 }}>{t('pd_add_phase_hint')}</div>
            <div style={{ display: 'grid', gap: 12 }}>
              <label style={{ fontSize: 11, color: CINEV.inkMute }}>
                {t('pd_add_phase_start')}
                <CinevDateDmyInput valueYmd={apStart} onValueYmdChange={setApStart} style={phaseAppendInput} />
              </label>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                <label style={{ fontSize: 11, color: CINEV.inkMute }}>
                  {t('pd_add_phase_weekday')}
                  <select value={apWd} onChange={(e) => setApWd(Number(e.target.value))} style={phaseAppendInput}>
                    {[0, 1, 2, 3, 4, 5, 6].map((d) => (
                      <option key={d} value={d}>{t(`pd_cal_dow${d}`)}</option>
                    ))}
                  </select>
                </label>
                <label style={{ fontSize: 11, color: CINEV.inkMute }}>
                  {t('pd_add_phase_time')}
                  <select value={apHour} onChange={(e) => setApHour(Number(e.target.value))} style={phaseAppendInput}>
                    {Array.from({ length: 14 }, (_, i) => i + 7).map((h) => (
                      <option key={h} value={h}>{String(h).padStart(2, '0')}:00</option>
                    ))}
                  </select>
                </label>
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                <label style={{ fontSize: 11, color: CINEV.inkMute }}>
                  {t('pd_add_phase_duration')}
                  <input
                    type="number"
                    min={15}
                    max={240}
                    step={5}
                    value={apDur}
                    onChange={(e) => setApDur(Number(e.target.value))}
                    style={{ ...phaseAppendInput, fontVariantNumeric: 'tabular-nums' }}
                  />
                </label>
                <label style={{ fontSize: 11, color: CINEV.inkMute }}>
                  {t('pd_add_phase_repeats')}
                  <input
                    type="number"
                    min={1}
                    max={52}
                    value={apCount}
                    onChange={(e) => setApCount(Number(e.target.value))}
                    style={{ ...phaseAppendInput, fontVariantNumeric: 'tabular-nums' }}
                  />
                </label>
              </div>
            </div>
            {apErr ? (
              <div style={{ marginTop: 12, fontSize: 12, color: '#c94343', lineHeight: 1.35 }}>{apErr}</div>
            ) : null}
            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10, marginTop: 18, flexWrap: 'wrap' }}>
              <button type="button" disabled={apBusy} onClick={() => setAddPhaseOpen(false)} style={wkBtn()}>{t('pd_add_phase_cancel')}</button>
              <button
                type="button"
                disabled={apBusy}
                onClick={() => void submitAddPhase()}
                style={{ ...wkBtn(), background: CINEV.teal, color: '#fff', borderColor: CINEV.teal }}
              >
                {apBusy ? t('pd_add_phase_busy') : t('pd_add_phase_submit')}
              </button>
            </div>
          </div>
        </div>,
      )}
      {monthOpen && renderPhasePlanPortal(
        <PhaseMonthModal onClose={() => setMonthOpen(false)} highlightKeys={highlightKeys} />,
      )}
      {outcomeModal && renderPhasePlanPortal(
        <AppointmentOutcomeModal
          appointmentId={outcomeModal.appointmentId}
          alreadyCancelled={outcomeModal.alreadyCancelled}
          cancellationInitiator={outcomeModal.cancellationInitiator}
          cancellationPatientDetail={outcomeModal.cancellationPatientDetail}
          cancellationNoticeTiming={outcomeModal.cancellationNoticeTiming}
          cancellationNote={outcomeModal.cancellationNote}
          appointmentNote={outcomeModal.appointmentNote}
          treatmentNoteEditOnly={Boolean(outcomeModal.treatmentNoteEditOnly)}
          allowTimeEdit={Boolean(outcomeModal.allowTimeEdit)}
          isSpecial={Boolean(outcomeModal.isSpecial)}
          outcomeTimeSuppressed={Boolean(outcomeModal.outcomeTimeSuppressed)}
          startIso={outcomeModal.startIso}
          endIso={outcomeModal.endIso}
          onClose={() => setOutcomeModal(null)}
          onSaved={() => { setOutcomeModal(null); if (onRefresh) onRefresh(); }}
        />,
      )}
      {sessionEditor && renderPhasePlanPortal(
        <PhaseSessionEditorModal
          isSpecial={Boolean(sessionEditor.isSpecial)}
          appointmentId={sessionEditor.appointmentId}
          currentSlot={sessionEditor.currentSlot}
          phaseTargets={sessionEditor.phaseTargets}
          initialLocationText={sessionEditor.initialLocationText}
          initialTherapistId={sessionEditor.initialTherapistId}
          initialTherapyModality={sessionEditor.initialTherapyModality}
          startIso={sessionEditor.startIso}
          endIso={sessionEditor.endIso}
          therapistRows={therapistRows}
          onClose={() => setSessionEditor(null)}
          onSaved={() => { setSessionEditor(null); if (onRefresh) onRefresh(); }}
        />,
      )}
    </div>
  );
}

// ======================= PATIENT DETAIL — Slice 004 (Vertretung + Bezugspersonen) =========
function guardianRelationLabel(type) {
  const m = {
    mother: 'pd_s4_gua_rel_mother',
    father: 'pd_s4_gua_rel_father',
    legal_guardian: 'pd_s4_gua_rel_legal',
    other: 'pd_s4_gua_rel_other',
  };
  return t(m[type] || 'pd_s4_gua_rel_other');
}

function GuardianLinkModal({ patientId, link, onClose, onSaved }) {
  useLang();
  const isEdit = link != null;
  const [addMode, setAddMode] = React.useState('search');
  const [searchQ, setSearchQ] = React.useState('');
  const [searchResults, setSearchResults] = React.useState([]);
  const [searchLoading, setSearchLoading] = React.useState(false);
  const [picked, setPicked] = React.useState(null);

  const [firstName, setFirstName] = React.useState(link?.first_name || '');
  const [lastName, setLastName] = React.useState(link?.last_name || '');
  const [email, setEmail] = React.useState(link?.email || '');
  const [phone, setPhone] = React.useState(link?.phone || '');
  const [preferredLang, setPreferredLang] = React.useState(link?.preferred_lang || 'de');
  const [relationshipType, setRelationshipType] = React.useState(link?.relationship_type || 'legal_guardian');
  const [isPrimary, setIsPrimary] = React.useState(Boolean(link?.is_primary));
  const [canReceiveMails, setCanReceiveMails] = React.useState(Boolean(link?.can_receive_mails));
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');

  React.useEffect(() => {
    if (isEdit || addMode !== 'search') return undefined;
    let cancelled = false;
    const tm = setTimeout(async () => {
      setSearchLoading(true);
      try {
        const qs = new URLSearchParams();
        qs.set('patientId', patientId);
        if (searchQ.trim()) qs.set('q', searchQ.trim());
        const data = await cinevApiGet(`/guardians/search?${qs.toString()}`);
        if (!cancelled) setSearchResults(data.items || []);
      } catch {
        if (!cancelled) setSearchResults([]);
      } finally {
        if (!cancelled) setSearchLoading(false);
      }
    }, 280);
    return () => { cancelled = true; clearTimeout(tm); };
  }, [isEdit, addMode, patientId, searchQ]);

  const save = async () => {
    setBusy(true);
    setErr('');
    try {
      const body = {
        firstName: firstName.trim(),
        lastName: lastName.trim(),
        email: email.trim() || null,
        phone: phone.trim() || null,
        preferredLang,
        relationshipType,
        isPrimary,
        canReceiveMails,
      };
      if (isEdit) {
        await cinevApiPatch(
          `/patients/${encodeURIComponent(patientId)}/guardians/${encodeURIComponent(link.id)}`,
          body,
        );
      } else {
        await cinevApiPost(`/patients/${encodeURIComponent(patientId)}/guardians`, body);
      }
      onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const linkExisting = async () => {
    if (!picked?.id) return;
    setBusy(true);
    setErr('');
    try {
      await cinevApiPost(`/patients/${encodeURIComponent(patientId)}/guardians`, {
        guardianPersonId: picked.id,
        relationshipType,
        isPrimary,
        canReceiveMails,
      });
      onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const relOpts = ['mother', 'father', 'legal_guardian', 'other'];
  const relSelect = (
    <select
      value={relationshipType}
      onChange={(e) => setRelationshipType(e.target.value)}
      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13 }}
    >
      {relOpts.map((k) => (
        <option key={k} value={k}>{guardianRelationLabel(k)}</option>
      ))}
    </select>
  );

  const tabBtn = (active) => ({
    flex: 1,
    padding: '8px 10px',
    borderRadius: 6,
    border: `1px solid ${active ? CINEV.teal : CINEV.line}`,
    background: active ? CINEV.soft : 'transparent',
    color: active ? CINEV.teal : CINEV.inkSoft,
    fontWeight: active ? 600 : 400,
    fontSize: 12.5,
    cursor: 'pointer',
    fontFamily: 'inherit',
  });

  return (
    <div
      onClick={onClose}
      role="presentation"
      style={{
        position: 'fixed', inset: 0, background: 'rgba(20,30,35,0.45)', zIndex: 100,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-modal="true"
        aria-labelledby="gua-modal-title"
        style={{
          background: CINEV.card, borderRadius: 12, padding: '20px 22px 22px', width: 480,
          boxShadow: '0 20px 60px rgba(0,0,0,0.25)', maxWidth: 'calc(100vw - 32px)', border: `1px solid ${CINEV.line}`,
        }}
      >
        <div id="gua-modal-title" style={{ fontSize: 15, fontWeight: 600, marginBottom: 14 }}>
          {isEdit ? t('pd_s4_gua_edit') : t('pd_s4_gua_add')}
        </div>

        {!isEdit ? (
          <div style={{ display: 'flex', gap: 8, marginBottom: 14 }}>
            <button type="button" style={tabBtn(addMode === 'search')} onClick={() => { setAddMode('search'); setPicked(null); }}>{t('pd_gua_mode_search')}</button>
            <button type="button" style={tabBtn(addMode === 'create')} onClick={() => { setAddMode('create'); setPicked(null); }}>{t('pd_gua_mode_create')}</button>
          </div>
        ) : null}

        {!isEdit && addMode === 'search' ? (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            <input
              value={searchQ}
              onChange={(e) => setSearchQ(e.target.value)}
              placeholder={t('pd_gua_search_ph')}
              aria-label={t('pd_gua_search_ph')}
              style={{ width: '100%', padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}
            />
            {searchLoading ? (
              <div style={{ fontSize: 12, color: CINEV.inkMute }}>{t('pd_gua_search_loading')}</div>
            ) : (
              <div style={{ maxHeight: 200, overflowY: 'auto', border: `1px solid ${CINEV.lineSoft}`, borderRadius: 8, background: CINEV.bg }}>
                {searchResults.length === 0 ? (
                  <div style={{ padding: 12, fontSize: 12, color: CINEV.inkMute }}>{t('pd_gua_search_empty')}</div>
                ) : (
                  searchResults.map((r) => {
                    const active = picked?.id === r.id;
                    return (
                      <button
                        key={r.id}
                        type="button"
                        onClick={() => setPicked(r)}
                        style={{
                          display: 'block', width: '100%', textAlign: 'left', padding: '10px 12px',
                          border: 'none', borderBottom: `1px solid ${CINEV.lineSoft}`, background: active ? CINEV.soft : 'transparent',
                          cursor: 'pointer', fontFamily: 'inherit',
                        }}
                      >
                        <div style={{ fontSize: 13, fontWeight: 600 }}>{`${r.first_name || ''} ${r.last_name || ''}`.trim()}</div>
                        <div style={{ fontSize: 11, color: CINEV.inkMute, marginTop: 2 }}>{[r.email, r.phone].filter(Boolean).join(' · ') || '—'}</div>
                      </button>
                    );
                  })
                )}
              </div>
            )}
            {picked ? (
              <div style={{ borderTop: `1px solid ${CINEV.lineSoft}`, paddingTop: 12, display: 'grid', gap: 10 }}>
                <div style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_gua_pick_hint')}</div>
                <div style={{ fontSize: 12.5, fontWeight: 600 }}>{`${picked.first_name || ''} ${picked.last_name || ''}`.trim()}</div>
                <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_rel_label')}{relSelect}</label>
                <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5, cursor: 'pointer' }}>
                  <input type="checkbox" checked={isPrimary} onChange={(e) => setIsPrimary(e.target.checked)} />
                  {t('pd_s4_gua_primary')}
                </label>
                <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5, cursor: 'pointer' }}>
                  <input type="checkbox" checked={canReceiveMails} onChange={(e) => setCanReceiveMails(e.target.checked)} />
                  {t('pd_s4_gua_mail')}
                </label>
              </div>
            ) : null}
          </div>
        ) : (
          <div style={{ display: 'grid', gap: 10 }}>
            <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_rel_label')}{relSelect}</label>
            <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_fn')}
              <input value={firstName} onChange={(e) => setFirstName(e.target.value)}
                style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13 }} />
            </label>
            <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_ln')}
              <input value={lastName} onChange={(e) => setLastName(e.target.value)}
                style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13 }} />
            </label>
            <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_email')}
              <input value={email} onChange={(e) => setEmail(e.target.value)}
                style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13 }} />
            </label>
            <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_phone')}
              <input value={phone} onChange={(e) => setPhone(e.target.value)}
                style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13 }} />
            </label>
            <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_lang')}
              <select value={preferredLang} onChange={(e) => setPreferredLang(e.target.value)}
                style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13 }}>
                <option value="de">DE</option>
                <option value="es">ES</option>
              </select>
            </label>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5, cursor: 'pointer' }}>
              <input type="checkbox" checked={isPrimary} onChange={(e) => setIsPrimary(e.target.checked)} />
              {t('pd_s4_gua_primary')}
            </label>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5, cursor: 'pointer' }}>
              <input type="checkbox" checked={canReceiveMails} onChange={(e) => setCanReceiveMails(e.target.checked)} />
              {t('pd_s4_gua_mail')}
            </label>
          </div>
        )}
        {err ? <div style={{ marginTop: 10, fontSize: 12, color: '#c94343' }}>{err}</div> : null}
        <div style={{ display: 'flex', gap: 8, marginTop: 18, justifyContent: 'flex-end' }}>
          <button type="button" onClick={onClose} disabled={busy}
            style={{ background: 'transparent', border: `1px solid ${CINEV.line}`, padding: '8px 14px', borderRadius: 6, fontFamily: 'inherit', cursor: busy ? 'default' : 'pointer' }}>
            {t('pd_s4_gua_cancel')}
          </button>
          {!isEdit && addMode === 'search' && picked ? (
            <button type="button" onClick={() => void linkExisting()} disabled={busy}
              style={{ background: CINEV.teal, color: '#fff', border: 'none', padding: '8px 14px', borderRadius: 6, fontFamily: 'inherit', fontWeight: 600, cursor: busy ? 'default' : 'pointer' }}>
              {busy ? '…' : t('pd_gua_link_btn')}
            </button>
          ) : null}
          {(isEdit || (!isEdit && addMode === 'create')) ? (
            <button type="button" onClick={() => void save()} disabled={busy || !firstName.trim() || !lastName.trim()}
              style={{ background: CINEV.teal, color: '#fff', border: 'none', padding: '8px 14px', borderRadius: 6, fontFamily: 'inherit', fontWeight: 600, cursor: busy ? 'default' : 'pointer' }}>
              {busy ? '…' : t('pd_s4_gua_save')}
            </button>
          ) : null}
        </div>
      </div>
    </div>
  );
}

// ======================= PATIENT DETAIL ============================
function PatientDetail({ row }) {
  useLang();
  const [patientSection, setPatientSection] = React.useState('therapy');
  const [showSpecial, setShowSpecial] = React.useState(false);
  const [specialLaunchPhaseId, setSpecialLaunchPhaseId] = React.useState(null);
  const [apiPatient, setApiPatient] = React.useState(null);
  const [patientRefresh, setPatientRefresh] = React.useState(0);
  const [repBusy, setRepBusy] = React.useState(false);
  const [repFlash, setRepFlash] = React.useState('');
  const [repErr, setRepErr] = React.useState('');
  const [guaModal, setGuaModal] = React.useState(null);
  const [stFirst, setStFirst] = React.useState('');
  const [stLast, setStLast] = React.useState('');
  const [stEmail, setStEmail] = React.useState('');
  const [stPhone, setStPhone] = React.useState('');
  const [stLang, setStLang] = React.useState('de');
  const [stStreet, setStStreet] = React.useState('');
  const [stPostal, setStPostal] = React.useState('');
  const [stCity, setStCity] = React.useState('');
  const [stBirth, setStBirth] = React.useState('');
  const [stSchoolName, setStSchoolName] = React.useState('');
  const [stSchoolAddr, setStSchoolAddr] = React.useState('');
  const [stTeacherId, setStTeacherId] = React.useState('');
  const [teachersList, setTeachersList] = React.useState([]);
  const [tchFilter, setTchFilter] = React.useState('');
  const [schSugQ, setSchSugQ] = React.useState('');
  const [schSugList, setSchSugList] = React.useState([]);
  const [schSugLoad, setSchSugLoad] = React.useState(false);
  const [stBusy, setStBusy] = React.useState(false);
  const [stErr, setStErr] = React.useState('');
  const [stFlash, setStFlash] = React.useState('');
  const [schBusy, setSchBusy] = React.useState(false);
  const [schErr, setSchErr] = React.useState('');
  const [schFlash, setSchFlash] = React.useState('');
  const [patReminders, setPatReminders] = React.useState([]);
  const [patRemLoading, setPatRemLoading] = React.useState(false);
  const [patRemErr, setPatRemErr] = React.useState('');
  const [showRmConfig, setShowRmConfig] = React.useState(false);
  const [rmTimingMode, setRmTimingMode] = React.useState('day_before');
  const [rmHour, setRmHour] = React.useState('08');
  const [rmMinute, setRmMinute] = React.useState('00');
  const [rmRecipient, setRmRecipient] = React.useState('guardian');
  const [rmPrefBusy, setRmPrefBusy] = React.useState(false);
  const [rmPrefErr, setRmPrefErr] = React.useState('');
  const [rmPrefFlash, setRmPrefFlash] = React.useState('');
  const fmtReminderDt = React.useCallback((iso) => {
    if (!iso) return { dateLine: '—', timeLine: '' };
    const loc = window.__cinevLang === 'es' ? 'es-CO' : 'de-DE';
    const d = new Date(iso);
    const dateLine = d.toLocaleDateString(loc, { weekday: 'short', day: '2-digit', month: '2-digit', year: 'numeric' });
    const tm = d.toLocaleTimeString(loc, { hour: '2-digit', minute: '2-digit' });
    const timeLine = loc === 'de-DE' ? `${tm} Uhr` : tm;
    return { dateLine, timeLine };
  }, []);
  const lang = window.__cinevLang || 'de';
  const liveDetail = typeof cinevLiveApi === 'function' && cinevLiveApi() && row.apiId;

  React.useEffect(() => {
    if (!liveDetail) {
      setApiPatient(null);
      return undefined;
    }
    let cancelled = false;
    (async () => {
      try {
        const data = await cinevApiGet(`/patients/${encodeURIComponent(row.apiId)}`);
        if (!cancelled) setApiPatient(data);
      } catch {
        if (!cancelled) setApiPatient(null);
      }
    })();
    return () => { cancelled = true; };
  }, [liveDetail, row.apiId, patientRefresh]);

  React.useEffect(() => {
    if (!apiPatient?.patient) return;
    const p = apiPatient.patient;
    setStFirst(p.first_name || '');
    setStLast(p.last_name || '');
    setStEmail(p.email != null ? String(p.email) : '');
    setStPhone(p.phone != null ? String(p.phone) : '');
    const L = (p.preferred_lang || 'de').toString().toLowerCase();
    setStLang(L.startsWith('es') ? 'es' : 'de');
    setRmTimingMode(p.reminder_relative_day === 'same_day' ? 'same_day' : 'day_before');
    const tt = p.reminder_send_time != null ? String(p.reminder_send_time).slice(0, 5) : '08:00';
    const rp = cinevWallTimeToQuarterParts(/^\d{2}:\d{2}$/.test(tt) ? tt : '08:00');
    setRmHour(rp.hour);
    setRmMinute(rp.minute);
    setRmRecipient(p.reminder_recipient === 'patient' ? 'patient' : 'guardian');
    setStStreet(p.address_line1 != null ? String(p.address_line1) : '');
    setStPostal(p.postal_code != null ? String(p.postal_code) : '');
    setStCity(p.city != null ? String(p.city) : '');
    const bd = p.birth_date;
    setStBirth(bd ? String(bd).slice(0, 10) : '');
    setStSchoolName(p.school_name != null ? String(p.school_name) : '');
    setStSchoolAddr(p.school_address != null ? String(p.school_address) : '');
    setStTeacherId(p.school_teacher_person_id ? String(p.school_teacher_person_id) : '');
  }, [apiPatient]);

  React.useEffect(() => {
    if (!liveDetail || patientSection !== 'contact') return undefined;
    let cancelled = false;
    (async () => {
      try {
        const data = await cinevApiGet('/teachers');
        if (!cancelled) setTeachersList(data.items || []);
      } catch {
        if (!cancelled) setTeachersList([]);
      }
    })();
    return () => { cancelled = true; };
  }, [liveDetail, patientSection]);

  React.useEffect(() => {
    if (patientSection !== 'contact' || !liveDetail) return undefined;
    let cancelled = false;
    const tm = setTimeout(async () => {
      setSchSugLoad(true);
      try {
        const qs = new URLSearchParams();
        if (schSugQ.trim()) qs.set('q', schSugQ.trim());
        const data = await cinevApiGet(`/school-suggestions?${qs.toString()}`);
        if (!cancelled) setSchSugList(data.items || []);
      } catch {
        if (!cancelled) setSchSugList([]);
      } finally {
        if (!cancelled) setSchSugLoad(false);
      }
    }, 280);
    return () => { cancelled = true; clearTimeout(tm); };
  }, [patientSection, liveDetail, schSugQ]);

  const teachersFiltered = React.useMemo(() => {
    const base = teachersList || [];
    const q = tchFilter.trim().toLowerCase();
    if (!q) return base;
    const matched = base.filter((tc) => {
      const n = `${tc.first_name || ''} ${tc.last_name || ''}`.toLowerCase();
      return n.includes(q) || String(tc.email || '').toLowerCase().includes(q);
    });
    if (stTeacherId) {
      const sel = base.find((tc) => String(tc.id) === String(stTeacherId));
      if (sel && !matched.some((tc) => String(tc.id) === String(stTeacherId))) return [sel, ...matched];
    }
    return matched;
  }, [teachersList, tchFilter, stTeacherId]);

  React.useEffect(() => {
    if (patientSection !== 'reminders' || !liveDetail) return undefined;
    let cancelled = false;
    setPatRemLoading(true);
    setPatRemErr('');
    (async () => {
      try {
        const data = await cinevApiGet(`/reminders/log?patientId=${encodeURIComponent(row.apiId)}&limit=100`);
        if (!cancelled) setPatReminders(data.items || []);
      } catch (e) {
        if (!cancelled) {
          setPatRemErr(e.message || String(e));
          setPatReminders([]);
        }
      } finally {
        if (!cancelled) setPatRemLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [patientSection, liveDetail, row.apiId, patientRefresh]);

  const displayName = apiPatient?.patient
    ? `${apiPatient.patient.first_name || ''} ${apiPatient.patient.last_name || ''}`.trim() || row.name
    : row.name;
  const displayParent = () => {
    if (apiPatient?.guardians?.length) {
      const g = apiPatient.guardians[0];
      const n = `${g.first_name || ''} ${g.last_name || ''}`.trim();
      return n || row.parent;
    }
    return row.parent;
  };

  const timeline = apiPatient?.activePlanTimeline;
  const liveSessions = timeline?.sessions;
  const useLiveSessions = Boolean(liveDetail && liveSessions && liveSessions.length > 0);
  const doneCount = useLiveSessions
    ? liveSessions.filter(
        (s) => s.appointment_status === 'done' || (!s.appointment_status && s.clinical_status === 'done'),
      ).length
    : row.done;
  const totalCount = useLiveSessions ? liveSessions.length : row.total;

  const chronicle = useLiveSessions
    ? buildLiveChronicleFromTimeline(timeline, lang)
    : Array.from({ length: row.total }).map((_, i) => {
        if (i < row.done) {
          if (row.name === 'Lara Schulz' && i === 6) return { n: i + 1, s: 'no_show', date: '31. März', note: t('pd_no_show_label') };
          if (row.name === 'Finn Jansen' && i === 2) return { n: i + 1, s: 'rescheduled', date: '24. März → 26. März', note: t('pd_rescheduled_label') };
          return { n: i + 1, s: 'completed', date: `KW ${10 + i}`, note: t('pd_done') };
        }
        if (i === row.done) return { n: i + 1, s: 'scheduled', date: row.next, note: t('pd_planned') };
        return { n: i + 1, s: 'scheduled', date: `KW ${17 + (i - row.done)}`, note: t('pd_planned') };
      });
  const statuses = chronicle.map((c) => c.s);

  const hasPlanTimeline = Boolean(liveDetail && timeline?.plan?.plan_id);

  const savePatientTile = async () => {
    if (!liveDetail || !row.apiId) return;
    setStBusy(true);
    setStErr('');
    setStFlash('');
    try {
      await cinevApiPatch(`/patients/${encodeURIComponent(row.apiId)}`, {
        firstName: stFirst.trim(),
        lastName: stLast.trim(),
        email: stEmail.trim() || null,
        phone: stPhone.trim() || null,
        preferredLang: stLang,
        addressLine1: stStreet.trim() || null,
        postalCode: stPostal.trim() || null,
        city: stCity.trim() || null,
        birthDate: stBirth.trim() || null,
      });
      setPatientRefresh((n) => n + 1);
      setStFlash(t('pd_s5_stam_saved'));
    } catch (e) {
      setStErr(e.message || String(e));
    } finally {
      setStBusy(false);
    }
  };

  const saveSchoolTile = async () => {
    if (!liveDetail || !row.apiId) return;
    setSchBusy(true);
    setSchErr('');
    setSchFlash('');
    try {
      await cinevApiPatch(`/patients/${encodeURIComponent(row.apiId)}`, {
        schoolName: stSchoolName.trim() || null,
        schoolAddress: stSchoolAddr.trim() || null,
        schoolTeacherPersonId: stTeacherId.trim() || null,
      });
      setPatientRefresh((n) => n + 1);
      setSchFlash(t('pd_s5_stam_saved'));
    } catch (e) {
      setSchErr(e.message || String(e));
    } finally {
      setSchBusy(false);
    }
  };

  const sectionTabBtn = (key) => ({
    border: 'none', padding: '7px 18px', borderRadius: 6, fontSize: 13,
    fontWeight: patientSection === key ? 600 : 400,
    cursor: 'pointer', fontFamily: 'inherit',
    background: patientSection === key ? CINEV.teal : 'transparent',
    color: patientSection === key ? '#fff' : CINEV.inkSoft,
  });

  const patientDetailScrollSection = {
    flex: 1,
    minHeight: 0,
    overflow: 'auto',
    WebkitOverflowScrolling: 'touch',
  };

  return (
    <div style={{
      padding: '20px 32px',
      boxSizing: 'border-box',
      height: '100%',
      minHeight: 0,
      flex: 1,
      display: 'flex',
      flexDirection: 'column',
      overflow: 'hidden',
    }}
    >
      {/* Oben fix: Name + Tabs */}
      <div style={{ flexShrink: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 16 }}>
          <PatientAvatar name={displayName} patientKey={row.apiId || displayName} size={32} />
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 22, fontWeight: 600, letterSpacing: -0.3 }}>{displayName}</div>
            <div style={{ fontSize: 12.5, color: CINEV.inkSoft, marginTop: 2 }}>
              {t('pd_contact_label')}: {displayParent()}
              {apiPatient?.patient?.is_minor ? ` · ${t('pt_minor_badge')}` : ''}
            </div>
          </div>
        </div>

        <div style={{ display: 'flex', background: CINEV.bg, borderRadius: 8, padding: 3, border: `1px solid ${CINEV.lineSoft}`, marginBottom: 20, width: 'fit-content', gap: 2 }}>
          <button type="button" style={sectionTabBtn('therapy')} onClick={() => setPatientSection('therapy')}>{t('pd_tab_therapy')}</button>
          <button type="button" style={sectionTabBtn('contact')} onClick={() => setPatientSection('contact')}>{t('pd_tab_contact')}</button>
          <button type="button" style={sectionTabBtn('reminders')} onClick={() => setPatientSection('reminders')}>{t('pd_tab_reminders')}</button>
        </div>
      </div>

      {/* ── EBENE 1: THERAPIE — Fortschritt fix, Phasenplan scrollt ── */}
      {patientSection === 'therapy' && (
        <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
          <div style={{ flexShrink: 0, background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`, padding: '18px 20px', marginBottom: 16 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 14 }}>
              <Avatar therapist={row.tp} size={32} therapistApiMap={row.therapistMap} />
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 600 }}>{t('pd_therapy_progress')}</div>
              </div>
              <div style={{ fontSize: 22, fontWeight: 700, color: CINEV.teal, fontVariantNumeric: 'tabular-nums' }}>
                {doneCount}<span style={{ fontSize: 14, color: CINEV.inkMute, fontWeight: 500 }}> / {totalCount}</span>
              </div>
            </div>
            <SessionDots done={doneCount} total={totalCount} statuses={statuses} size={12} gap={6}/>
            <div style={{ display: 'flex', gap: 14, marginTop: 12, fontSize: 11, color: CINEV.inkMute, flexWrap: 'wrap' }}>
              <Legend color={CINEV.green} label={t('pd_done')}/>
              <Legend color={'#c94343'} label={t('pd_no_show_label')}/>
              <Legend color={'#d9a42e'} label={t('pd_rescheduled_label')}/>
              <Legend color={CINEV.line} label={t('pd_planned')}/>
            </div>
          </div>

          <div style={{ flex: 1, minHeight: 0, overflow: 'auto', WebkitOverflowScrolling: 'touch' }}>
            <PhasePlanPanel
              row={row}
              therapistApiMap={row.therapistMap}
              liveTimeline={liveDetail && timeline ? timeline : null}
              onRefresh={() => setPatientRefresh((n) => n + 1)}
              onRequestSpecialAppt={(phaseDbId) => {
                setSpecialLaunchPhaseId(phaseDbId);
                setShowSpecial(true);
              }}
              specialApptEnabled={Boolean(hasPlanTimeline && row.apiId && useLiveSessions && timeline)}
            />
          </div>
        </div>
      )}

      {/* ── EBENE 2: KONTAKT ─────────────────────────────────── */}
      {patientSection === 'contact' && (
        <div style={patientDetailScrollSection}>
          {liveDetail && apiPatient?.patient ? (
            <React.Fragment>
              {/* Kachel 1: Patient */}
              <div style={{ background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`, padding: '16px 20px', marginBottom: 16 }}>
                <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 12 }}>{t('pd_contact_tile_patient')}</div>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: 12 }}>
                  <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_fn')}
                    <input value={stFirst} onChange={(e) => setStFirst(e.target.value)}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
                  </label>
                  <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_ln')}
                    <input value={stLast} onChange={(e) => setStLast(e.target.value)}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
                  </label>
                  <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_email')}
                    <input value={stEmail} onChange={(e) => setStEmail(e.target.value)} type="email"
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
                  </label>
                  <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_phone')}
                    <input value={stPhone} onChange={(e) => setStPhone(e.target.value)}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
                  </label>
                  <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_lang')}
                    <select value={stLang} onChange={(e) => setStLang(e.target.value)}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13 }}>
                      <option value="de">DE</option>
                      <option value="es">ES</option>
                    </select>
                  </label>
                  <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_contact_street')}
                    <input value={stStreet} onChange={(e) => setStStreet(e.target.value)}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
                  </label>
                  <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_contact_postal')}
                    <input value={stPostal} onChange={(e) => setStPostal(e.target.value)}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
                  </label>
                  <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_contact_city')}
                    <input value={stCity} onChange={(e) => setStCity(e.target.value)}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
                  </label>
                  <label style={{ fontSize: 11, color: CINEV.inkMute }} lang={cinevModalWallLang()}>{t('pd_contact_birth')}
                    <CinevDateDmyInput
                      valueYmd={stBirth}
                      onValueYmdChange={setStBirth}
                      style={{
                        ...cinevModalDatetimeFieldStyle(),
                        display: 'block',
                        width: '100%',
                        marginTop: 4,
                        boxSizing: 'border-box',
                      }}
                    />
                  </label>
                </div>
                <div style={{ marginTop: 14, display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
                  <button type="button" onClick={() => void savePatientTile()} disabled={stBusy || !stFirst.trim() || !stLast.trim()}
                    style={{
                      background: CINEV.teal, color: '#fff', border: 'none', padding: '8px 14px', borderRadius: 6,
                      fontFamily: 'inherit', fontWeight: 600, fontSize: 12.5, cursor: stBusy ? 'default' : 'pointer',
                    }}>{stBusy ? '…' : t('pd_contact_save_patient')}</button>
                  {stFlash ? <span style={{ fontSize: 11.5, color: CINEV.teal }}>{stFlash}</span> : null}
                  {stErr ? <span style={{ fontSize: 11.5, color: '#c94343' }}>{stErr}</span> : null}
                </div>
              </div>

              {/* Kachel 2: Vertretung */}
              <div style={{ background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`, padding: '16px 20px', marginBottom: 16 }}>
                <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 6 }}>{t('pd_contact_tile_rep')}</div>
                <div style={{ fontSize: 11.5, color: CINEV.inkMute, lineHeight: 1.45, marginBottom: 12 }}>{t('pd_s4_rep_help')}</div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 16, alignItems: 'center' }}>
                  <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: repBusy ? 'default' : 'pointer' }}>
                    <input
                      type="radio"
                      name="pd-rep"
                      checked={(apiPatient.patient.representation_mode || 'self') === 'self'}
                      disabled={repBusy}
                      onChange={() => {
                        if ((apiPatient.patient.representation_mode || 'self') === 'self') return;
                        (async () => {
                          setRepBusy(true); setRepErr(''); setRepFlash('');
                          try {
                            await cinevApiPatch(`/patients/${encodeURIComponent(row.apiId)}`, { representationMode: 'self' });
                            setPatientRefresh((n) => n + 1);
                            setRepFlash(t('pd_s4_rep_saved'));
                          } catch (e) { setRepErr(e.message || String(e)); }
                          finally { setRepBusy(false); }
                        })();
                      }}
                    />
                    {t('pd_s4_rep_self')}
                  </label>
                  <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: repBusy ? 'default' : 'pointer' }}>
                    <input
                      type="radio"
                      name="pd-rep"
                      checked={(apiPatient.patient.representation_mode || 'self') === 'guardian'}
                      disabled={repBusy}
                      onChange={() => {
                        if ((apiPatient.patient.representation_mode || 'self') === 'guardian') return;
                        (async () => {
                          setRepBusy(true); setRepErr(''); setRepFlash('');
                          try {
                            await cinevApiPatch(`/patients/${encodeURIComponent(row.apiId)}`, { representationMode: 'guardian' });
                            setPatientRefresh((n) => n + 1);
                            setRepFlash(t('pd_s4_rep_saved'));
                          } catch (e) { setRepErr(e.message || String(e)); }
                          finally { setRepBusy(false); }
                        })();
                      }}
                    />
                    {t('pd_s4_rep_guardian')}
                  </label>
                  {repBusy ? <span style={{ fontSize: 11.5, color: CINEV.inkMute }}>{t('pd_s4_rep_saving')}</span> : null}
                  {repFlash ? <span style={{ fontSize: 11.5, color: CINEV.teal }}>{repFlash}</span> : null}
                  {repErr ? <span style={{ fontSize: 11.5, color: '#c94343' }}>{repErr}</span> : null}
                </div>

                {/* Bezugspersonen */}
                <div style={{ borderTop: `1px solid ${CINEV.lineSoft}`, marginTop: 16, paddingTop: 14 }}>
                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
                    <div style={{ fontSize: 13, fontWeight: 600 }}>{t('pd_s4_gua_title')}</div>
                    <button type="button" onClick={() => setGuaModal({ mode: 'add' })}
                      style={{
                        background: CINEV.soft, color: CINEV.teal, border: `1px solid ${CINEV.teal}`,
                        padding: '6px 12px', borderRadius: 6, fontSize: 12, fontWeight: 500, cursor: 'pointer', fontFamily: 'inherit',
                      }}>{t('pd_s4_gua_add')}</button>
                  </div>
                  {!(apiPatient.guardians && apiPatient.guardians.length) ? (
                    <div style={{ fontSize: 12, color: CINEV.inkMute }}>—</div>
                  ) : (
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                      {apiPatient.guardians.map((g) => (
                        <div key={g.id} style={{
                          display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap',
                          padding: '10px 12px', borderRadius: 8, background: CINEV.bg, border: `1px solid ${CINEV.lineSoft}`,
                        }}>
                          <div style={{ flex: 1, minWidth: 0 }}>
                            <div style={{ fontSize: 13, fontWeight: 600 }}>
                              {`${g.first_name || ''} ${g.last_name || ''}`.trim() || '—'}
                            </div>
                            <div style={{ fontSize: 11, color: CINEV.inkMute, marginTop: 2 }}>
                              {guardianRelationLabel(g.relationship_type)}
                              {g.is_primary ? ` · ${t('pd_s4_gua_primary')}` : ''}
                              {g.can_receive_mails ? ` · ${t('pd_s4_gua_mail')}` : ''}
                            </div>
                          </div>
                          <button type="button" onClick={() => setGuaModal({ mode: 'edit', link: g })}
                            style={{ background: 'transparent', border: `1px solid ${CINEV.line}`, color: CINEV.teal, padding: '6px 10px', borderRadius: 6, fontSize: 11.5, cursor: 'pointer', fontFamily: 'inherit' }}>
                            {t('pd_s4_gua_edit')}
                          </button>
                          <button type="button" onClick={() => {
                            if (!window.confirm(t('pd_s4_gua_confirm_remove'))) return;
                            (async () => {
                              try {
                                await cinevApiDelete(`/patients/${encodeURIComponent(row.apiId)}/guardians/${encodeURIComponent(g.id)}`);
                                setPatientRefresh((n) => n + 1);
                              } catch (e) { window.alert(e.message || String(e)); }
                            })();
                          }}
                            style={{ background: 'transparent', border: `1px solid ${CINEV.line}`, color: '#c94343', padding: '6px 10px', borderRadius: 6, fontSize: 11.5, cursor: 'pointer', fontFamily: 'inherit' }}>
                            {t('pd_s4_gua_remove')}
                          </button>
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              </div>

              {/* Kachel 3: Schule */}
              <div style={{ background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`, padding: '16px 20px', marginBottom: 16 }}>
                <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 6 }}>{t('pd_contact_tile_school')}</div>
                <div style={{ fontSize: 11, color: CINEV.inkMute, lineHeight: 1.45, marginBottom: 12 }}>{t('pd_sch_pick_hint')}</div>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'flex-end', marginBottom: 12 }}>
                  <label style={{ fontSize: 11, color: CINEV.inkMute, flex: '1 1 220px', minWidth: 0 }}>
                    {t('pd_sch_search_label')}
                    <input
                      value={schSugQ}
                      onChange={(e) => setSchSugQ(e.target.value)}
                      placeholder={t('pd_sch_search_ph')}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}
                    />
                  </label>
                  <button
                    type="button"
                    onClick={() => {
                      setSchSugQ('');
                      setStSchoolName('');
                      setStSchoolAddr('');
                    }}
                    style={{
                      background: CINEV.soft, color: CINEV.teal, border: `1px solid ${CINEV.teal}`,
                      padding: '8px 12px', borderRadius: 6, fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit', whiteSpace: 'nowrap',
                    }}
                  >{t('pd_sch_new_btn')}</button>
                </div>
                {schSugLoad ? (
                  <div style={{ fontSize: 11.5, color: CINEV.inkMute, marginBottom: 12 }}>{t('pd_gua_search_loading')}</div>
                ) : (
                  <div style={{ maxHeight: 160, overflowY: 'auto', border: `1px solid ${CINEV.lineSoft}`, borderRadius: 8, background: CINEV.bg, marginBottom: 14 }}>
                    {schSugList.length === 0 ? (
                      <div style={{ padding: 10, fontSize: 12, color: CINEV.inkMute }}>{t('pd_sch_suggestions_empty')}</div>
                    ) : (
                      schSugList.map((s, si) => (
                        <button
                          key={`${si}-${s.school_name}-${s.school_address || ''}`}
                          type="button"
                          onClick={() => {
                            setStSchoolName(s.school_name != null ? String(s.school_name) : '');
                            setStSchoolAddr(s.school_address != null ? String(s.school_address) : '');
                          }}
                          style={{
                            display: 'block', width: '100%', textAlign: 'left', padding: '8px 12px',
                            border: 'none', borderBottom: `1px solid ${CINEV.lineSoft}`, background: 'transparent',
                            cursor: 'pointer', fontFamily: 'inherit', fontSize: 12.5,
                          }}
                        >
                          <span style={{ fontWeight: 600 }}>{s.school_name || '—'}</span>
                          {s.school_address ? <span style={{ color: CINEV.inkMute }}> · {s.school_address}</span> : null}
                        </button>
                      ))
                    )}
                  </div>
                )}
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: 12 }}>
                  <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_school_name')}
                    <input value={stSchoolName} onChange={(e) => setStSchoolName(e.target.value)}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
                  </label>
                  <label style={{ fontSize: 11, color: CINEV.inkMute, gridColumn: '1 / -1' }}>{t('pd_school_address')}
                    <input value={stSchoolAddr} onChange={(e) => setStSchoolAddr(e.target.value)}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
                  </label>
                  <label style={{ fontSize: 11, color: CINEV.inkMute, gridColumn: '1 / -1' }}>{t('pd_tch_filter_label')}
                    <input
                      value={tchFilter}
                      onChange={(e) => setTchFilter(e.target.value)}
                      placeholder={t('pd_tch_filter_ph')}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}
                    />
                  </label>
                  <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_school_teacher')}
                    <select value={stTeacherId} onChange={(e) => setStTeacherId(e.target.value)}
                      style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13 }}>
                      <option value="">{t('pd_school_teacher_none')}</option>
                      {teachersFiltered.map((tc) => (
                        <option key={tc.id} value={tc.id}>
                          {`${tc.first_name || ''} ${tc.last_name || ''}`.trim() || tc.id}
                        </option>
                      ))}
                    </select>
                  </label>
                </div>
                {tchFilter.trim() && teachersFiltered.length === 0 ? (
                  <div style={{ marginTop: 8, fontSize: 11.5, color: CINEV.inkMute }}>{t('pd_gua_search_empty')}</div>
                ) : null}
                <div style={{ marginTop: 14, display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
                  <button type="button" onClick={() => void saveSchoolTile()} disabled={schBusy}
                    style={{
                      background: CINEV.teal, color: '#fff', border: 'none', padding: '8px 14px', borderRadius: 6,
                      fontFamily: 'inherit', fontWeight: 600, fontSize: 12.5, cursor: schBusy ? 'default' : 'pointer',
                    }}>{schBusy ? '…' : t('pd_contact_save_school')}</button>
                  {schFlash ? <span style={{ fontSize: 11.5, color: CINEV.teal }}>{schFlash}</span> : null}
                  {schErr ? <span style={{ fontSize: 11.5, color: '#c94343' }}>{schErr}</span> : null}
                </div>
              </div>
            </React.Fragment>
          ) : liveDetail ? (
            <div style={{ fontSize: 12, color: CINEV.inkMute, marginBottom: 16 }}>
              {apiPatient === null ? t('pd_s4_loading') : t('pd_s4_live_only')}
            </div>
          ) : (
            <div style={{ fontSize: 12, color: CINEV.inkMute, marginBottom: 16 }}>{t('pd_s4_live_only')}</div>
          )}
        </div>
      )}

      {/* ── EBENE 3: ERINNERUNGEN ──────────────────────────── */}
      {patientSection === 'reminders' && (
        <div style={patientDetailScrollSection}>
          {/* Konfigurationsbereich */}
          <div style={{ marginBottom: 16 }}>
            <button
              onClick={() => setShowRmConfig(v => !v)}
              style={{ fontSize: 12, padding: '6px 14px', borderRadius: 7, border: `1px solid ${CINEV.line}`, background: showRmConfig ? CINEV.teal + '18' : CINEV.card, color: showRmConfig ? CINEV.teal : CINEV.inkSoft, cursor: 'pointer', fontWeight: 500, display: 'inline-flex', alignItems: 'center', gap: 6 }}
            >
              <span style={{ fontSize: 14 }}>⚙</span>
              {t('pd_rm_config_btn')}
              <span style={{ fontSize: 10, opacity: 0.7 }}>{showRmConfig ? '▲' : '▼'}</span>
            </button>
            {showRmConfig && (
              <div style={{ marginTop: 10, padding: '16px 18px', borderRadius: 9, background: CINEV.soft, border: `1px solid ${CINEV.lineSoft}` }}>
                <div style={{ fontWeight: 600, fontSize: 12.5, color: CINEV.inkSoft, marginBottom: 14 }}>{t('pd_rm_config_title')}</div>
                <div style={{ display: 'flex', gap: 32, flexWrap: 'wrap', alignItems: 'flex-end' }}>
                  <div>
                    <div style={{ fontSize: 11, color: CINEV.inkMute, marginBottom: 6, textTransform: 'uppercase', letterSpacing: 0.4 }}>{t('pd_rm_timing_label')}</div>
                    <div style={{ display: 'flex', gap: 6 }}>
                      {['day_before', 'same_day'].map(mode => (
                        <button
                          key={mode}
                          onClick={() => setRmTimingMode(mode)}
                          style={{ fontSize: 12, padding: '5px 14px', borderRadius: 6, border: `1px solid ${rmTimingMode === mode ? CINEV.teal : CINEV.line}`, background: rmTimingMode === mode ? CINEV.teal + '18' : CINEV.card, color: rmTimingMode === mode ? CINEV.teal : CINEV.inkSoft, cursor: 'pointer', fontWeight: rmTimingMode === mode ? 600 : 400 }}
                        >
                          {mode === 'day_before' ? t('pd_rm_timing_day_before') : t('pd_rm_timing_same_day')}
                        </button>
                      ))}
                    </div>
                  </div>
                  <div>
                    <div style={{ fontSize: 11, color: CINEV.inkMute, marginBottom: 6, textTransform: 'uppercase', letterSpacing: 0.4 }}>{t('pd_rm_time_label')}</div>
                    <CinevWallTimeQuarterSelects
                      variant="modal"
                      hour={rmHour}
                      minute={rmMinute}
                      onHourChange={setRmHour}
                      onMinuteChange={setRmMinute}
                    />
                  </div>
                  <div>
                    <div style={{ fontSize: 11, color: CINEV.inkMute, marginBottom: 6, textTransform: 'uppercase', letterSpacing: 0.4 }}>{t('pd_rm_recipient_label')}</div>
                    <select
                      value={rmRecipient}
                      onChange={(e) => setRmRecipient(e.target.value === 'patient' ? 'patient' : 'guardian')}
                      style={{ fontSize: 13, padding: '6px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, background: CINEV.card, color: CINEV.ink, minWidth: 220 }}
                    >
                      <option value="patient">{t('pd_rm_recipient_patient')}</option>
                      <option value="guardian">
                        {(() => {
                          const g = apiPatient?.guardians?.[0];
                          const n = g ? `${g.first_name || ''} ${g.last_name || ''}`.trim() : '';
                          return n ? `${t('pd_rm_recipient_guardian')} (${n})` : t('pd_rm_recipient_guardian');
                        })()}
                      </option>
                    </select>
                  </div>
                  <div style={{ paddingBottom: 2 }}>
                    <button
                      type="button"
                      disabled={!liveDetail || rmPrefBusy}
                      onClick={async () => {
                        if (!liveDetail || !row.apiId) return;
                        setRmPrefBusy(true);
                        setRmPrefErr('');
                        setRmPrefFlash('');
                        try {
                          const timeVal = `${cinevWallQuarterPartsToHhmm(rmHour, rmMinute)}:00`;
                          await cinevApiPatch(`/patients/${encodeURIComponent(row.apiId)}`, {
                            reminderRelativeDay: rmTimingMode,
                            reminderSendTime: timeVal,
                            reminderRecipient: rmRecipient,
                          });
                          setPatientRefresh((n) => n + 1);
                          setRmPrefFlash(t('pd_rm_save_ok'));
                          window.setTimeout(() => setRmPrefFlash(''), 2800);
                        } catch (e) {
                          setRmPrefErr(e.message || String(e));
                        } finally {
                          setRmPrefBusy(false);
                        }
                      }}
                      style={{ fontSize: 12, padding: '7px 18px', borderRadius: 7, border: 'none', background: CINEV.teal, color: '#fff', cursor: rmPrefBusy ? 'wait' : 'pointer', fontWeight: 600, opacity: !liveDetail || rmPrefBusy ? 0.55 : 1 }}
                    >
                      {rmPrefBusy ? '…' : t('pd_rm_save')}
                    </button>
                  </div>
                </div>
                {rmPrefErr ? <div style={{ fontSize: 12, color: '#c94343', marginTop: 10 }}>{rmPrefErr}</div> : null}
                {rmPrefFlash ? <div style={{ fontSize: 12, color: CINEV.green, marginTop: 10 }}>{rmPrefFlash}</div> : null}
              </div>
            )}
          </div>

          {!liveDetail ? (
            <div style={{ fontSize: 12, color: CINEV.inkMute }}>{t('pd_s4_live_only')}</div>
          ) : patRemLoading ? (
            <div style={{ fontSize: 12.5, color: CINEV.inkMute }}>…</div>
          ) : patRemErr ? (
            <div style={{ fontSize: 12, color: '#c94343' }}>{patRemErr}</div>
          ) : patReminders.length === 0 ? (
            <div style={{ fontSize: 12, color: CINEV.inkMute }}>{t('pd_reminders_empty')}</div>
          ) : (
            <div style={{ background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`, overflow: 'hidden' }}>
              <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
                <thead style={{ background: CINEV.bg }}>
                  <tr style={{ color: CINEV.inkMute, fontSize: 10.5, textTransform: 'uppercase', letterSpacing: 0.5 }}>
                    <Th>{t('pd_rm_col_appt')}</Th>
                    <Th>{t('pd_rm_col_reminder')}</Th>
                    <Th>{t('rm_log_to')}</Th>
                    <Th>{t('rm_log_status')}</Th>
                  </tr>
                </thead>
                <tbody>
                  {patReminders.map((r, i) => {
                    const statusColors = {
                      sent: CINEV.green, delivered: CINEV.green, opened: CINEV.teal,
                      confirmed: CINEV.greenDark, queued: CINEV.warn, pending: CINEV.warn, bounced: '#c94343',
                    };
                    const sc = statusColors[r.send_status] || CINEV.inkMute;
                    const apptFmt = fmtReminderDt(r.appointment_start);
                    const isSent = !!r.sent_at;
                    const remindIso = isSent ? r.sent_at : r.scheduled_at;
                    const remFmt = fmtReminderDt(remindIso);
                    const rawEmail = r.recipient_email != null ? String(r.recipient_email).trim() : '';
                    const isDemoEmail = rawEmail.endsWith('@invalid');
                    const tdDtStyle = { padding: '10px 12px', fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' };
                    return (
                      <tr key={r.id || i} style={{ borderTop: `1px solid ${CINEV.lineSoft}` }}>
                        <td style={tdDtStyle}>
                          <div style={{ fontWeight: 600 }}>{apptFmt.dateLine}</div>
                          {apptFmt.timeLine ? <div style={{ fontSize: 11, color: CINEV.inkMute }}>{apptFmt.timeLine}</div> : null}
                        </td>
                        <td style={tdDtStyle}>
                          {isSent ? (
                            <>
                              <div style={{ fontWeight: 600, color: CINEV.green }}>{remFmt.dateLine}</div>
                              {remFmt.timeLine ? <div style={{ fontSize: 11, color: CINEV.green }}>{remFmt.timeLine}</div> : null}
                            </>
                          ) : (
                            <>
                              <div style={{ fontWeight: 600 }}>{remFmt.dateLine}</div>
                              {remFmt.timeLine ? <div style={{ fontSize: 11, color: CINEV.inkMute }}>{remFmt.timeLine}</div> : null}
                            </>
                          )}
                        </td>
                        <td style={{ padding: '10px 8px', color: CINEV.inkSoft, fontSize: 11.5, wordBreak: 'break-all' }}>
                          {rawEmail ? (
                            <>
                              <div>{rawEmail}</div>
                              {isDemoEmail ? (
                                <div style={{ fontSize: 10, color: CINEV.inkMute, marginTop: 2 }}>{t('pd_rm_demo_email_hint')}</div>
                              ) : null}
                            </>
                          ) : '—'}
                        </td>
                        <td style={{ padding: '10px 8px' }}>
                          <span style={{ fontSize: 10, padding: '2px 8px', borderRadius: 10, fontWeight: 600, background: sc + '22', color: sc }}>
                            {r.send_status || '—'}
                          </span>
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          )}
        </div>
      )}

      {showSpecial && hasPlanTimeline ? (
        <SpecialApptModal
          patientId={row.apiId}
          timeline={timeline}
          therapistMap={row.therapistMap}
          initialPhaseId={specialLaunchPhaseId}
          onClose={() => { setShowSpecial(false); setSpecialLaunchPhaseId(null); }}
          onSaved={() => setPatientRefresh((n) => n + 1)}
        />
      ) : null}
      {guaModal && guaModal.mode === 'add' ? (
        <GuardianLinkModal
          patientId={row.apiId}
          link={null}
          onClose={() => setGuaModal(null)}
          onSaved={() => setPatientRefresh((n) => n + 1)}
        />
      ) : null}
      {guaModal && guaModal.mode === 'edit' && guaModal.link ? (
        <GuardianLinkModal
          patientId={row.apiId}
          link={guaModal.link}
          onClose={() => setGuaModal(null)}
          onSaved={() => setPatientRefresh((n) => n + 1)}
        />
      ) : null}
    </div>
  );
}

function SpecialApptModal({ onClose, onSaved, patientId, timeline, therapistMap, initialPhaseId }) {
  useLang();
  const phases = React.useMemo(() => {
    const p = timeline?.phases || [];
    return [...p].sort((a, b) => Number(a.phase_index) - Number(b.phase_index));
  }, [timeline?.phases]);

  const kindOptions = [
    { v: 'parent_talk', labelKey: 'pd_milestone_parent' },
    { v: 'school_talk', labelKey: 'pd_milestone_school' },
    { v: 'doctor', labelKey: 'sk_doctor' },
    { v: 'closing', labelKey: 'pd_milestone_closing' },
    { v: 'diag', labelKey: 'sk_diag' },
    { v: 'other', labelKey: 'sk_other' },
  ];
  const [kind, setKind] = React.useState('parent_talk');
  const [duration, setDuration] = React.useState(CINEV_DEFAULT_APPOINTMENT_DURATION_MIN);
  const [phaseId, setPhaseId] = React.useState('');
  React.useEffect(() => {
    if (initialPhaseId && phases.some((ph) => String(ph.id) === String(initialPhaseId))) {
      setPhaseId(String(initialPhaseId));
      return;
    }
    const d = phases.find((ph) => ph.status === 'active') || phases[0];
    if (d?.id) setPhaseId(String(d.id));
  }, [timeline?.plan?.plan_id, phases, initialPhaseId]);

  const [startDate, setStartDate] = React.useState(() => (
    typeof window.cinevYmdWallToday === 'function' ? window.cinevYmdWallToday() : ''
  ));
  const defHh = String(CINEV_DEFAULT_APPOINTMENT_START_HOUR).padStart(2, '0');
  const [wallHour, setWallHour] = React.useState(defHh);
  const [wallMinute, setWallMinute] = React.useState('00');
  React.useEffect(() => {
    const ymd = typeof window.cinevYmdWallToday === 'function' ? window.cinevYmdWallToday() : '';
    if (ymd) {
      setStartDate(ymd);
    } else {
      const d = new Date();
      const y = d.getFullYear();
      const mo = String(d.getMonth() + 1).padStart(2, '0');
      const day = String(d.getDate()).padStart(2, '0');
      setStartDate(`${y}-${mo}-${day}`);
    }
    setWallHour(String(CINEV_DEFAULT_APPOINTMENT_START_HOUR).padStart(2, '0'));
    setWallMinute('00');
  }, []);

  const therapistIds = React.useMemo(
    () => Object.keys(therapistMap || {}).filter((k) => k),
    [therapistMap],
  );
  const [therapistId, setTherapistId] = React.useState('');
  React.useEffect(() => {
    if (!therapistIds.length) return;
    setTherapistId((prev) => (prev && therapistIds.includes(prev) ? prev : therapistIds[0]));
  }, [therapistIds]);

  const [locationText, setLocationText] = React.useState('');
  const [note, setNote] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');

  const save = async () => {
    if (!phaseId || !startDate) {
      setErr(t('sp_validation_phase_start'));
      return;
    }
    const combinedLocal = `${startDate}T${cinevWallQuarterPartsToHhmm(wallHour, wallMinute)}`;
    const startMs = new Date(combinedLocal).getTime();
    if (Number.isNaN(startMs)) {
      setErr(t('sp_validation_phase_start'));
      return;
    }
    setBusy(true);
    setErr('');
    try {
      const startIso = new Date(combinedLocal).toISOString();
      const opId = typeof crypto !== 'undefined' && crypto.randomUUID ? crypto.randomUUID() : `op-${Date.now()}`;
      await cinevApiPost(`/patients/${encodeURIComponent(patientId)}/special-appointments`, {
        phaseId,
        kind,
        startAt: startIso,
        durationMinutes: duration,
        therapistId: therapistId || undefined,
        locationText: locationText.trim() || null,
        note: note.trim() || null,
        operationId: opId,
      });
      if (onSaved) onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(20,30,35,0.45)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 100, padding: 16, boxSizing: 'border-box' }}>
      <div
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-modal="true"
        lang={cinevModalWallLang()}
        style={{
          background: CINEV.card,
          borderRadius: 12,
          padding: '22px 26px 24px',
          width: 'min(460px, calc(100vw - 32px))',
          maxWidth: '100%',
          boxSizing: 'border-box',
          boxShadow: '0 20px 60px rgba(0,0,0,0.25)',
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'stretch',
          minWidth: 0,
        }}
      >
        <div style={{ fontSize: 11, color: '#6b4d8a', letterSpacing: 0.6, textTransform: 'uppercase', fontWeight: 600 }}>◆ {t('ap_type_special')}</div>
        <div style={{ fontSize: 20, fontWeight: 600, margin: '4px 0 18px', letterSpacing: -0.3 }}>{t('sp_title')}</div>
        <div style={{ display: 'grid', gap: 12, width: '100%', minWidth: 0, boxSizing: 'border-box' }}>
          <div>
            <div style={{ fontSize: 11, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 500, marginBottom: 6 }}>{t('sp_phase_label')}</div>
            <select
              value={phaseId}
              onChange={(e) => setPhaseId(e.target.value)}
              style={cinevModalDatetimeFieldStyle()}
            >
              {phases.map((ph) => (
                <option key={ph.id} value={ph.id}>
                  {t('pd_phase_label')} {Number(ph.phase_index) + 1}
                  {ph.status === 'active' ? ` · ${t('pd_phase_status_active')}` : ''}
                </option>
              ))}
            </select>
          </div>
          <div>
            <div style={{ fontSize: 11, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 500, marginBottom: 6 }}>{t('sp_kind')}</div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
              {kindOptions.map((ko) => (
                <button key={ko.v} type="button" onClick={() => setKind(ko.v)} style={{
                  padding: '7px 12px', border: kind === ko.v ? `1.5px solid #6b4d8a` : `1px solid ${CINEV.line}`,
                  background: kind === ko.v ? '#efe8f4' : CINEV.card, color: kind === ko.v ? '#6b4d8a' : CINEV.inkSoft,
                  borderRadius: 16, fontSize: 11.5, fontWeight: kind === ko.v ? 600 : 400, cursor: 'pointer', fontFamily: 'inherit',
                }}>{t(ko.labelKey)}</button>
              ))}
            </div>
          </div>
          <div style={{ width: '100%', minWidth: 0, boxSizing: 'border-box' }}>
            <div style={{ fontSize: 11, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 500, marginBottom: 6 }}>{t('sp_datetime_label')}</div>
            <div style={{
              display: 'flex', flexDirection: 'row', flexWrap: 'nowrap', alignItems: 'center', gap: 8,
              width: '100%', minWidth: 0, boxSizing: 'border-box',
            }}
            >
              <CinevDateDmyInput
                valueYmd={startDate}
                onValueYmdChange={setStartDate}
                style={{
                  ...cinevModalDatetimeFieldStyle(),
                  flex: '1 1 auto',
                  width: 'auto',
                  minWidth: '10.5rem',
                  minHeight: 0,
                }}
              />
              <CinevWallTimeQuarterSelects
                variant="modal"
                hour={wallHour}
                minute={wallMinute}
                onHourChange={setWallHour}
                onMinuteChange={setWallMinute}
              />
            </div>
          </div>
          <div>
            <div style={{ fontSize: 11, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 500, marginBottom: 6 }}>{t('sp_therapist_label')}</div>
            <select
              value={therapistId}
              onChange={(e) => setTherapistId(e.target.value)}
              style={cinevModalDatetimeFieldStyle()}
            >
              {therapistIds.map((tid) => (
                <option key={tid} value={tid}>{therapistDisplayName(tid, therapistMap)}</option>
              ))}
            </select>
          </div>
          <div>
            <div style={{ fontSize: 11, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 500, marginBottom: 6 }}>{t('sp_duration')}</div>
            <div style={{ display: 'flex', gap: 6 }}>
              {[15, 30, 45, 60, 90].map((d) => (
                <button key={d} type="button" onClick={() => setDuration(d)} style={{
                  flex: 1, padding: '8px', border: duration === d ? `1.5px solid ${CINEV.teal}` : `1px solid ${CINEV.line}`,
                  background: duration === d ? CINEV.soft : CINEV.card, color: duration === d ? CINEV.teal : CINEV.inkSoft,
                  borderRadius: 6, fontSize: 12, fontWeight: duration === d ? 600 : 400, cursor: 'pointer', fontFamily: 'inherit',
                }}>{d}<span style={{ fontSize: 10, opacity: 0.7, marginLeft: 1 }}>m</span></button>
              ))}
            </div>
          </div>
          <div>
            <div style={{ fontSize: 11, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 500, marginBottom: 6 }}>{t('sp_location')}</div>
            <input
              value={locationText}
              onChange={(e) => setLocationText(e.target.value)}
              placeholder={t('sp_location_ph')}
              style={cinevModalDatetimeFieldStyle()}
            />
          </div>
          <div>
            <div style={{ fontSize: 11, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 500, marginBottom: 6 }}>{t('sp_comment')}</div>
            <textarea
              value={note}
              onChange={(e) => setNote(e.target.value)}
              placeholder={t('sp_comment_ph')}
              rows={3}
              style={cinevModalDatetimeFieldStyle({ resize: 'vertical' })}
            />
          </div>
          {err ? <div style={{ fontSize: 12, color: '#c94343' }}>{err}</div> : null}
        </div>
        <div style={{ display: 'flex', gap: 8, marginTop: 22, width: '100%', minWidth: 0, boxSizing: 'border-box' }}>
          <button type="button" onClick={onClose} disabled={busy} style={{ flex: 1, background: 'transparent', border: `1px solid ${CINEV.line}`,
            padding: '10px', borderRadius: 6, fontSize: 13, color: CINEV.inkSoft, cursor: busy ? 'default' : 'pointer', fontFamily: 'inherit' }}>{t('fs_cancel')}</button>
          <button type="button" onClick={() => void save()} disabled={busy} style={{ flex: 2, background: '#6b4d8a', color: '#fff', border: 'none',
            padding: '10px', borderRadius: 6, fontSize: 13, fontWeight: 600, cursor: busy ? 'default' : 'pointer', fontFamily: 'inherit' }}>
            {busy ? t('sp_save_busy') : t('sp_book')}
          </button>
        </div>
      </div>
    </div>
  );
}

const TP_DISCIPLINE_OPTIONS = ['ergo', 'speech', 'neuro', 'behavioral', 'psychomotor'];
const TP_DISCIPLINE_LABEL_KEY = {
  ergo: 'ty_occupational', speech: 'ty_speech', neuro: 'ty_eeg', behavioral: 'ty_behavior', psychomotor: 'ty_psychomotor',
};

/** Liest disciplines aus API/pg (Array, String "{a,b}", numerisches Objekt). */
function coerceRowDisciplinesArray(raw) {
  if (raw == null) return [];
  if (Array.isArray(raw)) {
    return raw.map((x) => String(x).trim()).filter(Boolean);
  }
  if (typeof raw === 'object') {
    const keys = Object.keys(raw).filter((k) => /^\d+$/.test(k)).sort((a, b) => Number(a) - Number(b));
    if (keys.length) return keys.map((k) => String(raw[k]).trim()).filter(Boolean);
  }
  if (typeof raw === 'string') {
    const s = raw.trim();
    if (s.startsWith('{') && s.endsWith('}')) {
      const inner = s.slice(1, -1);
      if (!inner) return [];
      return inner.split(',').map((p) => p.replace(/^"(.*)"$/, '$1').trim()).filter(Boolean);
    }
    try {
      const j = JSON.parse(s);
      if (Array.isArray(j)) return j.map((x) => String(x).trim()).filter(Boolean);
    } catch {
      /* ignore */
    }
  }
  return [];
}

function therapistDisciplinesList(row) {
  const raw = row.disciplines ?? row.disciplineList;
  const flat = coerceRowDisciplinesArray(raw);
  if (flat.length) {
    const ok = flat.filter((d) => TP_DISCIPLINE_OPTIONS.includes(d));
    if (ok.length) return ok;
  }
  const d = String(row.discipline || 'behavioral').trim();
  return TP_DISCIPLINE_OPTIONS.includes(d) ? [d] : ['behavioral'];
}

function disciplineToSpecDisplayKeys(d) {
  const d0 = d || 'behavioral';
  const primary = {
    behavioral: 'ty_behavior',
    speech: 'ty_speech',
    ergo: 'ty_occupational',
    neuro: 'ty_eeg',
    psychomotor: 'ty_psychomotor',
  }[d0] || 'ty_behavior';
  return [primary];
}

function therapistCardSpecKeysFromRow(row) {
  const seen = new Set();
  const out = [];
  for (const disc of therapistDisciplinesList(row)) {
    for (const k of disciplineToSpecDisplayKeys(disc)) {
      if (!seen.has(k)) {
        seen.add(k);
        out.push(k);
      }
    }
  }
  return out;
}

function TherapistEditModal({ row, isCreate, onClose, onSaved, onDeleted }) {
  useLang();
  const id = !isCreate && row?.id != null ? String(row.id) : '';
  const [firstName, setFirstName] = React.useState(row?.first_name || '');
  const [lastName, setLastName] = React.useState(row?.last_name || '');
  const [email, setEmail] = React.useState(row?.email || '');
  const [phone, setPhone] = React.useState(row?.phone || '');
  const [preferredLang, setPreferredLang] = React.useState((row?.preferred_lang || 'de').slice(0, 2));
  const [disciplinesSel, setDisciplinesSel] = React.useState(() => therapistDisciplinesList(row || {}));
  const [isActive, setIsActive] = React.useState(row?.is_active !== false);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');

  React.useEffect(() => {
    setFirstName(row?.first_name || '');
    setLastName(row?.last_name || '');
    setEmail(row?.email || '');
    setPhone(row?.phone || '');
    setPreferredLang((row?.preferred_lang || 'de').slice(0, 2));
    setDisciplinesSel(therapistDisciplinesList(row || {}));
    setIsActive(row?.is_active !== false);
  }, [row, isCreate]);

  const toggleDiscipline = (d) => {
    setDisciplinesSel((prev) => {
      const set = new Set(prev);
      if (set.has(d)) {
        if (set.size <= 1) return prev;
        set.delete(d);
      } else {
        set.add(d);
      }
      return Array.from(set);
    });
  };

  const save = async () => {
    setBusy(true);
    setErr('');
    try {
      const payloadDisciplines = [...disciplinesSel].sort(
        (a, b) => TP_DISCIPLINE_OPTIONS.indexOf(a) - TP_DISCIPLINE_OPTIONS.indexOf(b),
      );
      const disc = payloadDisciplines[0] || 'behavioral';
      const body = {
        firstName: firstName.trim(),
        lastName: lastName.trim(),
        email: email.trim() || null,
        phone: phone.trim() || null,
        preferredLang,
        discipline: disc,
        disciplines: payloadDisciplines,
        isActive,
      };
      let updated;
      if (isCreate) {
        const created = await cinevApiPost('/therapists', {
          firstName: body.firstName,
          lastName: body.lastName,
          email: body.email,
          phone: body.phone,
          preferredLang: body.preferredLang,
          discipline: disc,
        });
        const tid = created?.therapistId ?? created?.therapist_id;
        if (tid == null) throw new Error(t('tp_err_no_id'));
        updated = await cinevApiPatch(`/therapists/${encodeURIComponent(String(tid))}`, body);
      } else {
        updated = await cinevApiPatch(`/therapists/${encodeURIComponent(id)}`, body);
      }
      onSaved(updated);
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  const remove = async () => {
    if (isCreate || !id) return;
    if (!window.confirm(t('tp_s5_delete_confirm'))) return;
    setBusy(true);
    setErr('');
    try {
      await cinevApiDelete(`/therapists/${encodeURIComponent(id)}`);
      if (typeof onDeleted === 'function') onDeleted();
      onClose();
    } catch (e) {
      if (e.status === 409 && e.body && e.body.errorCode === 'therapist_in_use') {
        setErr(t('tp_delete_in_use'));
      } else {
        setErr(e.message || String(e));
      }
    } finally {
      setBusy(false);
    }
  };

  return (
    <div
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0, background: 'rgba(20,30,35,0.45)',
        display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 120000,
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          background: CINEV.card, borderRadius: 12, padding: '22px 24px 20px', width: 420, maxWidth: '92vw',
          boxShadow: '0 20px 60px rgba(0,0,0,0.25)', border: `1px solid ${CINEV.line}`,
        }}
      >
        <div style={{ fontSize: 11, color: CINEV.inkMute, letterSpacing: 0.5, textTransform: 'uppercase', fontWeight: 600 }}>{isCreate ? t('tp_s5_new_title') : t('tp_s5_title')}</div>
        {isCreate ? (
          <div style={{ fontSize: 13, fontWeight: 400, color: CINEV.inkSoft, margin: '6px 0 16px', lineHeight: 1.45 }}>{t('tp_s5_new_hint')}</div>
        ) : (
          <div style={{ fontSize: 18, fontWeight: 600, margin: '6px 0 16px', letterSpacing: -0.3 }}>{[firstName, lastName].filter(Boolean).join(' ') || id.slice(0, 8)}</div>
        )}
        <div style={{ display: 'grid', gap: 12 }}>
          <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_fn')}
            <input value={firstName} onChange={(e) => setFirstName(e.target.value)}
              style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
          </label>
          <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_ln')}
            <input value={lastName} onChange={(e) => setLastName(e.target.value)}
              style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
          </label>
          <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_email')}
            <input value={email} onChange={(e) => setEmail(e.target.value)} type="email"
              style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
          </label>
          <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_phone')}
            <input value={phone} onChange={(e) => setPhone(e.target.value)}
              style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13, boxSizing: 'border-box' }}/>
          </label>
          <label style={{ fontSize: 11, color: CINEV.inkMute }}>{t('pd_s4_gua_lang')}
            <select value={preferredLang} onChange={(e) => setPreferredLang(e.target.value)}
              style={{ display: 'block', width: '100%', marginTop: 4, padding: '8px 10px', borderRadius: 6, border: `1px solid ${CINEV.line}`, fontFamily: 'inherit', fontSize: 13 }}>
              <option value="de">DE</option>
              <option value="es">ES</option>
            </select>
          </label>
          <div style={{ fontSize: 11, color: CINEV.inkMute }}>{t('tp_s5_disciplines')}</div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 6 }}>
            {TP_DISCIPLINE_OPTIONS.map((d) => {
              const on = disciplinesSel.includes(d);
              return (
                <label
                  key={d}
                  style={{
                    display: 'inline-flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 12.5,
                    padding: '6px 10px', borderRadius: 8, border: `1px solid ${on ? CINEV.teal : CINEV.line}`,
                    background: on ? CINEV.soft : 'transparent', color: CINEV.ink, userSelect: 'none',
                  }}
                >
                  <input type="checkbox" checked={on} onChange={() => toggleDiscipline(d)} style={{ accentColor: CINEV.teal }} />
                  {t(TP_DISCIPLINE_LABEL_KEY[d])}
                </label>
              );
            })}
          </div>
          <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5, cursor: 'pointer' }}>
            <input type="checkbox" checked={isActive} onChange={(e) => setIsActive(e.target.checked)} />
            {t('tp_s5_active')}
          </label>
        </div>
        {err ? <div style={{ marginTop: 10, fontSize: 12, color: '#c94343' }}>{err}</div> : null}
        <div style={{ display: 'flex', gap: 8, marginTop: 18, justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap' }}>
          <div>
            {!isCreate && id ? (
              <button
                type="button"
                onClick={() => void remove()}
                disabled={busy}
                style={{
                  background: 'transparent',
                  border: 'none',
                  padding: '8px 0',
                  fontFamily: 'inherit',
                  fontSize: 12.5,
                  fontWeight: 500,
                  color: CINEV.noShow.fg,
                  cursor: busy ? 'default' : 'pointer',
                  textDecoration: 'underline',
                  textUnderlineOffset: 2,
                }}
              >{t('tp_s5_delete')}</button>
            ) : null}
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button type="button" onClick={onClose} disabled={busy}
              style={{ background: 'transparent', border: `1px solid ${CINEV.line}`, padding: '8px 14px', borderRadius: 6, fontFamily: 'inherit', cursor: busy ? 'default' : 'pointer' }}>
              {t('pd_s4_gua_cancel')}
            </button>
            <button type="button" onClick={() => void save()} disabled={busy || !firstName.trim() || !lastName.trim() || disciplinesSel.length === 0}
              style={{ background: CINEV.teal, color: '#fff', border: 'none', padding: '8px 14px', borderRadius: 6, fontFamily: 'inherit', fontWeight: 600, cursor: busy ? 'default' : 'pointer' }}>
              {busy ? '…' : (isCreate ? t('tp_s5_create') : t('tp_s5_save'))}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ======================= THERAPISTS ============================
function TherapistsView({ highlightTherapistId, openCreateTherapistNonce = 0, onTherapistCreated } = {}) {
  useLang();
  const live = typeof cinevLiveApi === 'function' && cinevLiveApi();
  const [rows, setRows] = React.useState([]);
  const [err, setErr] = React.useState(null);
  const [tpRefresh, setTpRefresh] = React.useState(0);
  const [editTherapist, setEditTherapist] = React.useState(null);
  const [createTherapistOpen, setCreateTherapistOpen] = React.useState(false);
  const createNonceSeen = React.useRef(0);

  React.useEffect(() => {
    if (!live) return undefined;
    let cancelled = false;
    (async () => {
      setErr(null);
      try {
        const tData = await cinevApiGet('/therapists');
        if (cancelled) return;
        setRows(tData.items || []);
      } catch (e) {
        if (!cancelled) setErr(e.message || String(e));
      }
    })();
    return () => { cancelled = true; };
  }, [live, tpRefresh]);

  React.useEffect(() => {
    if (!live || !highlightTherapistId) return;
    const id = `tp-live-card-${highlightTherapistId}`;
    const t = window.setTimeout(() => {
      const el = document.getElementById(id);
      if (el) el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }, 100);
    return () => window.clearTimeout(t);
  }, [live, highlightTherapistId, rows]);

  React.useEffect(() => {
    if (!live) return;
    const n = Number(openCreateTherapistNonce) || 0;
    if (n > createNonceSeen.current) {
      createNonceSeen.current = n;
      setEditTherapist(null);
      setCreateTherapistOpen(true);
    }
  }, [live, openCreateTherapistNonce]);

  const team = React.useMemo(() => [...(rows || [])].sort((a, b) => {
    const byFn = String(a.first_name || '').localeCompare(String(b.first_name || ''), undefined, { sensitivity: 'base' });
    if (byFn !== 0) return byFn;
    return String(a.last_name || '').localeCompare(String(b.last_name || ''), undefined, { sensitivity: 'base' });
  }), [rows]);
  const therapistApiMap = React.useMemo(() => cinevTherapistMapFromItems(rows || []), [rows]);

  if (!live) {
    return (
      <div style={{ padding: '20px 32px' }}>
        <div style={{ padding: '10px 12px', borderRadius: 8, background: CINEV.soft, color: CINEV.inkSoft, fontSize: 12.5, border: `1px solid ${CINEV.line}` }}>
          {t('ui_proto_no_live')}
        </div>
      </div>
    );
  }

  return (
    <div style={{ padding: '20px 32px' }}>
      {err && (
        <div style={{ marginBottom: 12, padding: '10px 12px', borderRadius: 8, background: CINEV.noShow.bg, color: CINEV.noShow.fg, fontSize: 12.5 }}>{err}</div>
      )}
      <div style={{ columnWidth: 320, columnGap: 14 }}>
        {team.map((row) => {
          const id = String(row.id);
          const name = [row.first_name, row.last_name].filter(Boolean).join(' ') || id.slice(0, 8);
          const specs = therapistCardSpecKeysFromRow(row);
          const hi = highlightTherapistId && String(highlightTherapistId) === id;
          const tpLbl = { fontSize: 10, color: CINEV.inkMute, textTransform: 'uppercase', letterSpacing: 0.4 };
          const emailDisp = (row.email && String(row.email).trim()) || '—';
          const phoneDisp = (row.phone && String(row.phone).trim()) || '—';
          return (
            <div
              id={`tp-live-card-${id}`}
              key={id}
              style={{
                background: CINEV.card, borderRadius: 10, border: hi ? `2px solid ${CINEV.teal}` : `1px solid ${CINEV.line}`,
                padding: '16px 16px 14px', display: 'flex', flexDirection: 'column', gap: 12,
                breakInside: 'avoid', WebkitColumnBreakInside: 'avoid', pageBreakInside: 'avoid',
                marginBottom: 14,
              }}
            >
              <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
                <Avatar therapist={id} size={44} therapistApiMap={therapistApiMap}/>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{name}</div>
                </div>
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10 }}>
                  <div style={tpLbl}>{t('tp_specialties')}</div>
                  <div style={{ textAlign: 'right', minWidth: 0, flex: '1 1 auto' }}>
                    <div style={{ ...tpLbl, textTransform: 'none', fontVariantNumeric: 'tabular-nums', wordBreak: 'break-all' }}>{emailDisp}</div>
                    <div style={{ ...tpLbl, textTransform: 'none', fontVariantNumeric: 'tabular-nums', marginTop: 4 }}>{phoneDisp}</div>
                  </div>
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 4, justifyItems: 'start' }}>
                  {specs.map((s, i) => (
                    <span key={i} style={{ fontSize: 10.5, padding: '3px 8px', background: CINEV.soft, color: CINEV.teal, borderRadius: 10, fontWeight: 500 }}>{t(s)}</span>
                  ))}
                </div>
                <button
                  type="button"
                  onClick={() => { setCreateTherapistOpen(false); setEditTherapist(row); }}
                  style={{
                    alignSelf: 'flex-end',
                    marginTop: 2,
                    fontSize: 11.5, color: CINEV.teal, fontWeight: 600, cursor: 'pointer', background: 'transparent', border: 'none',
                    fontFamily: 'inherit', padding: 0, textDecoration: 'underline', textUnderlineOffset: 2,
                  }}
                >{t('tp_view_profile')}</button>
              </div>
            </div>
          );
        })}
      </div>
      {createTherapistOpen ? (
        <TherapistEditModal
          key={`tp-create-${openCreateTherapistNonce}`}
          isCreate
          row={{}}
          onClose={() => setCreateTherapistOpen(false)}
          onSaved={(updated) => {
            setCreateTherapistOpen(false);
            if (updated && updated.id != null) {
              const uid = String(updated.id);
              setRows((prev) => prev.map((r) => (String(r.id) === uid ? { ...r, ...updated } : r)));
              if (typeof onTherapistCreated === 'function') onTherapistCreated(uid);
            }
            setTpRefresh((n) => n + 1);
          }}
        />
      ) : editTherapist ? (
        <TherapistEditModal
          row={editTherapist}
          onClose={() => setEditTherapist(null)}
          onSaved={(updated) => {
            setEditTherapist(null);
            if (updated && updated.id != null) {
              const uid = String(updated.id);
              setRows((prev) => prev.map((r) => (String(r.id) === uid ? { ...r, ...updated } : r)));
            }
            setTpRefresh((n) => n + 1);
          }}
          onDeleted={() => {
            setTpRefresh((n) => n + 1);
          }}
        />
      ) : null}
    </div>
  );
}

// ======================= REMINDERS ============================
function reminderLogSubjectLabel(templateKey) {
  const k = String(templateKey || '');
  if (k === 'reminder_48h') return t('rm_tpl_reminder_48h');
  if (k === 'reminder_day') return t('rm_tpl_reminder_day');
  return k || '—';
}

function reminderLogWhenCell(row) {
  const iso = row.sent_at || row.scheduled_at;
  if (!iso) return '—';
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return '—';
  const dd = String(d.getDate()).padStart(2, '0');
  const mm = String(d.getMonth() + 1).padStart(2, '0');
  const hh = String(d.getHours()).padStart(2, '0');
  const min = String(d.getMinutes()).padStart(2, '0');
  return `${dd}.${mm}. · ${hh}:${min}`;
}

function reminderSendStatusForRm(sendStatus) {
  const st = String(sendStatus || '').toLowerCase();
  if (st === 'sent' || st === 'delivered') return 'delivered';
  if (st === 'opened') return 'opened';
  if (st === 'confirmed') return 'confirmed';
  if (st === 'queued' || st === 'pending') return 'pending';
  if (st === 'bounced' || st.includes('fail')) return 'bounced';
  return 'pending';
}

/** Log aus PostgreSQL (`mail_reminder`); keine Demo-Zeilen. */
function RemindersView() {
  useLang();
  const live = typeof cinevLiveApi === 'function' && cinevLiveApi();
  const [items, setItems] = React.useState([]);
  const [loading, setLoading] = React.useState(false);
  const [err, setErr] = React.useState(null);

  React.useEffect(() => {
    if (!live) return undefined;
    let cancelled = false;
    (async () => {
      setLoading(true);
      setErr(null);
      try {
        const d = await cinevApiGet('/reminders/log?limit=200');
        if (!cancelled) setItems(d.items || []);
      } catch (e) {
        if (!cancelled) setErr(e.message || String(e));
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [live]);

  if (!live) {
    return (
      <div style={{ padding: '20px 32px' }}>
        <div style={{ padding: '10px 12px', borderRadius: 8, background: CINEV.soft, color: CINEV.inkSoft, fontSize: 12.5, border: `1px solid ${CINEV.line}` }}>
          {t('ui_proto_no_live')}
        </div>
      </div>
    );
  }

  return (
    <div style={{ padding: '20px 32px', display: 'grid', gridTemplateColumns: '1fr 300px', gap: 20 }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        {err ? (
          <div style={{ padding: '10px 12px', borderRadius: 8, background: CINEV.noShow.bg, color: CINEV.noShow.fg, fontSize: 12.5 }}>{err}</div>
        ) : null}
        {loading ? (
          <div style={{ fontSize: 12.5, color: CINEV.inkMute }}>…</div>
        ) : null}
        <div style={{ background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`, overflow: 'hidden' }}>
          <div style={{ padding: '14px 18px', borderBottom: `1px solid ${CINEV.line}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div style={{ fontSize: 14, fontWeight: 600 }}>{t('rm_log_title')}</div>
            <span style={{ fontSize: 10, padding: '3px 7px', borderRadius: 10, background: CINEV.soft, color: CINEV.teal, fontWeight: 600 }}>{t('reminders_badge')}</span>
          </div>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
            <thead>
              <tr style={{ color: CINEV.inkMute, fontSize: 10.5, textTransform: 'uppercase', letterSpacing: 0.5, background: CINEV.bg }}>
                <Th>{t('rm_log_subject')}</Th><Th>{t('rm_log_to')}</Th><Th>{t('rm_log_when')}</Th><Th>{t('rm_log_status')}</Th>
              </tr>
            </thead>
            <tbody>
              {(items || []).length === 0 && !loading ? (
                <tr><td colSpan={4} style={{ padding: '14px 12px', color: CINEV.inkMute, fontSize: 12.5 }}>—</td></tr>
              ) : null}
              {(items || []).map((r) => (
                <tr key={r.id || `${r.scheduled_at}-${r.template_key}`} style={{ borderTop: `1px solid ${CINEV.lineSoft}` }}>
                  <td style={{ padding: '10px 12px' }}>
                    <div style={{ fontWeight: 500, fontSize: 12.5 }}>{reminderLogSubjectLabel(r.template_key)}</div>
                    <div style={{ fontSize: 11, color: CINEV.inkMute, marginTop: 1 }}>{r.patient_name || '—'}</div>
                  </td>
                  <td style={{ padding: '10px 8px', color: CINEV.inkSoft, fontSize: 11.5, wordBreak: 'break-all' }}>{r.recipient_email || '—'}</td>
                  <td style={{ padding: '10px 8px', color: CINEV.inkMute, fontSize: 11.5, fontVariantNumeric: 'tabular-nums' }}>{reminderLogWhenCell(r)}</td>
                  <td style={{ padding: '10px 8px' }}><RmStatus s={reminderSendStatusForRm(r.send_status)}/></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        <div style={{ background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`, padding: '14px 16px' }}>
          <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 10 }}>{t('rm_templates_title')}</div>
          <div style={{ fontSize: 12, color: CINEV.inkMute, lineHeight: 1.5 }}>{t('rm_placeholder_side')}</div>
        </div>
        <div style={{ background: `linear-gradient(135deg, ${CINEV.green}, ${CINEV.greenDark})`, borderRadius: 10, padding: '14px 16px', color: '#fff' }}>
          <div style={{ fontSize: 11, opacity: 0.8, letterSpacing: 0.4, textTransform: 'uppercase' }}>DSGVO / RGPD</div>
          <div style={{ fontSize: 12.5, marginTop: 4, lineHeight: 1.5 }}>
            {window.__cinevLang === 'es'
              ? 'Todos los correos contienen enlace de cancelación y aviso de privacidad. Los datos se almacenan cifrados en UE.'
              : 'Alle Mails enthalten Abmeldelink und Datenschutzhinweis. Daten verschlüsselt in EU gespeichert.'}
          </div>
        </div>
      </div>
    </div>
  );
}

function RmStatus({ s }) {
  const m = {
    delivered: [CINEV.soft, CINEV.teal, t('rm_status_delivered')],
    opened: [CINEV.tealLight + '33', CINEV.teal, t('rm_status_opened')],
    confirmed: [CINEV.green + '22', CINEV.greenDark, t('rm_status_confirmed')],
    pending: [CINEV.warn + '22', CINEV.warn, t('rm_status_pending')],
    bounced: ['#f4dcd5', '#a33a1a', t('rm_status_bounced')],
  };
  const [bg, fg, label] = m[s] || [CINEV.soft, CINEV.inkSoft, String(s || '—')];
  return <span style={{ fontSize: 10.5, padding: '3px 8px', borderRadius: 10, background: bg, color: fg, fontWeight: 600, whiteSpace: 'nowrap' }}>{label}</span>;
}

// ======================= STATS ============================
function statsDisciplineLabelKey(disc) {
  const m = {
    ergo: 'ty_occupational',
    speech: 'ty_speech',
    neuro: 'ty_eeg',
    behavioral: 'ty_behavior',
    psychomotor: 'ty_psychomotor',
  };
  return m[disc] || 'tp_s5_discipline';
}

function StatsView() {
  useLang();
  const live = typeof cinevLiveApi === 'function' && cinevLiveApi();
  const y = new Date().getFullYear();
  const [from, setFrom] = React.useState(`${y}-01-01`);
  const [to, setTo] = React.useState(`${y}-12-31`);
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [err, setErr] = React.useState(null);

  const load = React.useCallback(() => {
    if (!live) return;
    setLoading(true);
    setErr(null);
    const qs = new URLSearchParams({ from, to });
    cinevApiGet(`/reports/dashboard?${qs.toString()}`)
      .then(setData)
      .catch((e) => {
        setErr(e && e.message ? String(e.message) : t('st_error'));
        setData(null);
      })
      .finally(() => setLoading(false));
  }, [live, from, to]);

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

  if (!live) {
    return (
      <div style={{ padding: '20px 32px' }}>
        <div style={{
          background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`,
          padding: '20px 22px', maxWidth: 640, fontSize: 13.5, color: CINEV.inkSoft, lineHeight: 1.55,
        }}>
          {t('ui_proto_no_live')}
        </div>
      </div>
    );
  }

  const totals = data && data.totals ? data.totals : null;
  const weekSeries = (data && data.weekSeries) || [];
  const mix = (data && data.disciplineMix) || [];
  const tload = (data && data.therapistWeek) || [];
  const maxWeek = Math.max(1, ...weekSeries.map((w) => Number(w.total_n) || 0));

  const pct = (x) => (x == null || Number.isNaN(x) ? t('st_na') : `${Math.round(x * 1000) / 10}%`);

  return (
    <div style={{ padding: '20px 32px' }}>
      <div style={{ marginBottom: 14, maxWidth: 960 }}>
        <div style={{ fontSize: 18, fontWeight: 700, color: CINEV.ink }}>{t('st_title')}</div>
        <div style={{ fontSize: 12.5, color: CINEV.inkMute, marginTop: 4 }}>{t('st_sub')}</div>
        <div style={{ fontSize: 12, color: CINEV.inkSoft, marginTop: 8, lineHeight: 1.5 }}>{t('st_live_only')}</div>
      </div>

      <div style={{
        display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'flex-end', marginBottom: 18, maxWidth: 960,
      }}>
        <label style={{ fontSize: 11, color: CINEV.inkMute }} lang={cinevModalWallLang()}>
          <div>{t('st_from')}</div>
          <CinevDateDmyInput
            valueYmd={from}
            onValueYmdChange={setFrom}
            style={{ ...cinevModalDatetimeFieldStyle(), marginTop: 4, width: 'auto', minWidth: '10.5rem' }}
          />
        </label>
        <label style={{ fontSize: 11, color: CINEV.inkMute }} lang={cinevModalWallLang()}>
          <div>{t('st_to')}</div>
          <CinevDateDmyInput
            valueYmd={to}
            onValueYmdChange={setTo}
            style={{ ...cinevModalDatetimeFieldStyle(), marginTop: 4, width: 'auto', minWidth: '10.5rem' }}
          />
        </label>
        <button
          type="button"
          onClick={() => load()}
          style={{
            padding: '10px 16px', borderRadius: 8, border: `1px solid ${CINEV.teal}`,
            background: CINEV.teal, color: '#fff', fontWeight: 600, fontSize: 13, cursor: 'pointer', fontFamily: 'inherit',
          }}
        >
          {loading ? t('st_loading') : t('st_apply')}
        </button>
        {data && data.timezone ? (
          <span style={{ fontSize: 11, color: CINEV.inkMute, marginLeft: 4 }}>
            {t('st_tz')}: <span style={{ color: CINEV.inkSoft, fontVariantNumeric: 'tabular-nums' }}>{data.timezone}</span>
          </span>
        ) : null}
      </div>

      {err ? (
        <div style={{
          background: CINEV.noShow.bg, color: CINEV.noShow.fg, borderRadius: 10,
          border: `1px solid ${CINEV.noShow.borderSoft}`, padding: '14px 16px', maxWidth: 720, fontSize: 13,
        }}>
          {err}
        </div>
      ) : null}

      {totals ? (
        <div style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))',
          gap: 10,
          maxWidth: 960,
          marginBottom: 20,
        }}
        >
          {[
            ['a', t('st_kpi_active'), data.activePatients],
            ['b', t('st_kpi_appt'), totals.appointments],
            ['c', t('st_kpi_done'), totals.done],
            ['d', t('st_kpi_sched'), totals.scheduled],
            ['e', t('st_kpi_canc'), totals.cancelled],
            ['f', t('st_kpi_ns_count'), totals.no_show],
            ['g', t('st_kpi_completion'), pct(data.completionRate)],
            ['h', t('st_kpi_noshow'), pct(data.noShowRate)],
          ].map(([k, label, val]) => (
            <div
              key={k}
              style={{
                background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`,
                padding: '12px 14px',
              }}
            >
              <div style={{ fontSize: 10.5, color: CINEV.inkMute, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.3 }}>{label}</div>
              <div style={{ fontSize: 20, fontWeight: 700, color: CINEV.teal, marginTop: 6, fontVariantNumeric: 'tabular-nums' }}>{val}</div>
            </div>
          ))}
        </div>
      ) : !err && !loading ? (
        <div style={{ fontSize: 13, color: CINEV.inkSoft }}>{t('st_na')}</div>
      ) : null}

      {weekSeries.length > 0 ? (
        <div style={{
          background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`,
          padding: '16px 18px', maxWidth: 960, marginBottom: 16,
        }}
        >
          <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 12, color: CINEV.ink }}>{t('st_chart_sessions')}</div>
          <div style={{ display: 'flex', alignItems: 'flex-end', gap: 6, minHeight: 120, flexWrap: 'wrap' }}>
            {weekSeries.map((w) => {
              const total = Number(w.total_n) || 0;
              const done = Number(w.done_n) || 0;
              const h = Math.round((total / maxWeek) * 88);
              const hDone = total > 0 ? Math.round((done / total) * h) : 0;
              return (
                <div key={String(w.week_start)} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', width: 36 }}>
                  <div style={{ position: 'relative', width: 22, height: 92, background: CINEV.soft, borderRadius: 6, overflow: 'hidden' }}>
                    <div style={{
                      position: 'absolute', bottom: 0, left: 0, right: 0, height: h,
                      background: CINEV.tealLight + '55',
                    }}
                    />
                    <div style={{
                      position: 'absolute', bottom: 0, left: 0, right: 0, height: hDone,
                      background: CINEV.greenDark,
                    }}
                    />
                  </div>
                  <div style={{ fontSize: 9, color: CINEV.inkMute, marginTop: 4, fontVariantNumeric: 'tabular-nums', textAlign: 'center' }}>
                    {String(w.week_start).slice(5)}
                  </div>
                </div>
              );
            })}
          </div>
          <div style={{ fontSize: 11, color: CINEV.inkMute, marginTop: 8 }}>{t('st_week_legend')}</div>
        </div>
      ) : null}

      {mix.length > 0 ? (
        <div style={{
          background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`,
          padding: '16px 18px', maxWidth: 960, marginBottom: 16,
        }}
        >
          <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 10, color: CINEV.ink }}>{t('st_chart_mix')}</div>
          <ul style={{ margin: 0, paddingLeft: 18, color: CINEV.inkSoft, fontSize: 13, lineHeight: 1.6 }}>
            {mix.map((row) => (
              <li key={row.discipline} style={{ fontVariantNumeric: 'tabular-nums' }}>
                {t(statsDisciplineLabelKey(row.discipline))}: {row.cnt}
              </li>
            ))}
          </ul>
        </div>
      ) : null}

      {tload.length > 0 ? (
        <div style={{
          background: CINEV.card, borderRadius: 10, border: `1px solid ${CINEV.line}`,
          padding: '16px 18px', maxWidth: 960,
        }}
        >
          <div style={{ fontSize: 14, fontWeight: 600, marginBottom: 10, color: CINEV.ink }}>{t('st_chart_util')}</div>
          <ul style={{ margin: 0, paddingLeft: 18, color: CINEV.inkSoft, fontSize: 13, lineHeight: 1.6 }}>
            {tload.map((row) => (
              <li key={row.therapist_id} style={{ fontVariantNumeric: 'tabular-nums' }}>
                {[row.first_name, row.last_name].filter(Boolean).join(' ')}: {row.appt_n}
              </li>
            ))}
          </ul>
        </div>
      ) : null}
    </div>
  );
}

Object.assign(window, { WeekView, PatientsView, TherapistsView, RemindersView, StatsView, PatientDetail, NewTherapyModal, SpecialApptModal });
