// partner-dashboard.jsx — Partner portal dashboard (referrals, payments, settings)

// PartnerService enum (GET/PUT /v1/partner/me; canonical list at GET /v1/partner/services)
// ↔ human label.
const PARTNER_SERVICES = [
  { code: "Paintwork",                get label() { return t("PartnerDash.service_Paintwork"); } },
  { code: "BodyAndStructuralRepair",  get label() { return t("PartnerDash.service_BodyAndStructuralRepair"); } },
  { code: "WindscreenAndGlass",       get label() { return t("PartnerDash.service_WindscreenAndGlass"); } },
  { code: "WheelAlignmentAndTyres",   get label() { return t("PartnerDash.service_WheelAlignmentAndTyres"); } },
  { code: "MechanicalRepair",         get label() { return t("PartnerDash.service_MechanicalRepair"); } },
  { code: "ElectricalAndDiagnostics", get label() { return t("PartnerDash.service_ElectricalAndDiagnostics"); } },
  { code: "TrimAndCustomization",     get label() { return t("PartnerDash.service_TrimAndCustomization"); } },
];

// ── Display helpers ───────────────────────────────────────────────────────────
// Compact a referral's ref number for display: REF-<first 4>…<last 4> of the raw
// identifier. Strips an existing "REF-" prefix so it's never doubled, and leaves
// short ids (≤ 8 chars) whole — only long ones (e.g. API UUIDs) get truncated.
function fmtRefNumber(id) {
  const raw = String(id ?? "").replace(/^REF-/i, "");
  const core = raw.length > 8 ? `${raw.slice(0, 4)}…${raw.slice(-4)}` : raw;
  return `REF-${core}`;
}

// Bytes → "142 KB" / "1.4 MB". Mirrors the inline logic in DropZone / the offer modal.
function fmtFileSize(bytes) {
  const n = Number(bytes) || 0;
  return n < 1048576 ? `${(n / 1024).toFixed(0)} KB` : `${(n / 1048576).toFixed(1)} MB`;
}

// Auth-gated downloads return binary, so fetch as a blob and save programmatically.
function saveBlob(blob, fileName) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url; a.download = fileName || "document";
  document.body.appendChild(a); a.click(); a.remove();
  URL.revokeObjectURL(url);
}

// Partner onboarding/verification status is a [Flags] enum (GET /v1/partner/me),
// returned as a NUMBER (bit flags) — was a comma-separated name list before.
// Backend Domain.Partners.PartnerStatus:
//   SignupCompleted=1, ProfileInfoUpdated=2, RegistrationUploaded=4,
//   CertificationsUploaded=8, PaymentMethodCreated=16, AccountVerified=32.
// "ProfileComplete" = 63 (all bits). e.g. 31 = all but AccountVerified (pending
// admin review); 63 = fully verified.
const PARTNER_STATUS = {
  SignupCompleted: 1, ProfileInfoUpdated: 2, RegistrationUploaded: 4,
  CertificationsUploaded: 8, PaymentMethodCreated: 16, AccountVerified: 32,
};
// Derive onboarding/verification state from the numeric status. `known` is false
// only when status is absent (still loading / older API) — status===0 is a valid
// "nothing done yet" value and must still count as known, so we test for a finite
// number, not truthiness. Number() defensively tolerates a numeric string.
function partnerOnboarding(profile) {
  const m = Number(profile && profile.status);
  const known = Number.isFinite(m);
  return {
    known,
    profileInfo:    !!(m & PARTNER_STATUS.ProfileInfoUpdated),       // 2
    registration:   !!(m & PARTNER_STATUS.RegistrationUploaded),    // 4
    certifications: !!(m & PARTNER_STATUS.CertificationsUploaded),  // 8
    payment:        !!(m & PARTNER_STATUS.PaymentMethodCreated),    // 16
    verified:       !!(m & PARTNER_STATUS.AccountVerified),         // 32
    profileComplete:    (m & 14) === 14,   // business details + both docs (2|4|8)
    onboardingComplete: (m & 30) === 30,   // + payment (2|4|8|16) → pending verify
  };
}

// ── API mapping ───────────────────────────────────────────────────────────────
// The referrals LIST (GET /v1/partner/referrals) returns `vehicle` and
// `damages` per item, so those columns render real data. The single-referral
// DETAIL endpoint does NOT — the detail pages source vehicle/damage from a
// separate inspection fetch (see deriveReferralView), so this mapping's
// vehicle/damage fields are consumed only by the list table.

// Single source of truth for referral statuses: client value ↔ API name + badge styling.
// The two lookup maps below, BADGE_CFG, and the filter chip list all derive from this.
const REFERRAL_STATUSES = [
  { value: "new",       api: "New",       get label() { return t("PartnerDash.status_new"); },        dot: "#3B82F6", bg: "#EFF6FF", fg: "#1D4ED8" },
  { value: "reviewed",  api: "Reviewed",  get label() { return t("PartnerDash.status_reviewed"); },   dot: "#F59E0B", bg: "#FFFBEB", fg: "#B45309" },
  { value: "offersent", api: "OfferSent", get label() { return t("PartnerDash.status_offersent"); },  dot: "#22C55E", bg: "#F0FDF4", fg: "#166534" },
  { value: "expired",   api: "Expired",   get label() { return t("PartnerDash.status_expired"); },    dot: "#9CA3AF", bg: "#F3F4F6", fg: "#4B5563" },
  { value: "declined",  api: "Declined",  get label() { return t("PartnerDash.status_declined"); },   dot: "#EF4444", bg: "#FFF1F2", fg: "#BE123C" },
];

// API Status name → client value (e.g. "OfferSent" → "offersent").
const REFERRAL_STATUS_MAP = Object.fromEntries(REFERRAL_STATUSES.map(s => [s.api, s.value]));
// Reverse: client filter value → API-form Status param. "all" has no entry → no filter.
const FILTER_TO_API_STATUS = Object.fromEntries(REFERRAL_STATUSES.map(s => [s.value, s.api]));
// Client values in display order — used to build the filter chips.
const REFERRAL_STATUS_VALUES = REFERRAL_STATUSES.map(s => s.value);

// "RearLight" → "Rear Light", "MissingPart" → "Missing Part". Splits
// camel/Pascal-case enum names into spaced words, preserving capitalization.
const humanizeEnum = (s) => String(s).replace(/([a-z])([A-Z])/g, "$1 $2");

function mapApiReferral(r) {
  const value = typeof r.estimatedValue === "number"
    ? `€${r.estimatedValue.toLocaleString("de-DE")}`
    : "—";
  const vehicle = r.vehicle
    ? `${r.vehicle.make} ${r.vehicle.model} ${r.vehicle.year}`
    : "—";
  const damages = Array.isArray(r.damages) ? r.damages : [];
  // Line 1: distinct damaged parts, humanized and comma-joined. Drop any
  // damage missing a part so it can't surface as a literal "null".
  const parts = [...new Set(damages.map((d) => d.part).filter(Boolean).map(humanizeEnum))];
  const damage = parts.length ? parts.join(", ") : "—";
  // Line 2: total damage count (not distinct parts).
  const location = damages.length
    ? tn("PartnerDash.damageCount", damages.length)
    : "";
  return {
    id: r.id,
    inspectionId: r.inspectionId,
    status: REFERRAL_STATUS_MAP[r.status] || "new",
    ts: r.createdAt,
    dueAt: r.dueAt,
    range: value,
    estimatedValue: r.estimatedValue,
    live: true,
    vehicle,
    damage,
    location,
    damages,
  };
}

// ── Utilities ─────────────────────────────────────────────────────────────────

function fmtDate(ts) {
  const d = new Date(ts);
  const dd = String(d.getDate()).padStart(2, "0");
  const mm = String(d.getMonth() + 1).padStart(2, "0");
  const yy = String(d.getFullYear()).slice(-2);
  const HH = String(d.getHours()).padStart(2, "0");
  const MM = String(d.getMinutes()).padStart(2, "0");
  return `${dd}.${mm}.${yy} ${HH}:${MM}`;
}

// Stripe invoice date as "YYYY-MM-DD" (matches the invoice-history card design).
function fmtInvoiceDate(ts) {
  const d = new Date(ts);
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}

// Stripe amounts are integer minor units (cents). Render as "€12,34" (de-DE).
// Currency is EUR for this market, matching the €-prefix convention used elsewhere.
function fmtInvoiceAmount(totalCents, currency) {
  if (typeof totalCents !== "number" || Number.isNaN(totalCents)) return "—";
  const sym = (currency || "eur").toLowerCase() === "eur" ? "€" : `${(currency || "").toUpperCase()} `;
  return `${sym}${(totalCents / 100).toLocaleString("de-DE", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
}

// Long-form date "1 June 2026" — matches the "Next charge" card copy.
function fmtLongDate(ts) {
  const d = new Date(ts);
  return Number.isNaN(d.getTime()) ? "—" : d.toLocaleDateString(getLang() === "de" ? "de-DE" : "en-GB", { day: "numeric", month: "long", year: "numeric" });
}

const CountdownTimer = ({ ts, dueAt }) => {
  const deadline = dueAt ? new Date(dueAt).getTime() : new Date(ts).getTime() + 48 * 60 * 60 * 1000;
  const [rem, setRem] = React.useState(() => deadline - Date.now());

  React.useEffect(() => {
    const id = setInterval(() => setRem(deadline - Date.now()), 1000);
    return () => clearInterval(id);
  }, [deadline]);

  if (rem <= 0) {
    return <span style={{ fontSize: 11, color: "var(--mute)" }}>{t("PartnerDash.countdownExpired")}</span>;
  }

  const h = Math.floor(rem / 3_600_000);
  const m = Math.floor((rem % 3_600_000) / 60_000);
  const s = Math.floor((rem % 60_000) / 1_000);
  const urgent = rem < 6 * 3_600_000;

  return (
    <span style={{
      fontSize: 13, fontWeight: 600,
      color: urgent ? "#E25A1C" : "var(--ink)",
      letterSpacing: "0.03em",
    }}>
      {String(h).padStart(2, "0")}:{String(m).padStart(2, "0")}:{String(s).padStart(2, "0")}
    </span>
  );
};

// ── Badge components ──────────────────────────────────────────────────────────

// Maps client status value → i18n key (e.g. "offersent" → "PartnerDash.status_offersent").
// Used by DashBadge so the label is re-resolved via t() on every render (language-switching safe).
const STATUS_I18N_KEY = Object.fromEntries(
  REFERRAL_STATUSES.map(s => [s.value, "PartnerDash.status_" + s.value])
);

const BADGE_CFG = Object.fromEntries(
  REFERRAL_STATUSES.map(s => [s.value, { dot: s.dot, bg: s.bg, fg: s.fg }])
);

const LiveStatusBadge = ({ status, ts, dueAt }) => {
  const deadline = dueAt ? new Date(dueAt).getTime() : new Date(ts).getTime() + 48 * 60 * 60 * 1000;
  const [expired, setExpired] = React.useState(() => status === "new" && Date.now() > deadline);
  React.useEffect(() => {
    if (status !== "new") return;
    const ms = deadline - Date.now();
    if (ms <= 0) { setExpired(true); return; }
    // One timer that fires exactly at the deadline, instead of polling Date.now() every second.
    const id = setTimeout(() => setExpired(true), ms);
    return () => clearTimeout(id);
  }, [status, deadline]);
  return <DashBadge status={expired ? "expired" : status} />;
};

const DashBadge = ({ status }) => {
  const c = BADGE_CFG[status] || BADGE_CFG.new;
  const label = t(STATUS_I18N_KEY[status] || STATUS_I18N_KEY.new);
  return (
    <span data-testid="status-badge" data-status={status} style={{
      display: "inline-flex", alignItems: "center", gap: 5,
      padding: "3px 10px", borderRadius: 20,
      background: c.bg, color: c.fg,
      fontSize: 12.5, fontWeight: 600, letterSpacing: "0.01em",
    }}>
      <span style={{ width: 5, height: 5, borderRadius: "50%", background: c.dot, flexShrink: 0 }} />
      {label}
    </span>
  );
};

// ── Shared table row ──────────────────────────────────────────────────────────

const DashRow = ({ children, last, onClick }) => {
  const [hov, setHov] = React.useState(false);
  return (
    <tr
      onMouseEnter={() => setHov(true)}
      onMouseLeave={() => setHov(false)}
      onClick={onClick}
      style={{
        borderBottom: last ? "none" : "1px solid rgba(10,10,11,.05)",
        background: hov ? "#F0F4FF" : "transparent",
        transition: "background .1s",
        cursor: onClick ? "pointer" : "default",
      }}>
      {children}
    </tr>
  );
};

// ── Stat card ─────────────────────────────────────────────────────────────────

const DashStat = ({ label, value, sub, green }) => (
  <div style={{
    background: green ? "var(--accent)" : "#fff",
    borderRadius: 18,
    padding: "26px 28px 22px",
    border: green ? "none" : "1px solid rgba(10,10,11,.07)",
    boxShadow: green ? "0 4px 24px rgba(16,161,113,.28)" : "0 1px 4px rgba(10,10,11,.05)",
  }}>
    <div style={{
      fontSize: 10.5, fontWeight: 700, letterSpacing: "0.1em",
      textTransform: "uppercase",
      color: green ? "rgba(255,255,255,.6)" : "var(--mute)",
      marginBottom: 14,
    }}>{label}</div>
    <div style={{
      fontFamily: "var(--serif)", fontStyle: "italic",
      fontSize: 50, lineHeight: 1, letterSpacing: "-0.02em",
      color: green ? "#fff" : "var(--ink)",
      marginBottom: 12,
    }}>{value}</div>
    <div style={{ fontSize: 12, color: green ? "rgba(255,255,255,.55)" : "var(--mute)" }}>{sub}</div>
  </div>
);

// ── Referral table ────────────────────────────────────────────────────────────

const DashRefTable = ({ rows, onSelect }) => (
  <div style={{
    background: "#fff", borderRadius: 16,
    border: "1px solid rgba(10,10,11,.07)",
    overflow: "hidden",
    boxShadow: "0 1px 4px rgba(10,10,11,.04)",
  }}>
    <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 14 }}>
      <thead>
        <tr style={{ borderBottom: "1px solid rgba(10,10,11,.07)", background: "#FAFAF8" }}>
          {[
            t("PartnerDash.colRefNumber"),
            t("PartnerDash.colVehicle"),
            t("PartnerDash.colDamage"),
            t("PartnerDash.colDate"),
            t("PartnerDash.colEstValue"),
            t("PartnerDash.colStatus"),
            t("PartnerDash.colCountdown"),
          ].map(h => (
            <th key={h} style={{
              padding: "11px 18px", textAlign: "left",
              fontSize: 11, fontWeight: 700, color: "var(--mute)",
              letterSpacing: "0.07em", textTransform: "uppercase",
            }}>{h}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {rows.map((r, i) => {
          // Two-line damage content (part list + count).
          const damageLines = (
            <>
              <span>{r.damage}</span>
              <span style={{ display: "block", fontSize: 12, color: "var(--mute)", marginTop: 2 }}>{r.location}</span>
            </>
          );
          return (
            <DashRow key={r.id} last={i === rows.length - 1} onClick={() => onSelect && onSelect(r)}>
              <td style={{ padding: "14px 18px" }}>
                <span style={{ fontSize: 13, fontWeight: 600, letterSpacing: "-0.01em", color: "#1D4ED8", textDecoration: "underline", textDecorationColor: "rgba(29,78,216,.3)", textUnderlineOffset: 2 }}>{fmtRefNumber(r.id)}</span>
              </td>
              <td style={{ padding: "14px 18px", fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em", whiteSpace: "nowrap" }}>
                {r.vehicle}
              </td>
              <td style={{ padding: "14px 18px", color: "rgba(10,10,11,.6)", maxWidth: 220 }}>
                {damageLines}
              </td>
              <td style={{ padding: "14px 18px", color: "var(--mute)", fontSize: 13, whiteSpace: "nowrap" }}>{fmtDate(r.ts)}</td>
              <td style={{ padding: "14px 18px", fontWeight: 700, color: "var(--ink)", letterSpacing: "-0.02em", whiteSpace: "nowrap" }}>{r.range}</td>
              <td style={{ padding: "14px 18px" }}><LiveStatusBadge status={r.status} ts={r.ts} dueAt={r.dueAt} /></td>
              <td style={{ padding: "14px 18px" }}>
                {r.status === "new" && <CountdownTimer ts={r.ts} dueAt={r.dueAt} />}
              </td>
            </DashRow>
          );
        })}
      </tbody>
    </table>
  </div>
);

// Server-side pager for the referrals table. Renders nothing for a single page.
// Shows "X–Y of Z" plus prev/next and a windowed set of page numbers
// (first, last, and current ±1, with gaps collapsed to an ellipsis).
const DashPager = ({ page, totalPages, totalCount, pageSize, onPage }) => {
  if (totalPages <= 1) return null;
  const from = (page - 1) * pageSize + 1;
  const to   = Math.min(page * pageSize, totalCount);

  const window = [];
  for (let n = 1; n <= totalPages; n++) {
    if (n === 1 || n === totalPages || (n >= page - 1 && n <= page + 1)) window.push(n);
    else if (window[window.length - 1] !== null) window.push(null); // gap marker
  }

  const cell = {
    minWidth: 34, height: 34, padding: "0 10px", borderRadius: 9,
    fontFamily: "var(--sans)", fontSize: 13, fontWeight: 600,
    display: "inline-flex", alignItems: "center", justifyContent: "center",
    border: "1px solid rgba(10,10,11,.12)", background: "#fff",
    color: "var(--ink)", cursor: "default", transition: "all .12s",
  };
  const edge = (disabled) => ({ ...cell, opacity: disabled ? 0.4 : 1, color: disabled ? "var(--mute)" : "var(--ink)" });

  return (
    <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 16, flexWrap: "wrap", gap: 12 }}>
      <div style={{ fontSize: 13, color: "var(--mute)", fontFamily: "var(--sans)" }}>
        {t("PartnerDash.pagerShowing")} <strong style={{ color: "var(--ink)", fontWeight: 600 }}>{from}–{to}</strong> {t("PartnerDash.pagerOf")} <strong style={{ color: "var(--ink)", fontWeight: 600 }}>{totalCount}</strong>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
        <button onClick={() => page > 1 && onPage(page - 1)} disabled={page <= 1} style={edge(page <= 1)}>{t("PartnerDash.pagerPrev")}</button>
        {window.map((n, i) =>
          n === null
            ? <span key={`gap-${i}`} style={{ ...cell, border: "none", background: "transparent", color: "var(--mute)", minWidth: 18 }}>…</span>
            : <button key={n} onClick={() => onPage(n)}
                style={n === page
                  ? { ...cell, background: "var(--accent)", color: "#fff", border: "1px solid transparent" }
                  : cell}>{n}</button>
        )}
        <button onClick={() => page < totalPages && onPage(page + 1)} disabled={page >= totalPages} style={edge(page >= totalPages)}>{t("PartnerDash.pagerNext")}</button>
      </div>
    </div>
  );
};

// ── Page shell ────────────────────────────────────────────────────────────────

const ViewModeToggle = ({ value, onChange }) => (
  <div style={{
    display: "flex", alignItems: "center",
    background: "rgba(10,10,11,.06)", borderRadius: 10, padding: 3, gap: 2,
  }}>
    {[
      { id: "workshop",  label: t("PartnerDash.viewModeWorkshop") },
      { id: "gutachten", label: t("PartnerDash.viewModeGutachten") },
    ].map(({ id, label }) => {
      const on = value === id;
      return (
        <button key={id} onClick={() => onChange(id)} style={{
          appearance: "none", border: "none", fontFamily: "var(--sans)",
          padding: "5px 14px", borderRadius: 7,
          background: on ? "#fff" : "transparent",
          color: on ? "var(--ink)" : "rgba(10,10,11,.45)",
          fontSize: 13, fontWeight: on ? 600 : 500,
          letterSpacing: "-0.01em",
          boxShadow: on ? "0 1px 4px rgba(10,10,11,.10)" : "none",
          cursor: "default",
          transition: "background .15s, color .15s, box-shadow .15s",
        }}>{label}</button>
      );
    })}
  </div>
);

// Top-right account chip — business name + partner type from GET /v1/partner/me.
// Renders a neutral placeholder (initial "P", blank name) while the profile loads.
const DashUserChip = ({ partner, separator }) => {
  const p = partner || {};
  const bizName = p.businessName || "";  // PartnerProfileResponse.businessName is nullable; blank while loading
  const typeLabel = p.partnerType ? t("PartnerDash.partnerTypeLabel", { type: p.partnerType }) : t("PartnerDash.partnerLabel");
  const initial = ((bizName || "").trim()[0] || "P").toUpperCase();
  const sep = separator ? { paddingLeft: 16, borderLeft: "1px solid rgba(10,10,11,.08)" } : null;
  const chip = (
    <div style={{ display: "flex", alignItems: "center", gap: 10, ...sep }}>
      <div style={{
        width: 34, height: 34, borderRadius: "50%",
        background: "var(--accent-tint)", border: "1.5px solid var(--accent)",
        display: "grid", placeItems: "center",
        fontSize: 13, fontWeight: 700, color: "var(--accent-ink)", flexShrink: 0,
      }}>{initial}</div>
      <div>
        <div style={{ fontSize: 13.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em", lineHeight: 1.2 }}>{bizName}</div>
        <div style={{ fontSize: 11, color: "var(--mute)", marginTop: 1 }}>{typeLabel}</div>
      </div>
    </div>
  );
  return chip;
};

const DashPage = ({ title, subtitle, actions, viewMode, onViewModeChange, partner, children }) => (
  <div style={{ display: "flex", flexDirection: "column", minHeight: "100%" }}>
    <div style={{
      background: "#fff",
      borderBottom: "1px solid rgba(10,10,11,.07)",
      padding: "24px 64px",
      display: "flex", justifyContent: "space-between", alignItems: "center",
    }}>
      <div>
        <div style={{ fontSize: 28, fontWeight: 700, letterSpacing: "-0.04em", color: "var(--ink)", lineHeight: 1.2 }}>{title}</div>
        {subtitle && <div style={{ fontSize: 13, color: "var(--mute)", marginTop: 5 }}>{subtitle}</div>}
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 16 }}>
        {actions && <div>{actions}</div>}
        <DashUserChip partner={partner} separator />
      </div>
    </div>
    <div style={{ flex: 1, padding: "32px 64px", background: "#F5F4F1" }}>
      {children}
    </div>
  </div>
);

// ── Sidebar icons ─────────────────────────────────────────────────────────────

const SvgUsers = () => <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="17" height="17"><path d="M11 13.5v-1A3 3 0 008 9.5H4a3 3 0 00-3 3v1"/><circle cx="6" cy="5" r="2.5"/><path d="M14.5 13.5v-1a3 3 0 00-2-2.82"/><path d="M10.5 2.68a2.5 2.5 0 010 4.64"/></svg>;
const SvgCard = () => <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" width="17" height="17"><rect x="1" y="3.5" width="14" height="9" rx="1.5"/><path d="M1 7h14"/></svg>;
const SvgCog = () => <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="17" height="17"><circle cx="8" cy="8" r="2"/><path d="M8 1.5v1M8 13.5v1M1.5 8h1M13.5 8h1M3.4 3.4l.7.7M11.9 11.9l.7.7M3.4 12.6l.7-.7M11.9 4.1l.7-.7"/></svg>;
const SvgOut = () => <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="13" height="13"><path d="M6 14H3a1 1 0 01-1-1V3a1 1 0 011-1h3M10.5 11l3-3-3-3M13.5 8H6"/></svg>;

const SideNavItem = ({ id, label, Icon, badge, active, onTab }) => {
  const [hov, setHov] = React.useState(false);
  return (
    <button
      onClick={() => onTab(id)}
      onMouseEnter={() => setHov(true)}
      onMouseLeave={() => setHov(false)}
      style={{
        position: "relative",
        display: "flex", alignItems: "center", gap: 12,
        padding: "11px 14px 11px 16px",
        borderRadius: 10, border: "none",
        background: active ? "rgba(255,255,255,.10)" : hov ? "rgba(255,255,255,.06)" : "transparent",
        color: active ? "#fff" : hov ? "rgba(255,255,255,.8)" : "rgba(255,255,255,.45)",
        fontSize: 16, fontWeight: active ? 600 : 400,
        fontFamily: "var(--sans)", cursor: "default",
        transition: "background .13s, color .13s",
        textAlign: "left", width: "100%",
      }}>
      {active && (
        <span style={{
          position: "absolute", left: 0, top: "18%", bottom: "18%",
          width: 3, borderRadius: "0 3px 3px 0",
          background: "var(--accent)",
        }} />
      )}
      <span style={{ display: "flex", opacity: active ? 1 : hov ? 0.9 : 0.65, transition: "opacity .13s" }}><Icon /></span>
      {label}
      {badge > 0 && (
        <span data-testid={"nav-" + id + "-badge"} style={{
          marginLeft: "auto",
          fontSize: 11, fontWeight: 700,
          background: "var(--accent)", color: "#fff",
          borderRadius: 8, padding: "2px 7px", lineHeight: 1.3,
        }}>{badge}</span>
      )}
    </button>
  );
};

const SideSignOut = ({ onHome }) => {
  const [hov, setHov] = React.useState(false);
  return (
    <button
      onClick={onHome}
      data-testid="signout"
      onMouseEnter={() => setHov(true)}
      onMouseLeave={() => setHov(false)}
      style={{
        display: "inline-flex", alignItems: "center", gap: 7,
        appearance: "none", background: "none", border: "none",
        padding: "4px 0", fontFamily: "var(--sans)",
        fontSize: 16, color: hov ? "rgba(255,255,255,.7)" : "rgba(255,255,255,.32)",
        cursor: "default", transition: "color .13s",
      }}>
      <SvgOut /> {t("PartnerDash.signOut")}
    </button>
  );
};

const DashSidebar = ({ tab, onTab, onHome, newCount }) => {
  const items = [
    { id: "referrals", label: t("PartnerDash.tabReferrals"), Icon: SvgUsers, badge: newCount },
    { id: "payments",  label: t("PartnerDash.tabPayments"),  Icon: SvgCard },
    { id: "settings",  label: t("PartnerDash.tabSettings"),  Icon: SvgCog },
  ];

  return (
    <aside style={{
      width: 270, flexShrink: 0,
      background: "#18181B",
      display: "flex", flexDirection: "column",
      padding: "30px 0 28px",
      position: "sticky", top: 0, height: "100vh",
      overflow: "hidden",
    }}>
      <div style={{ padding: "0 26px 36px", position: "relative" }}>
        <a href="/referrals" onClick={(e) => { e.preventDefault(); onTab("referrals"); }} style={{ display: "inline-block", marginBottom: 10 }}>
          <div style={{ filter: "brightness(0) invert(1)", opacity: 0.88 }}>
            <Logo size={24} />
          </div>
        </a>
        <div data-testid="sidebar-title" style={{
          fontSize: 12, fontWeight: 700, letterSpacing: "0.18em",
          textTransform: "uppercase", color: "rgba(255,255,255,.28)",
        }}>{t("PartnerDash.sidebarTitle")}</div>
      </div>

      <nav style={{ flex: 1, display: "flex", flexDirection: "column", padding: "0 16px", gap: 2 }}>
        {items.map(({ id, label, Icon, badge }) => (
          <SideNavItem key={id} id={id} label={label} Icon={Icon} badge={badge} active={tab === id} onTab={onTab} />
        ))}
      </nav>

      <div style={{ padding: "16px 26px 0", borderTop: "1px solid rgba(255,255,255,.07)" }}>
        <SideSignOut onHome={onHome} />
      </div>
    </aside>
  );
};

// ── Referral detail page ──────────────────────────────────────────────────────

// Normalize a real DamageDetailResponse from GET /v1/inspections/{id} into the
// shape the damage cards already consume (type/part/location/confidence/estimate/ops/image).
function mapInspectionDamage(d) {
  const repairOps = d.repairOperations || [];
  const ops = repairOps.map(op => ({
    // Prefer the localized label ({en,de} already collapsed to a string by localizeDeep);
    // fall back to the raw enum for older/unlabelled data.
    name: op.operationTypeLabel || op.operationType,
    mat: `€${Math.round(op.materials || 0)}`,
    hrs: String(op.hours ?? 0),
    rate: `€${Math.round(op.rate || 0)}/hr`,
    total: `€${Math.round(op.total || 0)}`,
  }));
  const estimate = `€${Math.round(repairOps.reduce((a, op) => a + (op.total || 0), 0))}`;
  return {
    id: d.id,
    type: d.damageTypeLabel || d.damageType,
    part: d.partLabel || d.part,
    location: d.locationLabel || d.location,
    confidence: Math.round(d.confidence || 0),
    estimate,
    ops,
    image: d.image,
  };
}

const PhotoPlaceholder = ({ index }) => {
  const GRADS = [
    "linear-gradient(145deg,#1E2030,#2A2A3C)",
    "linear-gradient(145deg,#1F2820,#2A3A2C)",
    "linear-gradient(145deg,#281E20,#3C2A2C)",
    "linear-gradient(145deg,#20201E,#2C2A28)",
    "linear-gradient(145deg,#1E2028,#2A2C3C)",
  ];
  return (
    <div style={{ width: "100%", height: "100%", background: GRADS[(index || 0) % GRADS.length], display: "grid", placeItems: "center" }}>
      <svg viewBox="0 0 40 40" fill="none" stroke="rgba(255,255,255,.18)" strokeWidth="1.2" width="32" height="32">
        <rect x="4" y="10" width="32" height="22" rx="2.5"/>
        <circle cx="20" cy="21" r="6"/>
        <circle cx="20" cy="21" r="3.5"/>
        <path d="M14 10l1.5-3h9l1.5 3"/>
        <circle cx="31" cy="14" r="1.5" fill="rgba(255,255,255,.25)" stroke="none"/>
      </svg>
    </div>
  );
};

const VehicleDiagram = ({ damage }) => {
  const dl = (damage || "").toLowerCase();
  const front = dl.includes("front") || dl.includes("hood") || dl.includes("windshield");
  const rear  = dl.includes("rear")  || dl.includes("bumper") || dl.includes("taillight");
  const side  = dl.includes("door")  || dl.includes("panel")  || dl.includes("fender") || dl.includes("mirror");
  const roof  = dl.includes("roof");
  const hl = "#FF3B30"; const hlF = "rgba(255,59,48,.14)";
  return (
    <svg viewBox="0 0 180 300" width="100%" style={{ maxHeight: 200, display: "block", margin: "0 auto" }}>
      <ellipse cx="90" cy="285" rx="50" ry="8" fill="rgba(10,10,11,.05)"/>
      <path d="M65 30 Q90 20 115 30 L132 80 L140 190 L136 250 Q118 265 90 268 Q62 265 44 250 L40 190 L48 80 Z" fill="#EDECE8" stroke="rgba(10,10,11,.12)" strokeWidth="1.5"/>
      <path d="M74 82 Q90 74 106 82 L112 106 L68 106 Z" fill={front ? hlF : "#D4D8E0"} stroke={front ? hl : "rgba(10,10,11,.1)"} strokeWidth={front ? 1.5 : 0.8}/>
      <path d="M72 85 Q90 78 108 85 L116 140 L64 140 Z" fill={roof ? hlF : "#E0E0DC"} stroke={roof ? hl : "rgba(10,10,11,.08)"} strokeWidth={roof ? 1.5 : 0.8}/>
      <path d="M68 218 Q90 228 112 218 L116 200 L64 200 Z" fill={rear ? hlF : "#D4D8E0"} stroke={rear ? hl : "rgba(10,10,11,.1)"} strokeWidth={rear ? 1.5 : 0.8}/>
      {front && <path d="M68 28 Q90 22 112 28 L118 42 L62 42 Z" fill={hlF} stroke={hl} strokeWidth="1.5"/>}
      {rear  && <path d="M50 248 Q90 262 130 248 L132 260 Q90 276 48 260 Z" fill={hlF} stroke={hl} strokeWidth="1.5"/>}
      {side  && <rect x="40" y="120" width="10" height="70" rx="3" fill={hlF} stroke={hl} strokeWidth="1.5"/>}
      <rect x="26" y="78"  width="20" height="38" rx="4" fill="#333"/>
      <rect x="134" y="78" width="20" height="38" rx="4" fill="#333"/>
      <rect x="26" y="200" width="20" height="38" rx="4" fill="#333"/>
      <rect x="134" y="200" width="20" height="38" rx="4" fill="#333"/>
      <circle cx="36"  cy="97"  r="7" fill="#555"/>
      <circle cx="144" cy="97"  r="7" fill="#555"/>
      <circle cx="36"  cy="219" r="7" fill="#555"/>
      <circle cx="144" cy="219" r="7" fill="#555"/>
    </svg>
  );
};

// ── Shared detail-page atoms ──────────────────────────────────────────────────
// Reused across the Workshop/Gutachten referral detail pages and their modals.

// Fixed full-screen modal backdrop (decline / offer / accept overlays).
const dashOverlay = { position: "fixed", inset: 0, background: "rgba(10,10,11,.5)", backdropFilter: "blur(6px)", zIndex: 400, display: "flex", alignItems: "center", justifyContent: "center", padding: 24 };
// Standard 44px text input used in the offer/accept modals.
const dashModalInput = { width: "100%", height: 44, borderRadius: 10, border: "1.5px solid rgba(10,10,11,.15)", background: "#FAFAF9", padding: "0 14px", fontSize: 14, fontFamily: "var(--sans)", color: "var(--ink)", outline: "none", boxSizing: "border-box" };
// Inline red error banner (spread + override marginBottom at the call site).
const dashErrorBox = { fontSize: 13, color: "#BE123C", background: "#FFF1F2", border: "1px solid #FECDD3", borderRadius: 10, padding: "10px 14px", lineHeight: 1.5 };

// Search-plus glyph shown on photo hover. `size` matches the original 20/22px uses.
const MagnifierIcon = ({ size = 22 }) => (
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" width={size} height={size}><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35M11 8v6M8 11h6"/></svg>
);

// Damage-card thumbnail with hover zoom + magnifier overlay; falls back to a
// synthetic PhotoPlaceholder when the damage has no image. Owns its own hover state.
const DashCardPhoto = ({ image, alt, index, width }) => {
  const [hov, setHov] = React.useState(false);
  return (
    <div
      onMouseEnter={() => setHov(true)}
      onMouseLeave={() => setHov(false)}
      style={{ width, flexShrink: 0, background: "#1A1A1C", minHeight: 220, position: "relative", overflow: "hidden", borderRight: "1px solid rgba(10,10,11,.07)" }}>
      <div style={{ position: "absolute", inset: 0, transform: hov ? "scale(1.05)" : "scale(1)", transition: "transform .6s ease" }}>
        {image
          ? <img src={OK_API.damagePhotoUrl(image)} alt={alt} style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
          : <PhotoPlaceholder index={index} />}
      </div>
      {hov && (
        <div style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,.18)", display: "flex", alignItems: "center", justifyContent: "center" }}>
          <div style={{ background: "rgba(0,0,0,.35)", backdropFilter: "blur(8px)", borderRadius: "50%", padding: 10, color: "rgba(255,255,255,.9)" }}>
            <MagnifierIcon size={20} />
          </div>
        </div>
      )}
    </div>
  );
};

// Full-screen inspection-photo lightbox. Render conditionally: {open && <DashLightbox …/>}.
const DashLightbox = ({ image, onClose }) => (
  <div onClick={onClose} style={{ position: "fixed", inset: 0, background: "rgba(10,10,11,.7)", backdropFilter: "blur(8px)", zIndex: 300, display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
    <button onClick={onClose} style={{ position: "absolute", top: 24, right: 24, appearance: "none", background: "rgba(41,38,27,.12)", border: "1px solid rgba(10,10,11,.15)", borderRadius: "50%", padding: 8, cursor: "default", color: "rgba(255,255,255,.7)", display: "grid", placeItems: "center" }}>
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" width="26" height="26"><circle cx="12" cy="12" r="10"/><path d="M15 9l-6 6M9 9l6 6"/></svg>
    </button>
    <div style={{ width: "90vw", height: "85vh", borderRadius: 16, overflow: "hidden" }} onClick={e => e.stopPropagation()}>
      {image
        ? <img src={OK_API.inspectionPhotoUrl(image)} alt={t("PartnerDash.inspectionPhotoAlt")} style={{ width: "100%", height: "100%", objectFit: "contain", display: "block" }} />
        : <PhotoPlaceholder index={0} />}
    </div>
  </div>
);

// Large zoomable main inspection photo (right column of the detail pages).
// Owns its own hover state; `onOpen` opens the lightbox.
const DashMainPhoto = ({ image, onOpen }) => {
  const [hov, setHov] = React.useState(false);
  return (
    <div
      onMouseEnter={() => setHov(true)}
      onMouseLeave={() => setHov(false)}
      onClick={onOpen}
      style={{ position: "relative", flex: 1, minHeight: 0, borderRadius: 18, overflow: "hidden", border: "1px solid rgba(10,10,11,.1)", background: "#E8E6E0", cursor: "zoom-in" }}>
      <div style={{ position: "absolute", inset: 0, transform: hov ? "scale(1.05)" : "scale(1)", transition: "transform .7s ease" }}>
        {image
          ? <img src={OK_API.inspectionPhotoUrl(image)} alt={t("PartnerDash.inspectionPhotoAlt")} style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
          : <PhotoPlaceholder index={0} />}
      </div>
      {hov && (
        <div style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,.08)", display: "flex", alignItems: "center", justifyContent: "center" }}>
          <div style={{ background: "rgba(0,0,0,.4)", backdropFilter: "blur(8px)", borderRadius: "50%", padding: 12, color: "rgba(255,255,255,.9)" }}>
            <MagnifierIcon size={22} />
          </div>
        </div>
      )}
    </div>
  );
};

// Detail-page header bar: "← Referrals" back button + account chip.
const DashDetailHeader = ({ onBack, partner }) => (
  <div style={{ background: "#fff", borderBottom: "1px solid rgba(10,10,11,.07)", padding: "20px 64px", display: "flex", justifyContent: "space-between", alignItems: "center", flexShrink: 0 }}>
    <button onClick={onBack} data-testid="detail-back" style={{ display: "inline-flex", alignItems: "center", gap: 6, appearance: "none", background: "rgba(10,10,11,.05)", border: "none", borderRadius: 8, padding: "7px 13px", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 500, color: "rgba(10,10,11,.6)", cursor: "default" }}>
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" width="14" height="14"><path d="M15 18l-6-6 6-6"/></svg>
      {t("PartnerDash.backToReferrals")}
    </button>
    <DashUserChip partner={partner} />
  </div>
);

// Decline-confirmation modal. Copy varies by surface (referral vs case), so the
// title, confirm label, and body (children) are passed in.
const DashDeclineModal = ({ title, confirmLabel, busy, error, onCancel, onConfirm, children }) => (
  <div onClick={onCancel} style={dashOverlay}>
    <div onClick={e => e.stopPropagation()} style={{ background: "#fff", borderRadius: 20, padding: "36px 40px", maxWidth: 440, width: "100%", boxShadow: "0 24px 64px rgba(10,10,11,.18)" }}>
      <div style={{ width: 44, height: 44, borderRadius: 12, background: "#FFF1F2", border: "1px solid #FECDD3", display: "grid", placeItems: "center", marginBottom: 20 }}>
        <svg viewBox="0 0 24 24" fill="none" stroke="#BE123C" strokeWidth="1.8" strokeLinecap="round" width="22" height="22"><path d="M12 9v4M12 17h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/></svg>
      </div>
      <div data-testid="decline-modal-title" style={{ fontSize: 19, fontWeight: 700, color: "var(--ink)", letterSpacing: "-0.03em", marginBottom: 10 }}>{title}</div>
      <div style={{ fontSize: 14, color: "var(--mute)", lineHeight: 1.65, marginBottom: 28 }}>{children}</div>
      {error && (
        <div style={{ ...dashErrorBox, marginBottom: 18 }}>{error}</div>
      )}
      <div style={{ display: "flex", gap: 10 }}>
        <button onClick={onCancel} disabled={busy} style={{ flex: 1, height: 44, borderRadius: 10, background: "rgba(10,10,11,.05)", border: "1px solid rgba(10,10,11,.1)", fontSize: 14, fontWeight: 500, color: "var(--ink)", fontFamily: "var(--sans)", cursor: busy ? "default" : "pointer" }}>{t("PartnerDash.cancel")}</button>
        <button onClick={onConfirm} data-testid="decline-confirm" disabled={busy} style={{ flex: 1, height: 44, borderRadius: 10, background: "#BE123C", border: "none", fontSize: 14, fontWeight: 600, color: "#fff", fontFamily: "var(--sans)", cursor: busy ? "default" : "pointer", opacity: busy ? 0.6 : 1 }}>{busy ? t("PartnerDash.declining") : confirmLabel}</button>
      </div>
    </div>
  </div>
);

// Damage-cards section below the detail summary: loading note → error note → the
// list of cards rendered with the supplied `Card` component (workshop vs Gutachten).
const DashDamageList = ({ loading, error, damages, Card }) => {
  if (loading) {
    return (
      <div style={{ background: "#fff", borderRadius: 16, border: "1px solid rgba(10,10,11,.07)", padding: "60px 0", textAlign: "center", color: "var(--mute)", fontSize: 14 }}>
        {t("PartnerDash.loadingInspection")}
      </div>
    );
  }
  if (error) {
    return (
      <div style={{ fontSize: 13.5, color: "#BE123C", background: "#FFF1F2", border: "1px solid #FECDD3", borderRadius: 12, padding: "12px 16px", lineHeight: 1.6 }}>
        {t("PartnerDash.inspectionError", { error })}
      </div>
    );
  }
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      {damages.map((item, i) => (
        <Card key={item.id} item={item} index={i} />
      ))}
    </div>
  );
};

// Workshop damage card — shows labor, estimate, operations
const DamageItemCard = ({ item, index }) => {
  return (
    <div style={{ background: "#fff", borderRadius: 20, border: "1px solid rgba(10,10,11,.07)", overflow: "hidden", display: "flex", boxShadow: "0 1px 4px rgba(10,10,11,.04)" }}>
      <DashCardPhoto image={item.image} alt={item.type} index={index} width={380} />
      <div style={{ flex: 1, padding: "20px 24px 20px 32px" }}>
        <div style={{ marginBottom: 6 }}>
          <div style={{ fontSize: 20, fontWeight: 700, color: "var(--ink)", letterSpacing: "-0.03em" }}>{item.type}</div>
        </div>
        <div style={{ fontSize: 13.5, color: "var(--mute)", lineHeight: 1.55, marginBottom: 16 }}>
          {t("PartnerDash.damageDetectedWorkshop", { type: item.type, part: item.part, location: item.location, confidence: item.confidence })}
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: "0 16px", marginBottom: 16 }}>
          {[
            { head: t("PartnerDash.labelPart"),         val: item.part,     color: "#E25A1C" },
            { head: t("PartnerDash.labelLocation"),     val: item.location, color: "var(--ink)" },
            { head: t("PartnerDash.labelEstLaborTime"), val: `${item.ops.reduce((a, op) => a + parseFloat(op.hrs), 0).toFixed(1)} h`, color: "var(--ink)" },
            { head: t("PartnerDash.labelEstimate"),     val: item.estimate, color: "var(--ink)", big: true },
          ].map(({ head, val, color, big }) => (
            <div key={head}>
              <div style={{ fontSize: 9.5, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 5 }}>{head}</div>
              <div style={{ fontSize: big ? 22 : 14, fontWeight: big ? 700 : 500, color, letterSpacing: big ? "-0.04em" : "-0.01em", lineHeight: 1.1 }}>{val}</div>
            </div>
          ))}
        </div>
        <div>
          <div style={{ fontSize: 9.5, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 7 }}>{t("PartnerDash.labelOperations")}</div>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
            {item.ops.map(op => (
              <span key={op.name} style={{ display: "inline-block", padding: "4px 10px", borderRadius: 6, background: "rgba(10,10,11,.05)", border: "1px solid rgba(10,10,11,.08)", fontSize: 12, fontWeight: 500, color: "var(--mute)" }}>{op.name}</span>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};

// Gutachten damage card — assessment-focused, no labor/cost/operations
const GutachtenDamageCard = ({ item, index }) => {
  return (
    <div style={{ background: "#fff", borderRadius: 20, border: "1px solid rgba(10,10,11,.07)", overflow: "hidden", display: "flex", boxShadow: "0 1px 4px rgba(10,10,11,.04)" }}>
      <DashCardPhoto image={item.image} alt={item.type} index={index} width={320} />

      {/* Content */}
      <div style={{ flex: 1, padding: "24px 28px 24px 32px" }}>
        <div style={{ fontSize: 21, fontWeight: 700, color: "var(--ink)", letterSpacing: "-0.03em", marginBottom: 10 }}>{item.type}</div>
        <div style={{ fontSize: 13.5, color: "var(--mute)", lineHeight: 1.6, marginBottom: 20 }}>
          {t("PartnerDash.damageDetectedGutachten", { type: item.type, part: item.part, location: item.location })}
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "14px 24px" }}>
          {[
            { head: t("PartnerDash.labelAffectedPart"), val: item.part },
            { head: t("PartnerDash.labelLocation"),      val: item.location },
          ].map(({ head, val }) => (
            <div key={head}>
              <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 4 }}>{head}</div>
              <div style={{ fontSize: 15, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em" }}>{val}</div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

// Fetch one inspection by id. Returns { insp, loading, error }; no fetch when id is falsy.
const useInspection = (inspectionId) => {
  const [insp, setInsp] = React.useState(null);
  const [loading, setLoading] = React.useState(!!inspectionId);
  const [error, setError] = React.useState(null);
  React.useEffect(() => {
    if (!inspectionId) return;
    let alive = true;
    OK_API.getInspection(inspectionId)
      .then(d => { if (alive) { setInsp(d); setLoading(false); } })
      .catch(e => { if (alive) { setError(e.message || t("PartnerDash.inspectionLoadError")); setLoading(false); } });
    return () => { alive = false; };
  }, [inspectionId]);
  // Collapse { en, de } fields to the active language so the referral/detail pages render plain
  // strings (and re-localize instantly on a language toggle via getLang() in deps).
  const localizedInsp = React.useMemo(() => (insp ? localizeDeep(insp) : insp), [insp, getLang()]);
  return { insp: localizedInsp, loading, error };
};

// Normalize a fetched inspection into the fields the detail pages render. With no
// inspection attached to the referral, the data fields fall back to em-dashes.
const deriveReferralView = (insp) => {
  const damages     = insp ? (insp.damages || []).map(mapInspectionDamage) : [];
  const vehicle     = insp ? `${insp.make} ${insp.model} ${insp.year}` : "—";
  const vin         = insp ? (insp.vin || "-") : "-";
  const location    = insp ? insp.zipCode : "—";
  const parts       = insp ? [...new Set((insp.damagedParts || []).map(p => p.partLabel || p.part))].join(", ") : "—";
  const damageCount = insp ? (typeof insp.damageCount === "number" ? insp.damageCount : insp.damages.length) : "—";
  const mainImage   = insp && insp.inspectionImages && insp.inspectionImages[0] ? insp.inspectionImages[0].image : null;
  return { damages, vehicle, vin, location, parts, damageCount, mainImage };
};

const WorkshopReferralDetailPage = ({ referral: r, onBack, partner }) => {
  const [lightboxOpen, setLightboxOpen] = React.useState(false);
  const [showDecline, setShowDecline] = React.useState(false);
  const [declining, setDeclining] = React.useState(false);
  const [declineError, setDeclineError] = React.useState(null);
  const [showOffer, setShowOffer] = React.useState(false);
  const [offerMin, setOfferMin] = React.useState("");
  const [offerMax, setOfferMax] = React.useState("");
  const [workingDays, setWorkingDays] = React.useState("");
  const [needsInspection, setNeedsInspection] = React.useState(false);
  const [dropoffDate, setDropoffDate] = React.useState("");
  const [quoteFile, setQuoteFile] = React.useState(null);
  const [submitting, setSubmitting] = React.useState(false);
  const [submitError, setSubmitError] = React.useState(null);
  const [status, setStatus] = React.useState(r.status);

  const { insp, loading: inspLoading, error: inspError } = useInspection(r.inspectionId);
  const { damages, vehicle, vin, location, parts, damageCount, mainImage } = deriveReferralView(insp);

  const offerValid = offerMin.trim() && offerMax.trim() && String(workingDays).trim() && dropoffDate;

  const handleSubmitOffer = async () => {
    if (!offerValid || submitting) return;
    setSubmitting(true);
    setSubmitError(null);
    try {
      await OK_API.submitOffer(r.id, {
        minValue: offerMin.trim(),
        maxValue: offerMax.trim(),
        requiresInPersonInspection: needsInspection,
        estimatedWorkingDays: String(workingDays).trim(),
        earliestDropOffDate: dropoffDate,
        quote: quoteFile,
      });
      setShowOffer(false);
      setStatus("offersent");
      r.status = "offersent"; // keep the underlying referral in sync for the list
    } catch (e) {
      setSubmitError(e.message || t("PartnerDash.errSubmitOffer"));
    } finally {
      setSubmitting(false);
    }
  };

  const handleDecline = async () => {
    if (declining) return;
    setDeclining(true);
    setDeclineError(null);
    try {
      await OK_API.declineReferral(r.id);
      r.status = "declined"; // keep the underlying referral in sync for the list
      setShowDecline(false);
      onBack();              // return to the referral list
    } catch (e) {
      setDeclineError(e.message || t("PartnerDash.errDeclineReferral"));
    } finally {
      setDeclining(false);
    }
  };

  return (
    <div style={{ minHeight: "100%", background: "#F5F4F1", display: "flex", flexDirection: "column" }}>
      {/* Lightbox */}
      {lightboxOpen && <DashLightbox image={mainImage} onClose={() => setLightboxOpen(false)} />}

      {/* Decline confirmation */}
      {showDecline && (
        <DashDeclineModal
          title={t("PartnerDash.declineReferralTitle")}
          confirmLabel={t("PartnerDash.declineReferralConfirm")}
          busy={declining}
          error={declineError}
          onCancel={() => setShowDecline(false)}
          onConfirm={handleDecline}
        >
          {t("PartnerDash.declineReferralBody")} <strong style={{ color: "var(--ink)", fontWeight: 600 }}>{t("PartnerDash.cannotBeUndone")}</strong>{t("PartnerDash.referralWillBeDeclined")}
        </DashDeclineModal>
      )}

      {/* Make an offer overlay */}
      {showOffer && (
        <div onClick={() => setShowOffer(false)} style={dashOverlay}>
          <div onClick={e => e.stopPropagation()} style={{ background: "#fff", borderRadius: 20, padding: "36px 40px", maxWidth: 520, width: "100%", boxShadow: "0 24px 64px rgba(10,10,11,.18)", maxHeight: "90vh", overflowY: "auto" }}>
            <div style={{ fontSize: 21, fontWeight: 700, color: "var(--ink)", letterSpacing: "-0.03em", marginBottom: 4 }}>{t("PartnerDash.offerTitle")}</div>
            <div style={{ fontSize: 13.5, color: "var(--mute)", marginBottom: 28 }}>{t("PartnerDash.offerSubtitle", { id: r.id, vehicle: r.vehicle })}</div>

            <div style={{ marginBottom: 18 }}>
              <label style={{ display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.09em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 7 }}>{t("PartnerDash.offerValueLabel")}</label>
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
                <div>
                  <div style={{ fontSize: 11, color: "var(--mute)", marginBottom: 5 }}>{t("PartnerDash.offerMin")}</div>
                  <input value={offerMin} onChange={e => setOfferMin(e.target.value)} placeholder="€ 1,800" style={dashModalInput} />
                </div>
                <div>
                  <div style={{ fontSize: 11, color: "var(--mute)", marginBottom: 5 }}>{t("PartnerDash.offerMax")}</div>
                  <input value={offerMax} onChange={e => setOfferMax(e.target.value)} placeholder="€ 2,400" style={dashModalInput} />
                </div>
              </div>
            </div>

            <label style={{ display: "flex", alignItems: "flex-start", gap: 12, cursor: "pointer", marginBottom: 18, padding: "14px 16px", borderRadius: 12, background: "rgba(10,10,11,.03)", border: "1.5px solid rgba(10,10,11,.08)" }}>
              <input type="checkbox" checked={needsInspection} onChange={e => setNeedsInspection(e.target.checked)} style={{ width: 18, height: 18, accentColor: "var(--accent)", marginTop: 1, flexShrink: 0, cursor: "pointer" }} />
              <div>
                <div style={{ fontSize: 14, fontWeight: 500, color: "var(--ink)", marginBottom: 2 }}>{t("PartnerDash.offerInspectionRequired")}</div>
                <div style={{ fontSize: 12.5, color: "var(--mute)", lineHeight: 1.5 }}>{t("PartnerDash.offerInspectionHint")}</div>
              </div>
            </label>

            <div style={{ marginBottom: 18 }}>
              <label style={{ display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.09em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 7 }}>{t("PartnerDash.offerWorkingDays")}</label>
              <input value={workingDays} onChange={e => setWorkingDays(e.target.value)} type="number" min="1" data-testid="offer-working-days" placeholder={t("PartnerDash.phWorkingDays")} style={dashModalInput} />
            </div>

            <div style={{ marginBottom: 18 }}>
              <label style={{ display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.09em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 7 }}>{t("PartnerDash.offerDropoffDate")}</label>
              <input value={dropoffDate} onChange={e => setDropoffDate(e.target.value)} type="date" style={dashModalInput} />
            </div>

            <div style={{ marginBottom: 28 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 7 }}>
                <label style={{ fontSize: 11, fontWeight: 700, letterSpacing: "0.09em", textTransform: "uppercase", color: "var(--mute)" }}>{t("PartnerDash.offerUploadQuote")}</label>
                <span style={{ fontSize: 10, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--accent)", background: "var(--accent-tint)", border: "1px solid var(--accent)", borderRadius: 4, padding: "1px 6px" }}>{t("PartnerDash.optional")}</span>
              </div>
              <label
                onDragOver={e => e.preventDefault()}
                onDrop={e => { e.preventDefault(); const f = e.dataTransfer.files && e.dataTransfer.files[0]; if (f) setQuoteFile(f); }}
                style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 6, height: 120, borderRadius: 12, border: `1.5px dashed ${quoteFile ? "var(--accent)" : "rgba(10,10,11,.18)"}`, background: quoteFile ? "#F6FEF9" : "rgba(10,10,11,.02)", cursor: "pointer", transition: "border-color .15s, background .15s", textAlign: "center", padding: "0 16px", boxSizing: "border-box" }}>
                <input type="file" accept=".pdf,.doc,.docx,.png,.jpg" onChange={e => setQuoteFile(e.target.files?.[0] || null)} style={{ display: "none" }} />
                {quoteFile ? (
                  <>
                    <svg viewBox="0 0 24 24" fill="none" stroke="var(--accent-ink)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" width="24" height="24"><path d="M20 6L9 17l-5-5"/></svg>
                    <span style={{ fontSize: 13.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em", maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{quoteFile.name}</span>
                    <span style={{ fontSize: 12, color: "var(--mute)" }}>{fmtFileSize(quoteFile.size)} · {t("PartnerDash.clickToReplace")}</span>
                  </>
                ) : (
                  <>
                    <svg viewBox="0 0 24 24" fill="none" stroke="rgba(10,10,11,.3)" strokeWidth="1.5" strokeLinecap="round" width="22" height="22"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg>
                    <span style={{ fontSize: 13, color: "var(--mute)" }}>{t("PartnerDash.dropAFile")} <span style={{ color: "var(--accent)", fontWeight: 500 }}>{t("PartnerDash.browse")}</span></span>
                  </>
                )}
              </label>
              <div style={{ marginTop: 10, fontSize: 12.5, color: "var(--mute)", lineHeight: 1.6, background: "var(--accent-tint)", border: "1px solid rgba(16,161,113,.2)", borderRadius: 10, padding: "10px 14px" }}>
                <strong style={{ color: "var(--accent-ink)", fontWeight: 600 }}>{t("PartnerDash.tipLabel")}</strong> {t("PartnerDash.offerTipText")}
              </div>
            </div>

            {submitError && (
              <div style={{ ...dashErrorBox, marginBottom: 16 }}>
                {submitError}
              </div>
            )}

            <div style={{ display: "flex", gap: 10 }}>
              <button onClick={() => setShowOffer(false)} disabled={submitting} style={{ flex: 1, height: 46, borderRadius: 10, background: "rgba(10,10,11,.05)", border: "1px solid rgba(10,10,11,.1)", fontSize: 14, fontWeight: 500, color: "var(--ink)", fontFamily: "var(--sans)", cursor: submitting ? "default" : "pointer" }}>{t("PartnerDash.cancel")}</button>
              <button onClick={handleSubmitOffer} data-testid="offer-submit" disabled={submitting || !offerValid} style={{ flex: 2, height: 46, borderRadius: 10, background: "var(--accent)", border: "none", fontSize: 14, fontWeight: 600, color: "#fff", fontFamily: "var(--sans)", cursor: (submitting || !offerValid) ? "default" : "pointer", letterSpacing: "-0.01em", opacity: (submitting || !offerValid) ? 0.6 : 1 }}>{submitting ? t("PartnerDash.submitting") : t("PartnerDash.submitOffer")}</button>
            </div>
          </div>
        </div>
      )}

      {/* Header */}
      <DashDetailHeader onBack={onBack} partner={partner} />

      {/* Body */}
      <div style={{ flex: 1, padding: "32px 64px" }}>
        <div style={{ display: "grid", gridTemplateColumns: "5fr 7fr", gap: "0 80px", marginBottom: 40, alignItems: "stretch" }}>

          {/* Left column */}
          <div style={{ display: "flex", flexDirection: "column" }}>
            <div style={{ display: "grid", gridTemplateColumns: "130px 1fr", rowGap: 16, fontSize: 17, marginBottom: 20 }}>
              <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelReferralId")}</span>
              <span style={{ color: "var(--ink)", fontWeight: 500, letterSpacing: "-0.01em" }}>{r.id}</span>
              {r.inspectionId && <>
                <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelInspection")}</span>
                <span style={{ color: "var(--ink)", fontWeight: 500, letterSpacing: "-0.01em", wordBreak: "break-all" }}>{r.inspectionId}</span>
              </>}
              <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelVehicle")}</span>
              <span style={{ color: "var(--ink)", fontWeight: 500, letterSpacing: "-0.01em" }}>{vehicle}</span>
              <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelVin")}</span>
              <span style={{ color: "var(--ink)" }}>{vin}</span>
              <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelLocationDetail")}</span>
              <span style={{ color: "var(--ink)" }}>{location}</span>
              <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelReceived")}</span>
              <span style={{ color: "var(--ink)" }}>{fmtDate(r.ts)}</span>
              {r.dueAt && <>
                <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelRespondBy")}</span>
                <span style={{ color: "var(--ink)" }}>{fmtDate(r.dueAt)}</span>
              </>}
            </div>

            <div style={{ width: "80%", marginBottom: 20 }}>
              {insp && insp.diagram
                ? <img src={OK_API.diagramUrl(insp.diagram)} alt={t("PartnerDash.vehicleDiagramAlt")} style={{ width: "100%", display: "block", maxHeight: 200, objectFit: "contain", margin: "0 auto" }} />
                : <VehicleDiagram damage={r.damage} />}
            </div>

            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, marginBottom: 10 }}>
              <div style={{ background: "#fff", border: "1px solid rgba(10,10,11,.08)", borderRadius: 12, padding: "12px 16px", boxShadow: "0 1px 3px rgba(10,10,11,.04)" }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 6 }}>{t("PartnerDash.labelParts")}</div>
                <div style={{ fontSize: 15, fontWeight: 500, color: "var(--ink)", lineHeight: 1.45 }}>{parts}</div>
              </div>
              <div style={{ background: "#fff", border: "1px solid rgba(10,10,11,.08)", borderRadius: 12, padding: "12px 16px", boxShadow: "0 1px 3px rgba(10,10,11,.04)" }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 5 }}>{t("PartnerDash.labelDamages")}</div>
                <div style={{ fontSize: 28, fontWeight: 700, color: "var(--ink)", letterSpacing: "-0.04em" }}>{damageCount}</div>
              </div>
            </div>

            <div style={{ background: "#fff", border: "1px solid rgba(10,10,11,.08)", borderRadius: 12, padding: "12px 16px", boxShadow: "0 1px 3px rgba(10,10,11,.04)", marginBottom: 12 }}>
              <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 6 }}>{t("PartnerDash.labelDamageDescription")}</div>
              <div style={{ fontSize: 15, color: "var(--ink)", lineHeight: 1.6 }}>
                {insp ? (insp.estimateDescription || "—") : "—"}
              </div>
            </div>

            {(() => {
              const hasData = !!insp;
              const allOps = damages.flatMap(d => d.ops);
              const opHrs = allOps.reduce((a, op) => a + (parseFloat(op.hrs) || 0), 0);
              const totalHrs = insp
                ? (typeof insp.durationMinutes === "number" ? insp.durationMinutes / 60 : opHrs)
                : opHrs;
              const mockCost = allOps.reduce((a, op) => a + parseFloat(op.total.replace("€", "")), 0);
              const cost = insp
                ? (typeof insp.estimatedCost === "number" ? insp.estimatedCost : 0)
                : mockCost;
              return (
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, marginBottom: 28 }}>
                  <div style={{ background: "#fff", border: "1px solid rgba(10,10,11,.10)", borderRadius: 14, padding: "16px 18px", boxShadow: "0 2px 8px rgba(10,10,11,.07)", borderTop: "3px solid rgba(10,10,11,.18)" }}>
                    <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 8 }}>{t("PartnerDash.statLaborHours")}</div>
                    <div style={{ fontSize: 32, fontWeight: 800, color: "var(--ink)", letterSpacing: "-0.05em", lineHeight: 1 }}>{hasData ? <>{totalHrs.toFixed(1)}<span style={{ fontSize: 16, fontWeight: 600, letterSpacing: "-0.02em", marginLeft: 3 }}>h</span></> : "—"}</div>
                  </div>
                  <div style={{ background: "var(--accent)", border: "1px solid transparent", borderRadius: 14, padding: "16px 18px", boxShadow: "0 2px 12px rgba(16,161,113,.25)" }}>
                    <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "rgba(255,255,255,.7)", marginBottom: 8 }}>{t("PartnerDash.statRepairCost")}</div>
                    <div style={{ fontSize: 32, fontWeight: 800, color: "#fff", letterSpacing: "-0.05em", lineHeight: 1 }}>{hasData ? `€${cost.toLocaleString("de-DE")}` : "—"}</div>
                  </div>
                </div>
              );
            })()}

            {(status === "new" || status === "reviewed") ? (
              <div style={{ display: "flex", gap: 10 }}>
                <button onClick={() => setShowOffer(true)} data-testid="detail-action" style={{ flex: 1, height: 46, borderRadius: 10, background: "var(--accent)", color: "#fff", border: "none", fontSize: 14, fontWeight: 600, fontFamily: "var(--sans)", cursor: "pointer", letterSpacing: "-0.01em" }}>{t("PartnerDash.btnMakeOffer")}</button>
                <button onClick={() => { setDeclineError(null); setShowDecline(true); }} data-testid="detail-decline" style={{ height: 46, padding: "0 32px", borderRadius: 10, background: "transparent", color: "rgba(10,10,11,.55)", border: "1px solid rgba(10,10,11,.18)", fontSize: 14, fontWeight: 500, fontFamily: "var(--sans)", cursor: "pointer" }}>{t("PartnerDash.btnDecline")}</button>
              </div>
            ) : (
              <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", background: "#fff", border: "1px solid rgba(10,10,11,.08)", borderRadius: 12, padding: "15px 20px", boxShadow: "0 1px 3px rgba(10,10,11,.04)" }}>
                <span data-testid="detail-status-heading" style={{ fontSize: 15, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.02em" }}>{t("PartnerDash.referralStatus")}</span>
                <DashBadge status={status} />
              </div>
            )}
          </div>

          {/* Right: Images */}
          <div style={{ display: "flex", flexDirection: "column" }}>
            <DashMainPhoto image={mainImage} onOpen={() => setLightboxOpen(true)} />
          </div>
        </div>

        <div style={{ height: 1, background: "rgba(10,10,11,.07)", marginBottom: 32 }} />

        <DashDamageList loading={inspLoading} error={inspError} damages={damages} Card={DamageItemCard} />
      </div>
    </div>
  );
};

// ── Gutachten referral detail ─────────────────────────────────────────────────

const GutachtenReferralDetailPage = ({ referral: r, onBack, partner }) => {
  const [lightboxOpen, setLightboxOpen] = React.useState(false);
  const [showDecline, setShowDecline]   = React.useState(false);
  const [declining, setDeclining]       = React.useState(false);
  const [declineError, setDeclineError] = React.useState(null);
  const [showAccept, setShowAccept]       = React.useState(false);
  const [notes, setNotes]                 = React.useState("");
  const [estimatedDays, setEstimatedDays] = React.useState("");
  const [slots, setSlots] = React.useState([
    { date: "", from: "", to: "" },
    { date: "", from: "", to: "" },
    { date: "", from: "", to: "" },
  ]);
  const updateSlot = (i, field, val) =>
    setSlots(prev => prev.map((s, idx) => idx === i ? { ...s, [field]: val } : s));
  const [submitting, setSubmitting] = React.useState(false);
  const [submitError, setSubmitError] = React.useState(null);
  const [status, setStatus] = React.useState(r.status);

  const validSlots = slots.filter(s => s.date && s.from && s.to);
  const acceptValid = String(estimatedDays).trim() && Number(estimatedDays) > 0 && validSlots.length >= 1;

  const handleAccept = async () => {
    if (!acceptValid || submitting) return;
    setSubmitting(true);
    setSubmitError(null);
    try {
      const toTime = (t) => (/^\d{2}:\d{2}$/.test(t) ? `${t}:00` : t); // HH:mm → HH:mm:ss
      await OK_API.acceptReferral(r.id, {
        estimatedDays: parseInt(estimatedDays, 10),
        notes: notes.trim(),
        slots: validSlots.map(s => ({ date: s.date, fromTime: toTime(s.from), toTime: toTime(s.to) })),
      });
      setShowAccept(false);
      setStatus("offersent");
      r.status = "offersent"; // keep the underlying referral in sync for the list
    } catch (e) {
      setSubmitError(e.message || t("PartnerDash.errAcceptCase"));
    } finally {
      setSubmitting(false);
    }
  };

  const handleDecline = async () => {
    if (declining) return;
    setDeclining(true);
    setDeclineError(null);
    try {
      await OK_API.declineReferral(r.id);
      r.status = "declined"; // keep the underlying referral in sync for the list
      setShowDecline(false);
      onBack();              // return to the referral list
    } catch (e) {
      setDeclineError(e.message || t("PartnerDash.errDeclineCase"));
    } finally {
      setDeclining(false);
    }
  };

  const { insp, loading: inspLoading, error: inspError } = useInspection(r.inspectionId);
  const { damages, vehicle, vin, location, parts, damageCount, mainImage } = deriveReferralView(insp);

  return (
    <div style={{ minHeight: "100%", background: "#F5F4F1", display: "flex", flexDirection: "column" }}>

      {/* Lightbox */}
      {lightboxOpen && <DashLightbox image={mainImage} onClose={() => setLightboxOpen(false)} />}

      {/* Decline confirmation */}
      {showDecline && (
        <DashDeclineModal
          title={t("PartnerDash.declineCaseTitle")}
          confirmLabel={t("PartnerDash.declineCaseConfirm")}
          busy={declining}
          error={declineError}
          onCancel={() => setShowDecline(false)}
          onConfirm={handleDecline}
        >
          {t("PartnerDash.declineCaseBody")} <strong style={{ color: "var(--ink)", fontWeight: 600 }}>{t("PartnerDash.cannotBeUndoneShort")}</strong>
        </DashDeclineModal>
      )}

      {/* Accept case overlay */}
      {showAccept && (
        <div onClick={() => setShowAccept(false)} style={dashOverlay}>
          <div onClick={e => e.stopPropagation()} style={{ background: "#fff", borderRadius: 20, padding: "36px 40px", maxWidth: 480, width: "100%", boxShadow: "0 24px 64px rgba(10,10,11,.18)" }}>
            <div style={{ fontSize: 20, fontWeight: 700, color: "var(--ink)", letterSpacing: "-0.03em", marginBottom: 4 }}>{t("PartnerDash.acceptCaseTitle")}</div>
            <div style={{ fontSize: 13.5, color: "var(--mute)", marginBottom: 24 }}>
              {t("PartnerDash.acceptCaseSubtitle", { vehicle: r.vehicle })}
            </div>

            <div style={{ marginBottom: 18 }}>
              <label style={{ display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.09em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 7 }}>{t("PartnerDash.estimatedTimeDays")}</label>
              <input
                type="number" min="1" data-testid="accept-est-days" placeholder={t("PartnerDash.phEstDays")}
                value={estimatedDays} onChange={e => setEstimatedDays(e.target.value)}
                style={dashModalInput}
              />
            </div>

            <div style={{ marginBottom: 18 }}>
              <label style={{ display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.09em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 9 }}>{t("PartnerDash.availableAppointmentTimes")}</label>
              <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                {slots.map((slot, i) => (
                  <div key={i} style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 8, alignItems: "center" }}>
                    <div style={{ position: "relative" }}>
                      <input
                        type="date" value={slot.date}
                        onChange={e => updateSlot(i, "date", e.target.value)}
                        style={{ width: "100%", height: 40, borderRadius: 9, border: "1.5px solid rgba(10,10,11,.15)", background: "#FAFAF9", padding: "0 10px", fontSize: 13, fontFamily: "var(--sans)", color: slot.date ? "var(--ink)" : "var(--mute)", outline: "none", boxSizing: "border-box" }}
                      />
                    </div>
                    <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                      <input
                        type="time" value={slot.from}
                        onChange={e => updateSlot(i, "from", e.target.value)}
                        style={{ flex: 1, height: 40, borderRadius: 9, border: "1.5px solid rgba(10,10,11,.15)", background: "#FAFAF9", padding: "0 10px", fontSize: 13, fontFamily: "var(--sans)", color: slot.from ? "var(--ink)" : "var(--mute)", outline: "none", boxSizing: "border-box" }}
                      />
                    </div>
                    <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                      <span style={{ fontSize: 12, color: "var(--mute)", flexShrink: 0 }}>{t("PartnerDash.to")}</span>
                      <input
                        type="time" value={slot.to}
                        onChange={e => updateSlot(i, "to", e.target.value)}
                        style={{ flex: 1, height: 40, borderRadius: 9, border: "1.5px solid rgba(10,10,11,.15)", background: "#FAFAF9", padding: "0 10px", fontSize: 13, fontFamily: "var(--sans)", color: slot.to ? "var(--ink)" : "var(--mute)", outline: "none", boxSizing: "border-box" }}
                      />
                    </div>
                  </div>
                ))}
              </div>
            </div>

            <div style={{ marginBottom: 20 }}>
              <label style={{ display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.09em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 7 }}>{t("PartnerDash.notesLabel")} <span style={{ fontWeight: 400, textTransform: "none", letterSpacing: 0, opacity: 0.7 }}>{t("PartnerDash.optionalParens")}</span></label>
              <textarea
                value={notes} onChange={e => setNotes(e.target.value)}
                placeholder={t("PartnerDash.notesPlaceholder")}
                rows={3}
                style={{ width: "100%", borderRadius: 10, border: "1.5px solid rgba(10,10,11,.15)", background: "#FAFAF9", padding: "11px 14px", fontSize: 14, fontFamily: "var(--sans)", color: "var(--ink)", outline: "none", resize: "vertical", boxSizing: "border-box" }}
              />
            </div>

            <p style={{ margin: "0 0 14px", fontSize: 13, color: "var(--mute)", lineHeight: 1.55 }}>
              {t("PartnerDash.notifyCustomer")}
            </p>
            {submitError && (
              <div style={{ ...dashErrorBox, marginBottom: 14 }}>
                {submitError}
              </div>
            )}
            <div style={{ display: "flex", gap: 10 }}>
              <button onClick={() => setShowAccept(false)} disabled={submitting} style={{ flex: 1, height: 46, borderRadius: 10, background: "rgba(10,10,11,.05)", border: "1px solid rgba(10,10,11,.1)", fontSize: 14, fontWeight: 500, color: "var(--ink)", fontFamily: "var(--sans)", cursor: submitting ? "default" : "pointer" }}>{t("PartnerDash.cancel")}</button>
              <button onClick={handleAccept} data-testid="accept-confirm" disabled={submitting || !acceptValid} style={{ flex: 2, height: 46, borderRadius: 10, background: "var(--accent)", border: "none", fontSize: 14, fontWeight: 600, color: "#fff", fontFamily: "var(--sans)", cursor: (submitting || !acceptValid) ? "default" : "pointer", opacity: (submitting || !acceptValid) ? 0.6 : 1 }}>{submitting ? t("PartnerDash.submitting") : t("PartnerDash.confirm")}</button>
            </div>
          </div>
        </div>
      )}

      {/* Header */}
      <DashDetailHeader onBack={onBack} partner={partner} />

      {/* Body */}
      <div style={{ flex: 1, padding: "32px 64px" }}>
        <div style={{ display: "grid", gridTemplateColumns: "5fr 7fr", gap: "0 80px", marginBottom: 40, alignItems: "stretch" }}>

          {/* Left column */}
          <div style={{ display: "flex", flexDirection: "column" }}>
            <div style={{ display: "grid", gridTemplateColumns: "130px 1fr", rowGap: 16, fontSize: 17, marginBottom: 20 }}>
              <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelCaseId")}</span>
              <span style={{ color: "var(--ink)", fontWeight: 500, letterSpacing: "-0.01em" }}>{r.id}</span>
              {r.inspectionId && <>
                <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelInspection")}</span>
                <span style={{ color: "var(--ink)", fontWeight: 500, letterSpacing: "-0.01em", wordBreak: "break-all" }}>{r.inspectionId}</span>
              </>}
              <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelVehicle")}</span>
              <span style={{ color: "var(--ink)", fontWeight: 500, letterSpacing: "-0.01em" }}>{vehicle}</span>
              <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelVin")}</span>
              <span style={{ color: "var(--ink)" }}>{vin}</span>
              <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelLocationDetail")}</span>
              <span style={{ color: "var(--ink)" }}>{location}</span>
              <span style={{ color: "var(--mute)" }}>{t("PartnerDash.labelReceived")}</span>
              <span style={{ color: "var(--ink)" }}>{fmtDate(r.ts)}</span>
            </div>

            <div style={{ width: "80%", marginBottom: 20 }}>
              {insp && insp.diagram
                ? <img src={OK_API.diagramUrl(insp.diagram)} alt={t("PartnerDash.vehicleDiagramAlt")} style={{ width: "100%", display: "block", maxHeight: 200, objectFit: "contain", margin: "0 auto" }} />
                : <VehicleDiagram damage={r.damage} />}
            </div>

            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, marginBottom: 10 }}>
              <div style={{ background: "#fff", border: "1px solid rgba(10,10,11,.08)", borderRadius: 12, padding: "12px 16px", boxShadow: "0 1px 3px rgba(10,10,11,.04)" }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 6 }}>{t("PartnerDash.labelParts")}</div>
                <div style={{ fontSize: 15, fontWeight: 500, color: "var(--ink)", lineHeight: 1.45 }}>{parts}</div>
              </div>
              <div style={{ background: "#fff", border: "1px solid rgba(10,10,11,.08)", borderRadius: 12, padding: "12px 16px", boxShadow: "0 1px 3px rgba(10,10,11,.04)" }}>
                <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 5 }}>{t("PartnerDash.labelDamages")}</div>
                <div style={{ fontSize: 28, fontWeight: 700, color: "var(--ink)", letterSpacing: "-0.04em" }}>{damageCount}</div>
              </div>
            </div>

            <div style={{ background: "#fff", border: "1px solid rgba(10,10,11,.08)", borderRadius: 12, padding: "12px 16px", boxShadow: "0 1px 3px rgba(10,10,11,.04)", marginBottom: 12 }}>
              <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 6 }}>{t("PartnerDash.labelDamageDescription")}</div>
              <div style={{ fontSize: 15, color: "var(--ink)", lineHeight: 1.6 }}>
                {insp ? (insp.estimateDescription || "—") : "—"}
              </div>
            </div>

            {(() => {
              const hasData = !!insp;
              const allOps = damages.flatMap(d => d.ops);
              const mockCost = allOps.reduce((a, op) => a + parseFloat(op.total.replace("€", "")), 0);
              const cost = insp
                ? (typeof insp.estimatedCost === "number" ? insp.estimatedCost : 0)
                : mockCost;
              return (
                <div style={{ background: "#F0FDF4", border: "1px solid #BBF7D0", borderRadius: 14, padding: "16px 18px", marginBottom: 28 }}>
                  <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.1em", textTransform: "uppercase", color: "#15803D", opacity: 0.7, marginBottom: 8 }}>{t("PartnerDash.statRepairCost")}</div>
                  <div style={{ fontSize: 32, fontWeight: 800, color: "#166534", letterSpacing: "-0.05em", lineHeight: 1 }}>{hasData ? `€${cost.toLocaleString("de-DE")}` : "—"}</div>
                </div>
              );
            })()}

            {(status === "new" || status === "reviewed") ? (
              <div style={{ display: "flex", gap: 10 }}>
                <button onClick={() => setShowAccept(true)} data-testid="detail-action" style={{ flex: 1, height: 46, borderRadius: 10, background: "var(--accent)", color: "#fff", border: "none", fontSize: 14, fontWeight: 600, fontFamily: "var(--sans)", cursor: "pointer", letterSpacing: "-0.01em" }}>{t("PartnerDash.btnAcceptCase")}</button>
                <button onClick={() => { setDeclineError(null); setShowDecline(true); }} data-testid="detail-decline" style={{ height: 46, padding: "0 32px", borderRadius: 10, background: "transparent", color: "rgba(10,10,11,.55)", border: "1px solid rgba(10,10,11,.18)", fontSize: 14, fontWeight: 500, fontFamily: "var(--sans)", cursor: "pointer" }}>{t("PartnerDash.btnDecline")}</button>
              </div>
            ) : (
              <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", background: "#fff", border: "1px solid rgba(10,10,11,.08)", borderRadius: 12, padding: "15px 20px", boxShadow: "0 1px 3px rgba(10,10,11,.04)" }}>
                <span data-testid="detail-status-heading" style={{ fontSize: 15, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.02em" }}>{t("PartnerDash.caseStatus")}</span>
                <DashBadge status={status} />
              </div>
            )}
          </div>

          {/* Right: main photo */}
          <div style={{ display: "flex", flexDirection: "column" }}>
            <DashMainPhoto image={mainImage} onOpen={() => setLightboxOpen(true)} />
          </div>
        </div>

        <div style={{ height: 1, background: "rgba(10,10,11,.07)", marginBottom: 32 }} />

        {/* Gutachten damage cards */}
        <DashDamageList loading={inspLoading} error={inspError} damages={damages} Card={GutachtenDamageCard} />
      </div>
    </div>
  );
};

// ── Referrals tab ─────────────────────────────────────────────────────────────

const DashReferrals = ({ viewMode, onViewModeChange, filter = "all", onFilter, profile, stats, refreshStats, onTab, referralId, onSelectReferral, onBackToList }) => {
  const setFilter = onFilter || (() => {}); // filter is URL-driven (/referrals#<status>)
  const goBack = onBackToList || (() => {});
  const FILTERS = ["all", ...REFERRAL_STATUS_VALUES];
  const PAGE_SIZE = 20;

  // Server-side pagination: `items` holds one page of referrals; the pager is
  // driven by totalCount/totalPages from the API. `items === null` means not yet
  // loaded → the table shows its loading skeleton.
  const [page, setPage]             = React.useState(1);
  const [items, setItems]           = React.useState(null);
  const [totalCount, setTotalCount] = React.useState(0);
  const [totalPages, setTotalPages] = React.useState(1);
  const [loading, setLoading]       = React.useState(OK_API.isSignedIn());
  const [loadError, setLoadError]   = React.useState(null);

  // Snap back to page 1 when the filter changes (render-phase adjustment, so the
  // fetch effect below runs exactly once with the correct page+filter pair).
  const [pagedFilter, setPagedFilter] = React.useState(filter);
  if (filter !== pagedFilter) { setPagedFilter(filter); setPage(1); }

  // Fetch one page whenever page or filter changes (the server filters by Status),
  // or when refreshNonce is bumped on returning from a referral detail. loadedFilter
  // records which filter the current `items` were fetched with — set only when a
  // response lands, so it lags the `filter` prop during the pre-fetch render window.
  const [refreshNonce, setRefreshNonce] = React.useState(0);
  const [loadedFilter, setLoadedFilter] = React.useState(null);
  React.useEffect(() => {
    if (!OK_API.isSignedIn()) { setLoading(false); return; }
    let alive = true;
    setLoading(true);
    OK_API.getReferrals({ page, pageSize: PAGE_SIZE, status: FILTER_TO_API_STATUS[filter] })
      .then((res) => {
        if (!alive) return;
        setItems(res.items.map(mapApiReferral));
        setTotalCount(res.totalCount);
        setTotalPages(res.totalPages);
        setLoadedFilter(filter);
        setLoadError(null);
        setLoading(false);
      })
      .catch((e) => { if (alive) { setLoadError(e.message); setLoading(false); } });
    return () => { alive = false; };
  }, [page, filter, refreshNonce]);

  const data = items || [];

  // Referral selection is URL-driven (/referral/<id>). Resolve from the loaded
  // list once loading settles; if the id isn't there, fetch it directly so deep
  // links and refreshes still work.
  const inList = (referralId && !loading) ? (data.find(r => r.id === referralId) || null) : null;
  const [fetched, setFetched] = React.useState(null);
  const [fetchErr, setFetchErr] = React.useState(null);
  React.useEffect(() => {
    setFetched(null); setFetchErr(null);
    if (!referralId || inList || loading) return;
    let alive = true;
    OK_API.getReferral(referralId)
      .then(r => { if (alive) setFetched(mapApiReferral(r)); })
      .catch(e => { if (alive) setFetchErr(e.message || "Not found"); });
    return () => { alive = false; };
  }, [referralId, inList, loading]);

  // The referral being viewed (list item or deep-link fetch); null on the list view.
  const selected = referralId ? (inList || fetched) : null;

  // Opening a new referral marks it reviewed server-side. On success, patch the
  // object in place (the same sync pattern the detail pages use for offersent/
  // declined, keeping the list row's badge current) and refresh the stats so the
  // sidebar's new-count drops while the detail is still open. Best-effort: on
  // error the referral simply stays "new". No cleanup guard — even if the partner
  // navigates away mid-flight, the server has changed, so the patch and refresh
  // must still land.
  React.useEffect(() => {
    if (!selected || selected.status !== "new") return;
    OK_API.markReferralReviewed(selected.id)
      .then(() => { selected.status = "reviewed"; refreshStats(); })
      .catch(() => {});
  }, [selected, refreshStats]);

  // Returning from a detail to the list (Back button or browser back/forward)
  // re-fetches the stats and the current list page, so the cards and rows reflect
  // whatever changed while the detail was open (review, offer, decline).
  const prevReferralId = React.useRef(referralId);
  React.useEffect(() => {
    const wasDetail = !!prevReferralId.current;
    prevReferralId.current = referralId;
    if (wasDetail && !referralId) {
      refreshStats();
      setRefreshNonce((n) => n + 1);
    }
  }, [referralId, refreshStats]);

  if (referralId) {
    if (selected) {
      return viewMode === "gutachten"
        ? <GutachtenReferralDetailPage referral={selected} onBack={goBack} partner={profile} />
        : <WorkshopReferralDetailPage  referral={selected} onBack={goBack} partner={profile} />;
    }
    const wrap = (inner) => (
      <div style={{ minHeight: "100%", background: "#F5F4F1", padding: "32px 64px" }}>{inner}</div>
    );
    if (loading || (!fetched && !fetchErr)) {
      return wrap(
        <div style={{ background: "#fff", borderRadius: 16, border: "1px solid rgba(10,10,11,.07)", padding: "60px 0", textAlign: "center", color: "var(--mute)", fontSize: 14 }}>
          {t("PartnerDash.loadingReferral")}
        </div>
      );
    }
    return (
      <div style={{
        minHeight: "100vh", background: "#F5F4F1", padding: 28,
        display: "flex", alignItems: "center", justifyContent: "center",
      }}>
        <div className="card" style={{
          maxWidth: 440, width: "100%",
          padding: "44px 36px",
          textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center", gap: 18,
          boxShadow: "var(--shadow-md)",
        }}>
          <div style={{
            width: 56, height: 56, borderRadius: 16,
            background: "#FFF1F2", color: "#BE123C",
            display: "grid", placeItems: "center",
            fontSize: 26, fontWeight: 700, fontFamily: "var(--sans)", lineHeight: 1,
          }}>!</div>
          <div>
            <div style={{ fontSize: 21, fontWeight: 700, letterSpacing: "-0.02em", marginBottom: 8 }}>{t("PartnerDash.referralNotFound")}</div>
            <p style={{ margin: 0, fontSize: 14.5, color: "var(--ink-2)", lineHeight: 1.6, maxWidth: "34ch" }}>{t("PartnerDash.referralNotFoundBody")}</p>
          </div>
          <button className="btn btn-primary" onClick={goBack} style={{ marginTop: 4, justifyContent: "center" }}>
            {t("PartnerDash.backToReferralsList")} <Ic.arrow width="14" height="14" />
          </button>
        </div>
      </div>
    );
  }

  // The server has already filtered by status, so render the page as-is.
  const rows = data;

  return (
    <DashPage title={t("PartnerDash.tabReferrals")} viewMode={viewMode} onViewModeChange={onViewModeChange} partner={profile}>
      {/* Aggregate stats from GET /v1/partners/stats. Real values once loaded;
          neutral em-dash placeholders while stats are in flight (or errored). */}
      {(() => {
        const grid = (cards) => (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 16, marginBottom: 24 }}>{cards}</div>
        );
        if (stats) {
          // Guard against partial/empty responses (e.g. a partner with no jobs
          // yet → null averageJobValue) so cards show "—" instead of "€NaN".
          const num = (v) => (typeof v === "number" && !Number.isNaN(v) ? v : "—");
          const eur = (v) => (typeof v === "number" && !Number.isNaN(v) ? `€${Math.round(v).toLocaleString("de-DE")}` : "—");
          return grid([
            <DashStat key="new"   label={t("PartnerDash.statNewReferrals")}   value={num(stats.newReferrals)} sub={t("PartnerDash.statNewReferralsSub")}   green />,
            <DashStat key="open"  label={t("PartnerDash.statOpenReferrals")}  value={num(stats.openReferrals)}         sub={t("PartnerDash.statOpenReferralsSub")} />,
            <DashStat key="avg"   label={t("PartnerDash.statAvgJob")}         value={eur(stats.averageJobValue)}       sub={t("PartnerDash.statAvgJobSub")} />,
            <DashStat key="total" label={t("PartnerDash.statTotalReferrals")} value={num(stats.totalReferrals)}        sub={t("PartnerDash.statTotalReferralsSub")} />,
          ]);
        }
        // Stats not loaded yet (or non-critical fetch error) → neutral placeholders.
        return grid([
          <DashStat key="new"   label={t("PartnerDash.statNewReferrals")}   value="—" sub={t("PartnerDash.statNewReferralsSub")}   green />,
          <DashStat key="open"  label={t("PartnerDash.statOpenReferrals")}  value="—" sub={t("PartnerDash.statOpenReferralsSub")} />,
          <DashStat key="avg"   label={t("PartnerDash.statAvgJob")}         value="—" sub={t("PartnerDash.statAvgJobSub")} />,
          <DashStat key="total" label={t("PartnerDash.statTotalReferrals")} value="—" sub={t("PartnerDash.statTotalReferralsSub")} />,
        ]);
      })()}

      {loadError && (
        <div style={{
          display: "flex", alignItems: "flex-start", gap: 11,
          background: "#FFF1F2", border: "1px solid #FECDD3",
          borderRadius: 12, padding: "12px 16px", marginBottom: 20,
          fontSize: 13.5, color: "#BE123C", lineHeight: 1.6,
        }}>
          {t("PartnerDash.errLoadReferrals", { error: loadError })}
        </div>
      )}

      {(() => {
        // Onboarding/verification status banner — shown above the 48-hour notice.
        // Hidden when status is unknown (still loading) and for verified accounts.
        const onb = partnerOnboarding(profile);
        if (!onb.known || onb.verified) return null;

        if (onb.onboardingComplete) {
          // 1,2,4,8 all set but not verified → PendingVerification.
          return (
            <div style={{ display: "flex", alignItems: "flex-start", gap: 11, background: "#FFFBEB", border: "1px solid #FDE68A", borderRadius: 12, padding: "12px 16px", marginBottom: 20 }}>
              <svg viewBox="0 0 24 24" fill="none" stroke="#D97706" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" width="16" height="16" style={{ flexShrink: 0, marginTop: 2 }}>
                <circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/>
              </svg>
              <div data-testid="under-review-banner" style={{ fontSize: 13.5, color: "#92400E", lineHeight: 1.6 }}>
                <strong>{t("PartnerDash.profileUnderReview")}</strong>{" "}
                {t("PartnerDash.profileUnderReviewSub")}
              </div>
            </div>
          );
        }

        // Onboarding incomplete → tell the partner what's outstanding and where to go.
        const needDocs = !onb.profileInfo || !onb.registration || !onb.certifications;
        const needPay  = !onb.payment;
        const missing = [];
        if (!onb.profileInfo)    missing.push(t("PartnerDash.missingBusinessDetails"));
        if (!onb.registration)   missing.push(t("PartnerDash.missingBusinessReg"));
        if (!onb.certifications) missing.push(t("PartnerDash.missingCertifications"));
        if (!onb.payment)        missing.push(t("PartnerDash.missingPaymentMethod"));
        const list = missing.length > 1
          ? missing.slice(0, -1).join(", ") + " and " + missing[missing.length - 1]
          : missing[0];
        const primaryBtn = { appearance: "none", fontFamily: "var(--sans)", cursor: "pointer", height: 34, padding: "0 14px", borderRadius: 8, background: "var(--accent)", color: "#fff", border: "none", fontSize: 13, fontWeight: 600, letterSpacing: "-0.01em" };
        const outlineBtn = { appearance: "none", fontFamily: "var(--sans)", cursor: "pointer", height: 34, padding: "0 14px", borderRadius: 8, background: "#fff", color: "#9A3412", border: "1px solid #FED7AA", fontSize: 13, fontWeight: 600 };
        return (
          <div style={{ display: "flex", alignItems: "flex-start", gap: 11, background: "#FFF7ED", border: "1px solid #FED7AA", borderRadius: 12, padding: "12px 16px", marginBottom: 20 }}>
            <svg viewBox="0 0 24 24" fill="none" stroke="#EA580C" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" width="16" height="16" style={{ flexShrink: 0, marginTop: 2 }}>
              <path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><path d="M12 9v4"/><path d="M12 17h.01"/>
            </svg>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13.5, color: "#9A3412", lineHeight: 1.6 }}>
                <strong>{t("PartnerDash.finishSetup")}</strong>{" "}
                {t("PartnerDash.finishSetupNeed", { list })}
              </div>
              <div style={{ display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap" }}>
                {needDocs && <button type="button" onClick={() => onTab && onTab("settings")} style={primaryBtn}>{t("PartnerDash.completeInSettings")}</button>}
                {needPay  && <button type="button" onClick={() => onTab && onTab("payments")} style={needDocs ? outlineBtn : primaryBtn}>{t("PartnerDash.addPaymentMethod")}</button>}
              </div>
            </div>
          </div>
        );
      })()}

      <div style={{
        display: "flex", alignItems: "flex-start", gap: 11,
        background: "#EFF6FF", border: "1px solid rgba(29,78,216,.12)",
        borderRadius: 12, padding: "12px 16px", marginBottom: 20,
      }}>
        <svg viewBox="0 0 16 16" fill="none" stroke="#3B82F6" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" width="15" height="15" style={{ flexShrink: 0, marginTop: 2 }}>
          <circle cx="8" cy="8" r="6.5"/>
          <path d="M8 7.5v3M8 5.2v.3"/>
        </svg>
        <div style={{ fontSize: 13.5, color: "#1D4ED8", lineHeight: 1.6 }}>
          <strong>{t("PartnerDash.responseWindowTitle")}</strong>{" "}
          {t("PartnerDash.responseWindowBody")}
        </div>
      </div>

      <div style={{ display: "flex", gap: 6, marginBottom: 16 }}>
        {FILTERS.map(f => {
          const on = filter === f;
          return (
            <button key={f} onClick={() => setFilter(f)}
              data-testid={"filter-" + f} data-active={on ? "true" : "false"}
              style={{
                appearance: "none", fontFamily: "var(--sans)", cursor: "default",
                padding: "6px 14px", borderRadius: 20,
                background: on ? "var(--accent)" : "#fff",
                color: on ? "#fff" : "rgba(10,10,11,.55)",
                border: on ? "1px solid transparent" : "1px solid rgba(10,10,11,.12)",
                fontSize: 12.5, fontWeight: on ? 600 : 500,
                transition: "all .12s",
              }}>
              {f === "all" ? t("PartnerDash.filterAll") : t("PartnerDash.status_" + f)}
            </button>
          );
        })}
      </div>

      {loading ? (
        <div data-testid="referrals-loading" style={{
          background: "#fff", borderRadius: 16, border: "1px solid rgba(10,10,11,.07)",
          padding: "60px 0", textAlign: "center", color: "var(--mute)", fontSize: 14,
        }}>
          {t("PartnerDash.loadingReferrals")}
        </div>
      ) : (
        // data-filter records which server-side filter this content was loaded for
        // (loadedFilter, not the live prop — the prop flips a render before the fetch
        // even starts), so tests can wait for the right list instead of racing it.
        <div data-testid="referrals-list" data-filter={loadedFilter}>
          {rows.length > 0
            ? <DashRefTable rows={rows} onSelect={(r) => onSelectReferral && onSelectReferral(r.id)} />
            : (
              <div style={{
                background: "#fff", borderRadius: 16, border: "1px solid rgba(10,10,11,.07)",
                padding: "60px 0", textAlign: "center", color: "var(--mute)", fontSize: 14,
              }}>
                {t("PartnerDash.noReferrals")}
              </div>
            )}
        </div>
      )}

      {items && <DashPager page={page} totalPages={totalPages} totalCount={totalCount} pageSize={PAGE_SIZE} onPage={setPage} />}
    </DashPage>
  );
};

// ── Payments tab ──────────────────────────────────────────────────────────────

const DashPayments = ({ viewMode, onViewModeChange, profile }) => {
  // Invoice history from GET /v1/partner/billing/invoices (Stripe). `invoices`
  // is null until loaded; an error is kept separately so the expected
  // "billing not set up yet" 400 can render as an empty state rather than a failure.
  const [invoices, setInvoices]               = React.useState(null);
  const [invoicesError, setInvoicesError]     = React.useState(null);
  const [invoicesLoading, setInvoicesLoading] = React.useState(OK_API.isSignedIn());
  React.useEffect(() => {
    if (!OK_API.isSignedIn()) { setInvoicesLoading(false); return; }
    let alive = true;
    OK_API.getInvoices({ limit: 20 })
      .then((rows) => { if (alive) { setInvoices(Array.isArray(rows) ? rows : []); setInvoicesError(null); setInvoicesLoading(false); } })
      .catch((e) => { if (alive) { setInvoicesError(e); setInvoicesLoading(false); } });
    return () => { alive = false; };
  }, []);

  // Payment-method details for the visual from GET /v1/partner/billing/payment-method.
  // `card` holds the PaymentMethodSummary on a 200; it stays null on a 400
  // (Billing.NotConfigured) or any error. This payload is display-only — some methods
  // (e.g. Klarna) come back 200 with no card/wallet/Link fields, so it can't decide
  // whether a method exists; that's done from the partner status flag (see hasMethod).
  // Card holder isn't in the payload — we source it from the profile.
  const [card, setCard]                   = React.useState(null);
  const [methodLoading, setMethodLoading] = React.useState(OK_API.isSignedIn());
  React.useEffect(() => {
    if (!OK_API.isSignedIn()) { setMethodLoading(false); return; }
    let alive = true;
    OK_API.getPaymentMethod()
      .then((pm) => { if (alive) { setCard(pm); setMethodLoading(false); } })
      .catch(() => { if (alive) setMethodLoading(false); });
    return () => { alive = false; };
  }, []);
  // Whether a payment method is set is authoritative from the partner's onboarding
  // status (the PaymentMethodCreated bit in GET /v1/partner/me) — the billing payload
  // alone can't tell, since methods like Klarna return 200 with no card details.
  const onboarding = partnerOnboarding(profile);
  const hasMethod  = onboarding.payment;

  // Next auto-charge from GET /v1/partner/billing/upcoming-charge. Null until loaded;
  // a 400 (billing not set up / nothing scheduled) is swallowed so the row simply
  // doesn't render for that partner rather than showing an error.
  const [upcoming, setUpcoming] = React.useState(null);
  React.useEffect(() => {
    if (!OK_API.isSignedIn()) return;
    let alive = true;
    OK_API.getUpcomingCharge()
      .then((u) => { if (alive) setUpcoming(u); })
      .catch(() => {});
    return () => { alive = false; };
  }, []);

  // Both billing actions hand off to a Stripe-hosted page and bring the partner back
  // afterwards: "Manage Payment Methods" → Customer Portal, "Add Payment Method" →
  // Checkout/setup session. They share one create-session → extract-URL → redirect
  // flow (and one busy/error pair, since only one button is shown at a time). On
  // success we navigate away, so the busy flag is intentionally left set.
  const [sessionBusy, setSessionBusy]   = React.useState(false);
  const [sessionError, setSessionError] = React.useState(null);
  const startSession = async (apiCall, failMsg) => {
    if (sessionBusy) return;
    setSessionBusy(true);
    setSessionError(null);
    try {
      const r = await apiCall();
      const url = OK_API.sessionUrl(r);
      if (!url) throw new Error("No redirect URL returned.");
      window.location.assign(url);
    } catch (e) {
      setSessionError(e.message || failMsg);
      setSessionBusy(false);
    }
  };
  const manageMethods = () => startSession(OK_API.createBillingPortalSession, t("PartnerDash.errBillingPortal"));
  const addMethod     = () => startSession(OK_API.createSetupSession, t("PartnerDash.errPaymentSetup"));

  const signedIn = OK_API.isSignedIn();

  // Title-case a token list — humanizes unmapped wallet / method / brand values.
  const toTitleCase = (s) => String(s || "").replace(/\b\w/g, (c) => c.toUpperCase());
  // Friendly label for Stripe's snake_case wallet types (e.g. "apple_pay" → "Apple Pay").
  const walletName = (w) => ({ apple_pay: "Apple Pay", google_pay: "Google Pay", link: "Stripe Link" }[w]
    || (w ? toTitleCase(w.replace(/_/g, " ")) : "Wallet"));
  // Friendly label for a method `type` when there are no card details to show (e.g. Klarna).
  const methodTypeName = (t) => ({ card: "Card", klarna: "Klarna", sepa_debit: "SEPA Direct Debit", paypal: "PayPal", link: "Stripe Link" }[t]
    || (t ? toTitleCase(t.replace(/_/g, " ")) : "Payment method"));

  // Card brand → display name + official logo asset (downloaded into /assets/brands/).
  // Stripe returns canonical lowercase brand tokens; unknown values fall back to a
  // title-cased name + the generic art so the <img> never 404s. Paths are absolute per
  // the CLAUDE.md multi-segment-route trap.
  const CARD_BRANDS   = { visa: "Visa", mastercard: "Mastercard", amex: "American Express", discover: "Discover", diners: "Diners Club", jcb: "JCB", unionpay: "UnionPay", maestro: "Maestro" };
  const cardBrandKey  = (b) => String(b || "").toLowerCase();
  const cardBrandName = (b) => CARD_BRANDS[cardBrandKey(b)] || (b ? toTitleCase(String(b)) : "Card");
  const cardBrandLogo = (b) => { const k = cardBrandKey(b); return `/assets/brands/${CARD_BRANDS[k] ? k : "generic"}.svg`; };

  // Describe a payment method as a row — { logo, title, sub }. A card (type === "card")
  // gets its brand logo + "Brand •••• last4" + expiry; everything else (wallet, Stripe
  // Link, Klarna, …) gets a generic glyph + its method label. No card holder: we never
  // have the real name (it's only ever the business name).
  const describeMethod = (pm) => {
    pm = pm || {};
    if (pm.type === "card") {
      const brandName = cardBrandName(pm.brand);
      const exp = (pm.expMonth && pm.expYear) ? `${String(pm.expMonth).padStart(2, "0")}/${pm.expYear}` : null;
      return {
        logo: <img src={cardBrandLogo(pm.brand)} alt={brandName} height="26"
                   style={{ height: 26, width: "auto", borderRadius: 4, boxShadow: "0 0 0 1px rgba(10,10,11,.08)", flexShrink: 0 }} />,
        title: `${brandName} •••• ${pm.last4 || "••••"}`,
        sub: exp ? t("PartnerDash.cardExpires", { exp }) : null,
      };
    }
    let title, sub;
    if (pm.linkEmail)   { title = "Stripe Link";           sub = pm.linkEmail; }
    else if (pm.wallet) { title = walletName(pm.wallet);   sub = pm.last4 ? `•••• ${pm.last4}` : null; }
    else                { title = methodTypeName(pm.type); sub = null; }
    return {
      logo: <div style={{ width: 40, height: 26, borderRadius: 4, background: "var(--bg-2)", display: "grid", placeItems: "center", color: "var(--mute)", flexShrink: 0 }}><SvgCard /></div>,
      title, sub,
    };
  };

  // Shared compact method-row layout: logo slot + title + optional sub-line.
  const renderMethodRow = ({ logo, title, sub }) => (
    <div style={{ display: "flex", alignItems: "center", gap: 13 }}>
      {logo}
      <div style={{ minWidth: 0 }}>
        <div style={{ fontSize: 14, fontWeight: 600, color: "var(--ink)" }}>{title}</div>
        {sub && <div style={{ fontSize: 12.5, color: "var(--mute)", marginTop: 2, wordBreak: "break-all" }}>{sub}</div>}
      </div>
    </div>
  );

  // Billing-card body by state: loading note while status/details load → method visual
  // when one is on file → clean empty state (no fake card) otherwise.
  const methodNote = (text) => (
    <div style={{ padding: "26px 6px", textAlign: "center", color: "var(--mute)", fontSize: 13.5, lineHeight: 1.6 }}>{text}</div>
  );
  const methodBody = !onboarding.known
    ? methodNote(t("PartnerDash.loadingPaymentMethod"))
    : !hasMethod
      ? methodNote(t("PartnerDash.noPaymentMethod"))
      : methodLoading
        ? methodNote(t("PartnerDash.loadingPaymentMethod"))
        : renderMethodRow(describeMethod(card));

  // "Next charge" summary row, rendered when an upcoming charge is scheduled.
  const nextChargeRow = (dateText, amountText) => (
    <div style={{ marginTop: 14, padding: "13px 16px", borderRadius: 10, background: "#F5F4F1", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
      <div>
        <div style={{ fontSize: 13, fontWeight: 500, color: "var(--ink)" }}>{t("PartnerDash.nextCharge")}</div>
        <div style={{ fontSize: 12, color: "var(--mute)", marginTop: 2 }}>{t("PartnerDash.autoBilledOn", { date: dateText })}</div>
      </div>
      <div style={{ fontSize: 18, fontWeight: 800, color: "var(--ink)", letterSpacing: "-0.04em" }}>{amountText}</div>
    </div>
  );

  return (
    <DashPage title={t("PartnerDash.tabPayments")} viewMode={viewMode} onViewModeChange={onViewModeChange} partner={profile}>

      <div style={{ background: "#fff", borderRadius: 16, border: "1px solid rgba(10,10,11,.07)", padding: "28px 32px", boxShadow: "0 1px 4px rgba(10,10,11,.04)", marginBottom: 20 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 22, fontWeight: 700, color: "var(--ink)", letterSpacing: "-0.03em", marginBottom: 16 }}>{t("PartnerDash.payPerReferral")}</div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
              {[
                t("PartnerDash.tagNoSubscription"),
                t("PartnerDash.tagNoUpfrontCost"),
                t("PartnerDash.tagPayOnly"),
                t("PartnerDash.tagCancelAnytime"),
                t("PartnerDash.tagNoHiddenFees"),
              ].map(tag => (
                <span key={tag} style={{
                  display: "inline-flex", alignItems: "center", gap: 6,
                  padding: "6px 14px", borderRadius: 20,
                  background: "var(--accent-tint)", border: "1px solid rgba(16,161,113,.18)",
                  fontSize: 13.5, fontWeight: 500, color: "var(--accent-ink)",
                }}>
                  <svg viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" width="10" height="10"><path d="M2 6l2.5 2.5L10 3.5"/></svg>
                  {tag}
                </span>
              ))}
            </div>
          </div>
          <div style={{ textAlign: "right", flexShrink: 0, marginLeft: 40 }}>
            <div style={{ fontSize: 52, fontWeight: 800, color: "var(--ink)", letterSpacing: "-0.05em", lineHeight: 1 }}>€9,99</div>
            <div style={{ fontSize: 13, color: "var(--mute)", marginTop: 4 }}>{t("PartnerDash.perReferral")}</div>
          </div>
        </div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "3fr 2fr", gap: 16, marginBottom: 0, alignItems: "start" }}>

        <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>

          {/* Credit card */}
          <div style={{ background: "#fff", borderRadius: 16, border: "1px solid rgba(10,10,11,.07)", padding: "24px 28px", boxShadow: "0 1px 4px rgba(10,10,11,.04)" }}>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 18 }}>
              <div style={{ fontSize: 14, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.02em" }}>{t("PartnerDash.billingCard")}</div>
              {/* The Add / Manage buttons replace the old "Edit card" button; one is shown
                  per state once the method has loaded. */}
              {!onboarding.known ? null : (
                <button
                  onClick={signedIn ? (hasMethod ? manageMethods : addMethod) : undefined}
                  disabled={sessionBusy}
                  style={{ appearance: "none", border: "1px solid rgba(10,10,11,.13)", background: "transparent", borderRadius: 8, padding: "5px 12px", fontSize: 12.5, fontWeight: 500, color: "var(--ink)", fontFamily: "var(--sans)", cursor: signedIn && !sessionBusy ? "pointer" : "default", opacity: sessionBusy ? 0.55 : 1 }}>
                  {sessionBusy ? t("PartnerDash.sessionOpening") : (hasMethod ? t("PartnerDash.managePaymentMethods") : t("PartnerDash.addPaymentMethodBtn"))}
                </button>
              )}
            </div>
            {methodBody}
            {sessionError && (
              <div style={{ marginTop: 10, fontSize: 12, color: "#B42318" }}>{sessionError}</div>
            )}
            {upcoming && typeof upcoming.amountCents === "number"
              ? nextChargeRow(fmtLongDate(upcoming.nextBillingDate), fmtInvoiceAmount(upcoming.amountCents, upcoming.currency))
              : null}
          </div>
        </div>

        {/* Invoice history — real Stripe invoices (GET /v1/partner/billing/invoices). */}
        {(() => {
          const cell = {
            inv:  { padding: "13px 22px", fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em" },
            date: { padding: "13px 22px", color: "var(--mute)" },
            amt:  { padding: "13px 22px", fontSize: 15, fontWeight: 800, color: "var(--ink)", letterSpacing: "-0.03em" },
          };
          const card = (body) => (
            <div style={{ background: "#fff", borderRadius: 16, border: "1px solid rgba(10,10,11,.07)", overflow: "hidden", boxShadow: "0 1px 4px rgba(10,10,11,.04)" }}>
              <div style={{ padding: "18px 22px", borderBottom: "1px solid rgba(10,10,11,.06)" }}>
                <div style={{ fontSize: 14, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.02em" }}>{t("PartnerDash.invoiceHistory")}</div>
              </div>
              {body}
            </div>
          );
          const table = (rows) => (
            <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 14 }}>
              <thead>
                <tr style={{ borderBottom: "1px solid rgba(10,10,11,.07)", background: "#FAFAF8" }}>
                  {[t("PartnerDash.invoiceColInvoice"), t("PartnerDash.invoiceColDate"), t("PartnerDash.invoiceColAmount")].map(h => (
                    <th key={h} style={{ padding: "10px 22px", textAlign: "left", fontSize: 11, fontWeight: 700, color: "var(--mute)", letterSpacing: "0.07em", textTransform: "uppercase" }}>{h}</th>
                  ))}
                </tr>
              </thead>
              <tbody>{rows}</tbody>
            </table>
          );
          const note = (text) => (
            <div style={{ padding: "30px 22px", textAlign: "center", color: "var(--mute)", fontSize: 13.5, lineHeight: 1.6 }}>{text}</div>
          );

          if (invoicesLoading) return card(note(t("PartnerDash.loadingInvoices")));
          if (invoicesError) {
            // A 400 here means Stripe billing isn't configured yet — an expected
            // state, not a failure; show the server's explanation.
            return card(note(invoicesError.status === 400
              ? (invoicesError.message || t("PartnerDash.billingNotSetUp"))
              : t("PartnerDash.errLoadInvoices", { error: invoicesError.message })));
          }
          if (!invoices.length) return card(note(t("PartnerDash.noInvoices")));
          return card(table(invoices.map((inv, i) => {
            const label = inv.number || inv.id;
            return (
              <DashRow key={inv.id} last={i === invoices.length - 1}>
                <td style={cell.inv}>
                  {inv.hostedInvoiceUrl
                    ? <a href={inv.hostedInvoiceUrl} target="_blank" rel="noopener noreferrer" style={{ color: "#1D4ED8", textDecoration: "underline", textDecorationColor: "rgba(29,78,216,.3)", textUnderlineOffset: 2 }}>{label}</a>
                    : label}
                </td>
                <td style={cell.date}>{fmtInvoiceDate(inv.created)}</td>
                <td style={cell.amt}>{fmtInvoiceAmount(inv.total, inv.currency)}</td>
              </DashRow>
            );
          })));
        })()}
      </div>
    </DashPage>
  );
};

// ── Documents (settings) ──────────────────────────────────────────────────────

// A single uploaded document: name + size with download and delete actions.
// onDownload/onDelete are async, doc-bound handlers supplied by the parent.
const DocRow = ({ doc, onDownload, onDelete }) => {
  const [downloading, setDownloading] = React.useState(false);
  const [deleting, setDeleting]       = React.useState(false);
  const [hoverDel, setHoverDel]       = React.useState(false);
  const [error, setError]             = React.useState(null);
  const busy = downloading || deleting;

  const runDownload = async () => {
    if (busy) return;
    setError(null); setDownloading(true);
    try { await onDownload(); }
    catch (err) { setError(err.message || t("PartnerDash.errDownloadDoc")); }
    finally { setDownloading(false); }
  };
  const runDelete = async () => {
    if (busy) return;
    setError(null); setDeleting(true);
    try { await onDelete(); }   // on success the row unmounts (list refreshed)
    catch (err) { setError(err.message || t("PartnerDash.errDeleteDoc")); setDeleting(false); }
  };

  const iconBtn = (extra) => ({
    width: 34, height: 34, borderRadius: 8, display: "grid", placeItems: "center",
    background: "transparent", border: "1px solid rgba(10,10,11,.12)",
    color: "var(--mute)", cursor: busy ? "default" : "pointer",
    transition: "all .15s", flexShrink: 0, opacity: busy ? 0.6 : 1, ...extra,
  });

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 6, padding: "12px 14px", borderRadius: 10, border: "1px solid rgba(10,10,11,.08)", background: "#FCFCFB" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
        <div style={{ width: 38, height: 38, borderRadius: 9, background: "rgba(10,10,11,.05)", display: "grid", placeItems: "center", color: "var(--mute)", flexShrink: 0 }}>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></svg>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{doc.fileName}</div>
          <div style={{ fontSize: 12, color: "var(--mute)", marginTop: 1 }}>{fmtFileSize(doc.sizeBytes)}</div>
        </div>
        <button type="button" onClick={runDownload} disabled={busy} aria-label={t("PartnerDash.tipDownload")} title={t("PartnerDash.tipDownload")} style={iconBtn()}>
          {downloading
            ? <SpinnerIcon />
            : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" width="16" height="16"><path d="M12 3v12"/><path d="M7 10l5 5 5-5"/><path d="M5 21h14"/></svg>}
        </button>
        <button type="button" onClick={runDelete} disabled={busy} aria-label={t("PartnerDash.tipDelete")} title={t("PartnerDash.tipDelete")}
          onMouseEnter={() => setHoverDel(true)} onMouseLeave={() => setHoverDel(false)}
          style={iconBtn(hoverDel && !busy ? { color: "#BE123C", borderColor: "#FECDD3", background: "#FFF1F2" } : null)}>
          {deleting
            ? <SpinnerIcon />
            : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" width="16" height="16"><path d="M3 6h18"/><path d="M8 6V4a1 1 0 011-1h6a1 1 0 011 1v2"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6M14 11v6"/></svg>}
        </button>
      </div>
      {error && <div style={{ fontSize: 12.5, color: "#BE123C", lineHeight: 1.45, textAlign: "right" }}>{error}</div>}
    </div>
  );
};

// A staged (selected but not-yet-uploaded) file: name + size with a remove
// action. Dashed border distinguishes it from an already-uploaded DocRow.
const PendingRow = ({ file, onRemove, disabled }) => (
  <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "12px 14px", borderRadius: 10, border: "1px dashed rgba(10,10,11,.22)", background: "#FAFAF9", opacity: disabled ? 0.6 : 1 }}>
    <div style={{ width: 38, height: 38, borderRadius: 9, background: "rgba(10,10,11,.05)", display: "grid", placeItems: "center", color: "var(--mute)", flexShrink: 0 }}>
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></svg>
    </div>
    <div style={{ flex: 1, minWidth: 0 }}>
      <div style={{ fontSize: 13.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{file.name}</div>
      <div style={{ fontSize: 12, color: "var(--mute)", marginTop: 1 }}>{fmtFileSize(file.size)} · {t("PartnerDash.readyToUpload")}</div>
    </div>
    <button type="button" onClick={onRemove} disabled={disabled} aria-label={t("PartnerDash.tipRemove")} title={t("PartnerDash.tipRemove")} style={{
      width: 34, height: 34, borderRadius: 8, display: "grid", placeItems: "center",
      background: "transparent", border: "1px solid rgba(10,10,11,.12)", color: "var(--mute)",
      cursor: disabled ? "default" : "pointer", transition: "all .15s", flexShrink: 0,
    }}>
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" width="15" height="15"><path d="M18 6L6 18M6 6l12 12"/></svg>
    </button>
  </div>
);

// Documents section for Settings: business registration (single slot) and
// certifications (a list). Picking a file STAGES it (shown as a PendingRow);
// the partner then clicks Upload to send. Uploads return the refreshed
// PartnerProfileResponse (lifted via onProfileUpdate); deletes return 204, so
// we re-fetch /me.
const DocumentsSection = ({ profile, live, onProfileUpdate }) => {
  const data = profile || {};
  const businessReg = data.businessRegistration || null;
  const certs = Array.isArray(data.certifications) ? data.certifications : [];

  // Staged files awaiting an explicit Upload. Registration is a single slot;
  // certifications can be batched (the endpoint accepts a file collection).
  const [regPending, setRegPending]   = React.useState(null);
  const [certPending, setCertPending] = React.useState([]);
  const [regUploading, setRegUploading]   = React.useState(false);
  const [certUploading, setCertUploading] = React.useState(false);
  // Errors live next to the button that produced them: upload errors here,
  // download/delete errors inside their own DocRow.
  const [regError, setRegError]   = React.useState(null);
  const [certError, setCertError] = React.useState(null);

  const lift = (updated) => {
    if (onProfileUpdate && updated && typeof updated === "object") onProfileUpdate(updated);
  };
  // Delete returns 204 with no body — re-fetch /me to refresh the list.
  const refresh = async () => { lift(await OK_API.getPartnerMe()); };

  // Staging (picking a file just queues it — no network yet).
  const stageReg  = (file) => { if (live && file) { setRegError(null); setRegPending(file); } };
  const stageCert = (file) => { if (live && file) { setCertError(null); setCertPending((prev) => [...prev, file]); } };

  const submitReg = async () => {
    if (!live || !regPending) return;
    setRegError(null); setRegUploading(true);
    try { lift(await OK_API.uploadBusinessRegistration(regPending)); setRegPending(null); }
    catch (err) { setRegError(err.message || t("PartnerDash.errUploadDoc")); }
    finally { setRegUploading(false); }
  };
  const submitCerts = async () => {
    if (!live || !certPending.length) return;
    setCertError(null); setCertUploading(true);
    try { lift(await OK_API.uploadCertifications(certPending)); setCertPending([]); }
    catch (err) { setCertError(err.message || t("PartnerDash.errUploadDocs")); }
    finally { setCertUploading(false); }
  };

  // Download/delete errors are surfaced by the DocRow that owns the buttons,
  // so just let failures propagate.
  const downloadReg  = async () => { if (!live) return; saveBlob(await OK_API.downloadBusinessRegistration(), businessReg && businessReg.fileName); };
  const deleteReg    = async () => { if (!live) return; await OK_API.deleteBusinessRegistration(); await refresh(); };
  const downloadCert = async (doc) => { if (!live) return; saveBlob(await OK_API.downloadCertification(doc.id), doc.fileName); };
  const deleteCert   = async (doc) => { if (!live) return; await OK_API.deleteCertification(doc.id); await refresh(); };

  const groupLabel = { fontSize: 12.5, fontWeight: 600, color: "var(--ink)", marginBottom: 10, letterSpacing: "-0.01em" };
  const btnError   = { fontSize: 12.5, color: "#BE123C", lineHeight: 1.45 };
  const uploadButton = (onClick, loading, label) => (
    <button type="button" onClick={onClick} disabled={loading} style={{
      alignSelf: "flex-start", height: 40, padding: "0 20px", borderRadius: 9,
      background: "var(--accent)", color: "#fff", border: "none",
      fontSize: 13.5, fontWeight: 600, fontFamily: "var(--sans)",
      cursor: loading ? "default" : "pointer", letterSpacing: "-0.01em",
      opacity: loading ? 0.6 : 1, display: "inline-flex", alignItems: "center", gap: 8,
    }}>
      {loading && <SpinnerIcon />}
      {loading ? t("PartnerDash.uploading") : label}
    </button>
  );

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
      <div>
        <div style={groupLabel}>{t("PartnerDash.businessRegistration")}</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {businessReg && <DocRow doc={businessReg} onDownload={downloadReg} onDelete={deleteReg} />}
          {regPending
            ? <>
                <PendingRow file={regPending} onRemove={() => setRegPending(null)} disabled={regUploading} />
                <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
                  {uploadButton(submitReg, regUploading, businessReg ? t("PartnerDash.replaceDocument") : t("PartnerDash.uploadDocument"))}
                  {regError && <span style={btnError}>{regError}</span>}
                </div>
              </>
            : <DropZone label="" hint={t("PartnerDash.dropHintDoc")} file={null} onFile={stageReg} />}
        </div>
      </div>

      <div>
        <div style={groupLabel}>{t("PartnerDash.certifications")}</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
          {certs.map((doc) => (
            <DocRow key={doc.id} doc={doc} onDownload={() => downloadCert(doc)} onDelete={() => deleteCert(doc)} />
          ))}
          {certPending.map((file, i) => (
            <PendingRow key={`pending-${i}`} file={file} onRemove={() => setCertPending((prev) => prev.filter((_, j) => j !== i))} disabled={certUploading} />
          ))}
          <DropZone label="" hint={t("PartnerDash.dropHintCert")} file={null} onFile={stageCert} multiple />
          {certPending.length > 0 && (
            <div style={{ display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
              {uploadButton(submitCerts, certUploading, tn("PartnerDash.uploadFiles", certPending.length, { count: certPending.length }))}
              {certError && <span style={btnError}>{certError}</span>}
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

// ── Settings tab ──────────────────────────────────────────────────────────────

const DashSettings = ({ onHome, viewMode, onViewModeChange, profile, onProfileUpdate }) => {
  const live = !!profile;
  const [fullName, setFullName] = React.useState("");
  const [bizName, setBizName]   = React.useState("");
  const [email, setEmail]       = React.useState("");
  const [emailVerified, setEmailVerified] = React.useState(false);
  const [street, setStreet]           = React.useState("");
  const [houseNumber, setHouseNumber] = React.useState("");
  const [postcode, setPostcode]       = React.useState("");
  const [city, setCity]               = React.useState("");
  const [services, setServices] = React.useState([]); // PartnerService codes
  const [saved, setSaved]       = React.useState(false);
  const [saving, setSaving]     = React.useState(false);
  const [saveError, setSaveError] = React.useState(null);

  // Populate from the live profile (GET /v1/partner/me); fields stay blank until it loads.
  React.useEffect(() => {
    const p = profile || {};
    setFullName(p.fullName || "");
    setBizName(p.businessName || "");
    setEmail(p.email || "");
    setEmailVerified(!!p.emailVerified);
    setStreet(p.street || "");
    setHouseNumber(p.houseNumber || "");
    setPostcode(p.postcode || "");
    setCity(p.city || "");
    setServices(Array.isArray(p.servicesOffered) ? p.servicesOffered : []);
  }, [profile]);

  const toggleService = (code) => setServices(prev => prev.includes(code) ? prev.filter(x => x !== code) : [...prev, code]);

  const [currentPw, setCurrentPw]   = React.useState("");
  const [newPw, setNewPw]           = React.useState("");
  const [confirmPw, setConfirmPw]   = React.useState("");
  const [showCurrent, setShowCurrent] = React.useState(false);
  const [showNew, setShowNew]         = React.useState(false);
  const [showConfirm, setShowConfirm] = React.useState(false);
  const [pwSaved, setPwSaved]         = React.useState(false);
  const [pwError, setPwError]         = React.useState(null);
  const [pwSaving, setPwSaving]       = React.useState(false);

  // Persist the profile via PUT /v1/partner/me (Request4). Guard against saving
  // before the profile has loaded.
  const save = async (e) => {
    e.preventDefault();
    if (!live) return;
    setSaveError(null);
    setSaving(true);
    try {
      const updated = await OK_API.updatePartnerMe({
        fullName: fullName.trim(),
        businessName: bizName.trim() || null,
        street: street.trim(),
        houseNumber: houseNumber.trim(),
        postcode: postcode.trim(),
        city: city.trim(),
        // Required by the PUT body but not editable here — preserve from /me.
        countryCode: profile.countryCode || "de",
        phoneNumber: profile.phoneNumber || "",
        // Services are a Workshop-only concept; for appraisers preserve what /me returned.
        servicesOffered: viewMode === "workshop" ? services : (Array.isArray(profile.servicesOffered) ? profile.servicesOffered : []),
      });
      // PUT returns the updated PartnerProfileResponse — lift it so the header
      // chip and other tabs reflect the new business name without a reload.
      if (onProfileUpdate && updated && typeof updated === "object") onProfileUpdate(updated);
      setSaved(true);
      setTimeout(() => setSaved(false), 2000);
    } catch (err) {
      setSaveError(err.message || t("PartnerDash.errSaveSettings"));
    } finally {
      setSaving(false);
    }
  };

  const savePw = async (e) => {
    e.preventDefault();
    if (!currentPw) { setPwError(t("PartnerDash.errNoCurrentPw")); return; }
    if (newPw.length < 8) { setPwError(t("PartnerDash.errPwTooShort")); return; }
    if (newPw !== confirmPw) { setPwError(t("PartnerDash.errPwMismatch")); return; }
    setPwError(null);
    setPwSaving(true);
    try {
      await OK_API.changePassword(currentPw, newPw);
      setPwSaved(true);
      setCurrentPw(""); setNewPw(""); setConfirmPw("");
      setTimeout(() => setPwSaved(false), 2400);
    } catch (err) {
      setPwError(err.message || t("PartnerDash.errUpdatePw"));
    } finally {
      setPwSaving(false);
    }
  };

  const card = {
    background: "#fff", borderRadius: 16,
    border: "1px solid rgba(10,10,11,.07)",
    padding: "26px 28px", marginBottom: 18,
    boxShadow: "0 1px 4px rgba(10,10,11,.04)",
  };
  const sHead = { fontSize: 13.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.02em", marginBottom: 20 };

  return (
    <DashPage title={t("PartnerDash.tabSettings")} subtitle={t("PartnerDash.settingsSubtitle")} viewMode={viewMode} onViewModeChange={onViewModeChange} partner={profile}>
      <div style={{ maxWidth: 640 }}>
        <div style={card}>
          <div style={sHead}>{t("PartnerDash.businessInfo")}</div>
          {React.createElement(React.Fragment, null,
            <form onSubmit={save} style={{ display: "flex", flexDirection: "column", gap: 18 }}>
              {saveError && (
                <div style={{ background: "#FFF1F2", border: "1px solid #FECDD3", borderRadius: 8, padding: "9px 14px", fontSize: 13, color: "#BE123C", lineHeight: 1.5 }}>
                  {saveError}
                </div>
              )}
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
                <PartnerField label={t("PartnerDash.fieldContactName")}>
                  <input type="text" value={fullName} onChange={(e) => setFullName(e.target.value)} style={loginInputStyle} />
                </PartnerField>
                <PartnerField label={t("PartnerDash.fieldBusinessName")}>
                  <input type="text" value={bizName} onChange={(e) => setBizName(e.target.value)} style={loginInputStyle} />
                </PartnerField>
              </div>
              <PartnerField label={t("PartnerDash.fieldContactEmail")}>
                <div style={{ position: "relative" }}>
                  <input type="email" value={email} readOnly style={{ ...loginInputStyle, paddingRight: 104, background: "rgba(10,10,11,.04)", color: "var(--mute)", cursor: "not-allowed" }} />
                  <span style={{ position: "absolute", right: 12, top: "50%", transform: "translateY(-50%)", display: "inline-flex", alignItems: "center", gap: 5, fontSize: 11.5, fontWeight: 600, color: emailVerified ? "var(--accent-ink)" : "rgba(10,10,11,.5)" }}>
                    {emailVerified
                      ? <><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" width="12" height="12"><path d="M3 8l3.5 3.5L13 4.5"/></svg> {t("PartnerDash.emailVerified")}</>
                      : t("PartnerDash.emailUnverified")}
                  </span>
                </div>
              </PartnerField>
              <div style={{ display: "grid", gridTemplateColumns: "2.4fr 1fr", gap: 16 }}>
                <PartnerField label={t("PartnerDash.fieldStreet")}>
                  <input type="text" value={street} onChange={(e) => setStreet(e.target.value)} style={loginInputStyle} />
                </PartnerField>
                <PartnerField label={t("PartnerDash.fieldHouseNumber")}>
                  <input type="text" value={houseNumber} onChange={(e) => setHouseNumber(e.target.value)} style={loginInputStyle} />
                </PartnerField>
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "1fr 2fr", gap: 16 }}>
                <PartnerField label={t("PartnerDash.fieldPostcode")}>
                  <input type="text" value={postcode} onChange={(e) => setPostcode(e.target.value)} style={loginInputStyle} />
                </PartnerField>
                <PartnerField label={t("PartnerDash.fieldCity")}>
                  <input type="text" value={city} onChange={(e) => setCity(e.target.value)} style={loginInputStyle} />
                </PartnerField>
              </div>
              {/* Services are a Workshop-only concept — hidden for appraisers (Gutachter). */}
              {viewMode === "workshop" && (
                <PartnerField label={t("PartnerDash.fieldServicesOffered")}>
                  <div style={{ display: "flex", flexWrap: "wrap", gap: 8, paddingTop: 2 }}>
                    {PARTNER_SERVICES.map(({ code, label }) => {
                      const on = services.includes(code);
                      return (
                        <button key={code} type="button" data-testid={`service-${code}`} onClick={() => toggleService(code)} style={{
                          appearance: "none", fontFamily: "var(--sans)", cursor: "default",
                          fontSize: 12.5, fontWeight: on ? 600 : 400,
                          padding: "5px 12px", borderRadius: 20,
                          border: `1px solid ${on ? "var(--accent)" : "rgba(10,10,11,.13)"}`,
                          background: on ? "var(--accent-tint)" : "transparent",
                          color: on ? "var(--accent-ink)" : "rgba(10,10,11,.6)",
                          transition: "all .15s",
                        }}>
                          {on && "✓ "}{t("PartnerDash.service_" + code)}
                        </button>
                      );
                    })}
                  </div>
                </PartnerField>
              )}
              <div style={{ display: "flex", alignItems: "center", gap: 14, paddingTop: 2 }}>
                <button type="submit" disabled={saving} style={{
                  height: 40, padding: "0 20px", borderRadius: 9,
                  background: "var(--accent)", color: "#fff", border: "none",
                  fontSize: 13.5, fontWeight: 600, fontFamily: "var(--sans)",
                  cursor: saving ? "default" : "pointer", letterSpacing: "-0.01em",
                  opacity: saving ? 0.6 : 1,
                }}>{saving ? t("PartnerDash.saving") : t("PartnerDash.saveChanges")}</button>
                {saved && <span style={{ fontSize: 13, color: "var(--accent-ink)", fontWeight: 600 }}>{t("PartnerDash.saved")}</span>}
              </div>
            </form>
          )}
        </div>

        <div style={card}>
          <div style={sHead}>{t("PartnerDash.documentsSection")}</div>
          {React.createElement(React.Fragment, null,
            <DocumentsSection profile={profile} live={live} onProfileUpdate={onProfileUpdate} />
          )}
        </div>

        <div style={card}>
          <div style={sHead}>{t("PartnerDash.changePassword")}</div>
          <form onSubmit={savePw} style={{ display: "flex", flexDirection: "column", gap: 14 }}>
            {pwError && (
              <div style={{ background: "#FFF1F2", border: "1px solid #FECDD3", borderRadius: 8, padding: "9px 14px", fontSize: 13, color: "#BE123C", lineHeight: 1.5 }}>
                {pwError}
              </div>
            )}
            <PartnerField label={t("PartnerDash.fieldCurrentPassword")}>
              <div style={{ position: "relative" }}>
                <input type={showCurrent ? "text" : "password"} placeholder="••••••••" value={currentPw}
                  onChange={e => setCurrentPw(e.target.value)}
                  style={{ ...loginInputStyle, paddingRight: 42 }} />
                <button type="button" onClick={() => setShowCurrent(s => !s)} style={eyeButtonStyle}>
                  {showCurrent
                    ? <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" width="15" height="15"><path d="M17.9 17.9A9.9 9.9 0 0112 20C6 20 2 12 2 12a18 18 0 015.1-6.1M9.9 4.2A9.6 9.6 0 0112 4c6 0 10 8 10 8a18 18 0 01-2.1 3.1M1 1l22 22"/><path d="M10.5 10.6a3 3 0 004 4"/></svg>
                    : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" width="15" height="15"><path d="M2 12s4-8 10-8 10 8 10 8-4 8-10 8S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg>
                  }
                </button>
              </div>
            </PartnerField>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
              <PartnerField label={t("PartnerDash.fieldNewPassword")}>
                <div style={{ position: "relative" }}>
                  <input type={showNew ? "text" : "password"} placeholder={t("PartnerDash.phMinChars")} value={newPw}
                    onChange={e => setNewPw(e.target.value)}
                    style={{ ...loginInputStyle, paddingRight: 42 }} />
                  <button type="button" onClick={() => setShowNew(s => !s)} style={eyeButtonStyle}>
                    {showNew
                      ? <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" width="15" height="15"><path d="M17.9 17.9A9.9 9.9 0 0112 20C6 20 2 12 2 12a18 18 0 015.1-6.1M9.9 4.2A9.6 9.6 0 0112 4c6 0 10 8 10 8a18 18 0 01-2.1 3.1M1 1l22 22"/><path d="M10.5 10.6a3 3 0 004 4"/></svg>
                      : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" width="15" height="15"><path d="M2 12s4-8 10-8 10 8 10 8-4 8-10 8S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg>
                    }
                  </button>
                </div>
              </PartnerField>
              <PartnerField label={t("PartnerDash.fieldConfirmNewPassword")}>
                <div style={{ position: "relative" }}>
                  <input type={showConfirm ? "text" : "password"} placeholder="••••••••" value={confirmPw}
                    onChange={e => setConfirmPw(e.target.value)}
                    style={{ ...loginInputStyle, paddingRight: 42 }} />
                  <button type="button" onClick={() => setShowConfirm(s => !s)} style={eyeButtonStyle}>
                    {showConfirm
                      ? <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" width="15" height="15"><path d="M17.9 17.9A9.9 9.9 0 0112 20C6 20 2 12 2 12a18 18 0 015.1-6.1M9.9 4.2A9.6 9.6 0 0112 4c6 0 10 8 10 8a18 18 0 01-2.1 3.1M1 1l22 22"/><path d="M10.5 10.6a3 3 0 004 4"/></svg>
                      : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" width="15" height="15"><path d="M2 12s4-8 10-8 10 8 10 8-4 8-10 8S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg>
                    }
                  </button>
                </div>
              </PartnerField>
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 14, paddingTop: 2 }}>
              <button type="submit" disabled={pwSaving} style={{ height: 40, padding: "0 20px", borderRadius: 9, background: "var(--accent)", color: "#fff", border: "none", fontSize: 13.5, fontWeight: 600, fontFamily: "var(--sans)", cursor: pwSaving ? "default" : "pointer", letterSpacing: "-0.01em", opacity: pwSaving ? 0.6 : 1 }}>
                {pwSaving ? t("PartnerDash.updating") : t("PartnerDash.updatePassword")}
              </button>
              {pwSaved && (
                <span style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 13, color: "var(--accent-ink)", fontWeight: 600 }}>
                  <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" width="13" height="13"><path d="M3 8l3.5 3.5L13 4.5"/></svg>
                  {t("PartnerDash.passwordUpdated")}
                </span>
              )}
            </div>
          </form>
        </div>

        <div style={card}>
          <div style={sHead}>{t("PartnerDash.accountSection")}</div>
          <p style={{ margin: "0 0 16px", fontSize: 13.5, color: "var(--mute)", lineHeight: 1.6 }}>
            {t("PartnerDash.signOutDescription")}
          </p>
          <button onClick={onHome}
            data-testid="signout"
            style={{
              height: 40, padding: "0 18px", borderRadius: 9,
              background: "transparent", color: "rgba(10,10,11,.65)",
              border: "1px solid rgba(10,10,11,.18)",
              fontSize: 13.5, fontWeight: 500, fontFamily: "var(--sans)",
              cursor: "default", display: "inline-flex", alignItems: "center", gap: 8,
            }}>
            <SvgOut /> {t("PartnerDash.signOut")}
          </button>
        </div>
      </div>
    </DashPage>
  );
};

// ── File drop zone ────────────────────────────────────────────────────────────

const DropZone = ({ label, hint, file, onFile, multiple }) => {
  const [drag, setDrag] = React.useState(false);
  const ref = React.useRef();
  // With `multiple`, fan every dropped/picked file out to onFile (the caller
  // stages each); otherwise keep the single-file behaviour.
  const handle = (fileList) => {
    const files = Array.from(fileList || []);
    if (!files.length) return;
    if (multiple) files.forEach((f) => onFile(f));
    else onFile(files[0]);
  };
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
      {label ? <div style={{ fontSize: 12.5, fontWeight: 500, color: "rgba(10,10,11,.55)", marginBottom: 7 }}>{label}</div> : null}
      <div
        onDragOver={e => { e.preventDefault(); setDrag(true); }}
        onDragLeave={() => setDrag(false)}
        onDrop={e => { e.preventDefault(); setDrag(false); handle(e.dataTransfer.files); }}
        style={{
          border: `1.5px dashed ${drag ? "var(--accent)" : file ? "var(--accent)" : "rgba(10,10,11,.18)"}`,
          borderRadius: 14,
          padding: "28px 24px 24px",
          background: drag ? "var(--accent-tint)" : file ? "#F6FEF9" : "#FAFAF9",
          transition: "border-color .15s, background .15s",
          display: "flex", flexDirection: "column", alignItems: "center", gap: 10, textAlign: "center",
        }}>
        <input ref={ref} type="file" multiple={!!multiple} accept=".pdf,.jpg,.jpeg,.png" style={{ display: "none" }} onChange={e => { handle(e.target.files); e.target.value = ""; }} />
        <div style={{ width: 48, height: 48, borderRadius: 24, background: "rgba(10,10,11,.06)", display: "grid", placeItems: "center", color: file ? "var(--accent-ink)" : "var(--mute)" }}>
          {file
            ? <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" width="22" height="22"><path d="M20 6L9 17l-5-5"/></svg>
            : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" width="22" height="22"><path d="M4 14.899A7 7 0 1115.71 8h1.79a4.5 4.5 0 010 9H12"/><path d="M12 12v9"/><path d="M8 17l4-4 4 4"/></svg>
          }
        </div>
        {file ? (
          <>
            <div style={{ fontSize: 13.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em" }}>{file.name}</div>
            <div style={{ fontSize: 12, color: "var(--mute)" }}>{t("PartnerDash.dropZoneClickToReplace", { size: (file.size / 1024).toFixed(0) })}</div>
          </>
        ) : (
          <>
            <div style={{ fontSize: 13.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.01em" }}>{multiple ? t("PartnerDash.dropZoneMultiple") : t("PartnerDash.dropZoneSingle")}</div>
            <div style={{ fontSize: 12, color: "var(--mute)" }}>{hint}</div>
          </>
        )}
        <button
          type="button"
          onClick={() => ref.current.click()}
          style={{
            marginTop: 4, height: 36, padding: "0 20px", borderRadius: 8,
            background: "transparent", border: "1.5px solid rgba(10,10,11,.18)",
            fontSize: 13, fontWeight: 500, fontFamily: "var(--sans)",
            color: "var(--ink)", cursor: "default",
          }}>
          {multiple ? t("PartnerDash.browseFiles") : t("PartnerDash.browseFile")}
        </button>
      </div>
    </div>
  );
};

// ── Onboarding modal ──────────────────────────────────────────────────────────

const OnboardingModal = ({ onClose, profile, onProfileUpdate, initialStep = 1 }) => {
  const [userName, setUserName] = React.useState("");
  const [bizName, setBizName]   = React.useState("");
  const [street, setStreet]           = React.useState("");
  const [houseNumber, setHouseNumber] = React.useState("");
  const [postcode, setPostcode]       = React.useState("");
  const [city, setCity]               = React.useState("");
  const [phone, setPhone]       = React.useState("");

  // Prefill from the data the partner gave at sign-up (PartnerProfileResponse).
  // Only fills empty fields, so it never overwrites anything the user has typed.
  React.useEffect(() => {
    if (!profile || typeof profile !== "object") return;
    if (profile.fullName)     setUserName(prev => prev || profile.fullName);
    if (profile.businessName) setBizName(prev => prev || profile.businessName);
    if (profile.street)       setStreet(prev => prev || profile.street);
    if (profile.houseNumber)  setHouseNumber(prev => prev || profile.houseNumber);
    if (profile.postcode)     setPostcode(prev => prev || profile.postcode);
    if (profile.city)         setCity(prev => prev || profile.city);
    if (profile.phoneNumber)  setPhone(prev => prev || profile.phoneNumber);
    if (Array.isArray(profile.servicesOffered) && profile.servicesOffered.length)
      setServices(prev => prev.length ? prev : profile.servicesOffered);
  }, [profile]);
  const [services, setServices] = React.useState([]);
  const [regFile, setRegFile]   = React.useState(null);
  const [certFiles, setCertFiles] = React.useState([]);
  const toggleSvc = (code) => setServices(prev => prev.includes(code) ? prev.filter(x => x !== code) : [...prev, code]);
  // Service options come from the shared module-scope PARTNER_SERVICES table.
  // Services are a Workshop-only concept; appraisers (Gutachter) don't offer them.
  const isWorkshop = !(profile && profile.partnerType === "Appraiser");

  const [step, setStep] = React.useState(initialStep || 1);
  const [saving, setSaving]       = React.useState(false);
  const [saveError, setSaveError] = React.useState(null);

  // Continue → persist the profile via PUT /v1/partner/me, then advance to payment.
  const handleContinue = async () => {
    if (step !== 1) { onClose(); return; }
    setSaveError(null);
    setSaving(true);
    try {
      let latest = await OK_API.updatePartnerMe({
        fullName: userName,
        businessName: bizName || null,
        street: street.trim(),
        houseNumber: houseNumber.trim(),
        postcode: postcode.trim(),
        city: city.trim(),
        countryCode: "de", // fixed — German market, never shown as a field
        phoneNumber: phone.trim(),
        // Services are a Workshop-only concept; for appraisers preserve what /me returned.
        servicesOffered: isWorkshop ? services : ((profile && Array.isArray(profile.servicesOffered)) ? profile.servicesOffered : []),
      });
      // Persist any documents the partner attached (upload only — manage them
      // later in Settings → Documents). PUT replaces the registration; POST
      // appends to certifications. Each call returns the updated profile.
      if (regFile)         latest = await OK_API.uploadBusinessRegistration(regFile);
      if (certFiles.length) latest = await OK_API.uploadCertifications(certFiles);
      // Lift the freshest profile so Settings (incl. Documents) reflects the
      // edits/uploads without a reload.
      if (onProfileUpdate && latest && typeof latest === "object") onProfileUpdate(latest);
      setStep(2);
    } catch (err) {
      setSaveError(err.message || t("PartnerDash.errSaveDetails"));
    } finally {
      setSaving(false);
    }
  };

  // Step 2 → create a Stripe Checkout setup session and redirect to it. The card is
  // entered on Stripe's hosted page (never in-app); Stripe returns the partner here
  // when done, so on success we navigate away and don't clear the saving flag.
  const startCardSetup = async () => {
    setSaveError(null);
    setSaving(true);
    try {
      const r = await OK_API.createSetupSession();
      const url = OK_API.sessionUrl(r);
      if (!url) throw new Error(t("PartnerDash.errCardSetup"));
      window.location.assign(url);
    } catch (err) {
      setSaveError(err.message || t("PartnerDash.errCardSetup"));
      setSaving(false);
    }
  };

  const divider = <div style={{ height: 1, background: "rgba(10,10,11,.07)", margin: "4px 0" }} />;
  const sectionLabel = (txt) => (
    <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: "0.09em", textTransform: "uppercase", color: "var(--mute)", marginBottom: 14 }}>{txt}</div>
  );

  return (
    <>
    <div style={{ position: "fixed", inset: 0, zIndex: 1000, display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(10,10,11,.55)", backdropFilter: "blur(4px)" }}>
      <div style={{ width: 860, maxHeight: "90vh", background: "#fff", borderRadius: 20, boxShadow: "0 24px 64px rgba(10,10,11,.18)", display: "flex", flexDirection: "column" }}>

        {/* Header */}
        <div style={{ padding: "28px 36px 24px", borderBottom: "1px solid rgba(10,10,11,.07)", flexShrink: 0, position: "relative" }}>
          {/* TEMP: dismiss without saving — remove once the profile-save/override flow is fixed */}
          <button type="button" onClick={onClose} aria-label={t("PartnerDash.tipClose")} style={{
            position: "absolute", top: 18, right: 18, width: 32, height: 32, borderRadius: "50%",
            border: "1px solid rgba(10,10,11,.12)", background: "#fff", cursor: "pointer",
            display: "grid", placeItems: "center", color: "rgba(10,10,11,.55)",
          }}>
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" width="16" height="16"><path d="M18 6L6 18M6 6l12 12"/></svg>
          </button>
          <div data-testid="onboard-step-title" data-step={step} style={{ fontSize: 20, fontWeight: 700, letterSpacing: "-0.03em", color: "var(--ink)", marginBottom: 3 }}>
            {step === 1 ? t("PartnerDash.onboardStep1Title") : t("PartnerDash.onboardStep2Title")}
          </div>
          <p style={{ margin: 0, fontSize: 13.5, color: "var(--mute)", lineHeight: 1.55 }}>
            {step === 1
              ? t("PartnerDash.onboardStep1Subtitle")
              : t("PartnerDash.onboardStep2Subtitle")}
          </p>
        </div>

        {/* Scrollable body */}
        <div style={{ overflowY: "auto", padding: "28px 36px", display: "flex", flexDirection: "column", gap: 20 }}>

          {step === 1 ? (<>

            {sectionLabel(t("PartnerDash.businessInfo"))}
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
              <PartnerField label={t("PartnerDash.fieldYourName")}>
                <input type="text" placeholder={t("PartnerDash.phName")} value={userName} onChange={e => setUserName(e.target.value)} style={loginInputStyle} />
              </PartnerField>
              <PartnerField label={t("PartnerDash.fieldBusinessName")}>
                <input type="text" placeholder={t("PartnerDash.phBizName")} value={bizName} onChange={e => setBizName(e.target.value)} style={loginInputStyle} />
              </PartnerField>
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "2.4fr 1fr", gap: 14 }}>
              <PartnerField label={t("PartnerDash.fieldStreet")}>
                <input type="text" placeholder={t("PartnerDash.phStreet")} value={street} onChange={e => setStreet(e.target.value)} style={loginInputStyle} />
              </PartnerField>
              <PartnerField label={t("PartnerDash.fieldHouseNumber")}>
                <input type="text" placeholder="12" value={houseNumber} onChange={e => setHouseNumber(e.target.value)} style={loginInputStyle} />
              </PartnerField>
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 2fr", gap: 14 }}>
              <PartnerField label={t("PartnerDash.fieldPostcode")}>
                <input type="text" placeholder="80331" value={postcode} onChange={e => setPostcode(e.target.value)} style={loginInputStyle} />
              </PartnerField>
              <PartnerField label={t("PartnerDash.fieldCity")}>
                <input type="text" placeholder={t("PartnerDash.phCity")} value={city} onChange={e => setCity(e.target.value)} style={loginInputStyle} />
              </PartnerField>
            </div>
            <PartnerField label={t("PartnerDash.fieldPhoneNumber")}>
              <input type="tel" placeholder="+49 89 123456" value={phone} onChange={e => setPhone(e.target.value)} style={loginInputStyle} />
            </PartnerField>
            {/* Services are a Workshop-only concept — hidden for appraisers (Gutachter). */}
            {isWorkshop && (
              <PartnerField label={t("PartnerDash.fieldServicesOffered")}>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 8, paddingTop: 2 }}>
                  {PARTNER_SERVICES.map(({ code, label }) => {
                    const on = services.includes(code);
                    return (
                      <button key={code} type="button" data-testid={`service-${code}`} onClick={() => toggleSvc(code)} style={{
                        appearance: "none", fontFamily: "var(--sans)", cursor: "default",
                        fontSize: 12.5, fontWeight: on ? 600 : 400,
                        padding: "5px 12px", borderRadius: 20,
                        border: `1px solid ${on ? "var(--accent)" : "rgba(10,10,11,.13)"}`,
                        background: on ? "var(--accent-tint)" : "transparent",
                        color: on ? "var(--accent-ink)" : "rgba(10,10,11,.6)",
                        transition: "all .15s",
                      }}>
                        {on && "✓ "}{t("PartnerDash.service_" + code)}
                      </button>
                    );
                  })}
                </div>
              </PartnerField>
            )}

            {divider}

            {sectionLabel(t("PartnerDash.documentsSection"))}
            <DropZone label={t("PartnerDash.businessRegistration")} hint={t("PartnerDash.dropHintDoc")} file={regFile} onFile={setRegFile} />
            <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
              <DropZone label={t("PartnerDash.certifications")} hint={t("PartnerDash.dropHintCert")}
                file={null} onFile={(f) => setCertFiles((prev) => [...prev, f])} multiple />
              {certFiles.map((file, i) => (
                <PendingRow key={`cert-${i}`} file={file}
                  onRemove={() => setCertFiles((prev) => prev.filter((_, j) => j !== i))} disabled={saving} />
              ))}
            </div>

          </>) : (<>

            {sectionLabel(t("PartnerDash.sectionPaymentMethod"))}
            <div style={{ display: "flex", gap: 14, alignItems: "flex-start", padding: "18px 20px", borderRadius: 12, background: "var(--accent-tint)", border: "1px solid rgba(16,161,113,.18)" }}>
              <div style={{ width: 36, height: 36, borderRadius: "50%", background: "#fff", display: "grid", placeItems: "center", flexShrink: 0, border: "1px solid rgba(16,161,113,.2)" }}>
                <svg viewBox="0 0 16 16" fill="none" stroke="var(--accent-ink)" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" width="16" height="16"><rect x="2" y="7" width="12" height="7" rx="1.5"/><path d="M5 7V5a3 3 0 016 0v2"/></svg>
              </div>
              <div>
                <div style={{ fontSize: 14, fontWeight: 600, color: "var(--ink)", marginBottom: 3, letterSpacing: "-0.01em" }}>{t("PartnerDash.stripeSetupTitle")}</div>
                <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.55 }}>
                  {t("PartnerDash.stripeSetupBody")}
                </div>
              </div>
            </div>

            <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 4 }}>
              {[
                t("PartnerDash.benefitNoCharge"),
                t("PartnerDash.benefitFreeToJoin"),
                t("PartnerDash.benefitNoCommission"),
              ].map((text, i) => (
                <div key={i} style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
                  <div style={{ width: 18, height: 18, borderRadius: "50%", background: "var(--accent-tint)", display: "grid", placeItems: "center", flexShrink: 0, marginTop: 1 }}>
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" width="10" height="10" style={{ color: "var(--accent-ink)" }}><path d="M5 12.5l4.5 4.5L19 7"/></svg>
                  </div>
                  <span style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.55 }}>{text}</span>
                </div>
              ))}
            </div>

          </>)}

        </div>

        {/* Footer */}
        <div style={{ padding: "20px 36px", borderTop: "1px solid rgba(10,10,11,.07)", flexShrink: 0, display: "flex", flexDirection: "column", gap: 10 }}>
          {saveError && (
            <div style={{ background: "var(--danger-tint)", color: "var(--danger)", borderRadius: 8, padding: "10px 14px", fontSize: 13, lineHeight: 1.5 }}>
              {saveError}
            </div>
          )}
          <div style={{ display: "flex", gap: 10 }}>
            {step === 2 && (initialStep || 1) === 1 && (
              <button onClick={() => setStep(1)} style={{ height: 46, padding: "0 24px", borderRadius: 12, background: "transparent", border: "1.5px solid rgba(10,10,11,.13)", fontSize: 14, fontWeight: 500, fontFamily: "var(--sans)", color: "var(--ink)", cursor: "default" }}>
                {t("PartnerDash.back")}
              </button>
            )}
            <button onClick={step === 1 ? handleContinue : startCardSetup} disabled={saving} data-testid="onboard-cta" data-step={step} style={{ flex: 1, height: 46, borderRadius: 12, background: "var(--accent)", color: "#fff", border: "none", fontSize: 14, fontWeight: 600, fontFamily: "var(--sans)", cursor: "default", opacity: saving ? 0.7 : 1 }}>
              {step === 1 ? (saving ? t("PartnerDash.saving") : t("PartnerDash.continue")) : (saving ? t("PartnerDash.redirecting") : t("PartnerDash.listYourBusiness"))}
            </button>
          </div>
          {step === 2 && (
            <p style={{ margin: 0, fontSize: 11.5, color: "var(--mute)", textAlign: "center" }}>
              {t("PartnerDash.legalAgree")}{" "}
              <a href="/terms" target="_blank" rel="noopener noreferrer" style={{ color: "var(--ink-2)", textDecoration: "underline", textDecorationColor: "rgba(10,10,11,.3)" }}>{t("PartnerDash.legalTerms")}</a>
              {" "}{t("PartnerDash.legalAnd")}{" "}
              <a href="/privacy" target="_blank" rel="noopener noreferrer" style={{ color: "var(--ink-2)", textDecoration: "underline", textDecorationColor: "rgba(10,10,11,.3)" }}>{t("PartnerDash.legalPrivacy")}</a>.
            </p>
          )}
        </div>
      </div>
    </div>

    </>
  );
};

// ── Root dashboard page ───────────────────────────────────────────────────────

const PartnerDashPage = ({ onHome, tab = "referrals", onTab, filter = "all", onFilter, referralId, onSelectReferral, onBackToList }) => {
  // Onboarding modal visibility + which step to open at is decided from the
  // partner's status once /me loads (see effect below), not shown by default.
  const [showOnboarding, setShowOnboarding] = React.useState(false);
  const [onboardingStep, setOnboardingStep] = React.useState(1);

  // Profile the partner already provided at sign-up — used to prefill onboarding.
  // Referrals are now fetched a page at a time inside DashReferrals (server-side
  // pagination), so this page no longer loads the full list.
  const [profile, setProfile] = React.useState(null);

  // Aggregate program stats (GET /v1/partners/stats) — shared by the sidebar's
  // "new referrals" badge and the stat cards above the referral table. Account-
  // wide values, so fetched here rather than per-tab; re-fetched on tab switches
  // and by DashReferrals after events that change the counts (opening a new
  // referral, returning from a detail). Non-critical: on error placeholders stay.
  // Latest-wins: refreshes can overlap (e.g. the post-review refresh racing an
  // earlier fetch), so only the newest request may land in state.
  const [stats, setStats] = React.useState(null);
  const statsSeq = React.useRef(0);
  const refreshStats = React.useCallback(() => {
    if (!OK_API.isSignedIn()) return;
    const seq = ++statsSeq.current;
    OK_API.getPartnerStats()
      .then((s) => { if (seq === statsSeq.current) setStats(s); })
      .catch(() => {});
  }, []);
  React.useEffect(() => { refreshStats(); }, [tab, refreshStats]);

  // Detail-page design follows the account type: appraisers get the Gutachten
  // "accept case" flow, workshops get the "make an offer" flow.
  const viewMode = profile && profile.partnerType === "Appraiser" ? "gutachten" : "workshop";

  React.useEffect(() => {
    if (!OK_API.isSignedIn()) return;
    // Auto-open the onboarding modal only right after a login/activation (one-shot
    // flag set by goPartnerDash), never on a plain page refresh — a reload reloads
    // the app and clears the flag, so the persistent prompt is the box at the top of
    // the referral list (see DashReferrals) rather than the modal popping up again.
    const justAuthed = !!window.__okJustAuthed;
    window.__okJustAuthed = false;
    let alive = true;
    OK_API.getPartnerMe()
      .then((p) => {
        if (!alive) return;
        setProfile(p);
        // Gate onboarding off status: profile/docs incomplete → step 1;
        // only payment missing → step 2; complete or verified → no modal.
        const o = partnerOnboarding(p);
        if (justAuthed && o.known && !o.verified && !o.onboardingComplete) {
          setOnboardingStep(o.profileComplete ? 2 : 1);
          setShowOnboarding(true);
        }
      })
      .catch(() => { /* prefill is best-effort; onboarding still works without it */ });
    return () => { alive = false; };
  }, []);

  const signOut = () => { OK_API.signOut(); onHome(); };

  return (
    <div style={{ minHeight: "100vh", display: "flex", background: "#F5F4F1", fontFamily: "var(--sans)" }}>
      <DashSidebar tab={tab} onTab={onTab} onHome={signOut} newCount={stats ? stats.newReferrals : 0} />
      <main style={{ flex: 1, overflowY: "auto", minHeight: "100vh" }}>
        {tab === "referrals" && <DashReferrals viewMode={viewMode} filter={filter} onFilter={onFilter} profile={profile} stats={stats} refreshStats={refreshStats} onTab={onTab} referralId={referralId} onSelectReferral={onSelectReferral} onBackToList={onBackToList} />}
        {tab === "payments"  && <DashPayments  viewMode={viewMode} profile={profile} />}
        {tab === "settings"  && <DashSettings  onHome={signOut} viewMode={viewMode} profile={profile} onProfileUpdate={setProfile} />}
      </main>
      {showOnboarding && <OnboardingModal initialStep={onboardingStep} onClose={() => setShowOnboarding(false)} profile={profile} onProfileUpdate={setProfile} />}
    </div>
  );
};
