// GuestMaker CRM B2B · the pipeline board hero (app chrome, board, gates, dialogs).

const NAV = [
  { label: "Home", icon: "building" },
  { group: "DATA", items: [
    { label: "Contacts", icon: "users" },
    { label: "Bookings", icon: "cal" },
    { label: "Segments", icon: "layers" },
    { label: "Agencies & Companies", icon: "briefcase" },
    { label: "Sales Pipeline", icon: "board" },
    { label: "Activities", icon: "list" },
  ] },
  { group: "CHANNELS", collapsed: true },
  { group: "AI AGENTS", collapsed: true },
  { group: "CDP", collapsed: true },
  { group: "LOYALTY", collapsed: true },
];

function SideNav({ active = "Sales Pipeline" }) {
  return (
    <div style={{ width: 208, flexShrink: 0, borderRight: `1px solid ${RULE}`, background: PAPER, padding: "12px 10px", display: "flex", flexDirection: "column", gap: 2 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "4px 6px 12px" }}>
        <span style={{ width: 22, height: 22, borderRadius: 7, background: `linear-gradient(140deg,${FUCHSIA},${FUCHSIA_DARK})`, flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "center" }}><GMMark size={13} color="white" /></span>
        <span style={{ fontSize: 13, fontWeight: 700, letterSpacing: "-0.01em" }}>GuestMaker</span>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "7px 8px", border: `1px solid ${RULE}`, borderRadius: 7, background: SURFACE, marginBottom: 8 }}>
        <span style={{ width: 18, height: 18, borderRadius: 5, background: PLUM, color: "#fff", fontFamily: MONO, fontSize: 8, fontWeight: 700, display: "grid", placeItems: "center" }}>MA</span>
        <span style={{ fontSize: 11.5, fontWeight: 600, flex: 1 }}>Marea Hotels</span>
        <Icon name="chevron-d" size={11} color={FAINT} />
      </div>
      {NAV.map((n, i) => n.items ? (
        <div key={i} style={{ marginTop: 6 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 7, padding: "5px 8px" }}>
            <Icon name="database" size={12} color={INK} />
            <Mono size={9.5} color={INK} style={{ letterSpacing: "0.16em", fontWeight: 700, flex: 1 }}>{n.group}</Mono>
            <Icon name="chevron-d" size={10} color={FAINT} />
          </div>
          {n.items.map(it => (
            <div key={it.label} style={{
              display: "flex", alignItems: "center", gap: 8, padding: "6px 8px 6px 20px", borderRadius: 7,
              background: it.label === active ? FUCHSIA_PALE + "80" : "transparent",
              color: it.label === active ? FUCHSIA_DARK : DIM, fontWeight: it.label === active ? 600 : 500, fontSize: 11.5,
            }}>
              <Icon name={it.icon} size={12} color={it.label === active ? FUCHSIA_DARK : FAINT} />
              <span>{it.label}</span>
            </div>
          ))}
        </div>
      ) : n.group ? (
        <div key={i} style={{ display: "flex", alignItems: "center", gap: 7, padding: "6px 8px" }}>
          <Icon name={n.group === "CDP" ? "spark" : n.group === "LOYALTY" ? "trophy" : n.group === "AI AGENTS" ? "brain" : "send"} size={12} color={FAINT} />
          <Mono size={9.5} color={DIM} style={{ letterSpacing: "0.16em", fontWeight: 600, flex: 1 }}>{n.group}</Mono>
          <Icon name="chevron-r" size={10} color={FAINT} />
        </div>
      ) : (
        <div key={i} style={{ display: "flex", alignItems: "center", gap: 8, padding: "6px 8px", fontSize: 11.5, color: DIM, fontWeight: 500 }}>
          <Icon name={n.icon} size={12} color={FAINT} /><span>{n.label}</span>
        </div>
      ))}
      <div style={{ flex: 1 }} />
      <div style={{ borderTop: `1px solid ${RULE}`, paddingTop: 8, display: "flex", flexDirection: "column", gap: 2 }}>
        {[["Knowledge Base", "file"], ["Settings", "settings"]].map(([l, ic]) => (
          <div key={l} style={{ display: "flex", alignItems: "center", gap: 8, padding: "6px 8px", fontSize: 11.5, color: DIM }}>
            <Icon name={ic} size={12} color={FAINT} /><span>{l}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function ChromeButton({ children, icon, kind = "ghost", onClick, active }) {
  const solid = kind === "solid";
  return (
    <button onClick={onClick} style={{
      display: "inline-flex", alignItems: "center", gap: 6, height: 30, padding: "0 11px",
      borderRadius: 7, cursor: "pointer", fontFamily: SANS, fontSize: 11.5, fontWeight: 600,
      background: solid ? INK : active ? FUCHSIA_PALE + "70" : SURFACE,
      color: solid ? "#fff" : active ? FUCHSIA_DARK : INK,
      border: `1px solid ${solid ? INK : active ? FUCHSIA_PALE : RULE}`,
      whiteSpace: "nowrap", transition: "background .15s, border-color .15s",
    }}>
      {icon && <Icon name={icon} size={12} color={solid ? "#fff" : active ? FUCHSIA_DARK : DIM} />}
      {children}
    </button>
  );
}

function Select({ value, options, onChange, width = 132, icon }) {
  return (
    <label style={{ position: "relative", display: "inline-flex", alignItems: "center", width }}>
      {icon && <span style={{ position: "absolute", left: 9, pointerEvents: "none" }}><Icon name={icon} size={12} color={FAINT} /></span>}
      <select value={value} onChange={e => onChange(e.target.value)} style={{
        appearance: "none", width: "100%", height: 30, borderRadius: 7, border: `1px solid ${RULE}`,
        background: SURFACE, color: INK, fontFamily: SANS, fontSize: 11.5, fontWeight: 500,
        padding: icon ? "0 26px 0 26px" : "0 26px 0 10px", cursor: "pointer",
      }}>
        {options.map(o => <option key={o.v} value={o.v}>{o.l}</option>)}
      </select>
      <span style={{ position: "absolute", right: 9, pointerEvents: "none" }}><Icon name="chevron-d" size={11} color={FAINT} /></span>
    </label>
  );
}

// ─── gates ───
function stageIndex(pid, sid) { return PIPE_BY_ID[pid].stages.findIndex(s => s.id === sid); }

function missingFields(deal, targetStage) {
  const p = PIPE_BY_ID[deal.pid];
  const from = stageIndex(deal.pid, deal.stage), to = stageIndex(deal.pid, targetStage.id);
  const crossed = p.stages.slice(from + 1, to + 1);
  const out = [];
  const f = deal.f || {};
  const mice = deal.pid === "mice";
  crossed.forEach(s => {
    if (s.cat === "proposal") {
      if (!deal.contact) out.push(["Primary contact", "The proposal is addressed to a person, not a company."]);
      if (!deal.close) out.push(["Expected close date", "Required from the proposal boundary onwards."]);
      if (mice) {
        if (!f.from || !f.to) out.push(["Event dates", "Confirmed dates, or the first option of several."]);
        if (!f.pax) out.push(["Pax", "Head count drives the meeting-space quote."]);
      } else {
        if (!f.rateType) out.push(["Rate type", "Negotiated, static net, dynamic or series."]);
        if (!f.roomNights) out.push(["Room-night commitment", "The volume the rate is priced against."]);
      }
    }
    if (s.cat === "negotiation") {
      if (!deal.close) out.push(["Expected close date", "Negotiation without a date is not a forecast."]);
      const limit = s.rot || 30;
      if ((deal.lastAct ?? 99) > limit) out.push([`Activity in the last ${limit} days`, `Last logged activity was ${deal.lastAct} days ago. Log a call, an email or a note.`]);
      if (mice) {
        if (!f.accBudget || !f.meetBudget) out.push(["Budget breakdown", "Accommodation and meetings priced separately."]);
      } else {
        if (!f.allotment) out.push(["Allotment & release", "Rooms held and the release window in days."]);
      }
    }
    if (s.cat === "won") {
      if (mice) {
        if (!f.locator) out.push(["PMS locator", "The group block reference in the PMS."]);
        if (!f.signed) out.push(["Signed proposal", "Client acceptance on record before the deal is won."]);
      } else {
        if (!f.promo) out.push(["Booking-engine promo code", "This is the code that attributes every future reservation to the account."]);
        if (!f.payTerms) out.push(["Payment terms accepted", "Commission, settlement cycle and credit limit agreed."]);
      }
    }
  });
  return out;
}
function approvalGate(deal, targetStage) {
  const p = PIPE_BY_ID[deal.pid];
  const from = stageIndex(deal.pid, deal.stage), to = stageIndex(deal.pid, targetStage.id);
  if (to <= from) return null;                                  // backwards never gated
  if (targetStage.cat === "lost") return null;                  // marking lost never gated
  const crossed = p.stages.slice(from + 1, to + 1).filter(s => s.approval);
  if (!crossed.length) return null;
  if (deal.approval?.state === "granted") return null;
  return crossed[0];
}

// ─── deal card ───
function nextActivityTone(next) {
  if (!next) return { fg: AMBER, bg: "#FBF3E7", bd: "#EBDCC2", label: "No next action" };
  if (next.state === "overdue") return { fg: RED, bg: "#FBEDED", bd: "#EFD2D2", label: `${next.type} · overdue` };
  if (next.state === "today") return { fg: FUCHSIA_DARK, bg: FUCHSIA_PALE + "60", bd: FUCHSIA_PALE, label: `${next.type} · today` };
  return { fg: DIM, bg: PAPER, bd: RULE, label: `${next.type} · ${shortDate(next.date)}` };
}

function DealCard({ deal, stage, onOpen, onDragStart, dragging, selected }) {
  const acc = ACC_BY_ID[deal.acc];
  const rotting = stage.rot != null && (deal.lastAct ?? 0) > stage.rot;
  const tone = nextActivityTone(deal.next);
  const mice = deal.pid === "mice";
  const [hover, setHover] = useState(false);
  return (
    <div draggable onDragStart={e => onDragStart(e, deal)} onClick={() => onOpen(deal.id)}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{
        background: SURFACE, border: `1px solid ${selected ? FUCHSIA : hover ? FUCHSIA_PALE : RULE}`,
        borderLeft: rotting ? `3px solid ${AMBER}` : `1px solid ${selected ? FUCHSIA : hover ? FUCHSIA_PALE : RULE}`,
        borderRadius: 7, padding: "10px 11px 9px", cursor: "grab", opacity: dragging ? 0.35 : 1,
        boxShadow: hover ? "0 8px 20px -14px rgba(0,0,0,0.35)" : "none",
        transition: "box-shadow .15s, border-color .15s", position: "relative",
      }}>
      <div style={{ display: "flex", gap: 8, alignItems: "flex-start", marginBottom: 6 }}>
        <div style={{ fontSize: 12.5, fontWeight: 600, lineHeight: 1.35, letterSpacing: "-0.01em", flex: 1, textWrap: "pretty" }}>{deal.title}</div>
        {rotting && (
          <span style={{ display: "inline-flex", alignItems: "center", gap: 4, background: "#FBF3E7", border: "1px solid #EBDCC2", color: AMBER, borderRadius: 999, padding: "2px 6px", fontFamily: MONO, fontSize: 8.5, fontWeight: 700, letterSpacing: "0.06em", whiteSpace: "nowrap" }}>
            <Dot color={AMBER} size={4} /> WILTING
          </span>
        )}
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 7 }}>
        <Icon name="briefcase" size={11} color={FAINT} />
        <Mono size={10} color={DIM}>{acc.name}</Mono>
      </div>
      <div style={{ display: "flex", alignItems: "baseline", gap: 8, marginBottom: 7 }}>
        <span style={{ fontFamily: MONO, fontSize: 16, fontWeight: 700, letterSpacing: "-0.02em" }}>{eur(deal.value)}</span>
        {!mice && deal.f?.roomNights ? <Mono size={9.5} color={FAINT}>{num(deal.f.roomNights)} RN</Mono> : null}
        {mice && deal.f?.pax ? <Mono size={9.5} color={FAINT}>{deal.f.pax} pax</Mono> : null}
      </div>
      {mice && deal.f?.from && (
        <div style={{ display: "inline-flex", alignItems: "center", gap: 5, background: FUCHSIA_PALE + "45", border: `1px solid ${FUCHSIA_PALE}`, borderRadius: 5, padding: "3px 7px", marginBottom: 8 }}>
          <Icon name="cal" size={10} color={VIOLET} />
          <Mono size={9.5} color={VIOLET} style={{ fontWeight: 600 }}>{shortDate(deal.f.from)} – {shortDate(deal.f.to)}</Mono>
        </div>
      )}
      <div style={{ display: "flex", alignItems: "center", gap: 7, paddingTop: 7, borderTop: `1px dashed ${RULE}` }}>
        <Mono size={9.5} color={FAINT}>{shortDate(deal.created)}</Mono>
        <div style={{ flex: 1 }} />
        <span style={{ display: "inline-flex", alignItems: "center", gap: 4, background: tone.bg, border: `1px solid ${tone.bd}`, borderRadius: 5, padding: "2px 6px" }}>
          <Icon name={deal.next ? "cal" : "alert"} size={9.5} color={tone.fg} />
          <Mono size={9} color={tone.fg} style={{ fontWeight: 600 }}>{tone.label}</Mono>
        </span>
        <Avatar who={deal.owner} size={18} />
        <span style={{ display: "inline-flex", alignItems: "center", gap: 3 }}>
          <Icon name="clock" size={10} color={FAINT} /><Mono size={9} color={FAINT}>{deal.inStage}d</Mono>
        </span>
      </div>
      {deal.approval?.state === "pending" && (
        <div style={{ marginTop: 8, display: "flex", alignItems: "center", gap: 6, background: FUCHSIA_PALE + "50", border: `1px solid ${FUCHSIA_PALE}`, borderRadius: 5, padding: "4px 7px" }}>
          <Icon name="shield" size={10} color={FUCHSIA_DARK} />
          <Mono size={9} color={FUCHSIA_DARK} style={{ fontWeight: 600 }}>APPROVAL PENDING · {USER_BY_ID[deal.approval.by].name}</Mono>
        </div>
      )}
    </div>
  );
}

// ─── dialogs ───
function Modal({ title, sub, children, onClose, width = 480, tone = FUCHSIA, icon = "alert" }) {
  return (
    <div onClick={onClose} style={{
      position: "absolute", inset: 0, background: "rgba(26,20,26,0.42)", zIndex: 40,
      display: "grid", placeItems: "center", padding: 24, backdropFilter: "blur(2px)",
      animation: "gm-fadein .18s ease-out",
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width, maxHeight: "88%", overflow: "auto", background: SURFACE, borderRadius: 12,
        border: `1px solid ${RULE}`, boxShadow: "0 40px 90px -40px rgba(0,0,0,0.6)", padding: "20px 22px 18px",
      }}>
        <div style={{ display: "flex", gap: 11, alignItems: "flex-start", marginBottom: 14 }}>
          <span style={{ width: 30, height: 30, borderRadius: 8, background: tone + "18", border: `1px solid ${tone}44`, display: "grid", placeItems: "center", flexShrink: 0 }}>
            <Icon name={icon} size={15} color={tone} />
          </span>
          <div style={{ flex: 1 }}>
            <h3 style={{ fontFamily: SERIF, fontSize: 19, fontWeight: 500, margin: 0, letterSpacing: "-0.015em" }}>{title}</h3>
            {sub && <p style={{ fontSize: 12.5, color: DIM, margin: "5px 0 0", lineHeight: 1.5 }}>{sub}</p>}
          </div>
          <button onClick={onClose} style={{ background: "none", border: "none", cursor: "pointer", padding: 3 }}>
            <Icon name="x" size={14} color={FAINT} />
          </button>
        </div>
        {children}
      </div>
    </div>
  );
}

function GateBlocked({ deal, target, missing, onClose }) {
  return (
    <Modal onClose={onClose} tone={RED} icon="lock" width={520}
      title={`This deal is not ready for ${target.name}`}
      sub={`Checked on the server the moment the card was dropped. ${missing.length} requirement${missing.length > 1 ? "s" : ""} pending on the ${target.cat} boundary. A jump that skips several categories is checked against all of them.`}>
      <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 16 }}>
        {missing.map(([label, why], i) => (
          <div key={i} style={{ display: "flex", gap: 9, alignItems: "flex-start", background: "#FBEDED", border: "1px solid #EFD2D2", borderRadius: 7, padding: "9px 11px" }}>
            <Icon name="x" size={12} color={RED} style={{ marginTop: 2 }} />
            <div>
              <div style={{ fontSize: 12.5, fontWeight: 600 }}>{label}</div>
              <div style={{ fontSize: 11.5, color: DIM, lineHeight: 1.45, marginTop: 2 }}>{why}</div>
            </div>
          </div>
        ))}
      </div>
      <div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
        <ChromeButton onClick={onClose}>Back to board</ChromeButton>
        <ChromeButton kind="solid" icon="pen" onClick={onClose}>Fill the missing fields</ChromeButton>
      </div>
    </Modal>
  );
}

function ApprovalGateDialog({ deal, target, gateStage, onClose, onRequest, onGrant }) {
  const approver = USER_BY_ID[gateStage.approval];
  const pending = deal.approval?.state === "pending";
  return (
    <Modal onClose={onClose} tone={FUCHSIA} icon="shield" width={500}
      title={pending ? "Waiting on approval" : `${gateStage.name} requires approval`}
      sub={pending
        ? `Requested ${deal.approval.requested ? longDate(deal.approval.requested) : "today"}. The card stays where it is until ${approver.name} releases it. Moving backwards or marking the deal lost never needs approval.`
        : `Every approval-gated stage the move crosses is checked, not just the one you dropped on. ${approver.name} is the named releaser for this stage.`}>
      <div style={{ background: PAPER, border: `1px solid ${RULE}`, borderRadius: 8, padding: "11px 13px", marginBottom: 14 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
          <Avatar who={gateStage.approval} size={28} />
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 12.5, fontWeight: 600 }}>{approver.name}</div>
            <Mono size={10} color={DIM}>{approver.role} · authorised to release {gateStage.name}</Mono>
          </div>
          <Pill tone={pending ? "soon" : "tbc"}>{pending ? "Pending" : "Not requested"}</Pill>
        </div>
        {deal.approval?.note && (
          <p style={{ fontSize: 11.5, color: DIM, lineHeight: 1.5, margin: "10px 0 0", paddingTop: 10, borderTop: `1px dashed ${RULE}` }}>
            <b style={{ color: INK }}>Request note.</b> {deal.approval.note}
          </p>
        )}
      </div>
      <div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
        <ChromeButton onClick={onClose}>Cancel</ChromeButton>
        {!pending && <ChromeButton icon="send" onClick={onRequest}>Request approval</ChromeButton>}
        <ChromeButton kind="solid" icon="check" onClick={onGrant}>Release as {approver.initials}</ChromeButton>
      </div>
    </Modal>
  );
}

function LostDialog({ deal, onClose, onConfirm }) {
  const [reason, setReason] = useState("");
  const [note, setNote] = useState("");
  const [penalty, setPenalty] = useState("");
  return (
    <Modal onClose={onClose} tone={RED} icon="thumb-d" width={500}
      title="Close as lost" sub="Six typed causes plus a free comment. Without a reason the deal cannot be closed. This is what makes the loss-reason chart in analytics worth reading.">
      <div style={{ display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 7, marginBottom: 13 }}>
        {LOSS_REASONS.map(r => (
          <button key={r} onClick={() => setReason(r)} style={{
            padding: "8px 6px", borderRadius: 7, cursor: "pointer", fontFamily: SANS, fontSize: 11.5, fontWeight: 600,
            background: reason === r ? INK : SURFACE, color: reason === r ? "#fff" : INK,
            border: `1px solid ${reason === r ? INK : RULE}`,
          }}>{r}</button>
        ))}
      </div>
      <textarea value={note} onChange={e => setNote(e.target.value)} placeholder="What actually happened? (free comment)"
        style={{ width: "100%", minHeight: 62, resize: "vertical", borderRadius: 8, border: `1px solid ${RULE}`, padding: "9px 11px", fontFamily: SANS, fontSize: 12, color: INK, background: PAPER, marginBottom: 11 }} />
      {reason === "Event cancelled" && (
        <label style={{ display: "flex", alignItems: "center", gap: 9, marginBottom: 13, background: "#FBF3E7", border: "1px solid #EBDCC2", borderRadius: 7, padding: "9px 11px" }}>
          <Icon name="euro" size={13} color={AMBER} />
          <span style={{ fontSize: 11.5, color: INK, fontWeight: 500, flex: 1 }}>Cancellation penalty collected</span>
          <input value={penalty} onChange={e => setPenalty(e.target.value)} placeholder="0"
            style={{ width: 96, height: 28, borderRadius: 6, border: `1px solid ${RULE}`, padding: "0 9px", fontFamily: MONO, fontSize: 12, textAlign: "right", background: SURFACE }} />
        </label>
      )}
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8 }}>
        <Mono size={10} color={FAINT}>Penalties collected feed the production view</Mono>
        <div style={{ display: "flex", gap: 8 }}>
          <ChromeButton onClick={onClose}>Cancel</ChromeButton>
          <button disabled={!reason} onClick={() => onConfirm(reason, note, penalty)} style={{
            height: 30, padding: "0 13px", borderRadius: 7, border: "none", fontFamily: SANS, fontSize: 11.5, fontWeight: 600,
            background: reason ? RED : "#e6dcdc", color: reason ? "#fff" : FAINT, cursor: reason ? "pointer" : "not-allowed",
          }}>Close as lost</button>
        </div>
      </div>
    </Modal>
  );
}

function EditStagesDialog({ pipeline, stageCfg, setStageCfg, onClose }) {
  const [local, setLocal] = useState(() => JSON.parse(JSON.stringify(stageCfg)));
  const upd = (sid, k, v) => setLocal(l => ({ ...l, [sid]: { ...l[sid], [k]: v } }));
  const STAGE_SWATCHES = [SLATE, VIOLET, FUCHSIA, GREEN, RED, AMBER];
  return (
    <Modal onClose={onClose} tone={PLUM} icon="settings" width={620}
      title="Edit stages" sub={`${pipeline.label}, drag to reorder. A pipeline needs at least one qualification stage and exactly one Won and one Lost.`}>
      <div style={{ display: "flex", flexDirection: "column", gap: 9, maxHeight: 330, overflowY: "auto", paddingRight: 4, marginBottom: 14 }}>
        {pipeline.stages.map(s => {
          const c = local[s.id];
          const locked = s.cat === "won" || s.cat === "lost";
          return (
            <div key={s.id} style={{ display: "flex", gap: 10, background: PAPER, border: `1px solid ${RULE}`, borderRadius: 8, padding: "10px 12px" }}>
              <Icon name="grip" size={14} color={FAINT} style={{ marginTop: 4 }} />
              <div style={{ flex: 1 }}>
                <div style={{ display: "flex", gap: 8, alignItems: "center", marginBottom: 8 }}>
                  <input defaultValue={s.name} style={{ flex: 1, height: 28, borderRadius: 6, border: `1px solid ${RULE}`, background: SURFACE, padding: "0 9px", fontFamily: SANS, fontSize: 12, fontWeight: 600 }} />
                  <span style={{ display: "inline-flex", alignItems: "center", gap: 5, background: SURFACE, border: `1px solid ${RULE}`, borderRadius: 6, padding: "5px 8px" }}>
                    <Icon name="lock" size={10} color={FAINT} />
                    <Mono size={9.5} color={DIM} style={{ textTransform: "capitalize" }}>{s.cat}</Mono>
                  </span>
                  {!locked && <Icon name="trash" size={13} color={FAINT} />}
                </div>
                <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
                  <span style={{ display: "flex", gap: 4 }}>
                    {STAGE_SWATCHES.map(sw => (
                      <span key={sw} onClick={() => upd(s.id, "color", sw)} style={{
                        width: 17, height: 17, borderRadius: 5, background: sw, cursor: "pointer",
                        boxShadow: c.color === sw ? `0 0 0 2px ${SURFACE}, 0 0 0 3.5px ${sw}` : "none",
                      }} />
                    ))}
                  </span>
                  <label style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 11.5, color: DIM }}>
                    Probability
                    <input value={c.prob} onChange={e => upd(s.id, "prob", Number(e.target.value) || 0)} style={{ width: 52, height: 26, borderRadius: 6, border: `1px solid ${RULE}`, background: SURFACE, padding: "0 7px", fontFamily: MONO, fontSize: 11.5, textAlign: "right" }} />%
                  </label>
                  {!locked && (
                    <label style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 11.5, color: DIM }}>
                      Rotting after
                      <input value={c.rot ?? ""} onChange={e => upd(s.id, "rot", Number(e.target.value) || 0)} style={{ width: 46, height: 26, borderRadius: 6, border: `1px solid ${RULE}`, background: SURFACE, padding: "0 7px", fontFamily: MONO, fontSize: 11.5, textAlign: "right" }} />
                      days without activity
                    </label>
                  )}
                </div>
                {!locked && (
                  <div style={{ display: "flex", alignItems: "center", gap: 9, marginTop: 9, paddingTop: 9, borderTop: `1px dashed ${RULE}` }}>
                    <Icon name="shield" size={12} color={c.approval ? FUCHSIA : FAINT} />
                    <span style={{ fontSize: 11.5, color: c.approval ? INK : DIM, fontWeight: 500 }}>Requires approval</span>
                    <span onClick={() => upd(s.id, "approval", c.approval ? null : "ml")} style={{
                      width: 34, height: 19, borderRadius: 999, background: c.approval ? FUCHSIA : "#ddd6cc", position: "relative", cursor: "pointer", transition: "background .15s",
                    }}>
                      <span style={{ position: "absolute", top: 2, left: c.approval ? 17 : 2, width: 15, height: 15, borderRadius: "50%", background: "#fff", transition: "left .15s" }} />
                    </span>
                    {c.approval && (
                      <span style={{ display: "inline-flex", alignItems: "center", gap: 6, background: SURFACE, border: `1px solid ${RULE}`, borderRadius: 999, padding: "3px 9px 3px 4px" }}>
                        <Avatar who={c.approval} size={17} />
                        <span style={{ fontSize: 11, fontWeight: 600 }}>{USER_BY_ID[c.approval].name}</span>
                      </span>
                    )}
                  </div>
                )}
              </div>
            </div>
          );
        })}
      </div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <ChromeButton icon="plus">Add stage</ChromeButton>
        <div style={{ display: "flex", gap: 8 }}>
          <ChromeButton onClick={onClose}>Cancel</ChromeButton>
          <ChromeButton kind="solid" icon="check" onClick={() => { setStageCfg(local); onClose(); }}>Save changes</ChromeButton>
        </div>
      </div>
    </Modal>
  );
}

function NewDealDialog({ pid, onClose, onCreate }) {
  const mice = pid === "mice";
  const [form, setForm] = useState({ title: "", acc: mice ? "pfz" : "amex", value: "", contact: "", owner: "ko", close: "", pax: "", rn: "" });
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const accOpts = ACCOUNTS.filter(a => !mice || a.profile === "MICE").map(a => ({ v: a.id, l: a.name }));
  const ctOpts = [{ v: "", l: ",  none yet , " }, ...CONTACTS.filter(c => c.acc === form.acc).map(c => ({ v: c.id, l: `${c.name} · ${c.role}` }))];
  const field = { height: 30, borderRadius: 7, border: `1px solid ${RULE}`, background: SURFACE, padding: "0 10px", fontFamily: SANS, fontSize: 12, width: "100%" };
  return (
    <Modal onClose={onClose} tone={GREEN} icon="plus" width={520}
      title="New deal" sub={`Lands in the first qualification stage of ${PIPE_BY_ID[pid].label} and starts its own stage history. Gate fields are asked for later, at the boundary that needs them.`}>
      <div style={{ display: "grid", gap: 10, marginBottom: 14 }}>
        <label style={{ display: "grid", gap: 4 }}>
          <Mono size={9.5} color={DIM} style={{ letterSpacing: "0.14em", fontWeight: 600 }}>DEAL NAME</Mono>
          <input value={form.title} onChange={e => set("title", e.target.value)} placeholder={mice ? "e.g. Bayer sales kickoff Mar 2027" : "e.g. Continental Corporate, 2027 rate agreement"} style={field} />
        </label>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          <label style={{ display: "grid", gap: 4 }}>
            <Mono size={9.5} color={DIM} style={{ letterSpacing: "0.14em", fontWeight: 600 }}>ACCOUNT</Mono>
            <Select value={form.acc} options={accOpts} onChange={v => { set("acc", v); set("contact", ""); }} width="100%" />
          </label>
          <label style={{ display: "grid", gap: 4 }}>
            <Mono size={9.5} color={DIM} style={{ letterSpacing: "0.14em", fontWeight: 600 }}>PRIMARY CONTACT</Mono>
            <Select value={form.contact} options={ctOpts} onChange={v => set("contact", v)} width="100%" />
          </label>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10 }}>
          <label style={{ display: "grid", gap: 4 }}>
            <Mono size={9.5} color={DIM} style={{ letterSpacing: "0.14em", fontWeight: 600 }}>VALUE €</Mono>
            <input value={form.value} onChange={e => set("value", e.target.value.replace(/[^\d]/g, ""))} placeholder="42000" style={{ ...field, fontFamily: MONO }} />
          </label>
          <label style={{ display: "grid", gap: 4 }}>
            <Mono size={9.5} color={DIM} style={{ letterSpacing: "0.14em", fontWeight: 600 }}>{mice ? "PAX" : "ROOM NIGHTS"}</Mono>
            <input value={mice ? form.pax : form.rn} onChange={e => set(mice ? "pax" : "rn", e.target.value.replace(/[^\d]/g, ""))} placeholder={mice ? "120" : "800"} style={{ ...field, fontFamily: MONO }} />
          </label>
          <label style={{ display: "grid", gap: 4 }}>
            <Mono size={9.5} color={DIM} style={{ letterSpacing: "0.14em", fontWeight: 600 }}>OWNER</Mono>
            <Select value={form.owner} options={USERS.map(u => ({ v: u.id, l: u.name }))} onChange={v => set("owner", v)} width="100%" />
          </label>
        </div>
      </div>
      <div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
        <ChromeButton onClick={onClose}>Cancel</ChromeButton>
        <ChromeButton kind="solid" icon="check" onClick={() => onCreate(form)}>Create deal</ChromeButton>
      </div>
    </Modal>
  );
}

// ─── board ───
function ColumnHeader({ stage, deals, cfg }) {
  const sum = deals.reduce((s, d) => s + d.value, 0);
  return (
    <div style={{ borderTop: `2.5px solid ${cfg.color}`, background: SURFACE, border: `1px solid ${RULE}`, borderTopColor: cfg.color, borderRadius: "3px 3px 7px 7px", padding: "9px 11px", display: "flex", alignItems: "center", gap: 7 }}>
      <Dot color={cfg.color} size={7} />
      <span style={{ fontSize: 12.5, fontWeight: 600, letterSpacing: "-0.01em" }}>{stage.name}</span>
      <span style={{ background: PAPER, border: `1px solid ${RULE}`, borderRadius: 999, minWidth: 19, height: 18, display: "grid", placeItems: "center", padding: "0 5px" }}>
        <Mono size={9.5} color={DIM} style={{ fontWeight: 700 }}>{deals.length}</Mono>
      </span>
      {cfg.approval && <Icon name="lock" size={11} color={FUCHSIA} />}
      <div style={{ flex: 1 }} />
      <Mono size={11} color={INK} style={{ fontWeight: 700 }}>{eur(sum)}</Mono>
    </div>
  );
}

function Toast({ msg, tone = GREEN }) {
  if (!msg) return null;
  return (
    <div style={{
      position: "absolute", bottom: 18, left: "50%", transform: "translateX(-50%)", zIndex: 60,
      background: INK, color: "#fff", borderRadius: 999, padding: "9px 16px", display: "flex", alignItems: "center", gap: 9,
      boxShadow: "0 20px 40px -20px rgba(0,0,0,0.7)", animation: "gm-fadein .2s ease-out",
    }}>
      <Dot color={tone} size={7} pulse />
      <span style={{ fontSize: 12, fontWeight: 500 }}>{msg}</span>
    </div>
  );
}

function PipelineBoard() {
  const [pid, setPid] = useState("corp");
  const [view, setView] = useState("board");
  const [deals, setDeals] = useState(() => DEALS.map(d => ({ ...d })));
  const [stageCfg, setStageCfg] = useState(() => Object.fromEntries(
    PIPELINES.flatMap(p => p.stages.map(s => [s.id, { color: s.color, prob: s.prob, rot: s.rot, approval: s.approval }]))));
  const [q, setQ] = useState("");
  const [owner, setOwner] = useState("all");
  const [mine, setMine] = useState(false);
  const [rotOnly, setRotOnly] = useState(false);
  const [sort, setSort] = useState("manual");
  const [drag, setDrag] = useState(null);
  const [hoverCol, setHoverCol] = useState(null);
  const [openId, setOpenId] = useState(null);
  const [dialog, setDialog] = useState(null);
  const [toast, setToast] = useState(null);

  const pipeline = PIPE_BY_ID[pid];
  const cfg = sid => stageCfg[sid];
  useEffect(() => { if (!toast) return; const t = setTimeout(() => setToast(null), 2600); return () => clearTimeout(t); }, [toast]);

  const visible = deals.filter(d => d.pid === pid)
    .filter(d => !q || (d.title + ACC_BY_ID[d.acc].name).toLowerCase().includes(q.toLowerCase()))
    .filter(d => owner === "all" || d.owner === owner)
    .filter(d => !mine || d.owner === "ko")
    .filter(d => { if (!rotOnly) return true; const s = cfg(d.stage); return s.rot != null && (d.lastAct ?? 0) > s.rot; });
  const sorted = arr => sort === "manual" ? arr
    : sort === "value" ? [...arr].sort((a, b) => b.value - a.value)
    : sort === "next" ? [...arr].sort((a, b) => (a.next?.date || "9999") < (b.next?.date || "9999") ? -1 : 1)
    : [...arr].sort((a, b) => b.inStage - a.inStage);

  const openStages = pipeline.stages.filter(s => s.cat !== "won" && s.cat !== "lost");
  const wonStage = pipeline.stages.find(s => s.cat === "won");
  const lostStage = pipeline.stages.find(s => s.cat === "lost");
  const wonDeals = visible.filter(d => d.stage === wonStage.id);
  const lostDeals = visible.filter(d => d.stage === lostStage.id);

  function patch(id, obj) { setDeals(ds => ds.map(d => d.id === id ? { ...d, ...obj } : d)); }

  function attemptMove(deal, target) {
    const targetCfg = { ...target, ...cfg(target.id) };
    if (deal.stage === target.id) return;
    if (target.cat === "lost") { setDialog({ kind: "lost", deal, target }); return; }
    const back = stageIndex(pid, target.id) < stageIndex(pid, deal.stage);
    if (!back) {
      const missing = missingFields(deal, targetCfg);
      if (missing.length) { setDialog({ kind: "gate", deal, target: targetCfg, missing }); return; }
      const gate = approvalGate(deal, targetCfg);
      if (gate) { setDialog({ kind: "approval", deal, target: targetCfg, gateStage: { ...gate, approval: cfg(gate.id).approval } }); return; }
    }
    patch(deal.id, { stage: target.id, inStage: 0, lastAct: 0, approval: undefined, lost: undefined, won: target.cat === "won" ? "2026-08-12" : undefined });
    setToast(back ? `Moved back to ${target.name}, no approval needed going backwards.` : `${deal.title.split(", ")[0].trim()} → ${target.name}`);
  }

  const dropProps = stage => ({
    onDragOver: e => { e.preventDefault(); setHoverCol(stage.id); },
    onDragLeave: () => setHoverCol(h => h === stage.id ? null : h),
    onDrop: e => { e.preventDefault(); setHoverCol(null); const d = deals.find(x => x.id === drag); setDrag(null); if (d) attemptMove(d, stage); },
  });

  const totals = {
    open: visible.filter(d => openStages.some(s => s.id === d.stage)).reduce((s, d) => s + d.value, 0),
    weighted: visible.filter(d => openStages.some(s => s.id === d.stage)).reduce((s, d) => s + d.value * cfg(d.stage).prob / 100, 0),
  };
  const openDeal = deals.find(d => d.id === openId);

  return (
    <div style={{ position: "relative", display: "flex", background: SURFACE, minHeight: 760 }}>
      <SideNav />
      <div style={{ flex: 1, minWidth: 0, display: "flex", flexDirection: "column", background: SURFACE }}>
        {/* topbar */}
        <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 18px", borderBottom: `1px solid ${RULE}` }}>
          <Mono size={9.5} color={FAINT} style={{ letterSpacing: "0.16em", fontWeight: 600 }}>DATA</Mono>
          <Icon name="chevron-r" size={10} color={FAINT} />
          <Mono size={9.5} color={INK} style={{ letterSpacing: "0.16em", fontWeight: 700 }}>SALES PIPELINE</Mono>
          <div style={{ flex: 1 }} />
          <Icon name="bell" size={14} color={FAINT} />
          <Avatar who="ko" size={24} />
        </div>
        {/* page head */}
        <div style={{ padding: "16px 18px 0", display: "flex", alignItems: "flex-start", gap: 14 }}>
          <div style={{ flex: 1 }}>
            <h3 style={{ fontFamily: SERIF, fontSize: 27, fontWeight: 500, margin: 0, letterSpacing: "-0.02em" }}>Sales Pipeline</h3>
            <div style={{ display: "flex", alignItems: "center", gap: 14, marginTop: 6 }}>
              <Mono size={10.5} color={DIM}>OPEN <b style={{ color: INK }}>{eur(totals.open)}</b></Mono>
              <Mono size={10.5} color={DIM}>WEIGHTED <b style={{ color: FUCHSIA_DARK }}>{eur(totals.weighted)}</b></Mono>
              <Mono size={10.5} color={DIM}>{visible.length} DEALS · {pipeline.stages.length} STAGES</Mono>
            </div>
          </div>
          <div style={{ display: "flex", gap: 7, flexWrap: "wrap", justifyContent: "flex-end" }}>
            <ChromeButton icon="users">Sharing</ChromeButton>
            <ChromeButton icon="plus">New pipeline</ChromeButton>
            <ChromeButton icon="settings" onClick={() => setDialog({ kind: "stages" })}>Edit stages</ChromeButton>
            <ChromeButton kind="solid" icon="plus" onClick={() => setDialog({ kind: "new" })}>New deal</ChromeButton>
          </div>
        </div>
        {/* view tabs */}
        <div style={{ display: "flex", gap: 20, padding: "14px 18px 0", borderBottom: `1px solid ${RULE}` }}>
          {[["board", "Board", "board"], ["list", "List", "list"], ["analytics", "Analytics", "chart"]].map(([k, l, ic]) => (
            <button key={k} onClick={() => setView(k)} style={{
              display: "inline-flex", alignItems: "center", gap: 7, padding: "0 2px 9px", background: "none", cursor: "pointer",
              border: "none", borderBottom: `2px solid ${view === k ? FUCHSIA : "transparent"}`,
              color: view === k ? INK : DIM, fontFamily: SANS, fontSize: 13, fontWeight: view === k ? 600 : 500,
            }}>
              <Icon name={ic} size={13} color={view === k ? FUCHSIA : FAINT} />{l}
            </button>
          ))}
        </div>
        {/* pipeline tabs */}
        <div style={{ display: "flex", gap: 18, padding: "12px 18px 0", borderBottom: `1px solid ${RULE_SOFT}` }}>
          {PIPELINES.map(p => (
            <button key={p.id} onClick={() => setPid(p.id)} style={{
              display: "inline-flex", alignItems: "center", gap: 7, padding: "0 2px 9px", background: "none", cursor: "pointer",
              border: "none", borderBottom: `2px solid ${pid === p.id ? FUCHSIA : "transparent"}`,
              color: pid === p.id ? INK : DIM, fontFamily: SANS, fontSize: 12.5, fontWeight: pid === p.id ? 600 : 500,
            }}>
              <Icon name={p.icon} size={12.5} color={pid === p.id ? FUCHSIA : FAINT} />{p.label}
            </button>
          ))}
        </div>

        {view === "analytics" ? (
          <div style={{ padding: "16px 18px 22px", background: CREAM, flex: 1 }}>
            {window.B2BAnalyticsPanel ? <B2BAnalyticsPanel embedded /> : null}
          </div>
        ) : (
          <>
            {/* toolbar */}
            <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "12px 18px", flexWrap: "wrap" }}>
              <label style={{ position: "relative", display: "inline-flex", alignItems: "center" }}>
                <span style={{ position: "absolute", left: 9 }}><Icon name="search" size={12} color={FAINT} /></span>
                <input value={q} onChange={e => setQ(e.target.value)} placeholder="Search deals..." style={{
                  width: 190, height: 30, borderRadius: 7, border: `1px solid ${RULE}`, background: SURFACE,
                  padding: "0 10px 0 27px", fontFamily: SANS, fontSize: 11.5,
                }} />
              </label>
              <Select value={owner} icon="user" width={148} onChange={setOwner}
                options={[{ v: "all", l: "All owners" }, ...USERS.map(u => ({ v: u.id, l: u.name }))]} />
              <ChromeButton icon="user" active={mine} onClick={() => setMine(m => !m)}>Mine</ChromeButton>
              <ChromeButton icon="wilt" active={rotOnly} onClick={() => setRotOnly(r => !r)}>Rotting only</ChromeButton>
              <Select value={sort} icon="sort" width={172} onChange={setSort}
                options={[{ v: "manual", l: "Manual order" }, { v: "value", l: "Value · high to low" }, { v: "next", l: "Next activity" }, { v: "stale", l: "Days in stage" }]} />
              <div style={{ flex: 1 }} />
              <span style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "#E9F3EC", border: "1px solid #BFDDC9", borderRadius: 999, padding: "4px 10px" }}>
                <Icon name="trophy" size={11} color="#256B45" />
                <Mono size={10} color="#256B45" style={{ fontWeight: 700 }}>WON {wonDeals.length} · {eur(wonDeals.reduce((s, d) => s + d.value, 0))}</Mono>
              </span>
              <span style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "#FBEDED", border: "1px solid #EFD2D2", borderRadius: 999, padding: "4px 10px" }}>
                <Icon name="thumb-d" size={11} color={RED} />
                <Mono size={10} color={RED} style={{ fontWeight: 700 }}>LOST {lostDeals.length} · {eur(lostDeals.reduce((s, d) => s + d.value, 0))}</Mono>
              </span>
            </div>

            {view === "board" ? (
              <div style={{ padding: "4px 18px 18px", background: CREAM, borderTop: `1px solid ${RULE_SOFT}`, flex: 1 }}>
                <div style={{ display: "grid", gridTemplateColumns: `repeat(${openStages.length}, minmax(0,1fr))`, gap: 12, paddingTop: 14 }}>
                  {openStages.map(s => {
                    const c = cfg(s.id);
                    const col = sorted(visible.filter(d => d.stage === s.id));
                    const hot = hoverCol === s.id;
                    return (
                      <div key={s.id} {...dropProps(s)} style={{ display: "flex", flexDirection: "column", gap: 9, minHeight: 380 }}>
                        <ColumnHeader stage={s} deals={col} cfg={c} />
                        <div style={{
                          display: "flex", flexDirection: "column", gap: 9, flex: 1, borderRadius: 8, padding: hot ? 7 : 0,
                          background: hot ? FUCHSIA_PALE + "40" : "transparent",
                          outline: hot ? `1.5px dashed ${FUCHSIA}` : "none", transition: "background .12s",
                        }}>
                          {col.map(d => (
                            <DealCard key={d.id} deal={d} stage={{ ...s, ...c }} selected={openId === d.id}
                              dragging={drag === d.id} onOpen={setOpenId}
                              onDragStart={(e, dd) => { setDrag(dd.id); e.dataTransfer.effectAllowed = "move"; }} />
                          ))}
                          {!col.length && (
                            <div style={{ border: `1px dashed ${RULE}`, borderRadius: 8, padding: "18px 10px", textAlign: "center" }}>
                              <Mono size={10} color={FAINT}>No deals in this stage</Mono>
                            </div>
                          )}
                          <Mono size={9.5} color={FAINT} style={{ paddingLeft: 2 }}>
                            {c.prob}% · rots after {c.rot}d{c.approval ? ` · approval ${USER_BY_ID[c.approval].initials}` : ""}
                          </Mono>
                        </div>
                      </div>
                    );
                  })}
                </div>
                {/* won / lost dock — appears while dragging, exactly like the product */}
                <div style={{
                  display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginTop: 14,
                  maxHeight: drag ? 92 : 0, opacity: drag ? 1 : 0, overflow: "hidden",
                  transition: "max-height .22s ease, opacity .18s ease",
                }}>
                  {[wonStage, lostStage].map(s => {
                    const hot = hoverCol === s.id;
                    const isWon = s.cat === "won";
                    return (
                      <div key={s.id} {...dropProps(s)} style={{
                        border: `1.5px dashed ${hot ? (isWon ? GREEN : RED) : RULE}`, borderRadius: 10,
                        background: hot ? (isWon ? "#E9F3EC" : "#FBEDED") : SURFACE,
                        display: "grid", placeItems: "center", padding: "18px 12px", transition: "background .12s, border-color .12s",
                      }}>
                        <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
                          <Icon name={isWon ? "trophy" : "thumb-d"} size={16} color={isWon ? GREEN : RED} />
                          <div>
                            <div style={{ fontSize: 13, fontWeight: 600 }}>{s.name}</div>
                            <Mono size={10} color={DIM}>{isWon ? "Gate: promo code + payment terms" : "Gate: typed loss reason"}</Mono>
                          </div>
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>
            ) : (
              <ListView deals={sorted(visible)} cfg={cfg} onOpen={setOpenId} pipeline={pipeline} />
            )}
          </>
        )}

        {/* dialogs */}
        {dialog?.kind === "gate" && <GateBlocked {...dialog} onClose={() => setDialog(null)} />}
        {dialog?.kind === "approval" && (
          <ApprovalGateDialog {...dialog}
            onClose={() => setDialog(null)}
            onRequest={() => {
              patch(dialog.deal.id, { approval: { state: "pending", by: dialog.gateStage.approval, requested: "2026-08-12", note: dialog.deal.approval?.note } });
              setDialog(null); setToast(`Approval requested from ${USER_BY_ID[dialog.gateStage.approval].name}. The card stays put.`);
            }}
            onGrant={() => {
              const d = { ...dialog.deal, approval: { state: "granted", by: dialog.gateStage.approval } };
              patch(d.id, { approval: d.approval });
              const missing = missingFields(d, dialog.target);
              if (missing.length) { setDialog({ kind: "gate", deal: d, target: dialog.target, missing }); return; }
              patch(d.id, { stage: dialog.target.id, inStage: 0, lastAct: 0 });
              setDialog(null); setToast(`Released by ${USER_BY_ID[dialog.gateStage.approval].name} → ${dialog.target.name}`);
            }} />
        )}
        {dialog?.kind === "lost" && (
          <LostDialog {...dialog} onClose={() => setDialog(null)}
            onConfirm={(reason, note, penalty) => {
              patch(dialog.deal.id, { stage: dialog.target.id, inStage: 0, lost: { reason, note, penalty: Number(penalty) || 0, date: "2026-08-12" } });
              setDialog(null); setToast(`Closed as lost · ${reason}${penalty ? ` · penalty ${eurFull(Number(penalty))}` : ""}`);
            }} />
        )}
        {dialog?.kind === "stages" && <EditStagesDialog pipeline={pipeline} stageCfg={stageCfg} setStageCfg={setStageCfg} onClose={() => setDialog(null)} />}
        {dialog?.kind === "new" && (
          <NewDealDialog pid={pid} onClose={() => setDialog(null)}
            onCreate={form => {
              const id = "n" + Date.now();
              const first = pipeline.stages[0];
              setDeals(ds => [{
                id, pid, stage: first.id, title: form.title || "Untitled deal", acc: form.acc,
                value: Number(form.value) || 0, owner: form.owner, contact: form.contact || null,
                created: "2026-08-12", close: form.close || null, inStage: 0, lastAct: 0, next: null, tags: ["New"],
                f: pid === "mice"
                  ? { pax: Number(form.pax) || 0, rooms: 0, board: "", space: "", accBudget: 0, meetBudget: 0, locator: "", signed: false }
                  : { rateType: "", roomNights: Number(form.rn) || 0, adr: 0, allotment: 0, release: 0, hotels: [], promo: "", payTerms: false },
              }, ...ds]);
              setDialog(null); setToast("Deal created in the first qualification stage · history started");
            }} />
        )}
        {openDeal && (
          <DealDrawer deal={openDeal} cfg={stageCfg} onClose={() => setOpenId(null)}
            onMove={target => attemptMove(openDeal, target)}
            onPatch={obj => patch(openDeal.id, obj)} />
        )}
        <Toast msg={toast} />
      </div>
    </div>
  );
}

// ─── list view ───
function ListView({ deals, cfg, onOpen, pipeline }) {
  const th = { textAlign: "left", padding: "8px 10px", borderBottom: `1px solid ${RULE}`, position: "sticky", top: 0, background: PAPER };
  const td = { padding: "9px 10px", borderBottom: `1px solid ${RULE_SOFT}`, fontSize: 12 };
  return (
    <div style={{ padding: "0 18px 20px", background: CREAM, flex: 1 }}>
      <div style={{ border: `1px solid ${RULE}`, borderRadius: 9, overflow: "hidden", background: SURFACE }}>
        <table style={{ width: "100%", borderCollapse: "collapse", fontFamily: SANS }}>
          <thead>
            <tr>
              {["Deal", "Account", "Stage", "Value", "Weighted", "Close", "Owner", "Next activity", "In stage"].map(h => (
                <th key={h} style={th}><Mono size={9.5} color={DIM} style={{ letterSpacing: "0.14em", fontWeight: 700 }}>{h.toUpperCase()}</Mono></th>
              ))}
            </tr>
          </thead>
          <tbody>
            {deals.map(d => {
              const s = pipeline.stages.find(x => x.id === d.stage);
              const c = cfg(d.stage);
              const tone = nextActivityTone(d.next);
              return (
                <tr key={d.id} onClick={() => onOpen(d.id)} style={{ cursor: "pointer" }}>
                  <td style={{ ...td, fontWeight: 600 }}>{d.title}</td>
                  <td style={td}><span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}><AccountBadge id={d.acc} size={18} />{ACC_BY_ID[d.acc].name}</span></td>
                  <td style={td}><span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}><Dot color={c.color} size={6} /><Mono size={10.5} color={INK}>{s.name}</Mono>{c.approval && <Icon name="lock" size={10} color={FUCHSIA} />}</span></td>
                  <td style={{ ...td, fontFamily: MONO, fontWeight: 700 }}>{eur(d.value)}</td>
                  <td style={{ ...td, fontFamily: MONO, color: FUCHSIA_DARK }}>{eur(d.value * c.prob / 100)}</td>
                  <td style={{ ...td, fontFamily: MONO, color: DIM }}>{shortDate(d.close)}</td>
                  <td style={td}><Avatar who={d.owner} size={19} /></td>
                  <td style={td}><Mono size={10} color={tone.fg}>{tone.label}</Mono></td>
                  <td style={{ ...td, fontFamily: MONO, color: DIM }}>{d.inStage}d</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

Object.assign(window, { PipelineBoard, ChromeButton, Select, Modal, Toast, SideNav, nextActivityTone, missingFields, approvalGate, stageIndex });
