// ============================================================================
// DayOff.jsx — «Вихідні» (дизайн handoff_dayoffs, інтегровано в CRM).
//   function DayOff({ isMobile }) → window.DayOff
// Десктоп і мобілка в одному файлі. Дані — GET /api/dayoffs; кожна дія повертає
// свіжий знімок (сервер — джерело правди, оптимізму немає). Іконка — глобальний <Icon>.
// Наші правила: субота/неділя робочі й доступні; закритий лише понеділок. Власник
// (isOwner) керує будь-яким днем будь-кого (у т.ч. минулим цього тижня — allowPast).
// ============================================================================
(function () {
  const { useState, useEffect, useRef } = React;
  const Icon = window.Icon;   // глобальна перф-іконка CRM ({name,size,color,style})

  // keyframes + утиліти (один раз). Скоуповано під .do-root, щоб не чіпати інші екрани.
  if (typeof document !== "undefined" && !document.getElementById("dayoff-style")) {
    const st = document.createElement("style"); st.id = "dayoff-style";
    st.textContent =
      "@keyframes popIn{from{opacity:0;transform:scale(.97)}to{opacity:1;transform:scale(1)}}" +
      "@keyframes fadeIn{from{opacity:0}to{opacity:1}}@keyframes fade{from{opacity:0}to{opacity:1}}" +
      "@keyframes sheetUp{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}" +
      "@keyframes msheetUp{from{transform:translateY(100%)}to{transform:translateY(0)}}" +
      "@keyframes toastInM{from{opacity:0;transform:translate(-50%,10px)}to{opacity:1;transform:translate(-50%,0)}}" +
      "@keyframes doBlink{0%,100%{opacity:1}50%{opacity:.35}}" +
      ".do-root .label{font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--fg-muted)}" +
      ".do-root .pulse-dot{width:8px;height:8px;border-radius:50%;background:var(--warning);animation:doBlink 1.2s infinite;flex-shrink:0}" +
      ".do-root .no-bar{scrollbar-width:none}.do-root .no-bar::-webkit-scrollbar{display:none}";
    document.head.appendChild(st);
  }

  // ── дати (локальний час) ────────────────────────────────────────────────────
  const WEEKDAYS = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Нд"];
  const pad = n => String(n).padStart(2, "0");
  const ymd = d => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
  const dd = iso => String(+iso.slice(8, 10));
  const mon = iso => ["січ", "лют", "бер", "кві", "тра", "чер", "лип", "сер", "вер", "жов", "лис", "гру"][+iso.slice(5, 7) - 1];
  const fmtDate = iso => `${dd(iso)} ${mon(iso)}`;
  const TODAY = ymd(new Date());

  // ── примітиви ────────────────────────────────────────────────────────────────
  function Button({ variant = "primary", size = "md", children, onClick, style, leftIcon, disabled }) {
    const base = { display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8, border: 0, borderRadius: 8, fontFamily: "inherit", fontWeight: 500, cursor: disabled ? "default" : "pointer", whiteSpace: "nowrap", transition: "all 150ms cubic-bezier(.2,0,0,1)", opacity: disabled ? 0.5 : 1 };
    const sizes = { sm: { height: 28, padding: "0 10px", fontSize: 12, borderRadius: 6 }, md: { height: 36, padding: "0 16px", fontSize: 13 } };
    const variants = {
      primary: { background: "var(--accent)", color: "#fff" },
      secondary: { background: "var(--bg-raised)", color: "var(--fg-primary)", border: "1px solid var(--border-default)" },
      ghost: { background: "transparent", color: "var(--fg-secondary)" },
      danger: { background: "rgba(244,63,94,.14)", color: "var(--danger)", border: "1px solid rgba(244,63,94,.3)" },
      success: { background: "rgba(16,185,129,.14)", color: "var(--success)", border: "1px solid rgba(16,185,129,.3)" },
    };
    return <button onClick={disabled ? undefined : onClick} style={{ ...base, ...sizes[size], ...variants[variant], ...style }}>{leftIcon && <Icon name={leftIcon} size={size === "sm" ? 14 : 16} />}{children}</button>;
  }
  function Avatar({ name, size = 28, ring }) {
    const initials = (name || "?").split(" ").map(s => s[0]).slice(0, 2).join("");
    return <div style={{ width: size, height: size, borderRadius: "50%", background: "var(--bg-raised)", color: "var(--fg-primary)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: size * 0.4, fontWeight: 600, flexShrink: 0, border: ring ? "1px solid var(--accent)" : "1px solid var(--border-default)" }}>{initials}</div>;
  }

  // ── візуал стану дня ──────────────────────────────────────────────────────────
  function dayVisual(d) {
    if (d.ownerFixed) return { key: "fixed", fg: "var(--accent)", bg: "var(--accent-soft)", bd: "var(--accent)", icon: "lock", title: d.isPast ? "Був відгул" : "Закріплено" };
    if (d.pending) return { key: "pending", fg: "var(--warning)", bg: "rgba(245,158,11,.10)", bd: "rgba(245,158,11,.55)", icon: "hourglass", title: "На розгляді" };
    if (d.booked) return { key: "booked", fg: "var(--accent)", bg: "var(--accent-soft)", bd: "var(--accent)", icon: "check", title: d.isPast ? "Був відгул" : "Відгул" };
    if (!d.bookable) return { key: "blocked", fg: "var(--fg-muted)", bg: "transparent", bd: "var(--border-subtle)", icon: null, title: d.reason };
    return { key: "free", fg: "var(--fg-secondary)", bg: "transparent", bd: "var(--border-default)", icon: "plus", title: "Взяти відгул" };
  }

  // ── клітинка дня (десктоп) ───────────────────────────────────────────────────
  function DayCell({ d, onBook, onRelease, onRequest, owner }) {
    const [hover, setHover] = useState(false);
    const v = dayVisual(d);
    const dim = d.isPast && !owner;
    // Логіка кліку: власник керує будь-яким днем (крім понеділка/на розгляді); менеджер — за правилами.
    let clickable = false, act = null, hoverLabel = null;
    if (owner) {
      if (!d.isMonday && !d.pending) {
        if (d.booked) { clickable = true; act = () => onRelease(d); hoverLabel = "Зняти"; }
        else { clickable = true; act = () => onBook(d); hoverLabel = "Відмітити"; }
      }
    } else {
      if (v.key === "free") { clickable = true; act = () => onBook(d); hoverLabel = "Взяти"; }
      else if (v.key === "booked" && !d.isPast) { clickable = true; act = () => onRelease(d); hoverLabel = "Зняти"; }
      else if (v.key === "fixed" && !d.isPast) { clickable = true; act = () => onRequest(d, "move"); hoverLabel = "Перенести"; }
      else if (v.key === "pending") { clickable = true; act = () => onRequest(d, "view"); hoverLabel = "Деталі"; }
    }
    // Для власника вільний/заблокований день показуємо як «+ Відмітити».
    const ownerFree = owner && !d.booked && !d.isMonday && !d.pending;
    const showIcon = ownerFree ? "plus" : v.icon;
    const suggest = v.key === "free" && d.rec === "suggest";
    let bodyLabel = ownerFree ? "Відмітити" : v.title;
    if (hover && clickable && hoverLabel) bodyLabel = hoverLabel;
    const releaseHover = hover && clickable && d.booked && v.key !== "fixed";

    return (
      <button onClick={clickable ? act : undefined} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
        title={!d.bookable && d.reason && !owner ? d.reason : v.title}
        style={{
          position: "relative", display: "flex", flexDirection: "column", gap: 8, padding: "10px 10px 12px", minHeight: 92, borderRadius: 10, textAlign: "left", fontFamily: "inherit",
          cursor: clickable ? "pointer" : "default", opacity: dim ? 0.5 : 1,
          background: hover && clickable && !d.booked ? "var(--bg-hover)" : (suggest && !hover ? "rgba(99,102,241,.05)" : v.bg),
          border: `1px solid ${releaseHover ? "var(--danger)" : (suggest ? "rgba(99,102,241,.4)" : v.bd)}`,
          borderStyle: v.key === "pending" ? "dashed" : "solid", transition: "all 140ms cubic-bezier(.2,0,0,1)",
        }}>
        <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between" }}>
          <span style={{ fontSize: 11, fontWeight: 600, letterSpacing: ".03em", textTransform: "uppercase", color: "var(--fg-muted)" }}>{d.label}</span>
          <span style={{ fontFamily: "var(--font-mono)", fontSize: 15, fontWeight: 600, color: d.isToday ? "var(--accent)" : "var(--fg-secondary)" }}>{dd(d.date)}</span>
        </div>
        <div style={{ marginTop: "auto", display: "flex", alignItems: "center", gap: 6, minHeight: 20 }}>
          {releaseHover ? <Icon name="x" size={15} color="var(--danger)" />
            : showIcon && <Icon name={showIcon} size={15} color={releaseHover ? "var(--danger)" : (ownerFree && !hover ? "var(--fg-disabled)" : v.fg)} />}
          <span style={{ fontSize: 12, fontWeight: 500, color: releaseHover ? "var(--danger)" : (ownerFree && !hover && !suggest ? "var(--fg-disabled)" : v.fg) }}>{bodyLabel}</span>
        </div>
        {suggest && !hover && (
          <span style={{ position: "absolute", top: 8, right: 34, display: "inline-flex", alignItems: "center", gap: 3, height: 15, padding: "0 5px", borderRadius: 4, background: "var(--accent-soft)", color: "var(--accent)", fontSize: 9, fontWeight: 700 }}><Icon name="sparkles" size={9} /> РАДИМО</span>
        )}
        {d.isToday && <span style={{ position: "absolute", left: 10, bottom: -1, width: 22, height: 2, borderRadius: 2, background: "var(--accent)" }} />}
      </button>
    );
  }

  function QuotaMeter({ taken, quota, pending }) {
    return (
      <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
        <span style={{ display: "inline-flex", gap: 4 }}>
          {Array.from({ length: quota }).map((_, i) => (
            <span key={i} style={{ width: 18, height: 6, borderRadius: 3, background: i < taken ? "var(--accent)" : (i < taken + pending ? "rgba(245,158,11,.6)" : "var(--bg-raised)"), border: i < taken + pending ? "0" : "1px solid var(--border-default)" }} />
          ))}
        </span>
        <span style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--fg-secondary)" }}>{taken}/{quota}</span>
      </span>
    );
  }

  function Legend() {
    const items = [
      { c: "var(--accent)", fill: true, label: "Мій відгул" },
      { c: "var(--accent)", icon: "lock", label: "Закріплено власником" },
      { c: "var(--warning)", dashed: true, label: "На розгляді" },
      { c: "var(--fg-disabled)", label: "Недоступно (понеділок/минуле)" },
    ];
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 16, flexWrap: "wrap" }}>
        {items.map((it, i) => (
          <span key={i} style={{ display: "inline-flex", alignItems: "center", gap: 7, fontSize: 11.5, color: "var(--fg-muted)" }}>
            <span style={{ width: 14, height: 14, borderRadius: 4, flexShrink: 0, display: "inline-flex", alignItems: "center", justifyContent: "center", background: it.fill ? "var(--accent-soft)" : "transparent", border: `1px ${it.dashed ? "dashed" : "solid"} ${it.fill ? "var(--accent)" : it.c}` }}>{it.icon && <Icon name={it.icon} size={9} color={it.c} />}</span>
            {it.label}
          </span>
        ))}
      </div>
    );
  }

  // ── оглядова матриця команди ─────────────────────────────────────────────────
  function coverageTone(off, total) {
    const present = total - off;
    if (present <= 2) return { fg: "var(--danger)", bg: "var(--status-cancelled-soft)" };
    if (present <= 3) return { fg: "var(--warning)", bg: "var(--status-preparing-soft)" };
    return { fg: "var(--success)", bg: "transparent" };
  }
  function TeamMatrix({ team, meUsername }) {
    if (!team || !team.length) return null;
    const total = team.length;
    const flat = t => [...t.weeks[0].days, ...t.weeks[1].days];
    const ALL = flat(team[0]).map(d => d.date);
    const coverage = ALL.map((iso, ci) => team.reduce((n, t) => n + (flat(t)[ci].booked ? 1 : 0), 0));
    const cell = (d) => {
      if (d.ownerFixed) return { bg: "var(--accent-soft)", dot: "var(--accent)", icon: "lock" };
      if (d.pending) return { bg: "rgba(245,158,11,.12)", dot: "var(--warning)", ring: true };
      if (d.booked) return { bg: "var(--accent-soft)", dot: "var(--accent)" };
      return { bg: "transparent", dot: null };
    };
    const col = 34, nameW = 176;
    return (
      <div style={{ overflowX: "auto" }} className="no-bar">
        <div style={{ minWidth: nameW + col * 14 + 20 }}>
          <div style={{ display: "flex", alignItems: "flex-end" }}>
            <div style={{ width: nameW, flexShrink: 0 }} />
            {["Цей тиждень", "Наступний тиждень"].map((lbl, wi) => (
              <div key={wi} style={{ width: col * 7, flexShrink: 0, padding: "0 0 6px", borderLeft: "1px solid var(--border-subtle)" }}>
                <span style={{ fontSize: 10.5, fontWeight: 600, letterSpacing: ".03em", textTransform: "uppercase", color: "var(--fg-muted)", paddingLeft: 8 }}>{lbl}</span>
              </div>
            ))}
          </div>
          <div style={{ display: "flex", alignItems: "center" }}>
            <div style={{ width: nameW, flexShrink: 0, fontSize: 11, color: "var(--fg-muted)", fontWeight: 500 }}>Співробітник</div>
            {ALL.map((iso, i) => (
              <div key={iso} style={{ width: col, flexShrink: 0, textAlign: "center", borderLeft: i % 7 === 0 ? "1px solid var(--border-subtle)" : "none", paddingBottom: 4 }}>
                <div style={{ fontSize: 9.5, fontWeight: 600, textTransform: "uppercase", color: "var(--fg-muted)" }}>{WEEKDAYS[i % 7]}</div>
                <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 600, color: iso === TODAY ? "var(--accent)" : "var(--fg-secondary)" }}>{dd(iso)}</div>
              </div>
            ))}
          </div>
          {team.map(t => {
            const days = flat(t);
            const isMe = t.username === meUsername;
            return (
              <div key={t.username} style={{ display: "flex", alignItems: "center", height: 40, borderTop: "1px solid var(--border-subtle)", background: isMe ? "var(--bg-hover)" : "transparent" }}>
                <div style={{ width: nameW, flexShrink: 0, display: "flex", alignItems: "center", gap: 8, paddingRight: 8 }}>
                  <Avatar name={t.name} size={24} ring={isMe} />
                  <span style={{ fontSize: 12.5, fontWeight: isMe ? 600 : 500, color: "var(--fg-primary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{t.name}{isMe && <span style={{ color: "var(--fg-muted)", fontWeight: 400 }}> · ви</span>}</span>
                </div>
                {days.map((d, i) => {
                  const c = cell(d);
                  return (
                    <div key={d.date} style={{ width: col, flexShrink: 0, height: "100%", display: "flex", alignItems: "center", justifyContent: "center", borderLeft: i % 7 === 0 ? "1px solid var(--border-subtle)" : "none", background: c.bg }}>
                      {c.icon ? <Icon name={c.icon} size={12} color={c.dot} />
                        : c.dot ? <span style={{ width: 9, height: 9, borderRadius: "50%", background: c.dot, boxShadow: c.ring ? "0 0 0 3px rgba(245,158,11,.18)" : "none" }} />
                          : (d.isPast ? <span style={{ width: 3, height: 3, borderRadius: "50%", background: "var(--border-strong)" }} /> : null)}
                    </div>
                  );
                })}
              </div>
            );
          })}
          <div style={{ display: "flex", alignItems: "center", height: 38, borderTop: "1px solid var(--border-default)" }}>
            <div style={{ width: nameW, flexShrink: 0, display: "flex", alignItems: "center", gap: 6, fontSize: 11, fontWeight: 600, color: "var(--fg-secondary)" }}><Icon name="users" size={13} color="var(--fg-muted)" /> На зміні</div>
            {ALL.map((iso, i) => {
              const off = coverage[i]; const present = total - off; const tone = coverageTone(off, total);
              return (
                <div key={iso} style={{ width: col, flexShrink: 0, height: "100%", display: "flex", alignItems: "center", justifyContent: "center", borderLeft: i % 7 === 0 ? "1px solid var(--border-subtle)" : "none" }}>
                  <span style={{ minWidth: 20, height: 20, padding: "0 4px", borderRadius: 6, display: "inline-flex", alignItems: "center", justifyContent: "center", fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 700, color: tone.fg, background: tone.bg }}>{present}</span>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  // ── заявки ────────────────────────────────────────────────────────────────────
  function ActionBadge({ action }) {
    const m = action === "cancel"
      ? { label: "Скасувати", icon: "x-circle", c: "var(--danger)", bg: "var(--status-cancelled-soft)" }
      : { label: "Перенести", icon: "arrow-right-left", c: "var(--info)", bg: "var(--status-confirmed-soft)" };
    return <span style={{ display: "inline-flex", alignItems: "center", gap: 5, height: 20, padding: "0 8px", borderRadius: 999, background: m.bg, color: m.c, fontSize: 11, fontWeight: 600 }}><Icon name={m.icon} size={12} /> {m.label}</span>;
  }
  function RequestCard({ r, onResolve }) {
    return (
      <div style={{ padding: 14, borderRadius: 10, background: "var(--bg-raised)", border: "1px solid var(--border-default)", display: "flex", flexDirection: "column", gap: 10 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
          <Avatar name={r.name} size={30} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 600, color: "var(--fg-primary)" }}>{r.name}</div>
            <div style={{ fontSize: 11, color: "var(--fg-muted)" }}>@{r.username}</div>
          </div>
          <ActionBadge action={r.action} />
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--font-mono)", fontSize: 12.5, color: "var(--fg-secondary)" }}>
          <span style={{ padding: "3px 8px", borderRadius: 6, background: "var(--accent-soft)", color: "var(--accent)", fontWeight: 600 }}>{fmtDate(r.fromDate)}</span>
          {r.action === "move" && <><Icon name="arrow-right" size={14} color="var(--fg-muted)" /><span style={{ padding: "3px 8px", borderRadius: 6, background: "var(--bg-panel)", border: "1px solid var(--border-default)", fontWeight: 600 }}>{fmtDate(r.toDate)}</span></>}
        </div>
        {r.note && <div style={{ fontSize: 12, color: "var(--fg-secondary)", lineHeight: 1.45, padding: "8px 10px", borderRadius: 8, background: "var(--bg-panel)", borderLeft: "2px solid var(--border-strong)" }}>«{r.note}»</div>}
        <div style={{ display: "flex", gap: 8 }}>
          <Button variant="success" size="sm" leftIcon="check" style={{ flex: 1 }} onClick={() => onResolve(r, true)}>Схвалити</Button>
          <Button variant="danger" size="sm" leftIcon="x" style={{ flex: 1 }} onClick={() => onResolve(r, false)}>Відхилити</Button>
        </div>
      </div>
    );
  }
  function MyRequestRow({ r, onCancel }) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "9px 12px", borderRadius: 9, background: "rgba(245,158,11,.07)", border: "1px dashed rgba(245,158,11,.45)" }}>
        <span className="pulse-dot" />
        <ActionBadge action={r.action} />
        <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--fg-secondary)" }}>{fmtDate(r.fromDate)}{r.action === "move" && <><Icon name="arrow-right" size={12} color="var(--fg-muted)" />{fmtDate(r.toDate)}</>}</span>
        <span style={{ fontSize: 11.5, color: "var(--fg-muted)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", flex: 1 }}>{r.note}</span>
        <span style={{ fontSize: 11, color: "var(--warning)", fontWeight: 600, whiteSpace: "nowrap" }}>На розгляді</span>
        {onCancel && <button onClick={() => onCancel(r)} title="Відкликати заявку" style={{ width: 26, height: 26, flexShrink: 0, border: 0, borderRadius: 6, background: "transparent", color: "var(--fg-muted)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}><Icon name="x" size={14} /></button>}
      </div>
    );
  }

  // ── актор-світчер (власник діє за співробітника) ─────────────────────────────
  function ActorSwitcher({ team, me, actor, onPick }) {
    const [open, setOpen] = useState(false);
    const wrap = useRef(null);
    useEffect(() => { const h = e => { if (wrap.current && !wrap.current.contains(e.target)) setOpen(false); }; document.addEventListener("mousedown", h); return () => document.removeEventListener("mousedown", h); }, []);
    const current = actor ? team.find(t => t.username === actor) : null;
    const first = n => (n || "").split(" ")[0];
    return (
      <div ref={wrap} style={{ position: "relative" }}>
        <button onClick={() => setOpen(o => !o)} style={{ display: "inline-flex", alignItems: "center", gap: 8, height: 36, padding: "0 10px 0 12px", borderRadius: 8, background: actor ? "var(--accent-soft)" : "var(--bg-raised)", cursor: "pointer", fontFamily: "inherit", border: `1px solid ${actor ? "var(--accent)" : "var(--border-default)"}`, color: "var(--fg-primary)", fontSize: 13, fontWeight: 500 }}>
          <Icon name="user-cog" size={15} color={actor ? "var(--accent)" : "var(--fg-muted)"} />
          <span style={{ color: "var(--fg-muted)", fontWeight: 400 }}>Графік:</span>
          <span style={{ color: actor ? "var(--accent)" : "var(--fg-primary)" }}>{current ? first(current.name) : (me ? "Себе" : "Оберіть")}</span>
          <Icon name="chevron-down" size={15} color="var(--fg-muted)" />
        </button>
        {open && (
          <div style={{ position: "absolute", top: 42, right: 0, zIndex: 40, minWidth: 220, background: "var(--bg-raised)", border: "1px solid var(--border-default)", borderRadius: 10, boxShadow: "var(--shadow-2)", padding: 6, animation: "popIn 140ms ease" }}>
            <button onClick={() => { onPick(null); setOpen(false); }} style={rowStyle(!actor)}>
              {me ? <Avatar name={me.name} size={26} /> : <span style={{ width: 26, height: 26, borderRadius: "50%", display: "inline-flex", alignItems: "center", justifyContent: "center", background: "var(--bg-panel)", border: "1px solid var(--border-default)" }}><Icon name="users" size={14} color="var(--fg-muted)" /></span>}
              <span style={{ flex: 1, textAlign: "left" }}>{me ? `Себе (${first(me.name)})` : "— огляд команди"}</span>{!actor && <Icon name="check" size={15} color="var(--accent)" />}</button>
            <div style={{ height: 1, background: "var(--border-subtle)", margin: "5px 4px" }} />
            {team.filter(t => !me || t.username !== me.username).map(t => (
              <button key={t.username} onClick={() => { onPick(t.username); setOpen(false); }} style={rowStyle(actor === t.username)}><Avatar name={t.name} size={26} /> <span style={{ flex: 1, textAlign: "left" }}>{t.name}</span>{actor === t.username && <Icon name="check" size={15} color="var(--accent)" />}</button>
            ))}
          </div>
        )}
      </div>
    );
  }
  const rowStyle = on => ({ display: "flex", alignItems: "center", gap: 10, width: "100%", height: 40, padding: "0 8px", borderRadius: 8, border: 0, background: on ? "var(--accent-soft)" : "transparent", color: "var(--fg-primary)", fontSize: 13, fontWeight: 500, cursor: "pointer", fontFamily: "inherit" });

  // ── модалка заявки (десктоп) ─────────────────────────────────────────────────
  function RequestModal({ day, freeDays, onClose, onSubmit }) {
    const [action, setAction] = useState("move");
    const [target, setTarget] = useState(freeDays[0]?.date || "");
    const [note, setNote] = useState("");
    return (
      <div style={{ position: "fixed", inset: 0, zIndex: 80, display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
        <div onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,.55)", animation: "fadeIn 140ms" }} />
        <div style={{ position: "relative", width: 460, maxWidth: "100%", background: "var(--bg-raised)", border: "1px solid var(--border-default)", borderRadius: 14, boxShadow: "var(--shadow-2)", animation: "popIn 160ms ease", overflow: "hidden" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "16px 18px", borderBottom: "1px solid var(--border-subtle)" }}>
            <span style={{ width: 34, height: 34, borderRadius: 9, background: "var(--accent-soft)", display: "flex", alignItems: "center", justifyContent: "center" }}><Icon name="lock" size={17} color="var(--accent)" /></span>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 15, fontWeight: 600, color: "var(--fg-primary)" }}>Закріплений відгул · {fmtDate(day.date)}</div>
              <div style={{ fontSize: 11.5, color: "var(--fg-muted)" }}>Змінити можна лише через заявку власнику</div>
            </div>
            <button onClick={onClose} style={{ width: 30, height: 30, border: 0, borderRadius: 7, background: "transparent", color: "var(--fg-muted)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}><Icon name="x" size={17} /></button>
          </div>
          <div style={{ padding: 18, display: "flex", flexDirection: "column", gap: 16 }}>
            <div style={{ display: "flex", gap: 8 }}>
              {[{ k: "move", label: "Перенести", icon: "arrow-right-left" }, { k: "cancel", label: "Скасувати", icon: "x-circle" }].map(o => (
                <button key={o.k} onClick={() => setAction(o.k)} style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", gap: 7, height: 40, borderRadius: 9, cursor: "pointer", fontFamily: "inherit", fontSize: 13, fontWeight: 500, background: action === o.k ? "var(--accent-soft)" : "var(--bg-panel)", border: `1px solid ${action === o.k ? "var(--accent)" : "var(--border-default)"}`, color: action === o.k ? "var(--accent)" : "var(--fg-secondary)" }}><Icon name={o.icon} size={15} /> {o.label}</button>
              ))}
            </div>
            {action === "move" && (
              <div>
                <div className="label" style={{ marginBottom: 8 }}>Новий день</div>
                <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 8 }}>
                  {freeDays.map(d => (
                    <button key={d.date} onClick={() => setTarget(d.date)} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 2, padding: "8px 4px", borderRadius: 8, cursor: "pointer", fontFamily: "inherit", background: target === d.date ? "var(--accent-soft)" : "var(--bg-panel)", border: `1px solid ${target === d.date ? "var(--accent)" : "var(--border-default)"}` }}>
                      <span style={{ fontSize: 10, fontWeight: 600, textTransform: "uppercase", color: "var(--fg-muted)" }}>{d.label}</span>
                      <span style={{ fontFamily: "var(--font-mono)", fontSize: 15, fontWeight: 600, color: target === d.date ? "var(--accent)" : "var(--fg-primary)" }}>{dd(d.date)}</span>
                    </button>
                  ))}
                  {!freeDays.length && <span style={{ fontSize: 12, color: "var(--fg-muted)", gridColumn: "1/-1" }}>Немає вільних днів для перенесення.</span>}
                </div>
              </div>
            )}
            <div>
              <div className="label" style={{ marginBottom: 8 }}>Коментар для власника {action === "cancel" && <span style={{ textTransform: "none", letterSpacing: 0, color: "var(--fg-disabled)" }}>· необовʼязково</span>}</div>
              <textarea value={note} onChange={e => setNote(e.target.value)} rows={3} placeholder={action === "cancel" ? "Чому звільняєте день…" : "Причина перенесення…"} style={{ width: "100%", boxSizing: "border-box", resize: "none", padding: "10px 12px", borderRadius: 9, background: "var(--bg-panel)", color: "var(--fg-primary)", border: "1px solid var(--border-default)", outline: "none", fontFamily: "inherit", fontSize: 13, lineHeight: 1.5 }} />
            </div>
          </div>
          <div style={{ display: "flex", justifyContent: "flex-end", gap: 10, padding: "14px 18px", borderTop: "1px solid var(--border-subtle)", background: "var(--bg-panel)" }}>
            <Button variant="ghost" onClick={onClose}>Скасувати</Button>
            <Button variant="primary" leftIcon="send" disabled={action === "move" && !target} onClick={() => onSubmit({ action, fromDate: day.date, toDate: action === "move" ? target : null, note })}>Надіслати заявку</Button>
          </div>
        </div>
      </div>
    );
  }

  // ── мобільні примітиви ────────────────────────────────────────────────────────
  function Segment({ tabs, value, onChange }) {
    return (
      <div style={{ display: "flex", gap: 4, padding: 4, margin: "0 16px 8px", background: "var(--bg-panel)", borderRadius: 11, border: "1px solid var(--border-subtle)" }}>
        {tabs.map(t => {
          const on = value === t.key;
          return (
            <button key={t.key} onClick={() => onChange(t.key)} style={{ flex: 1, height: 34, borderRadius: 8, border: 0, cursor: "pointer", fontFamily: "inherit", fontSize: 12.5, fontWeight: 600, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 6, background: on ? "var(--accent)" : "transparent", color: on ? "#fff" : "var(--fg-secondary)" }}>
              {t.label}
              {t.badge > 0 && <span style={{ minWidth: 17, height: 17, padding: "0 4px", borderRadius: 999, fontFamily: "var(--font-mono)", fontSize: 10, fontWeight: 700, display: "inline-flex", alignItems: "center", justifyContent: "center", background: on ? "rgba(255,255,255,.25)" : "var(--accent)", color: "#fff" }}>{t.badge}</span>}
            </button>
          );
        })}
      </div>
    );
  }
  function MDayRow({ d, onBook, onRelease, onRequest, owner }) {
    const v = dayVisual(d);
    const suggest = v.key === "free" && d.rec === "suggest";
    let clickable = false, handle = null;
    if (owner) {
      if (!d.isMonday && !d.pending) { clickable = true; handle = () => (d.booked ? onRelease(d) : onBook(d)); }
    } else {
      if (v.key === "free") { clickable = true; handle = () => onBook(d); }
      else if (v.key === "booked" && !d.isPast) { clickable = true; handle = () => onRelease(d); }
      else if (v.key === "fixed" && !d.isPast) { clickable = true; handle = () => onRequest(d, "move"); }
      else if (v.key === "pending") { clickable = true; handle = () => onRequest(d, "view"); }
    }
    const filled = v.key === "booked" || v.key === "fixed";
    const ownerFree = owner && !d.booked && !d.isMonday && !d.pending;
    let right;
    if (ownerFree) right = <span style={{ display: "inline-flex", alignItems: "center", gap: 5, color: "var(--fg-muted)", fontSize: 13, fontWeight: 500 }}><Icon name="plus" size={16} /> Відмітити</span>;
    else if (v.key === "free") right = <span style={{ display: "inline-flex", alignItems: "center", gap: 5, color: suggest ? "var(--accent)" : "var(--fg-muted)", fontSize: 13, fontWeight: 500 }}><Icon name="plus" size={16} /> Взяти</span>;
    else if (v.key === "booked") right = <span style={{ display: "inline-flex", alignItems: "center", gap: 5, color: "var(--accent)", fontSize: 13, fontWeight: 600 }}>{clickable && <Icon name="x" size={15} color="var(--fg-muted)" />} Відгул</span>;
    else if (v.key === "fixed") right = <span style={{ display: "inline-flex", alignItems: "center", gap: 5, color: "var(--accent)", fontSize: 13, fontWeight: 600 }}><Icon name="lock" size={14} /> {owner ? "Зняти" : "Закріплено"}</span>;
    else if (v.key === "pending") right = <span style={{ display: "inline-flex", alignItems: "center", gap: 5, color: "var(--warning)", fontSize: 13, fontWeight: 600 }}><span className="pulse-dot" /> На розгляді</span>;
    else right = <span style={{ fontSize: 12.5, color: "var(--fg-disabled)" }}>{d.reason}</span>;
    return (
      <button onClick={clickable ? handle : undefined} style={{ display: "flex", alignItems: "center", gap: 12, width: "100%", padding: "12px 14px", borderRadius: 11, textAlign: "left", fontFamily: "inherit", cursor: clickable ? "pointer" : "default", opacity: d.isPast && !owner ? 0.5 : 1, background: filled ? "var(--accent-soft)" : (suggest ? "rgba(99,102,241,.05)" : "var(--bg-panel)"), border: `1px ${v.key === "pending" ? "dashed" : "solid"} ${filled ? "var(--accent)" : (suggest ? "rgba(99,102,241,.4)" : v.key === "pending" ? "rgba(245,158,11,.5)" : "var(--border-subtle)")}` }}>
        <div style={{ width: 42, flexShrink: 0, display: "flex", flexDirection: "column", alignItems: "center" }}>
          <span style={{ fontSize: 10.5, fontWeight: 600, textTransform: "uppercase", color: "var(--fg-muted)" }}>{d.label}</span>
          <span style={{ fontFamily: "var(--font-mono)", fontSize: 18, fontWeight: 700, color: d.isToday ? "var(--accent)" : "var(--fg-primary)" }}>{dd(d.date)}</span>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          {d.isToday && <span style={{ fontSize: 10.5, fontWeight: 700, color: "var(--accent)", letterSpacing: ".03em" }}>СЬОГОДНІ</span>}
          {!d.isToday && suggest && <span style={{ display: "inline-flex", alignItems: "center", gap: 3, fontSize: 10.5, fontWeight: 700, color: "var(--accent)", letterSpacing: ".02em" }}><Icon name="sparkles" size={11} /> РАДИМО ВЗЯТИ</span>}
          {!d.isToday && !suggest && <span style={{ fontSize: 12, color: "var(--fg-muted)" }}>{fmtDate(d.date)}</span>}
        </div>
        {right}
      </button>
    );
  }
  function BottomSheet({ onClose, heightPct = 90, children }) {
    const [drag, setDrag] = useState(0);
    const start = useRef(null);
    const onDown = e => { start.current = (e.touches ? e.touches[0].clientY : e.clientY); };
    const onMove = e => { if (start.current == null) return; const y = (e.touches ? e.touches[0].clientY : e.clientY); setDrag(Math.max(0, y - start.current)); };
    const onUp = () => { if (drag > 100) onClose(); setDrag(0); start.current = null; };
    return (
      <div style={{ position: "fixed", inset: 0, zIndex: 90, display: "flex", flexDirection: "column", justifyContent: "flex-end" }}>
        <div onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,.6)", animation: "fade 160ms" }} />
        <div style={{ position: "relative", height: heightPct === "auto" ? "auto" : `${heightPct}%`, maxHeight: "94%", background: "var(--bg-panel)", borderTopLeftRadius: 18, borderTopRightRadius: 18, borderTop: "1px solid var(--border-default)", display: "flex", flexDirection: "column", overflow: "hidden", transform: `translateY(${drag}px)`, transition: start.current == null ? "transform 200ms cubic-bezier(.2,0,0,1)" : "none", animation: "msheetUp 240ms cubic-bezier(.2,0,0,1)" }}>
          <div onMouseDown={onDown} onMouseMove={onMove} onMouseUp={onUp} onTouchStart={onDown} onTouchMove={onMove} onTouchEnd={onUp} style={{ padding: "10px 0 6px", cursor: "grab", flexShrink: 0 }}>
            <div style={{ width: 36, height: 4, borderRadius: 2, background: "var(--border-strong)", margin: "0 auto" }} />
          </div>
          {children}
        </div>
      </div>
    );
  }
  function SheetHead({ title, sub, onBack }) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 6, padding: "4px 12px 12px 8px", borderBottom: "1px solid var(--border-subtle)", flexShrink: 0 }}>
        <button onClick={onBack} style={{ width: 36, height: 36, border: 0, background: "transparent", color: "var(--fg-secondary)", borderRadius: 8, display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer" }}><Icon name="x" size={18} /></button>
        <div style={{ flex: 1, minWidth: 0 }}>
          <h3 style={{ fontSize: 15, fontWeight: 600, margin: 0, color: "var(--fg-primary)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{title}</h3>
          {sub && <div style={{ fontSize: 11, color: "var(--fg-muted)" }}>{sub}</div>}
        </div>
      </div>
    );
  }
  function MActorSheet({ team, me, actor, onPick, onClose }) {
    const first = n => (n || "").split(" ")[0];
    return (
      <BottomSheet onClose={onClose} heightPct="auto">
        <SheetHead title="Діяти за співробітника" sub="Власник редагує чужий графік" onBack={onClose} />
        <div style={{ padding: 12, display: "flex", flexDirection: "column", gap: 4, maxHeight: 420, overflow: "auto" }}>
          <button onClick={() => { onPick(null); onClose(); }} style={actorRow(!actor)}><Avatar name={me.name} size={30} /> <span style={{ flex: 1, textAlign: "left", fontSize: 14, fontWeight: 500 }}>Себе ({first(me.name)})</span>{!actor && <Icon name="check" size={17} color="var(--accent)" />}</button>
          {team.filter(t => t.username !== me.username).map(t => (
            <button key={t.username} onClick={() => { onPick(t.username); onClose(); }} style={actorRow(actor === t.username)}><Avatar name={t.name} size={30} /> <span style={{ flex: 1, textAlign: "left", fontSize: 14, fontWeight: 500 }}>{t.name}</span>{actor === t.username && <Icon name="check" size={17} color="var(--accent)" />}</button>
          ))}
        </div>
      </BottomSheet>
    );
  }
  const actorRow = on => ({ display: "flex", alignItems: "center", gap: 12, width: "100%", height: 50, padding: "0 12px", borderRadius: 10, border: `1px solid ${on ? "var(--accent)" : "transparent"}`, background: on ? "var(--accent-soft)" : "transparent", color: "var(--fg-primary)", cursor: "pointer", fontFamily: "inherit" });
  function MRequestSheet({ day, freeDays, onClose, onSubmit }) {
    const [action, setAction] = useState("move");
    const [target, setTarget] = useState(freeDays[0]?.date || "");
    const [note, setNote] = useState("");
    return (
      <BottomSheet onClose={onClose} heightPct="auto">
        <SheetHead title={`Закріплений відгул · ${fmtDate(day.date)}`} sub="Зміна лише через заявку власнику" onBack={onClose} />
        <div style={{ padding: 16, display: "flex", flexDirection: "column", gap: 16 }}>
          <div style={{ display: "flex", gap: 8 }}>
            {[{ k: "move", label: "Перенести", icon: "arrow-right-left" }, { k: "cancel", label: "Скасувати", icon: "x-circle" }].map(o => (
              <button key={o.k} onClick={() => setAction(o.k)} style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center", gap: 6, height: 42, borderRadius: 10, cursor: "pointer", fontFamily: "inherit", fontSize: 13, fontWeight: 500, background: action === o.k ? "var(--accent-soft)" : "var(--bg-base)", border: `1px solid ${action === o.k ? "var(--accent)" : "var(--border-default)"}`, color: action === o.k ? "var(--accent)" : "var(--fg-secondary)" }}><Icon name={o.icon} size={15} /> {o.label}</button>
            ))}
          </div>
          {action === "move" && (
            <div>
              <div className="label" style={{ marginBottom: 8 }}>Новий день</div>
              <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 8 }}>
                {freeDays.map(d => (
                  <button key={d.date} onClick={() => setTarget(d.date)} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 2, padding: "9px 4px", borderRadius: 9, cursor: "pointer", fontFamily: "inherit", background: target === d.date ? "var(--accent-soft)" : "var(--bg-base)", border: `1px solid ${target === d.date ? "var(--accent)" : "var(--border-default)"}` }}>
                    <span style={{ fontSize: 10, fontWeight: 600, textTransform: "uppercase", color: "var(--fg-muted)" }}>{d.label}</span>
                    <span style={{ fontFamily: "var(--font-mono)", fontSize: 16, fontWeight: 700, color: target === d.date ? "var(--accent)" : "var(--fg-primary)" }}>{dd(d.date)}</span>
                  </button>
                ))}
                {!freeDays.length && <span style={{ fontSize: 12, color: "var(--fg-muted)", gridColumn: "1/-1" }}>Немає вільних днів.</span>}
              </div>
            </div>
          )}
          <div>
            <div className="label" style={{ marginBottom: 8 }}>Коментар для власника</div>
            <textarea value={note} onChange={e => setNote(e.target.value)} rows={3} placeholder={action === "cancel" ? "Чому звільняєте день…" : "Причина перенесення…"} style={{ width: "100%", boxSizing: "border-box", resize: "none", padding: "10px 12px", borderRadius: 10, background: "var(--bg-base)", color: "var(--fg-primary)", border: "1px solid var(--border-default)", outline: "none", fontFamily: "inherit", fontSize: 14, lineHeight: 1.5 }} />
          </div>
          <Button variant="primary" leftIcon="send" disabled={action === "move" && !target} onClick={() => onSubmit({ action, fromDate: day.date, toDate: action === "move" ? target : null, note })} style={{ height: 46, borderRadius: 12 }}>Надіслати заявку</Button>
        </div>
      </BottomSheet>
    );
  }
  function ToastM({ toast }) {
    if (!toast) return null;
    return (
      <div style={{ position: "fixed", bottom: 90, left: "50%", transform: "translateX(-50%)", zIndex: 95, display: "flex", alignItems: "center", gap: 10, padding: "11px 16px", borderRadius: 11, maxWidth: "88%", background: "var(--bg-raised)", border: "1px solid var(--border-default)", boxShadow: "var(--shadow-2)", animation: "toastInM 220ms cubic-bezier(.2,0,0,1)" }}>
        <Icon name={toast.icon || "check-circle"} size={16} color={toast.tone || "var(--success)"} />
        <span style={{ fontSize: 12.5, color: "var(--fg-primary)", fontWeight: 500 }}>{toast.msg}</span>
      </div>
    );
  }

  // ── дані + дії (спільний хук) ────────────────────────────────────────────────
  function useDayoffs() {
    const [data, setData] = useState(null);
    const [err, setErr] = useState("");
    const [actor, setActor] = useState(null);
    const [toast, setToast] = useState(null);
    const busy = useRef(false);
    const load = () => fetch("/api/dayoffs").then(r => r.json()).then(j => { if (j.error) setErr(j.error); else setData(j); }).catch(e => setErr(e.message));
    useEffect(() => { load(); }, []);
    const showToast = (msg, icon = "check-circle", tone = "var(--success)") => { setToast({ msg, icon, tone }); clearTimeout(showToast._t); showToast._t = setTimeout(() => setToast(null), 3000); };
    const call = (method, url, body, okMsg, okIcon, okTone) => {
      if (busy.current) return Promise.resolve();
      busy.current = true;
      return fetch(url, { method, headers: body ? { "Content-Type": "application/json" } : undefined, body: body ? JSON.stringify(body) : undefined })
        .then(async r => { const j = await r.json().catch(() => ({})); if (!r.ok) throw new Error(j.error || ("HTTP " + r.status)); return j; })
        .then(j => { setData(j); if (okMsg) showToast(okMsg, okIcon, okTone); })
        .catch(e => showToast(e.message, "alert-triangle", "var(--warning)"))
        .finally(() => { busy.current = false; });
    };
    const withUser = b => (actor ? { ...b, username: actor } : b);
    const book = d => call("POST", "/api/dayoffs", withUser({ date: d.date }), `Відгул на ${fmtDate(d.date)} заброньовано`, "calendar-check");
    const release = d => call("DELETE", "/api/dayoffs", withUser({ date: d.date }), `Відгул на ${fmtDate(d.date)} знято`, "calendar-x", "var(--fg-secondary)");
    const submitRequest = p => call("POST", "/api/dayoffs/request", withUser(p), "Заявку надіслано", "send");
    const cancelMyRequest = r => call("DELETE", "/api/dayoffs/request/" + r.id, null, "Заявку відкликано", "undo-2", "var(--fg-secondary)");
    const resolve = (r, approve) => call("POST", "/api/dayoffs/requests/" + r.id + "/resolve", { approve }, approve ? "Заявку схвалено" : "Заявку відхилено", approve ? "check-circle" : "x-circle", approve ? "var(--success)" : "var(--danger)");
    return { data, err, actor, setActor, toast, showToast, book, release, submitRequest, cancelMyRequest, resolve };
  }

  const subjectOf = (data, actor) => (actor ? (data.team || []).find(t => t.username === actor) || data.me : data.me);

  // ── десктоп ──────────────────────────────────────────────────────────────────
  function Desktop(dk) {
    const { data, actor, setActor, book, release, submitRequest, cancelMyRequest, resolve, toast } = dk;
    const [modal, setModal] = useState(null);
    const owner = data.isOwner;
    const subject = subjectOf(data, actor);
    const board = subject ? subject.weeks : [];
    const totalTaken = board.reduce((n, w) => n + w.taken, 0);
    const totalQuota = board.reduce((n, w) => n + w.quota, 0);
    const freeDays = board.flatMap(w => w.days).filter(d => d.bookable && !d.isPast);
    const openRequest = (d, mode) => { if (mode === "view") { dk.showToast(`Заявка на ${fmtDate(d.date)} ще на розгляді`, "hourglass", "var(--warning)"); return; } setModal({ day: d }); };
    const requests = data.requests || [];
    const hasRail = owner && requests.length > 0;

    return (
      <div className="do-root" style={{ flex: 1, display: "flex", minHeight: 0, background: "var(--bg-base)", color: "var(--fg-primary)" }}>
        <div style={{ flex: 1, overflow: "auto", minWidth: 0, padding: 24, display: "flex", flexDirection: "column", gap: 20 }}>
          {/* контроли */}
          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, color: "var(--fg-muted)" }}><Icon name="clock" size={14} /> Бронювання до <b style={{ color: "var(--fg-secondary)", fontFamily: "var(--font-mono)", fontWeight: 600 }}>{data.cutoffHour}:00</b> напередодні · понеділок закритий</span>
            <div style={{ flex: 1 }} />
            {owner && <ActorSwitcher team={data.team || []} me={data.me} actor={actor} onPick={setActor} />}
          </div>

          {actor && (
            <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 14px", borderRadius: 10, background: "var(--accent-soft)", border: "1px solid var(--accent)" }}>
              <Icon name="user-cog" size={16} color="var(--accent)" />
              <span style={{ fontSize: 13, color: "var(--fg-primary)" }}>Ви редагуєте графік <b>{subject.name}</b> від імені власника.</span>
              <div style={{ flex: 1 }} />
              <button onClick={() => setActor(null)} style={{ border: 0, background: "transparent", color: "var(--accent)", fontFamily: "inherit", fontSize: 12.5, fontWeight: 600, cursor: "pointer" }}>Повернутись до себе</button>
            </div>
          )}

          {/* власник без вибраного співробітника — підказка (свого графіка немає) */}
          {!subject && owner && (
            <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "16px 18px", borderRadius: 12, background: "var(--bg-panel)", border: "1px dashed var(--border-default)" }}>
              <Icon name="user-cog" size={18} color="var(--fg-muted)" />
              <span style={{ fontSize: 13, color: "var(--fg-secondary)" }}>Ви — власник, у вихідних не берете участі. Оберіть співробітника у «Графік» вгорі, щоб переглянути чи відмітити його вихідні.</span>
            </div>
          )}

          {/* мій план (або графік обраного співробітника) */}
          {subject && (
          <section style={{ background: "var(--bg-panel)", border: "1px solid var(--border-subtle)", borderRadius: 14, overflow: "hidden" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "16px 20px", borderBottom: "1px solid var(--border-subtle)" }}>
              <Avatar name={subject.name} size={34} ring />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 15, fontWeight: 600, color: "var(--fg-primary)" }}>{actor ? subject.name : "Мій графік відгулів"}</div>
                <div style={{ fontSize: 12, color: "var(--fg-muted)" }}>Квота — {subject.quota} на тиждень</div>
              </div>
              <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "6px 12px", borderRadius: 9, background: "var(--bg-raised)", border: "1px solid var(--border-default)" }}>
                <span style={{ fontSize: 11.5, color: "var(--fg-muted)" }}>Взято за 2 тижні</span>
                <span style={{ fontFamily: "var(--font-mono)", fontSize: 14, fontWeight: 700, color: "var(--accent)" }}>{totalTaken}<span style={{ color: "var(--fg-disabled)" }}>/{totalQuota}</span></span>
              </div>
            </div>
            <div style={{ padding: 20, display: "flex", flexDirection: "column", gap: 18 }}>
              {board.map(w => (
                <div key={w.key}>
                  <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 10 }}>
                    <h4 style={{ fontSize: 13.5, fontWeight: 600, color: "var(--fg-primary)", margin: 0 }}>{w.label}</h4>
                    <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--fg-muted)" }}>{fmtDate(w.days[0].date)} – {fmtDate(w.days[6].date)}</span>
                    <div style={{ flex: 1 }} />
                    <QuotaMeter taken={w.taken} quota={w.quota} pending={w.days.filter(d => d.pending).length} />
                  </div>
                  <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 8 }}>
                    {w.days.map(d => <DayCell key={d.date} d={d} onBook={book} onRelease={release} onRequest={openRequest} owner={owner} />)}
                  </div>
                </div>
              ))}
              {(subject.pending || []).length > 0 && (
                <div style={{ display: "flex", flexDirection: "column", gap: 8, paddingTop: 4 }}>
                  <div className="label">Мої заявки на розгляді · {subject.pending.length}</div>
                  {subject.pending.map(r => <MyRequestRow key={r.id} r={r} onCancel={actor ? null : cancelMyRequest} />)}
                </div>
              )}
              <div style={{ paddingTop: 4 }}><Legend /></div>
            </div>
          </section>
          )}

          {/* команда */}
          <section style={{ background: "var(--bg-panel)", border: "1px solid var(--border-subtle)", borderRadius: 14, overflow: "hidden" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "16px 20px", borderBottom: "1px solid var(--border-subtle)" }}>
              <Icon name="calendar-range" size={18} color="var(--fg-muted)" />
              <h4 style={{ fontSize: 15, fontWeight: 600, color: "var(--fg-primary)", margin: 0 }}>Команда · огляд на 2 тижні</h4>
              <span style={{ fontSize: 12, color: "var(--fg-muted)" }}>{(data.team || []).length} співробітників</span>
              <div style={{ flex: 1 }} />
              <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 11.5, color: "var(--fg-muted)" }}><Icon name="triangle-alert" size={13} color="var(--warning)" /> червоне — ризик покриття</span>
            </div>
            <div style={{ padding: "16px 20px 20px" }}><TeamMatrix team={data.team || []} meUsername={data.me.username} /></div>
          </section>
        </div>

        {hasRail && (
          <aside style={{ width: 360, flexShrink: 0, borderLeft: "1px solid var(--border-subtle)", background: "var(--bg-panel)", display: "flex", flexDirection: "column", minHeight: 0 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "16px 18px", borderBottom: "1px solid var(--border-subtle)" }}>
              <Icon name="inbox" size={18} color="var(--accent)" />
              <h4 style={{ fontSize: 15, fontWeight: 600, color: "var(--fg-primary)", margin: 0 }}>Заявки</h4>
              <span style={{ minWidth: 20, height: 20, padding: "0 6px", borderRadius: 999, background: "var(--accent)", color: "#fff", fontSize: 11.5, fontWeight: 700, fontFamily: "var(--font-mono)", display: "inline-flex", alignItems: "center", justifyContent: "center" }}>{requests.length}</span>
              <div style={{ flex: 1 }} />
              <span style={{ fontSize: 11, color: "var(--fg-muted)" }}>потребують рішення</span>
            </div>
            <div style={{ flex: 1, overflow: "auto", padding: 16, display: "flex", flexDirection: "column", gap: 12 }}>
              {requests.map(r => <RequestCard key={r.id} r={r} onResolve={resolve} />)}
            </div>
          </aside>
        )}

        {modal && <RequestModal day={modal.day} freeDays={freeDays} onClose={() => setModal(null)} onSubmit={p => { submitRequest(p); setModal(null); }} />}
        {toast && (
          <div style={{ position: "fixed", bottom: 24, left: "50%", transform: "translateX(-50%)", zIndex: 90, display: "flex", alignItems: "center", gap: 10, padding: "12px 18px", background: "var(--bg-raised)", border: "1px solid var(--border-default)", borderRadius: 10, boxShadow: "var(--shadow-2)", animation: "sheetUp 200ms ease" }}>
            <Icon name={toast.icon} size={17} color={toast.tone} /> <span style={{ fontSize: 13, color: "var(--fg-primary)" }}>{toast.msg}</span>
          </div>
        )}
      </div>
    );
  }

  // ── мобілка ──────────────────────────────────────────────────────────────────
  function Mobile(dk) {
    const { data, actor, setActor, book, release, submitRequest, cancelMyRequest, resolve, toast } = dk;
    const [tab, setTab] = useState("me");
    const [actorSheet, setActorSheet] = useState(false);
    const [modal, setModal] = useState(null);
    const owner = data.isOwner;
    const subject = subjectOf(data, actor);
    const board = subject ? subject.weeks : [];
    const totalTaken = board.reduce((n, w) => n + w.taken, 0);
    const totalQuota = board.reduce((n, w) => n + w.quota, 0);
    const freeDays = board.flatMap(w => w.days).filter(d => d.bookable && !d.isPast);
    const requests = data.requests || [];
    const openRequest = (d, mode) => { if (mode === "view") { dk.showToast(`Заявка на ${fmtDate(d.date)} на розгляді`, "hourglass", "var(--warning)"); return; } setModal({ day: d }); };
    const tabs = [{ key: "me", label: owner ? "Графік" : "Мій графік" }, { key: "team", label: "Команда" }];
    if (owner) tabs.push({ key: "req", label: "Заявки", badge: requests.length });

    return (
      <div className="do-root" style={{ height: "100%", display: "flex", flexDirection: "column", background: "var(--bg-base)", position: "relative", overflow: "hidden" }}>
        <div style={{ padding: "calc(10px + env(safe-area-inset-top)) 16px 8px", flexShrink: 0 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <h1 style={{ fontSize: 19, fontWeight: 600, color: "var(--fg-primary)", margin: 0 }}>Вихідні</h1>
            <span style={{ fontSize: 12, color: "var(--fg-muted)" }}>2 тижні</span>
            <div style={{ flex: 1 }} />
            {owner && <button onClick={() => setActorSheet(true)} style={{ display: "inline-flex", alignItems: "center", gap: 6, height: 32, padding: "0 10px", borderRadius: 9, fontFamily: "inherit", fontSize: 12, fontWeight: 600, cursor: "pointer", background: actor ? "var(--accent-soft)" : "var(--bg-panel)", border: `1px solid ${actor ? "var(--accent)" : "var(--border-default)"}`, color: actor ? "var(--accent)" : "var(--fg-secondary)" }}><Icon name="user-cog" size={14} /> {actor && subject ? subject.name.split(" ")[0] : "Оберіть"}</button>}
          </div>
          <div style={{ display: "inline-flex", alignItems: "center", gap: 6, marginTop: 4, fontSize: 11.5, color: "var(--fg-muted)" }}><Icon name="clock" size={12} /> До <b style={{ color: "var(--fg-secondary)", fontFamily: "var(--font-mono)", fontWeight: 600 }}>{data.cutoffHour}:00</b> напередодні · понеділок закритий</div>
        </div>
        <Segment tabs={tabs} value={tab} onChange={setTab} />

        <div className="no-bar" style={{ flex: 1, overflow: "auto", padding: "0 16px 96px" }}>
          {tab === "me" && !subject && (
            <div style={{ display: "flex", alignItems: "flex-start", gap: 10, padding: "16px 14px", borderRadius: 12, background: "var(--bg-panel)", border: "1px dashed var(--border-default)", marginTop: 4 }}>
              <Icon name="user-cog" size={17} color="var(--fg-muted)" />
              <span style={{ fontSize: 12.5, color: "var(--fg-secondary)", lineHeight: 1.5 }}>Ви — власник, у вихідних не берете участі. Натисніть «Оберіть» угорі, щоб переглянути чи відмітити вихідні співробітника.</span>
            </div>
          )}
          {tab === "me" && subject && (
            <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
              {actor && (
                <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "9px 12px", borderRadius: 10, background: "var(--accent-soft)", border: "1px solid var(--accent)" }}>
                  <Icon name="user-cog" size={14} color="var(--accent)" />
                  <span style={{ fontSize: 12, color: "var(--fg-primary)", flex: 1 }}>Графік <b>{subject.name}</b></span>
                  <button onClick={() => setActor(null)} style={{ border: 0, background: "transparent", color: "var(--accent)", fontFamily: "inherit", fontSize: 12, fontWeight: 600, cursor: "pointer" }}>Себе</button>
                </div>
              )}
              <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "12px 14px", borderRadius: 12, background: "var(--bg-panel)", border: "1px solid var(--border-subtle)" }}>
                <Avatar name={subject.name} size={36} ring />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 600, color: "var(--fg-primary)" }}>{actor ? subject.name : "Мій графік"}</div>
                  <div style={{ fontSize: 11.5, color: "var(--fg-muted)" }}>Квота {subject.quota} / тиждень</div>
                </div>
                <div style={{ textAlign: "right" }}>
                  <div style={{ fontFamily: "var(--font-mono)", fontSize: 18, fontWeight: 700, color: "var(--accent)" }}>{totalTaken}<span style={{ color: "var(--fg-disabled)" }}>/{totalQuota}</span></div>
                  <div style={{ fontSize: 10.5, color: "var(--fg-muted)" }}>за 2 тижні</div>
                </div>
              </div>
              {board.map(w => (
                <div key={w.key}>
                  <div style={{ display: "flex", alignItems: "center", gap: 10, margin: "2px 2px 8px" }}>
                    <h4 style={{ fontSize: 13, fontWeight: 600, color: "var(--fg-primary)", margin: 0 }}>{w.label}</h4>
                    <div style={{ flex: 1 }} />
                    <QuotaMeter taken={w.taken} quota={w.quota} pending={w.days.filter(d => d.pending).length} />
                  </div>
                  <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                    {w.days.map(d => <MDayRow key={d.date} d={d} onBook={book} onRelease={release} onRequest={openRequest} owner={owner} />)}
                  </div>
                </div>
              ))}
              {(subject.pending || []).length > 0 && (
                <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                  <div className="label">Мої заявки · {subject.pending.length}</div>
                  {subject.pending.map(r => <MyRequestRow key={r.id} r={r} onCancel={actor ? null : cancelMyRequest} />)}
                </div>
              )}
              <div style={{ paddingTop: 2 }}><Legend /></div>
            </div>
          )}
          {tab === "team" && (
            <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 12, color: "var(--fg-muted)" }}><Icon name="move-horizontal" size={14} /> Проведіть таблицю вбік · {(data.team || []).length} співробітників</div>
              <div style={{ padding: "14px 12px", borderRadius: 12, background: "var(--bg-panel)", border: "1px solid var(--border-subtle)" }}><TeamMatrix team={data.team || []} meUsername={data.me.username} /></div>
            </div>
          )}
          {tab === "req" && (
            <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
              {requests.length === 0
                ? <div style={{ textAlign: "center", padding: "48px 20px", color: "var(--fg-muted)" }}><Icon name="check-check" size={30} color="var(--fg-disabled)" style={{ marginBottom: 10 }} /><div style={{ fontSize: 14, fontWeight: 500, color: "var(--fg-secondary)" }}>Черга порожня</div><div style={{ fontSize: 12, marginTop: 4 }}>Усі заявки опрацьовані</div></div>
                : requests.map(r => <RequestCard key={r.id} r={r} onResolve={resolve} />)}
            </div>
          )}
        </div>

        {actorSheet && <MActorSheet team={data.team || []} me={data.me} actor={actor} onPick={setActor} onClose={() => setActorSheet(false)} />}
        {modal && <MRequestSheet day={modal.day} freeDays={freeDays} onClose={() => setModal(null)} onSubmit={p => { submitRequest(p); setModal(null); }} />}
        <ToastM toast={toast} />
      </div>
    );
  }

  function DayOff({ isMobile }) {
    const dk = useDayoffs();
    if (dk.err && !dk.data) return <div className="do-root" style={{ padding: 24, color: "#FCA5A5" }}>{dk.err}</div>;
    if (!dk.data) return <div className="do-root" style={{ padding: 24, color: "var(--fg-muted)" }}>Завантаження…</div>;
    return isMobile ? <Mobile {...dk} /> : <Desktop {...dk} />;
  }

  window.DayOff = DayOff;
})();
