// ============================================================================
// «Відправники НП» (owner-only) — Етапи 1-2 переносу ТТН у CRM:
// конфіг відправників (ФОП/фізособа, ключ у БД або .env, ліміти, дефолтне
// відділення) + додавання нових через UI + журнал ЕН з діями
// (друк наклейки/А4, редагування, видалення в НП).
// Старий флоу (ЕН з адмінки Хорошопа → ttnNotifier) НЕ зачіпає.
// ============================================================================
const { useState: nsUseState, useEffect: nsUseEffect } = React;

async function nsJson(url, opts) {
  const r = await fetch(url, opts);
  let j = {}; try { j = await r.json(); } catch {}
  if (!r.ok) throw new Error(j.error || ("HTTP " + r.status));
  return j;
}
const nsBody = (obj) => ({ headers: { "Content-Type": "application/json" }, body: JSON.stringify(obj) });
const nsFmt = (n) => (Number(n) || 0).toLocaleString("uk-UA");
const NS_SOURCE = { request: "Документ", order: "Замовлення", manual: "Вручну" };
const NS_TYPE = { fop: "ФОП", fiz: "Фізособа" };

// PDF друк: тягнемо blob з токеном (звичайний <a href> не несе Authorization)
async function nsOpenPdf(number, fmt) {
  const r = await fetch(`/api/np/ttn/${encodeURIComponent(number)}/label?fmt=${fmt}`);
  if (!r.ok) { let j = {}; try { j = await r.json(); } catch {} throw new Error(j.error || "HTTP " + r.status); }
  const blob = await r.blob();
  window.open(URL.createObjectURL(blob), "_blank");
}

const nsInp = (w) => ({
  width: w, height: 30, padding: "0 8px", borderRadius: 7, border: "1px solid var(--border-default)",
  background: "var(--bg-raised)", color: "var(--fg-primary)", fontFamily: "var(--font-mono)", fontSize: 12,
  outline: "none", boxSizing: "border-box",
});
const nsBtn = (variant) => ({
  height: 30, padding: "0 12px", borderRadius: 7, fontFamily: "inherit", fontSize: 11.5, fontWeight: 500, cursor: "pointer",
  border: "1px solid " + (variant === "primary" ? "var(--accent-ring)" : variant === "danger" ? "rgba(248,113,113,.4)" : "var(--border-default)"),
  background: variant === "primary" ? "var(--accent-soft)" : variant === "danger" ? "rgba(248,113,113,.1)" : "var(--bg-raised)",
  color: variant === "primary" ? "var(--accent)" : variant === "danger" ? "#FCA5A5" : "var(--fg-secondary)",
});
const nsIconBtn = {
  width: 26, height: 26, border: "1px solid var(--border-default)", borderRadius: 6, background: "var(--bg-raised)",
  color: "var(--fg-muted)", cursor: "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center",
};

// ── Форма нового відправника ────────────────────────────────────────────────
function NsAddSender({ onDone, onCancel }) {
  const [f, setF] = nsUseState({ label: "", sender_type: "fop", api_key: "", mono_token: "", default_city: "Новоселиця", default_branch: "1", daily_limit: "0", monthly_cod_limit: "0" });
  const set = (k) => (v) => setF(p => ({ ...p, [k]: v }));
  const [check, setCheck] = nsUseState(null);   // null | "loading" | {counterparty,type,phone} | {error}
  const [monoCheck, setMonoCheck] = nsUseState(null); // null | "loading" | {name,income,balance} | {error}
  const [busy, setBusy] = nsUseState(false);
  const [err, setErr] = nsUseState("");
  const doCheck = () => {
    setCheck("loading");
    nsJson("/api/np/shipping/check-key", { method: "POST", ...nsBody({ apiKey: f.api_key.trim() }) })
      .then(r => setCheck(r)).catch(e => setCheck({ error: e.message }));
  };
  const doMonoCheck = () => {
    setMonoCheck("loading");
    nsJson("/api/np/shipping/check-mono", { method: "POST", ...nsBody({ token: f.mono_token.trim() }) })
      .then(r => setMonoCheck(r)).catch(e => setMonoCheck({ error: e.message }));
  };
  const add = () => {
    if (busy) return;
    setBusy(true); setErr("");
    nsJson("/api/np/shipping/senders", { method: "POST", ...nsBody({ ...f, api_key: f.api_key.trim(), mono_token: f.mono_token.trim(), daily_limit: Number(f.daily_limit) || 0, monthly_cod_limit: Number(f.monthly_cod_limit) || 0 }) })
      .then(() => { setBusy(false); onDone(); })
      .catch(e => { setBusy(false); setErr(e.message); });
  };
  const pill = (active) => ({
    height: 28, padding: "0 11px", borderRadius: 999, fontFamily: "inherit", fontSize: 11.5, fontWeight: 500, cursor: "pointer", whiteSpace: "nowrap", flexShrink: 0,
    border: "1px solid " + (active ? "rgba(110,231,183,.5)" : "var(--border-default)"),
    background: active ? "rgba(110,231,183,.12)" : "var(--bg-raised)", color: active ? "#6EE7B7" : "var(--fg-secondary)",
  });
  const lbl = { fontSize: 10.5, fontWeight: 600, letterSpacing: ".04em", textTransform: "uppercase", color: "var(--fg-muted)", display: "block", marginBottom: 5 };
  // Телефон: усі поля в колонку на всю ширину (фікс-ширини 240-300px розпирали екран)
  const nrw = typeof window !== "undefined" && window.innerWidth <= 768;
  const fw = (w) => nsInp(nrw ? "100%" : w);
  const fieldBox = nrw ? { width: "100%" } : {};
  return (
    <div style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-subtle)", background: "var(--bg-raised)", display: "flex", flexDirection: "column", gap: 12 }}>
      <div style={{ display: "flex", gap: nrw ? 12 : 14, flexWrap: "wrap", alignItems: nrw ? "stretch" : "flex-end", flexDirection: nrw ? "column" : "row" }}>
        <div style={fieldBox}><span style={lbl}>Назва (як показувати в CRM)</span><input style={{ ...fw(240), fontFamily: "inherit" }} value={f.label} onChange={e => set("label")(e.target.value)} placeholder="ФОП Іваненко Іван / Іваненко Іван"/></div>
        <div style={fieldBox}>
          <span style={lbl}>Тип (інформативно)</span>
          <div style={{ display: "flex", gap: 6 }}>
            <button onClick={() => set("sender_type")("fop")} style={pill(f.sender_type === "fop")}>ФОП</button>
            <button onClick={() => set("sender_type")("fiz")} style={pill(f.sender_type === "fiz")}>Фізособа</button>
          </div>
        </div>
        <div style={fieldBox}><span style={lbl}>API-ключ НП цього відправника</span>
          <div style={{ display: "flex", gap: 6 }}>
            <input style={fw(300)} value={f.api_key} onChange={e => { set("api_key")(e.target.value); setCheck(null); }} placeholder="32 символи з кабінету НП"/>
            <button onClick={doCheck} disabled={!f.api_key.trim()} style={{ ...nsBtn(), flexShrink: 0 }}>{check === "loading" ? "…" : "Перевірити"}</button>
          </div>
        </div>
        <div style={fieldBox}><span style={lbl}>Monobank-токен (опц., обороти ФОП)</span>
          <div style={{ display: "flex", gap: 6 }}>
            <input style={fw(250)} value={f.mono_token} onChange={e => { set("mono_token")(e.target.value); setMonoCheck(null); }} placeholder="api.monobank.ua"/>
            <button onClick={doMonoCheck} disabled={!f.mono_token.trim()} style={{ ...nsBtn(), flexShrink: 0 }}>{monoCheck === "loading" ? "…" : "Перевірити"}</button>
          </div>
        </div>
        <div style={fieldBox}><span style={lbl}>Відділення відправки</span>
          <div style={{ display: "flex", gap: 6 }}>
            <input style={nrw ? nsInp("70%") : nsInp(120)} value={f.default_city} onChange={e => set("default_city")(e.target.value)} placeholder="Місто"/>
            <input style={nrw ? nsInp("30%") : nsInp(50)} value={f.default_branch} onChange={e => set("default_branch")(e.target.value)} placeholder="№"/>
          </div>
        </div>
        {nrw ? (
          <div style={{ display: "flex", gap: 12 }}>
            <div style={{ flex: 1 }}><span style={lbl}>Ліміт/день</span><input style={nsInp("100%")} value={f.daily_limit} onChange={e => set("daily_limit")(e.target.value)}/></div>
            <div style={{ flex: 1 }}><span style={lbl}>Накладені/міс, ₴</span><input style={nsInp("100%")} value={f.monthly_cod_limit} onChange={e => set("monthly_cod_limit")(e.target.value)}/></div>
          </div>
        ) : (
          <React.Fragment>
            <div><span style={lbl}>Ліміт/день</span><input style={nsInp(64)} value={f.daily_limit} onChange={e => set("daily_limit")(e.target.value)}/></div>
            <div><span style={lbl}>Накладені/міс, ₴</span><input style={nsInp(100)} value={f.monthly_cod_limit} onChange={e => set("monthly_cod_limit")(e.target.value)}/></div>
          </React.Fragment>
        )}
        <div style={{ display: "flex", gap: 8, ...(nrw ? { width: "100%" } : {}) }}>
          <button onClick={add} disabled={busy || !f.label.trim() || !f.api_key.trim()} style={{ ...nsBtn("primary"), ...(nrw ? { flex: 1, height: 40 } : {}), opacity: (!f.label.trim() || !f.api_key.trim()) ? 0.5 : 1 }}>{busy ? "Додаю…" : "Додати"}</button>
          <button onClick={onCancel} style={{ ...nsBtn(), ...(nrw ? { flex: 1, height: 40 } : {}) }}>Скасувати</button>
        </div>
      </div>
      {check && check !== "loading" && (check.error
        ? <div style={{ fontSize: 12, color: "#FCA5A5" }}>Ключ не пройшов: {check.error}</div>
        : <div style={{ fontSize: 12, color: "#6EE7B7" }}>НП бачить за ключем: <b>{check.counterparty}</b>{check.type ? " · " + (check.type === "PrivatePerson" ? "приватна особа" : "організація/ФОП") : ""}{check.phone ? " · тел. " + check.phone : ""}</div>)}
      {monoCheck && monoCheck !== "loading" && (monoCheck.error
        ? <div style={{ fontSize: 12, color: "#FCA5A5" }}>Mono не пройшов: {monoCheck.error}</div>
        : <div style={{ fontSize: 12, color: "#6EE7B7" }}>Mono: <b>{monoCheck.name || "ОК"}</b> · ФОП-рахунок знайдено · надходження {monoCheck.month}: <b>{nsFmt(monoCheck.income)} ₴</b>{monoCheck.truncated ? " (≥, обрізано на 500 оп.)" : ""}{monoCheck.balance != null ? " · баланс " + nsFmt(monoCheck.balance) + " ₴" : ""}</div>)}
      {err && <div style={{ fontSize: 12, color: "#FCA5A5" }}>{err}</div>}
    </div>
  );
}

// ── Конфіг рахунків ФОПа (єдиний реєстр) ────────────────────────────────────
// Тумблер «виставляє рахунки» + реквізити продавця + TG-група + sheet + режим №.
// Працює через той самий механізм edits/val/setVal, що й решта полів відправника —
// зміни зберігає спільна кнопка «Зберегти».
function NsInvoiceConfig({ s, val, setVal }) {
  const on = !!val(s, "issues_invoices");
  const noMode = val(s, "invoice_no_mode") || "auto";
  const lbl = { fontSize: 9.5, fontWeight: 600, letterSpacing: ".04em", textTransform: "uppercase", color: "var(--fg-muted)", display: "block", marginBottom: 4 };
  const txtInp = { ...nsInp("100%"), fontFamily: "inherit" };
  const pill = (active) => ({
    height: 26, padding: "0 10px", borderRadius: 999, fontFamily: "inherit", fontSize: 11, cursor: "pointer", whiteSpace: "nowrap",
    border: "1px solid " + (active ? "rgba(110,231,183,.5)" : "var(--border-default)"),
    background: active ? "rgba(110,231,183,.12)" : "var(--bg-raised)", color: active ? "#6EE7B7" : "var(--fg-secondary)",
  });
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 10, padding: 12, background: "var(--bg-raised)", borderRadius: 8, border: "1px solid var(--border-subtle)" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
        <Icon name="receipt" size={14} color="#22D3EE"/>
        <span style={{ fontSize: 12.5, fontWeight: 600, color: "var(--fg-primary)", flex: 1 }}>Виставляє рахунки</span>
        <button onClick={() => setVal(s.id, "issues_invoices")(!on)} title={on ? "Вимкнути" : "Увімкнути"} style={{
          width: 38, height: 21, borderRadius: 999, border: "1px solid var(--border-default)", cursor: "pointer", position: "relative", flexShrink: 0,
          background: on ? "rgba(110,231,183,.25)" : "var(--bg-raised)",
        }}>
          <span style={{ position: "absolute", top: 2, left: on ? 18 : 2, width: 15, height: 15, borderRadius: "50%", background: on ? "#6EE7B7" : "var(--fg-muted)", transition: "left 150ms" }}/>
        </button>
      </div>
      {on && (
        <React.Fragment>
          <div style={{ fontSize: 11, color: "var(--fg-muted)", marginTop: -2 }}>Реквізити друкуються в рахунку/видатковій. Зберігаються в реєстр — далі ФОП доступний у picker'і рахунків і в Банках.</div>
          <div><span style={lbl}>Назва продавця (реквізит)</span><input style={txtInp} value={val(s, "seller_name") || ""} onChange={e => setVal(s.id, "seller_name")(e.target.value)} placeholder="ФОП Прізвище Імʼя По батькові"/></div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1.6fr", gap: 8 }}>
            <div><span style={lbl}>ЄДРПОУ/ІПН</span><input style={nsInp("100%")} value={val(s, "seller_edrpou") || ""} onChange={e => setVal(s.id, "seller_edrpou")(e.target.value)} placeholder="0000000000"/></div>
            <div><span style={lbl}>IBAN</span><input style={nsInp("100%")} value={val(s, "seller_iban") || ""} onChange={e => setVal(s.id, "seller_iban")(e.target.value)} placeholder="UA…"/></div>
          </div>
          <div><span style={lbl}>Банк</span><input style={txtInp} value={val(s, "seller_bank") || ""} onChange={e => setVal(s.id, "seller_bank")(e.target.value)} placeholder="МОНО (УНІВЕРСАЛ БАНК)"/></div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
            <div><span style={lbl}>Telegram-група (chatId)</span><input style={nsInp("100%")} value={val(s, "invoice_tg_group") || ""} onChange={e => setVal(s.id, "invoice_tg_group")(e.target.value)} placeholder="-100…"/></div>
            <div><span style={lbl}>Google Sheet (id, дзеркало)</span><input style={nsInp("100%")} value={val(s, "invoice_sheet_id") || ""} onChange={e => setVal(s.id, "invoice_sheet_id")(e.target.value)} placeholder="1AbC…"/></div>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: noMode === "auto" ? "1fr auto" : "1fr", gap: 8, alignItems: "end" }}>
            <div>
              <span style={lbl}>Нумерація рахунків</span>
              <div style={{ display: "flex", gap: 6 }}>
                <button onClick={() => setVal(s.id, "invoice_no_mode")("auto")} style={pill(noMode === "auto")}>Лічильник (1, 2, 3…)</button>
                <button onClick={() => setVal(s.id, "invoice_no_mode")("order")} style={pill(noMode === "order")}>№ = № замовлення</button>
              </div>
            </div>
            {noMode === "auto" && (
              <div style={{ width: 130 }}>
                <span style={lbl}>Останній виданий №</span>
                <input style={nsInp("100%")} value={val(s, "invoice_no_start") || ""} onChange={e => setVal(s.id, "invoice_no_start")(e.target.value.replace(/[^\d]/g, ""))} inputMode="numeric" placeholder="напр. 50"/>
              </div>
            )}
          </div>
          {noMode === "auto" && <div style={{ fontSize: 10.5, color: "var(--fg-muted)", marginTop: -4 }}>Впишіть скільки рахунків уже виставлено в старій таблиці — наступний почнеться з цього +1 (без скидання нумерації). Далі рахується автоматично.</div>}
        </React.Fragment>
      )}
    </div>
  );
}

// ── Модал редагування ЕН ─────────────────────────────────────────────────────
function NsEditTtn({ ttn, onClose, onSaved }) {
  const e0 = ttn.edit || {};
  const r0 = ttn.recipient || {};
  const [f, setF] = nsUseState({
    cargoType: e0.cargoType || "Documents", weight: e0.weight || "0.5", seats: e0.seats || "1",
    // місця: з OptionsSeat (вага+габарити на кожне) або одне місце зі старих полів
    seatsArr: (e0.seatsList && e0.seatsList.length)
      ? e0.seatsList.map(s => ({ weight: String(s.weight || ""), length: String(s.length || ""), width: String(s.width || ""), height: String(s.height || "") }))
      : [{ weight: String(e0.weight || ""), length: String(e0.length || ""), width: String(e0.width || ""), height: String(e0.height || "") }],
    cost: e0.cost || "300", description: e0.description || "",
    payerType: e0.payerType || "Recipient", paymentMethod: e0.paymentMethod || "Cash",
    codMode: e0.codMode || "none", codAmount: String(e0.codAmount || ""),
    recipientName: r0.name || "", recipientPhone: r0.phone || "",
  });
  const set = (k) => (v) => setF(p => ({ ...p, [k]: v }));
  const setSeat = (i, k) => (v) => setF(p => ({ ...p, seatsArr: p.seatsArr.map((s, j) => j === i ? { ...s, [k]: v } : s) }));
  const addSeat = () => setF(p => ({ ...p, seatsArr: [...p.seatsArr, { weight: "", length: "", width: "", height: "" }] }));
  const delSeat = (i) => setF(p => ({ ...p, seatsArr: p.seatsArr.filter((_, j) => j !== i) }));
  // Дублювати останнє місце N разів (великі відправки з однаковими місцями)
  const [dupN, setDupN] = nsUseState("5");
  const dupSeat = () => {
    const n = Math.max(1, Math.min(99, parseInt(dupN, 10) || 1));
    setF(p => {
      const last = p.seatsArr[p.seatsArr.length - 1] || { weight: "", length: "", width: "", height: "" };
      return { ...p, seatsArr: [...p.seatsArr, ...Array.from({ length: n }, () => ({ ...last }))] };
    });
  };
  const seatsTotalW = f.seatsArr.reduce((a, s) => a + (Number(s.weight) || 0), 0);
  const [busy, setBusy] = nsUseState(false);
  const [err, setErr] = nsUseState("");
  const save = () => {
    if (busy) return;
    setBusy(true); setErr("");
    nsJson("/api/np/ttn/" + encodeURIComponent(ttn.number), { method: "PUT", ...nsBody({
      cargoType: f.cargoType,
      weight: f.cargoType === "Parcel" ? String(seatsTotalW || f.weight) : f.weight,
      seats: f.seats, cost: f.cost, description: f.description,
      seatsList: f.cargoType === "Parcel" ? f.seatsArr : undefined,
      payerType: f.payerType, paymentMethod: f.paymentMethod,
      cod: { mode: f.codMode, amount: Number(f.codAmount) || 0 },
      recipientName: f.recipientName, recipientPhone: f.recipientPhone,
    }) }).then(() => { setBusy(false); onSaved(); }).catch(e => { setBusy(false); setErr(e.message); });
  };
  const pill = (active) => ({
    height: 28, padding: "0 11px", borderRadius: 999, fontFamily: "inherit", fontSize: 11.5, cursor: "pointer", whiteSpace: "nowrap", flexShrink: 0,
    border: "1px solid " + (active ? "rgba(110,231,183,.5)" : "var(--border-default)"),
    background: active ? "rgba(110,231,183,.12)" : "var(--bg-raised)", color: active ? "#6EE7B7" : "var(--fg-secondary)",
  });
  const lbl = { fontSize: 10.5, fontWeight: 600, letterSpacing: ".04em", textTransform: "uppercase", color: "var(--fg-muted)", display: "block", marginBottom: 5 };
  return (
    <div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,.6)", zIndex: 90, display: "flex", alignItems: "center", justifyContent: "center",
      padding: "max(14px, env(safe-area-inset-top)) 8px max(14px, env(safe-area-inset-bottom))", boxSizing: "border-box" }}>
      <div style={{ width: "min(480px, 96vw)", maxHeight: "100%", overflowY: "auto", overflowX: "hidden", background: "var(--bg-panel)", borderRadius: 12, border: "1px solid var(--border-default)", boxShadow: "0 24px 64px rgba(0,0,0,.5)", padding: 20, display: "flex", flexDirection: "column", gap: 14, boxSizing: "border-box" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <Icon name="pencil" size={15} color="var(--accent)"/>
          <div style={{ flex: 1, fontSize: 14, fontWeight: 600, color: "var(--fg-primary)" }}>Редагувати ЕН <span style={{ fontFamily: "var(--font-mono)" }}>{ttn.number}</span></div>
          <button onClick={onClose} style={{ ...nsIconBtn, border: 0 }}><Icon name="x" size={15}/></button>
        </div>
        <div style={{ fontSize: 11.5, color: "var(--fg-muted)" }}>Зміни доступні, поки посилку не прийняли у відділенні. Відділення/місто тут не міняємо — для цього видаліть ЕН і створіть нову.</div>
        <div style={{ display: "flex", gap: 14, flexWrap: "wrap" }}>
          <div style={{ flex: 1, minWidth: 180 }}><span style={lbl}>ПІБ отримувача</span><input style={{ ...nsInp("100%"), fontFamily: "inherit" }} value={f.recipientName} onChange={e => set("recipientName")(e.target.value)} placeholder="Прізвище Ім'я По батькові"/></div>
          <div><span style={lbl}>Телефон отримувача</span><input style={nsInp(150)} value={f.recipientPhone} onChange={e => set("recipientPhone")(e.target.value)} placeholder="380…"/></div>
        </div>
        <div style={{ display: "flex", gap: 14, flexWrap: "wrap" }}>
          <div><span style={lbl}>Вантаж</span>
            <div style={{ display: "flex", gap: 5 }}>
              <button onClick={() => set("cargoType")("Documents")} style={pill(f.cargoType === "Documents")}>Документи</button>
              <button onClick={() => set("cargoType")("Parcel")} style={pill(f.cargoType === "Parcel")}>Посилка</button>
            </div>
          </div>
          {f.cargoType === "Documents" && <div><span style={lbl}>Вага, кг</span><input style={nsInp(70)} value={f.weight} onChange={e => set("weight")(e.target.value)}/></div>}
          {f.cargoType === "Documents" && <div><span style={lbl}>Місць</span><input style={nsInp(50)} value={f.seats} onChange={e => set("seats")(e.target.value)}/></div>}
          <div><span style={lbl}>Оцінка, ₴</span><input style={nsInp(80)} value={f.cost} onChange={e => set("cost")(e.target.value)}/></div>
        </div>
        {f.cargoType === "Parcel" && (
          <div>
            <span style={lbl}>Місця ({f.seatsArr.length}) · вага ЕН = {(seatsTotalW || 0).toFixed(2).replace(/\.00$/, "")} кг</span>
            {/* Вузький екран: інпути на 1fr, обʼємна вага — окремим рядком (інакше грид ширший за телефон) */}
            {(() => { const nrw = typeof window !== "undefined" && window.innerWidth <= 768; return (
            <div style={{ display: "grid", gridTemplateColumns: nrw ? "14px repeat(4, minmax(0, 1fr)) 24px" : "16px 72px 58px 58px 58px minmax(100px, 1fr) 24px", gap: 6, alignItems: "center" }}>
              <span/>
              <span style={{ fontSize: 9, fontWeight: 600, letterSpacing: ".03em", textTransform: "uppercase", color: "var(--fg-muted)" }}>Вага, кг</span>
              <span style={{ fontSize: 9, fontWeight: 600, letterSpacing: ".03em", textTransform: "uppercase", color: "var(--fg-muted)" }}>Довж.</span>
              <span style={{ fontSize: 9, fontWeight: 600, letterSpacing: ".03em", textTransform: "uppercase", color: "var(--fg-muted)" }}>Шир.</span>
              <span style={{ fontSize: 9, fontWeight: 600, letterSpacing: ".03em", textTransform: "uppercase", color: "var(--fg-muted)" }}>Вис.</span>
              {!nrw && <span/>}<span/>
              {f.seatsArr.map((s, i) => {
                const vw = (Number(s.length) > 0 && Number(s.width) > 0 && Number(s.height) > 0)
                  ? (Number(s.length) * Number(s.width) * Number(s.height)) / 4000 : 0;
                const volLabel = (
                  <span style={{ fontSize: nrw ? 10.5 : 11, color: vw > (Number(s.weight) || 0) ? "#FBBF24" : "var(--fg-muted)", whiteSpace: nrw ? "normal" : "nowrap", ...(nrw ? { gridColumn: "2 / 6", marginTop: -2, marginBottom: 2 } : {}) }}>
                    {vw > 0 ? "обʼємна " + vw.toFixed(2) + " кг" + (vw > (Number(s.weight) || 0) ? " ←тариф" : "") : "без габаритів"}
                  </span>
                );
                return (
                  <React.Fragment key={i}>
                    <span style={{ fontSize: 11, color: "var(--fg-muted)" }}>{i + 1}.</span>
                    <input style={nsInp("100%")} value={s.weight} onChange={e => setSeat(i, "weight")(e.target.value)} placeholder="—"/>
                    <input style={nsInp("100%")} value={s.length} onChange={e => setSeat(i, "length")(e.target.value)} placeholder="—"/>
                    <input style={nsInp("100%")} value={s.width} onChange={e => setSeat(i, "width")(e.target.value)} placeholder="—"/>
                    <input style={nsInp("100%")} value={s.height} onChange={e => setSeat(i, "height")(e.target.value)} placeholder="—"/>
                    {!nrw && volLabel}
                    {f.seatsArr.length > 1
                      ? <button onClick={() => delSeat(i)} title="Прибрати місце" style={{ width: 22, height: 22, border: "1px solid var(--border-default)", borderRadius: 6, background: "transparent", color: "#FCA5A5", cursor: "pointer", fontSize: 12, lineHeight: 1 }}>×</button>
                      : <span/>}
                    {nrw && volLabel}
                  </React.Fragment>
                );
              })}
            </div>
            ); })()}
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 6, flexWrap: "wrap" }}>
              <button onClick={addSeat} style={{ height: 24, padding: "0 10px", borderRadius: 999, border: "1px dashed var(--border-default)", background: "transparent", color: "var(--fg-secondary)", fontFamily: "inherit", fontSize: 11, cursor: "pointer" }}>+ Місце</button>
              <button onClick={dupSeat} title="Додати копії останнього місця" style={{ height: 24, padding: "0 10px", borderRadius: 999, border: "1px dashed var(--border-default)", background: "transparent", color: "var(--fg-secondary)", fontFamily: "inherit", fontSize: 11, cursor: "pointer" }}>⧉ Дублювати останнє ×</button>
              <input value={dupN} onChange={e => setDupN(e.target.value.replace(/\D/g, "").slice(0, 2))} inputMode="numeric" style={{ width: 36, height: 24, padding: "0 5px", borderRadius: 6, border: "1px solid var(--border-default)", background: "var(--bg-raised)", color: "var(--fg-primary)", fontFamily: "var(--font-mono)", fontSize: 11.5, textAlign: "center", outline: "none", boxSizing: "border-box" }}/>
            </div>
          </div>
        )}
        <div><span style={lbl}>Опис</span><input style={{ ...nsInp("100%"), fontFamily: "inherit" }} value={f.description} onChange={e => set("description")(e.target.value)}/></div>
        <div style={{ display: "flex", gap: 14, flexWrap: "wrap" }}>
          <div><span style={lbl}>Платник</span>
            <div style={{ display: "flex", gap: 5 }}>
              <button onClick={() => set("payerType")("Recipient")} style={pill(f.payerType === "Recipient")}>Отримувач</button>
              <button onClick={() => set("payerType")("Sender")} style={pill(f.payerType === "Sender")}>Відправник</button>
            </div>
          </div>
          <div><span style={lbl}>Оплата</span>
            <div style={{ display: "flex", gap: 5 }}>
              <button onClick={() => set("paymentMethod")("Cash")} style={pill(f.paymentMethod === "Cash")}>Готівка</button>
              <button onClick={() => set("paymentMethod")("NonCash")} style={pill(f.paymentMethod === "NonCash")}>Безготівка</button>
            </div>
          </div>
        </div>
        <div style={{ display: "flex", gap: 14, flexWrap: "wrap", alignItems: "flex-end" }}>
          <div><span style={lbl}>Післяплата</span>
            <div style={{ display: "flex", gap: 5, flexWrap: "wrap" }}>
              <button onClick={() => set("codMode")("none")} style={pill(f.codMode === "none")}>Немає</button>
              <button onClick={() => set("codMode")("redelivery")} style={pill(f.codMode === "redelivery")}>Гроші назад</button>
              <button onClick={() => set("codMode")("afterpayment")} style={pill(f.codMode === "afterpayment")}>Контроль оплати</button>
            </div>
          </div>
          {f.codMode !== "none" && <div><span style={lbl}>Сума, ₴</span><input style={nsInp(90)} value={f.codAmount} onChange={e => set("codAmount")(e.target.value)}/></div>}
        </div>
        {err && <div style={{ fontSize: 12, color: "#FCA5A5" }}>{err}</div>}
        <div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
          <button onClick={onClose} style={nsBtn()}>Скасувати</button>
          <button onClick={save} disabled={busy} style={nsBtn("primary")}>{busy ? "Зберігаю…" : "Зберегти в НП"}</button>
        </div>
      </div>
    </div>
  );
}

function NpSenders({ isMobile }) {
  const [senders, setSenders] = nsUseState(null);
  const [ttns, setTtns] = nsUseState([]);
  const [edits, setEdits] = nsUseState({});     // id → {поле: значення} незбережені зміни
  const [saving, setSaving] = nsUseState("");
  const [err, setErr] = nsUseState("");
  const [adding, setAdding] = nsUseState(false);
  const [editTtn, setEditTtn] = nsUseState(null);
  const [rowBusy, setRowBusy] = nsUseState("");  // number, для дій журналу
  const [invCfgOpen, setInvCfgOpen] = nsUseState({}); // id → розгорнуто конфіг рахунків
  const [ttnFilter, setTtnFilter] = nsUseState("all"); // фільтр журналу ЕН за відправником
  const toggleInvCfg = (id) => setInvCfgOpen(p => ({ ...p, [id]: !p[id] }));

  // Групуємо відправників: спершу ФОПи, потім фізособи (з заголовком групи).
  // Маркер-обʼєкт {_hdr,_n} вставляється перед групою; у .map він рендериться як заголовок.
  const isFop = (s) => s.sender_type !== "fiz";
  const groupedSenders = (() => {
    if (!senders) return null;
    const fop = senders.filter(isFop), fiz = senders.filter(s => !isFop(s));
    const out = [];
    if (fop.length) { out.push({ _hdr: "ФОПи", _n: fop.length }); out.push(...fop); }
    if (fiz.length) { out.push({ _hdr: "Фізособи", _n: fiz.length }); out.push(...fiz); }
    return out;
  })();
  // Журнал ЕН з фільтром за відправником
  const ttnsView = ttnFilter === "all" ? ttns : ttns.filter(t => String(t.sender_id) === String(ttnFilter));

  const load = () => {
    nsJson("/api/np/shipping/senders").then(j => setSenders(j.senders || [])).catch(e => setErr(e.message));
    nsJson("/api/np/shipping/ttns?limit=50").then(j => setTtns(j.ttns || [])).catch(() => {});
  };
  nsUseEffect(load, []);

  const val = (s, k) => (edits[s.id] && edits[s.id][k] !== undefined) ? edits[s.id][k] : s[k];
  const setVal = (id, k) => (v) => setEdits(p => ({ ...p, [id]: { ...(p[id] || {}), [k]: v } }));
  const isDirty = (id) => edits[id] && Object.keys(edits[id]).length > 0;

  const putSender = (id, patch, after) => {
    setSaving(id); setErr("");
    nsJson("/api/np/shipping/senders/" + id, { method: "PUT", ...nsBody(patch) })
      .then(() => { setSaving(""); after && after(); load(); })
      .catch(e => { setSaving(""); setErr(e.message); });
  };
  const save = (s) => {
    if (!isDirty(s.id) || saving) return;
    const patch = { ...edits[s.id] };
    for (const k of ["daily_limit", "monthly_cod_limit", "priority", "invoice_no_start"]) if (patch[k] !== undefined) patch[k] = Number(patch[k]) || 0;
    putSender(s.id, patch, () => setEdits(p => { const n = { ...p }; delete n[s.id]; return n; }));
  };
  const toggleActive = (s) => putSender(s.id, { active: !val(s, "active") });
  const changeKey = (s) => {
    const k = window.prompt(`Новий API-ключ для «${s.label}» (32 символи).\nКлюч збережеться в базі CRM:`);
    if (k === null) return;
    putSender(s.id, { api_key: k.trim() });
  };
  const changeMono = (s) => {
    const k = window.prompt(`Monobank API-токен для «${s.label}» (api.monobank.ua).\nОбороти рахуємо ЛИШЕ по ФОП-рахунку. Порожнє значення — прибрати токен:`);
    if (k === null) return;
    putSender(s.id, { mono_token: k.trim() });
  };
  const delSender = (s) => {
    if (!window.confirm(`Видалити відправника «${s.label}»? (можливо лише якщо в нього немає ЕН)`)) return;
    setErr("");
    nsJson("/api/np/shipping/senders/" + s.id, { method: "DELETE" }).then(load).catch(e => setErr(e.message));
  };

  // Дії журналу
  const print = (t, fmt) => {
    setRowBusy(t.number);
    nsOpenPdf(t.number, fmt).then(() => setRowBusy("")).catch(e => { setRowBusy(""); setErr("Друк: " + e.message); });
  };
  const delTtn = (t) => {
    const msg = t.notified
      ? `ЕН ${t.number} ВЖЕ відправлена в групу постачальника!\n\nПісля видалення в ту саму групу автоматично піде попередження «ТТН скасована — не відправляйте».\n\nВидалити ЕН у Новій Пошті?`
      : `Видалити ЕН ${t.number} у Новій Пошті? Дію не можна скасувати.`;
    if (!window.confirm(msg)) return;
    setRowBusy(t.number); setErr("");
    nsJson("/api/np/ttn/" + encodeURIComponent(t.number), { method: "DELETE" })
      .then(r => { setRowBusy(""); if (r && r.warned) window.alert(r.warned); load(); })
      .catch(e => { setRowBusy(""); setErr(e.message); });
  };

  const th = { fontSize: 10.5, fontWeight: 600, letterSpacing: ".04em", textTransform: "uppercase", color: "var(--fg-muted)", textAlign: "left", padding: "8px 10px", whiteSpace: "nowrap", borderBottom: "1px solid var(--border-subtle)" };
  const td = { padding: "10px 10px", fontSize: 12.5, color: "var(--fg-primary)", borderBottom: "1px solid var(--border-subtle)", verticalAlign: "middle", whiteSpace: "nowrap" };

  // ── МОБІЛЬНА версія: картки замість широких таблиць ─────────────────────────
  if (isMobile) {
    const mLbl = { fontSize: 9.5, fontWeight: 600, letterSpacing: ".04em", textTransform: "uppercase", color: "var(--fg-muted)" };
    const mCard = { background: "var(--bg-panel)", border: "1px solid var(--border-subtle)", borderRadius: 12, padding: 12, display: "flex", flexDirection: "column", gap: 10 };
    const mStat = (label, value, color) => (
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={mLbl}>{label}</div>
        <div style={{ fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 600, color: color || "var(--fg-primary)", marginTop: 2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{value}</div>
      </div>
    );
    return (
      <div style={{ flex: 1, overflowY: "auto", padding: "12px 12px 96px", display: "flex", flexDirection: "column", gap: 12 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <Icon name="send" size={15} color="#E11D48"/>
          <span style={{ fontSize: 14, fontWeight: 600, color: "var(--fg-primary)", flex: 1 }}>Відправники</span>
          <button onClick={() => setAdding(a => !a)} style={nsBtn("primary")}>+ Додати</button>
          <button onClick={load} title="Оновити" style={nsIconBtn}><Icon name="refresh-cw" size={14}/></button>
        </div>
        {adding && (
          <div style={{ position: "fixed", inset: 0, zIndex: 95, background: "rgba(0,0,0,.6)", display: "flex", flexDirection: "column", justifyContent: "flex-end" }} onClick={() => setAdding(false)}>
            <div onClick={e => e.stopPropagation()} style={{ maxHeight: "90dvh", overflowY: "auto", overflowX: "hidden", background: "var(--bg-panel)", borderRadius: "16px 16px 0 0", border: "1px solid var(--border-default)", borderBottom: 0, paddingBottom: "max(16px, env(safe-area-inset-bottom))" }}>
              <div style={{ padding: "10px 16px 0" }}>
                <div style={{ width: 36, height: 4, borderRadius: 2, background: "var(--border-strong)", margin: "0 auto 10px" }}/>
                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <span style={{ fontSize: 14, fontWeight: 600, color: "var(--fg-primary)", flex: 1 }}>Новий відправник</span>
                  <button onClick={() => setAdding(false)} style={{ ...nsIconBtn, border: 0 }}><Icon name="x" size={15}/></button>
                </div>
              </div>
              <NsAddSender onDone={() => { setAdding(false); load(); }} onCancel={() => setAdding(false)}/>
            </div>
          </div>
        )}
        {err && <div style={{ fontSize: 12, color: "#FCA5A5" }}>{err}</div>}
        {senders === null && <div style={{ fontSize: 12, color: "var(--fg-muted)" }}>Завантажую…</div>}

        {(groupedSenders || []).map(s => {
          if (s._hdr) return (
            <div key={"h" + s._hdr} style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 4, padding: "0 2px" }}>
              <span style={{ fontSize: 11, fontWeight: 700, letterSpacing: ".04em", textTransform: "uppercase", color: "var(--fg-muted)" }}>{s._hdr}</span>
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--fg-muted)" }}>{s._n}</span>
              <span style={{ flex: 1, height: 1, background: "var(--border-subtle)" }}/>
            </div>
          );
          const st = s.stats || {};
          const active = !!val(s, "active");
          const monoInc = (s.mono && s.mono.income != null) ? s.mono.income : null;
          const overDay = s.daily_limit > 0 && st.today >= s.daily_limit;
          const overMonth = s.monthly_cod_limit > 0 && Math.max(st.monthCod || 0, monoInc || 0) >= s.monthly_cod_limit;
          return (
            <div key={s.id} style={{ ...mCard, opacity: active && s.hasKey ? 1 : 0.6 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                <span style={{ width: 8, height: 8, borderRadius: "50%", flexShrink: 0, background: s.hasKey ? "#34D399" : "#F87171" }}/>
                <span style={{ fontWeight: 600, fontSize: 13.5, color: "var(--fg-primary)", flex: 1, minWidth: 120 }}>{s.label}</span>
                <span style={{ fontSize: 10, padding: "1px 6px", borderRadius: 999, border: "1px solid var(--border-default)", color: "var(--fg-muted)", flexShrink: 0 }}>{NS_TYPE[s.sender_type] || "ФОП"}</span>
                <button onClick={() => toggleActive(s)} disabled={saving === s.id} style={{
                  width: 38, height: 21, borderRadius: 999, border: "1px solid var(--border-default)", cursor: "pointer", position: "relative", flexShrink: 0,
                  background: active ? "rgba(110,231,183,.25)" : "var(--bg-raised)",
                }}>
                  <span style={{ position: "absolute", top: 2, left: active ? 18 : 2, width: 15, height: 15, borderRadius: "50%", background: active ? "#6EE7B7" : "var(--fg-muted)", transition: "left 150ms" }}/>
                </button>
              </div>
              {s.recommended && <span style={{ alignSelf: "flex-start", fontSize: 10, fontWeight: 700, padding: "2px 8px", borderRadius: 999, background: "rgba(110,231,183,.14)", color: "#6EE7B7", border: "1px solid rgba(110,231,183,.35)" }}>РЕКОМЕНДОВАНИЙ</span>}
              <div style={{ display: "flex", gap: 10 }}>
                {mStat("Сьогодні", (st.today ?? "—") + (s.daily_limit ? "/" + s.daily_limit : ""), overDay ? "#F87171" : null)}
                {mStat("Тиждень", st.week ?? "—")}
                {mStat("Місяць", st.monthCount ?? "—")}
              </div>
              <div style={{ display: "flex", gap: 10 }}>
                {mStat("Накладені, міс", nsFmt(st.monthCod) + " ₴", overMonth ? "#F87171" : null)}
                {mStat("Mono ФОП, міс", monoInc != null ? (s.mono.truncated ? "≥" : "") + nsFmt(monoInc) + " ₴" + (s.mono.stale ? " ⚠" : "") : (s.hasMono ? (s.mono && s.mono.error ? "помилка" : "…") : "—"), overMonth ? "#F87171" : (monoInc != null ? "#6EE7B7" : "var(--fg-muted)"))}
              </div>
              <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
                <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--fg-muted)" }}>ключ {s.hasKey ? "•••" + s.keyTail : "—"}</span>
                <button onClick={() => changeKey(s)} title="Замінити ключ НП" style={{ ...nsIconBtn, width: 26, height: 26 }}><Icon name="key-round" size={12}/></button>
                <button onClick={() => changeMono(s)} title={s.hasMono ? "Замінити Monobank-токен" : "Додати Monobank-токен"} style={{ ...nsIconBtn, width: 26, height: 26, color: s.hasMono ? "var(--fg-muted)" : "var(--accent)" }}><Icon name="landmark" size={12}/></button>
                <button onClick={() => toggleInvCfg(s.id)} title="Налаштування рахунків" style={{ ...nsIconBtn, width: 26, height: 26, color: val(s, "issues_invoices") ? "#22D3EE" : "var(--fg-muted)" }}><Icon name="receipt" size={12}/></button>
                <span style={{ flex: 1 }}/>
                <button onClick={() => delSender(s)} title="Видалити (лише без ЕН)" style={{ ...nsIconBtn, width: 26, height: 26 }}><Icon name="trash-2" size={12}/></button>
              </div>
              {invCfgOpen[s.id] && <NsInvoiceConfig s={s} val={val} setVal={setVal}/>}
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
                <div><div style={mLbl}>Ліміт/день</div><input style={{ ...nsInp("100%"), marginTop: 3 }} value={val(s, "daily_limit")} onChange={e => setVal(s.id, "daily_limit")(e.target.value)}/></div>
                <div><div style={mLbl}>Ліміт накл./міс, ₴</div><input style={{ ...nsInp("100%"), marginTop: 3 }} value={val(s, "monthly_cod_limit")} onChange={e => setVal(s.id, "monthly_cod_limit")(e.target.value)}/></div>
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "1fr 64px 64px", gap: 8 }}>
                <div><div style={mLbl}>Місто відправки</div><input style={{ ...nsInp("100%"), marginTop: 3 }} value={val(s, "default_city")} onChange={e => setVal(s.id, "default_city")(e.target.value)}/></div>
                <div><div style={mLbl}>Відд.</div><input style={{ ...nsInp("100%"), marginTop: 3 }} value={val(s, "default_branch")} onChange={e => setVal(s.id, "default_branch")(e.target.value)}/></div>
                <div><div style={mLbl}>Пріор.</div><input style={{ ...nsInp("100%"), marginTop: 3 }} value={val(s, "priority")} onChange={e => setVal(s.id, "priority")(e.target.value)}/></div>
              </div>
              {isDirty(s.id) && <button onClick={() => save(s)} disabled={saving === s.id} style={{ ...nsBtn("primary"), height: 36 }}>{saving === s.id ? "Зберігаю…" : "Зберегти зміни"}</button>}
            </div>
          );
        })}

        {/* ── Журнал ЕН ── */}
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 6, flexWrap: "wrap" }}>
          <Icon name="truck" size={15} color="#FB7185"/>
          <span style={{ fontSize: 14, fontWeight: 600, color: "var(--fg-primary)", flex: 1 }}>Останні ЕН з CRM</span>
          <select value={ttnFilter} onChange={e => setTtnFilter(e.target.value)} title="Фільтр за відправником" style={{ height: 30, maxWidth: 170, padding: "0 8px", background: "var(--bg-raised)", color: ttnFilter === "all" ? "var(--fg-muted)" : "var(--fg-primary)", border: "1px solid var(--border-default)", borderRadius: 8, fontSize: 12, fontFamily: "inherit", cursor: "pointer", outline: "none" }}>
            <option value="all">Усі відправники</option>
            {(senders || []).map(s => <option key={s.id} value={s.id}>{s.label}</option>)}
          </select>
        </div>
        {ttns.length === 0 && <div style={{ fontSize: 12, color: "var(--fg-muted)" }}>Поки порожньо — зʼявляться після створення ТТН через CRM.</div>}
        {ttns.length > 0 && ttnsView.length === 0 && <div style={{ fontSize: 12, color: "var(--fg-muted)" }}>Немає ЕН для обраного відправника.</div>}
        {ttnsView.map(t => {
          const sender = (senders || []).find(s => s.id === t.sender_id);
          const r = t.recipient || {};
          const busy = rowBusy === t.number;
          const statusColor = [102, 103, 105, 108].includes(Number(t.status_code)) ? "#FCA5A5"
                            : [9, 10, 11].includes(Number(t.status_code)) ? "#6EE7B7" : "var(--fg-secondary)";
          return (
            <div key={t.id} style={{ ...mCard, opacity: t.deleted ? 0.5 : 1 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                <span style={{ fontFamily: "var(--font-mono)", fontSize: 13.5, fontWeight: 600, color: "var(--fg-primary)" }}>{t.number}{t.deleted ? " ✕" : ""}</span>
                {t.notified && !t.deleted && <span style={{ fontSize: 9.5, fontWeight: 600, padding: "1px 6px", borderRadius: 999, background: "rgba(110,231,183,.12)", border: "1px solid rgba(110,231,183,.35)", color: "#6EE7B7" }}>в групі</span>}
                <span style={{ flex: 1 }}/>
                <span style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, color: "var(--fg-muted)" }}>{new Date(t.created_at).toLocaleString("uk-UA", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })}</span>
              </div>
              <div style={{ fontSize: 12, color: "var(--fg-secondary)" }}>{sender ? sender.label : t.sender_id} · {NS_SOURCE[t.source] || t.source}{t.request_id ? " #" + t.request_id : ""}{t.order_id ? " №" + t.order_id : ""}</div>
              {(r.org || r.name || r.dest) && <div style={{ fontSize: 12, color: "var(--fg-secondary)" }}>{r.org || r.name || ""}{r.dest ? " · " + r.dest : ""}</div>}
              <div style={{ display: "flex", gap: 10 }}>
                {mStat("Оцінка", nsFmt(t.declared_cost) + " ₴")}
                {mStat("Накладений", t.cod_amount ? nsFmt(t.cod_amount) + " ₴" : "—")}
                {mStat("Доставка", t.delivery_cost ? nsFmt(t.delivery_cost) + " ₴" : "—")}
              </div>
              <div style={{ fontSize: 12, color: statusColor }}>{t.status_text || "Статус ще не трекається"}</div>
              {!t.deleted && (
                <div style={{ display: "flex", gap: 6, opacity: busy ? 0.5 : 1 }}>
                  <button onClick={() => print(t, "zebra")} style={{ ...nsBtn(), flex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 5 }}><Icon name="printer" size={12}/>100×100</button>
                  <button onClick={() => print(t, "a4")} style={{ ...nsBtn(), flex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 5 }}><Icon name="file-text" size={12}/>А4</button>
                  {t.editable && <button onClick={() => setEditTtn(t)} style={{ ...nsIconBtn, width: 32, height: 30 }}><Icon name="pencil" size={13}/></button>}
                  <button onClick={() => delTtn(t)} style={{ ...nsIconBtn, width: 32, height: 30, color: "#FCA5A5" }}><Icon name="trash-2" size={13}/></button>
                </div>
              )}
            </div>
          );
        })}

        {editTtn && <NsEditTtn ttn={editTtn} onClose={() => setEditTtn(null)} onSaved={() => { setEditTtn(null); load(); }}/>}
      </div>
    );
  }

  return (
    <div style={{ flex: 1, overflowY: "auto", padding: isMobile ? 12 : 24 }}>
      {/* ── ФОПи / фізособи ── */}
      <div style={{ background: "var(--bg-panel)", border: "1px solid var(--border-subtle)", borderRadius: 12, overflow: "hidden", marginBottom: 20 }}>
        <div style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-subtle)", display: "flex", alignItems: "center", gap: 10 }}>
          <Icon name="send" size={16} color="#E11D48"/>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 14, fontWeight: 600, color: "var(--fg-primary)" }}>Відправники</div>
            <div style={{ fontSize: 11.5, color: "var(--fg-muted)" }}>ФОПи та фізособи. Ліміти, навантаження, рекомендація (враховує надходження на ФОП-рахунок Monobank, якщо заданий токен). 0 = без ліміту.</div>
          </div>
          <button onClick={() => setAdding(a => !a)} style={nsBtn("primary")}><span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}><Icon name="plus" size={13}/>Додати відправника</span></button>
          <button onClick={load} title="Оновити" style={nsIconBtn}><Icon name="refresh-cw" size={14}/></button>
        </div>
        {adding && <NsAddSender onDone={() => { setAdding(false); load(); }} onCancel={() => setAdding(false)}/>}
        {err && <div style={{ padding: "8px 16px", fontSize: 12, color: "#FCA5A5" }}>{err}</div>}
        <div style={{ overflowX: "auto" }}>
          <table style={{ borderCollapse: "collapse", width: "100%", minWidth: 1200 }}>
            <thead><tr>
              <th style={th}>Відправник</th>
              <th style={th}>Ключ</th>
              <th style={th}>Актив</th>
              <th style={th}>Сьогодні</th>
              <th style={th}>Тиждень</th>
              <th style={th}>Місяць</th>
              <th style={th}>Накладені, міс</th>
              <th style={th}>Mono ФОП, міс</th>
              <th style={th}>Ліміт/день</th>
              <th style={th}>Ліміт накл./міс, ₴</th>
              <th style={th}>Відділення відправки</th>
              <th style={th}>Пріор.</th>
              <th style={th}></th>
            </tr></thead>
            <tbody>
              {senders === null && <tr><td style={td} colSpan={13}>Завантажую…</td></tr>}
              {(groupedSenders || []).map(s => {
                if (s._hdr) return (
                  <tr key={"h" + s._hdr}>
                    <td colSpan={13} style={{ padding: "12px 10px 6px", fontSize: 11, fontWeight: 700, letterSpacing: ".04em", textTransform: "uppercase", color: "var(--fg-muted)", borderBottom: "1px solid var(--border-subtle)" }}>
                      {s._hdr} <span style={{ fontFamily: "var(--font-mono)", color: "var(--fg-muted)" }}>· {s._n}</span>
                    </td>
                  </tr>
                );
                const st = s.stats || {};
                const active = !!val(s, "active");
                const monoInc = (s.mono && s.mono.income != null) ? s.mono.income : null;
                const overDay = s.daily_limit > 0 && st.today >= s.daily_limit;
                const overMonth = s.monthly_cod_limit > 0 && Math.max(st.monthCod || 0, monoInc || 0) >= s.monthly_cod_limit;
                return (
                  <React.Fragment key={s.id}>
                  <tr style={{ opacity: active && s.hasKey ? 1 : 0.55 }}>
                    <td style={td}>
                      <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                        <span style={{ width: 8, height: 8, borderRadius: "50%", flexShrink: 0, background: s.hasKey ? "#34D399" : "#F87171" }} title={s.hasKey ? "Ключ ОК" : "Немає валідного ключа"}></span>
                        <span style={{ fontWeight: 500 }}>{s.label}</span>
                        <span style={{ fontSize: 10, padding: "1px 6px", borderRadius: 999, border: "1px solid var(--border-default)", color: "var(--fg-muted)" }}>{NS_TYPE[s.sender_type] || "ФОП"}</span>
                        {val(s, "issues_invoices") && <span title="Виставляє рахунки" style={{ fontSize: 10, fontWeight: 600, padding: "1px 6px", borderRadius: 999, background: "rgba(34,211,238,.12)", color: "#22D3EE", border: "1px solid rgba(34,211,238,.35)" }}>₴ рахунки</span>}
                        {s.recommended && <span style={{ fontSize: 10, fontWeight: 700, padding: "2px 7px", borderRadius: 999, background: "rgba(110,231,183,.14)", color: "#6EE7B7", border: "1px solid rgba(110,231,183,.35)" }}>РЕКОМЕНДОВАНИЙ</span>}
                      </div>
                    </td>
                    <td style={td}>
                      <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--fg-muted)" }}>{s.hasKey ? "•••" + s.keyTail : "—"}</span>
                      <button onClick={() => changeKey(s)} title="Замінити ключ" style={{ ...nsIconBtn, marginLeft: 6, width: 22, height: 22 }}><Icon name="key-round" size={11}/></button>
                    </td>
                    <td style={td}>
                      <button onClick={() => toggleActive(s)} disabled={saving === s.id} title={active ? "Вимкнути" : "Увімкнути"} style={{
                        width: 38, height: 21, borderRadius: 999, border: "1px solid var(--border-default)", cursor: "pointer", position: "relative",
                        background: active ? "rgba(110,231,183,.25)" : "var(--bg-raised)", transition: "background 150ms",
                      }}>
                        <span style={{ position: "absolute", top: 2, left: active ? 18 : 2, width: 15, height: 15, borderRadius: "50%", background: active ? "#6EE7B7" : "var(--fg-muted)", transition: "left 150ms" }}></span>
                      </button>
                    </td>
                    <td style={{ ...td, fontFamily: "var(--font-mono)", color: overDay ? "#F87171" : "var(--fg-primary)" }}>{st.today ?? "—"}{s.daily_limit ? "/" + s.daily_limit : ""}</td>
                    <td style={{ ...td, fontFamily: "var(--font-mono)" }}>{st.week ?? "—"}</td>
                    <td style={{ ...td, fontFamily: "var(--font-mono)" }}>{st.monthCount ?? "—"}</td>
                    <td style={{ ...td, fontFamily: "var(--font-mono)", color: overMonth ? "#F87171" : "var(--fg-primary)" }}>{nsFmt(st.monthCod)} ₴</td>
                    <td style={{ ...td, fontFamily: "var(--font-mono)", color: overMonth ? "#F87171" : "var(--fg-primary)" }}
                        title={s.mono && s.mono.error ? "Mono: " + s.mono.error : (s.mono ? "Надходження на ФОП-рахунок Monobank за " + s.mono.month + (s.mono.name ? " · " + s.mono.name : "") : (s.hasMono ? "Оборот ще не підтягнувся (фоновий рефреш ~10 хв)" : "Токен Monobank не заданий"))}>
                      {monoInc != null
                        ? <span>{s.mono.truncated ? "≥" : ""}{nsFmt(monoInc)} ₴{s.mono.stale ? <span style={{ color: "#FBBF24" }}> ⚠</span> : null}</span>
                        : (s.hasMono ? <span style={{ color: s.mono && s.mono.error ? "#FCA5A5" : "var(--fg-muted)" }}>{s.mono && s.mono.error ? "помилка" : "…"}</span> : <span style={{ color: "var(--fg-muted)" }}>—</span>)}
                      <button onClick={() => changeMono(s)} title={s.hasMono ? "Замінити Monobank-токен" : "Додати Monobank-токен"} style={{ ...nsIconBtn, marginLeft: 6, width: 22, height: 22, color: s.hasMono ? "var(--fg-muted)" : "var(--accent)" }}><Icon name="landmark" size={11}/></button>
                    </td>
                    <td style={td}><input style={nsInp(64)} value={val(s, "daily_limit")} onChange={e => setVal(s.id, "daily_limit")(e.target.value)}/></td>
                    <td style={td}><input style={nsInp(110)} value={val(s, "monthly_cod_limit")} onChange={e => setVal(s.id, "monthly_cod_limit")(e.target.value)}/></td>
                    <td style={td}>
                      <input style={{ ...nsInp(110), marginRight: 6 }} value={val(s, "default_city")} onChange={e => setVal(s.id, "default_city")(e.target.value)} placeholder="Місто"/>
                      <input style={nsInp(50)} value={val(s, "default_branch")} onChange={e => setVal(s.id, "default_branch")(e.target.value)} placeholder="№"/>
                    </td>
                    <td style={td}><input style={nsInp(48)} value={val(s, "priority")} onChange={e => setVal(s.id, "priority")(e.target.value)}/></td>
                    <td style={td}>
                      <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
                        {isDirty(s.id) && <button onClick={() => save(s)} disabled={saving === s.id} style={nsBtn("primary")}>{saving === s.id ? "…" : "Зберегти"}</button>}
                        <button onClick={() => toggleInvCfg(s.id)} title="Налаштування рахунків" style={{ ...nsIconBtn, color: val(s, "issues_invoices") ? "#22D3EE" : "var(--fg-muted)" }}><Icon name="receipt" size={12}/></button>
                        <button onClick={() => delSender(s)} title="Видалити (лише без ЕН)" style={nsIconBtn}><Icon name="trash-2" size={12}/></button>
                      </div>
                    </td>
                  </tr>
                  {invCfgOpen[s.id] && (
                    <tr>
                      <td colSpan={13} style={{ ...td, padding: "0 10px 12px" }}>
                        <div style={{ maxWidth: 720 }}><NsInvoiceConfig s={s} val={val} setVal={setVal}/></div>
                      </td>
                    </tr>
                  )}
                  </React.Fragment>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>

      {/* ── Журнал ЕН, створених через CRM ── */}
      <div style={{ background: "var(--bg-panel)", border: "1px solid var(--border-subtle)", borderRadius: 12, overflow: "hidden" }}>
        <div style={{ padding: "14px 16px", borderBottom: "1px solid var(--border-subtle)", display: "flex", alignItems: "center", gap: 12 }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 14, fontWeight: 600, color: "var(--fg-primary)" }}>Останні ЕН з CRM</div>
            <div style={{ fontSize: 11.5, color: "var(--fg-muted)" }}>Друк наклейки (zebra/А4), редагування і видалення працюють, поки посилку не прийняли у відділенні. ЕН з адмінки Хорошопа сюди не потрапляють.</div>
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 6, flexShrink: 0 }}>
            <span style={{ fontSize: 11.5, color: "var(--fg-muted)" }}>Відправник:</span>
            <select value={ttnFilter} onChange={e => setTtnFilter(e.target.value)} style={{ height: 32, maxWidth: 220, padding: "0 10px", background: "var(--bg-raised)", color: ttnFilter === "all" ? "var(--fg-muted)" : "var(--fg-primary)", border: "1px solid var(--border-default)", borderRadius: 8, fontSize: 12.5, fontFamily: "inherit", cursor: "pointer", outline: "none" }}>
              <option value="all">Усі відправники</option>
              {(senders || []).map(s => <option key={s.id} value={s.id}>{s.label}</option>)}
            </select>
          </div>
        </div>
        <div style={{ overflowX: "auto" }}>
          <table style={{ borderCollapse: "collapse", width: "100%", minWidth: 1000 }}>
            <thead><tr>
              <th style={th}>Коли</th>
              <th style={th}>ТТН</th>
              <th style={th}>Відправник</th>
              <th style={th}>Звідки</th>
              <th style={th}>Отримувач</th>
              <th style={th}>Оцінка</th>
              <th style={th}>Накладений</th>
              <th style={th}>Доставка</th>
              <th style={th}>Статус НП</th>
              <th style={th}>Хто</th>
              <th style={th}>Дії</th>
            </tr></thead>
            <tbody>
              {ttns.length === 0 && <tr><td style={td} colSpan={11}><span style={{ color: "var(--fg-muted)" }}>Поки порожньо — зʼявляться після створення ТТН через CRM</span></td></tr>}
              {ttns.length > 0 && ttnsView.length === 0 && <tr><td style={td} colSpan={11}><span style={{ color: "var(--fg-muted)" }}>Немає ЕН для обраного відправника</span></td></tr>}
              {ttnsView.map(t => {
                const sender = (senders || []).find(s => s.id === t.sender_id);
                const r = t.recipient || {};
                const busy = rowBusy === t.number;
                return (
                  <tr key={t.id} style={{ opacity: t.deleted ? 0.45 : 1 }}>
                    <td style={{ ...td, fontFamily: "var(--font-mono)", fontSize: 11.5, color: "var(--fg-muted)" }}>{new Date(t.created_at).toLocaleString("uk-UA", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })}</td>
                    <td style={{ ...td, fontFamily: "var(--font-mono)", fontWeight: 600 }} title={Array.isArray(t.products) && t.products.length ? "Товари цієї ЕН:\n" + t.products.map(p => "• " + p.title + (p.quantity > 1 ? " — " + p.quantity + " шт" : "")).join("\n") : undefined}>
                      {t.number}{t.deleted ? " ✕" : ""}
                      {t.notified && !t.deleted && <span title="PDF відправлено в групу постачальника — редагування заблоковано" style={{ marginLeft: 6, fontSize: 9.5, fontFamily: "inherit", fontWeight: 600, padding: "1px 6px", borderRadius: 999, background: "rgba(110,231,183,.12)", border: "1px solid rgba(110,231,183,.35)", color: "#6EE7B7" }}>в групі</span>}
                    </td>
                    <td style={td}>{sender ? sender.label : t.sender_id}</td>
                    <td style={td}>{NS_SOURCE[t.source] || t.source}{t.request_id ? " #" + t.request_id : ""}{t.order_id ? " №" + t.order_id : ""}</td>
                    <td style={{ ...td, maxWidth: 240, overflow: "hidden", textOverflow: "ellipsis" }} title={r.dest || ""}>{r.org || r.name || "—"}{r.dest ? " · " + r.dest : ""}</td>
                    <td style={{ ...td, fontFamily: "var(--font-mono)" }}>{nsFmt(t.declared_cost)} ₴</td>
                    <td style={{ ...td, fontFamily: "var(--font-mono)" }}>{t.cod_amount ? nsFmt(t.cod_amount) + " ₴" : "—"}</td>
                    <td style={{ ...td, fontFamily: "var(--font-mono)" }}>{t.delivery_cost ? nsFmt(t.delivery_cost) + " ₴" : "—"}</td>
                    <td style={{ ...td, maxWidth: 170, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                                 color: [102, 103, 105, 108].includes(Number(t.status_code)) ? "#FCA5A5"
                                      : [9, 10, 11].includes(Number(t.status_code)) ? "#6EE7B7" : "var(--fg-secondary)" }}
                        title={t.status_text ? t.status_text + (t.status_updated_at ? " · " + new Date(t.status_updated_at).toLocaleString("uk-UA", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" }) : "") : ""}>
                      {t.status_text || "—"}
                    </td>
                    <td style={{ ...td, color: "var(--fg-muted)" }}>{t.created_by || "—"}</td>
                    <td style={td}>
                      {!t.deleted && (
                        <div style={{ display: "flex", gap: 5, opacity: busy ? 0.5 : 1 }}>
                          <button onClick={() => print(t, "zebra")} title="Наклейка 100×100 (zebra)" style={nsIconBtn}><Icon name="printer" size={12}/></button>
                          <button onClick={() => print(t, "a4")} title="ЕН А4" style={nsIconBtn}><Icon name="file-text" size={12}/></button>
                          {t.editable && <button onClick={() => setEditTtn(t)} title="Редагувати ЕН" style={nsIconBtn}><Icon name="pencil" size={12}/></button>}
                          <button onClick={() => delTtn(t)} title={t.notified ? "Видалити ЕН (групу буде попереджено «не відправляйте»)" : "Видалити ЕН у НП"} style={{ ...nsIconBtn, color: "#FCA5A5" }}><Icon name="trash-2" size={12}/></button>
                        </div>
                      )}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>

      {editTtn && <NsEditTtn ttn={editTtn} onClose={() => setEditTtn(null)} onSaved={() => { setEditTtn(null); load(); }}/>}
    </div>
  );
}

window.NpSenders = NpSenders;
window.NsEditTtn = NsEditTtn;   // редактор ЕН — використовується і в картці замовлення (Orders.jsx)
