// Gruv Gear employee inventory dashboard.
// Ledger direction focused on internal stock control, holds, payment deadlines,
// and regional operations.

const INV_STATUS = {
  holding: 'Holding',
  expired: 'Expired',
  shipped: 'Shipped',
};

const INV_EMPLOYEE = {
  name: 'Ari Chen',
  role: 'Inventory Ops',
  id: 'EMP-0142',
};

const addDays = (d) => {
  const x = new Date();
  x.setDate(x.getDate() + d);
  return x;
};

const dateISO = (d) => d.toISOString().slice(0, 10);
const shortDate = (d) => new Date(d + 'T12:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric' });

const MOCK_HOLDS = [
  { id: 'SO-73108', sku: 'FWPRO-BLK-MD', region: 'Shanghai', account: 'Marlowe & Finch', qty: 120, deadline: dateISO(addDays(2)), status: 'holding' },
  { id: 'SO-73112', sku: 'FWPRO-WHT-LG-TT', region: 'California', account: 'North Coast Bass', qty: 420, deadline: dateISO(addDays(1)), status: 'holding' },
  { id: 'SO-73088', sku: 'FW-1PK-BLU-LG', region: 'California', account: 'Tone Barn', qty: 80, deadline: dateISO(addDays(-1)), status: 'holding' },
  { id: 'SO-73041', sku: 'SLIIV-TECH2-PRO', region: 'Germany', account: 'Berlin Backline', qty: 44, deadline: dateISO(addDays(-3)), status: 'holding' },
  { id: 'SO-73002', sku: 'AMG500-FKD', region: 'Shanghai', account: 'Stage Supply APAC', qty: 96, deadline: dateISO(addDays(4)), status: 'holding' },
  { id: 'SO-72991', sku: 'FWPRO-BLK-LG', region: 'Germany', account: 'Roma Music Works', qty: 72, deadline: dateISO(addDays(3)), status: 'holding' },
  { id: 'SO-72944', sku: 'FW-1PK-WHT-XL', region: 'Shanghai', account: 'Pickup House', qty: 58, deadline: dateISO(addDays(-5)), status: 'shipped' },
  { id: 'SO-72920', sku: 'SLIIV-TECH2-15', region: 'California', account: 'Session Depot', qty: 36, deadline: dateISO(addDays(-2)), status: 'shipped' },
];

const PROD_STATUS_STYLES = {
  'In Production': { bg: '#fffaeb', color: '#b54708' },
  'Sampling':      { bg: '#f4f3ff', color: '#6927da' },
  'Ready to Ship': { bg: '#ecfdf3', color: '#067647' },
  'Paid in Full':  { bg: '#d1fae5', color: '#065f46' },
  'Pending':       { bg: '#f2f4f7', color: '#344054' },
  'On Hold':       { bg: '#eaf0ff', color: '#2e5dff' },
  'Delayed':       { bg: '#fef3f2', color: '#b42318' },
};

// Production Board — one status map per production stage (mockup 19JUN2026).
const STAGE_SAMPLING_STYLES = {
  'Sample requested': { bg: '#f2f4f7', color: '#344054' },
  'In process':       { bg: '#fffaeb', color: '#b54708' },
  'Sample approved':  { bg: '#d1fae5', color: '#065f46' },
  'On hold':          { bg: '#fee4e2', color: '#b42318' },
};
const STAGE_PO_STYLES = {
  'PO issued':    { bg: '#f2f4f7', color: '#344054' },
  'PO confirmed': { bg: '#eaf0ff', color: '#2e5dff' },
  'Deposit paid': { bg: '#d1fae5', color: '#065f46' },
  'On hold':      { bg: '#fee4e2', color: '#b42318' },
};
const STAGE_PRODUCTION_STYLES = {
  'Scheduled':     { bg: '#f2f4f7', color: '#344054' },
  'In production': { bg: '#fffaeb', color: '#b54708' },
  'Complete':      { bg: '#d1fae5', color: '#065f46' },
  'On hold':       { bg: '#fee4e2', color: '#b42318' },
};
const STAGE_QC_STYLES = {
  'Scheduled': { bg: '#f2f4f7', color: '#344054' },
  'Checking':  { bg: '#fffaeb', color: '#b54708' },
  'Passed':    { bg: '#d1fae5', color: '#065f46' },
  'On hold':   { bg: '#fee4e2', color: '#b42318' },
};
const STAGE_PAYMENT_STYLES = {
  'Ready to pay': { bg: '#f2f4f7', color: '#344054' },
  'Pending':      { bg: '#fffaeb', color: '#b54708' },
  'Paid':         { bg: '#d1fae5', color: '#065f46' },
  'On hold':      { bg: '#fee4e2', color: '#b42318' },
};
const STAGE_SHIPMENT_STYLES = {
  'Ready to ship': { bg: '#f2f4f7', color: '#344054' },
  'In transit':    { bg: '#eaf0ff', color: '#2e5dff' },
  'Received':      { bg: '#d1fae5', color: '#065f46' },
  'On hold':       { bg: '#fee4e2', color: '#b42318' },
};
// Order Dashboard — status maps (mockup 19JUN2026).
const ORDER_INVOICING_STYLES = {
  'Invoice created':     { bg: '#f2f4f7', color: '#344054' },
  'Sent for payment':    { bg: '#fffaeb', color: '#b54708' },
  'Received / NET terms': { bg: '#d1fae5', color: '#065f46' },
  'On hold':             { bg: '#fee4e2', color: '#b42318' },
};
const ORDER_SHIPMENT_STYLES = {
  'Ready to ship': { bg: '#f2f4f7', color: '#344054' },
  'Prep and QC':   { bg: '#fffaeb', color: '#b54708' },
  'Shipped':       { bg: '#d1fae5', color: '#065f46' },
  'On hold':       { bg: '#fee4e2', color: '#b42318' },
};
const ORDER_FINAL_STYLES = {
  'Notify customer': { bg: '#eaf0ff', color: '#2e5dff' },
  'NET terms':       { bg: '#fffaeb', color: '#b54708' },
  'Closed':          { bg: '#d1fae5', color: '#065f46' },
  'Cancelled':       { bg: '#fee4e2', color: '#b42318' },
};

// An unset status renders gray/blank rather than defaulting to a stage's first
// state — a status only shows once someone has actually set it (feedback JUN2026).
const STATUS_EMPTY_STYLE = { bg: '#eceef2', color: '#98a2b3' };
const STATUS_EMPTY_LABEL = '—';

// Every board status is drawn as a rectangular right-pointing arrow so the row
// reads as a progress flow (like a PM tool's status pipeline).
function statusChipStyle(s) {
  return {
    display: 'inline-block',
    padding: '4px 15px 4px 11px',
    background: s.bg, color: s.color,
    fontSize: 11, fontWeight: 700, whiteSpace: 'nowrap',
    clipPath: 'polygon(0 0, calc(100% - 8px) 0, 100% 50%, calc(100% - 8px) 100%, 0 100%)',
  };
}

const LEAD_AVATAR_BG = { AC: '#2e5dff', RP: '#16a34a', SK: '#0891b2', TO: '#b45309', JM: '#7c3aed' };

// Production Board rows (mockup 19JUN2026): SKU · PO · comments · factory · qty ·
// lead time · days elapsed · 5 production stages · deposit + target/actual dates.
const MOCK_PRODUCTIONS = [
  { id: 1, item: 'Stadium Bag', sku: 'VB01-BLK', comments: [{ author: 'Ari Chen', text: 'PO issued to factory', date: '4/10/2026' }], poNum: '1460', factory: 'Easy Carry Thailand', qty: 300, leadTime: '90', depositDate: '2026-04-12', stageSampling: 'Sample approved', stageSamplingNote: 'SEP 13', stagePO: 'PO issued', stagePONote: 'SEP 13', stageProduction: 'Scheduled', stageQC: 'Scheduled', stagePayment: 'Ready to pay', stageShipment: 'Ready to ship', stageShipmentNote: 'getting quote' },
  { id: 2, item: 'DuoStrap Neo (Black)', sku: 'DS2-NEO-BLK', comments: [{ author: 'Tyler O.', text: 'Sampling in progress', date: '5/1/2026' }], poNum: '1461', factory: 'Tianjin Acoustic', qty: 500, leadTime: '60', depositDate: '2026-04-20', stageSampling: 'In process', stagePO: 'Deposit paid', stageProduction: 'In production', stageProductionNote: 'ETA SEP 18', stageQC: 'Scheduled', stagePayment: 'Pending', stageShipment: 'Ready to ship' },
  { id: 3, item: 'FretWraps Pro 1-Pack (Black, Medium)', sku: 'FWPRO-BLK-MD', comments: [{ author: 'Ari Chen', text: 'Deposit paid', date: '4/15/2026' }, { author: 'Ryan Park', text: 'On track', date: '5/3/2026' }], poNum: '1467', factory: '3-Way Musical', qty: 288, leadTime: '60', depositDate: '2026-03-28', stageSampling: 'Sample approved', stagePO: 'Deposit paid', stagePONote: 'SEP 15', stageProduction: 'Complete', stageProductionNote: 'OCT 10', stageQC: 'Passed', stagePayment: 'Paid', stagePaymentNote: 'OCT 13', stageShipment: 'In transit', stageShipmentNote: 'OCT 16' },
  { id: 4, item: 'JP FretWraps', sku: 'FW-JP-3PK', comments: [{ author: 'Ryan Park', text: 'Factory confirmed lead time', date: '4/10/2026' }], poNum: '1469', factory: 'Wenxin', qty: 10000, leadTime: '45', depositDate: '2026-04-05', stageSampling: 'Sample approved', stagePO: 'Deposit paid', stageProduction: 'In production', stageQC: 'Checking', stagePayment: 'Pending', stageShipment: 'Ready to ship' },
  { id: 5, item: 'FretWraps Black 1PK, 3PK', sku: 'FW-BLK-MIX', comments: [{ author: 'Ari Chen', text: 'Mix ratio: 60% 1PK, 40% 3PK', date: '4/20/2026' }], poNum: '1471', factory: 'Wenxin', qty: 10000, leadTime: '45', depositDate: '2026-04-22', stageSampling: 'On hold', stagePO: 'PO confirmed', stageProduction: 'On hold', stageQC: 'Scheduled', stagePayment: 'Ready to pay', stageShipment: 'On hold' },
  { id: 6, item: 'FretWraps Assorted 1PK', sku: 'FW-1PK-AST', comments: [{ author: 'Sam Kim', text: 'Assortment approved', date: '4/22/2026' }], poNum: '1472', factory: 'Wenxin', qty: 11050, leadTime: '45', depositDate: '2026-04-25', stageSampling: 'Sample requested', stagePO: 'PO issued', stageProduction: 'Scheduled', stageQC: 'Scheduled', stagePayment: 'Ready to pay', stageShipment: 'Ready to ship' },
];

// Order Dashboard rows (mockup 19JUN2026): invoice · comments · order received ·
// customer · salesperson · owner · origin · invoicing · shipment · final · ship · tracking.
const MOCK_ORDERS = [
  { id: 1, invoiceNum: 'GG-20212', comments: [{ author: 'Prince', text: 'Customer confirmed PO', date: '5/22/2026' }], orderReceived: '2026-05-22', customer: 'Ernie Ball USA', salesperson: 'Jay', owner: 'Prince', origin: 'Domestic', warehouse: 'Shanghai', invoicing: 'Invoice created', invoicingNote: 'JUL 12', shipment: 'Ready to ship', finalStatus: 'Notify customer', tracking: '1Z4808ELG48938' },
  { id: 2, invoiceNum: 'GG-20213', comments: [], orderReceived: '2026-05-24', customer: 'Thomann GmbH', salesperson: 'Mia', owner: 'Prince', origin: 'International', warehouse: 'Germany', invoicing: 'Sent for payment', invoicingNote: 'JUL 12', shipment: 'Prep and QC', shipmentNote: 'waiting for quote', finalStatus: 'NET terms', finalStatusNote: 'ETA AUG 25', tracking: '' },
  { id: 3, invoiceNum: 'GG-20214', comments: [{ author: 'Jay', text: 'Awaiting wire confirmation', date: '5/26/2026' }], orderReceived: '2026-05-25', customer: 'Sweetwater', salesperson: 'Jay', owner: 'Dana', origin: 'Domestic', warehouse: 'California', invoicing: 'Received / NET terms', shipment: 'Shipped', shipmentNote: 'JUL 25', finalStatus: 'Closed', tracking: '1Z4808ELG50021' },
  { id: 4, invoiceNum: 'GG-20215', comments: [], orderReceived: '2026-05-27', customer: 'Nakano Japan', salesperson: 'Mia', owner: 'Dana', origin: 'International', warehouse: 'Shanghai', invoicing: 'On hold', invoicingNote: 'backordered', shipment: 'On hold', finalStatus: 'Cancelled', tracking: '' },
  { id: 5, invoiceNum: 'GG-20216', comments: [{ author: 'Dana', text: 'Rush order — expedite', date: '5/28/2026' }], orderReceived: '2026-05-28', customer: 'Guitar Center', salesperson: 'Jay', owner: 'Prince', origin: 'Domestic', warehouse: 'California', invoicing: 'Invoice created', shipment: 'Prep and QC', finalStatus: 'Notify customer', tracking: '' },
];

// New rows start with every status blank (see STATUS_EMPTY_STYLE) — statuses are
// set as the item actually moves through each stage, not defaulted up front.
const makeEmptyProduction = () => ({ item: 'New item', sku: '', comments: [], poNum: '', factory: '', qty: 0, leadTime: '', depositDate: '', stageSampling: '', stagePO: '', stageProduction: '', stageQC: '', stagePayment: '', stageShipment: '' });
const makeEmptyOrder = () => ({ invoiceNum: '', comments: [], orderReceived: '', customer: 'New customer', salesperson: '', owner: '', origin: 'Domestic', warehouse: 'Shanghai', invoicing: '', shipment: '', finalStatus: '', tracking: '' });

const PROD_COLUMNS = [
  { key: 'item', label: 'Product SKU', type: 'namesku', width: 200 },
  { key: 'poNum', label: 'Invoice #', type: 'text', mono: true, width: 100 },
  { key: 'comments', label: 'Comments', type: 'comments', align: 'center', width: 80 },
  { key: 'factory', label: 'Factory Name', type: 'text', width: 150 },
  { key: 'qty', label: 'Quantity', type: 'number', align: 'right', width: 90 },
  { key: 'leadTime', label: 'Lead time', type: 'leaddays', width: 110 },
  { key: 'daysElapsed', label: 'Days Elapsed', type: 'dayselapsed', align: 'right', width: 100 },
  { key: 'stageSampling', label: '1 · Sampling', type: 'status', styles: STAGE_SAMPLING_STYLES, group: 'Production Stages', width: 150 },
  { key: 'stagePO', label: '2 · Purchase Order', type: 'status', styles: STAGE_PO_STYLES, group: 'Production Stages', width: 150 },
  { key: 'stageProduction', label: '3 · Production', type: 'status', styles: STAGE_PRODUCTION_STYLES, group: 'Production Stages', width: 150 },
  { key: 'stageQC', label: '4 · QC Inspection', type: 'status', styles: STAGE_QC_STYLES, group: 'Production Stages', width: 150 },
  { key: 'stagePayment', label: '5 · Payment', type: 'status', styles: STAGE_PAYMENT_STYLES, group: 'Production Stages', width: 140 },
  { key: 'stageShipment', label: '6 · Shipment', type: 'status', styles: STAGE_SHIPMENT_STYLES, group: 'Production Stages', width: 150 },
];

const ORDER_COLUMNS = [
  { key: 'invoiceNum', label: 'Invoice #', type: 'text', mono: true, width: 110 },
  { key: 'comments', label: 'Comments', type: 'comments', align: 'center', width: 80 },
  { key: 'orderReceived', label: 'Order received', type: 'date', width: 130 },
  { key: 'customer', label: 'Customer', type: 'text', width: 170, sortable: true },
  { key: 'salesperson', label: 'Salesperson', type: 'text', width: 120, sortable: true },
  { key: 'owner', label: 'Owner', type: 'text', width: 120, sortable: true },
  { key: 'warehouse', label: 'Warehouse', type: 'warehouse', width: 140, sortable: true },
  { key: 'invoicing', label: 'Invoicing', type: 'status', styles: ORDER_INVOICING_STYLES, group: 'Order Status', width: 160 },
  { key: 'shipment', label: 'Shipment', type: 'status', styles: ORDER_SHIPMENT_STYLES, group: 'Order Status', width: 150 },
  { key: 'finalStatus', label: 'Final Status', type: 'status', styles: ORDER_FINAL_STYLES, group: 'Order Status', width: 160 },
  { key: 'tracking', label: 'Tracking', type: 'text', mono: true, width: 160 },
];

// Status-type column keys — the meaningful state changes we log to the activity feed.
const PROD_STATUS_KEYS = new Set(PROD_COLUMNS.filter((c) => c.type === 'status').map((c) => c.key));
const ORDER_STATUS_KEYS = new Set(ORDER_COLUMNS.filter((c) => c.type === 'status').map((c) => c.key));

function boardDaysElapsed(iso) {
  if (!iso) return '';
  const d = new Date(iso + 'T12:00:00');
  if (isNaN(d.getTime())) return '';
  return Math.max(0, Math.round((Date.now() - d.getTime()) / 86400000));
}

function useInventoryDashboard(catalog, token) {
  const seeded = React.useMemo(() => catalog.map((p) => ({ ...p, stock: { ...p.stock } })), [catalog]);
  const [products, setProducts] = React.useState(seeded);
  const [region, setRegion] = React.useState('Shanghai');
  const [search, setSearch] = React.useState('');
  const [category, setCategory] = React.useState('All');
  const [sort, setSort] = React.useState({ key: 'afterHold', dir: 'asc' });
  const [editorSku, setEditorSku] = React.useState(null);
  const [newOpen, setNewOpen] = React.useState(false);
  const [pendingStockChange, setPendingStockChange] = React.useState(null);
  const [holds, setHolds] = React.useState([]);
  const today = dateISO(new Date());

  // Real order-driven stock holds (replaces the demo mocks).
  React.useEffect(() => {
    if (!token) return undefined;
    let cancelled = false;
    fetch('/api/inventory?resource=holds', { headers: { Authorization: `Bearer ${token}` } })
      .then((r) => (r.ok ? r.json() : Promise.reject(new Error('holds'))))
      .then((d) => { if (!cancelled) setHolds(d.holds || []); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [token]);

  const [importStatus, setImportStatus] = React.useState(null);

  // Append an activity-log entry (fire-and-forget; actor derived server-side).
  const logAudit = React.useCallback((action, target, detail) => {
    if (!token) return;
    fetch('/api/inventory', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
      body: JSON.stringify({ action: 'log', entry: { action, target, detail, warehouse: region } }),
    }).catch(() => {});
  }, [token, region]);

  // Load server-authoritative on-hand for a region, overlay it onto stock, and
  // surface stock-only SKUs. Reused by the region effect and the CSV import.
  const loadStock = React.useCallback((rgn = region) => {
    return fetch(`/api/inventory?resource=stock&warehouse=${encodeURIComponent(rgn)}`)
      .then((r) => (r.ok ? r.json() : Promise.reject(new Error('stock'))))
      .then((d) => {
        const rows = d.stock || [];
        const m = {};
        rows.forEach((s) => { m[s.sku] = s; });
        setProducts((prev) => {
          const merged = prev.map((p) => ({ ...p, stock: { ...p.stock, [rgn]: m[p.sku] ? m[p.sku].on_hand : 0 } }));
          const known = new Set(merged.map((p) => p.sku));
          rows.forEach((s) => {
            if (known.has(s.sku)) return;
            merged.push({
              sku: s.sku, name: s.name || s.sku, category: s.category || 'Uncategorized',
              color: '', size: '—', moq: 1, retail: 0, buy: 0, discount: 0, image: '',
              description: '', bullets: [], stock: { Shanghai: 0, California: 0, Germany: 0, [rgn]: s.on_hand },
            });
          });
          return merged;
        });
      });
  }, [region]);

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

  const holdRows = React.useMemo(() => holds.map((h) => {
    const deadline = h.deadline ? String(h.deadline).slice(0, 10) : '9999-12-31';
    const status = h.status || 'holding';
    return {
      id: h.id, sku: h.sku, region: h.warehouse, account: h.account || '—', qty: Number(h.qty) || 0, deadline, status,
      computedStatus: status === 'holding' && deadline < today ? 'expired' : status,
    };
  }), [holds, today]);

  const enriched = React.useMemo(() => products.map((p) => {
    const related = holdRows.filter((h) => h.sku === p.sku && h.region === region);
    const holding = related.filter((h) => h.computedStatus === 'holding').reduce((a, h) => a + h.qty, 0);
    const expired = related.filter((h) => h.computedStatus === 'expired').reduce((a, h) => a + h.qty, 0);
    const shipped = related.filter((h) => h.computedStatus === 'shipped').reduce((a, h) => a + h.qty, 0);
    const primaryHold = related.find((h) => h.computedStatus === 'holding') || related.find((h) => h.computedStatus === 'expired') || related.find((h) => h.computedStatus === 'shipped');
    const base = Number(p.stock[region] || 0);
    const onHand = Math.max(0, base - shipped);
    const afterHold = onHand - holding;
    const shortage = Math.max(0, -afterHold);
    const statusLabel = shortage > 0 ? 'Short' : holding > 0 ? 'Held' : afterHold < 25 ? 'Low' : 'Healthy';
    return { ...p, base, onHand, holding, expired, shipped, afterHold, shortage, related, holdId: primaryHold?.id || '', holdDeadline: primaryHold?.deadline || '9999-12-31', statusLabel };
  }), [products, region, holdRows]);

  const categories = React.useMemo(() => ['All', ...new Set(products.map((p) => p.category))], [products]);

  const filtered = React.useMemo(() => {
    const q = search.trim().toLowerCase();
    const list = enriched.filter((p) => {
      const matchesSearch = !q || p.name.toLowerCase().includes(q) || p.sku.toLowerCase().includes(q) || p.category.toLowerCase().includes(q);
      if (q) return matchesSearch;
      return (category === 'All' || p.category === category) && matchesSearch;
    });
    const sign = sort.dir === 'asc' ? 1 : -1;
    return [...list].sort((a, b) => {
      const av = typeof a[sort.key] === 'string' ? a[sort.key].toLowerCase() : a[sort.key];
      const bv = typeof b[sort.key] === 'string' ? b[sort.key].toLowerCase() : b[sort.key];
      if (av < bv) return -1 * sign;
      if (av > bv) return 1 * sign;
      return a.sku.localeCompare(b.sku);
    });
  }, [enriched, category, search, sort]);

  const stats = React.useMemo(() => ({
    skus: enriched.length,
    onHand: enriched.reduce((a, p) => a + p.onHand, 0),
    holding: enriched.reduce((a, p) => a + p.holding, 0),
    afterHold: enriched.reduce((a, p) => a + Math.max(0, p.afterHold), 0),
    shortageSkus: enriched.filter((p) => p.shortage > 0).length,
    expired: enriched.reduce((a, p) => a + p.expired, 0),
  }), [enriched]);

  const toggleSort = (key) => setSort((s) => ({ key, dir: s.key === key && s.dir === 'asc' ? 'desc' : 'asc' }));
  const applyStockUpdate = (sku, next, targetRegion = region) => setProducts((prev) => prev.map((p) => p.sku === sku ? { ...p, stock: { ...p.stock, [targetRegion]: Math.max(0, Number(next) || 0) } } : p));
  const updateStock = (sku, next) => {
    const product = enriched.find((p) => p.sku === sku);
    const nextBase = Math.max(0, Number(next) || 0);
    setPendingStockChange({
      sku,
      region,
      current: product?.base || 0,
      currentOnHand: product?.onHand || 0,
      next: nextBase,
      nextOnHand: Math.max(0, nextBase - (product?.shipped || 0)),
      name: product?.name || sku,
    });
  };
  const confirmStockChange = () => {
    if (!pendingStockChange) return;
    const { sku, next, region: rgn } = pendingStockChange;
    applyStockUpdate(sku, next, rgn); // optimistic
    setPendingStockChange(null);
    if (!token) return;
    // Persist to product_stock (only if changed) — logs 'stock.update' server-side.
    fetch('/api/inventory', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
      body: JSON.stringify({ action: 'set-stock', warehouse: rgn, sku, onHand: next }),
    }).then(() => loadStock(rgn)).catch(() => {});
  };
  const cancelStockChange = () => setPendingStockChange(null);
  const patchProduct = (sku, patch) => {
    setProducts((prev) => prev.map((p) => p.sku === sku ? { ...p, ...patch, stock: { ...p.stock, ...(patch.stock || {}) } } : p));
    logAudit('product.edit', sku, `Edited ${Object.keys(patch).filter((k) => k !== 'stock').join(', ') || 'product'}`);
  };
  const deleteProduct = (sku) => {
    setProducts((prev) => prev.filter((p) => p.sku !== sku));
    logAudit('product.delete', sku, 'Deleted product');
  };
  // Deleting a category reassigns every SKU in it to the "No Category" bucket.
  const deleteCategory = (cat) => {
    if (!cat || cat === 'All' || cat === 'No Category') return;
    setProducts((prev) => prev.map((p) => p.category === cat ? { ...p, category: 'No Category' } : p));
    setCategory((c) => (c === cat ? 'All' : c));
    logAudit('category.delete', cat, 'Reassigned SKUs to No Category');
  };
  // Deleting a color clears that label from every SKU that used it.
  const deleteColor = (color) => {
    if (!color) return;
    setProducts((prev) => prev.map((p) => p.color === color ? { ...p, color: '' } : p));
    logAudit('color.delete', color, 'Cleared color from SKUs');
  };
  const addProduct = (form) => {
    setProducts((prev) => {
      if (prev.some((p) => p.sku === form.sku)) return prev;
      return [{ ...form, id: form.sku, moq: Number(form.moq) || 1, retail: Number(form.retail) || 0, buy: Number(form.buy) || 0, discount: 0.6, image: form.image || '', description: '', bullets: [], stock: { Shanghai: 0, California: 0, Germany: 0, ...form.stock } }, ...prev];
    });
    logAudit('product.add', form.sku, `Added ${form.name || form.sku}`);
  };
  // Import a stock CSV or XLSX (SKU + on-hand) → update on_hand where changed.
  // Format is detected by content (xlsx = "PK" ZIP magic), not the extension —
  // so a mislabeled file (e.g. an .xlsx renamed to .csv) still imports.
  const importCSV = (file) => {
    if (!file || !token) return;
    const reader = new FileReader();
    reader.onload = async () => {
      const bytes = new Uint8Array(reader.result || new ArrayBuffer(0));
      const isXlsx = bytes[0] === 0x50 && bytes[1] === 0x4b; // "PK" — OOXML/ZIP
      let rows;
      try {
        if (isXlsx) {
          if (!window.XLSX) { setImportStatus({ type: 'error', text: 'Spreadsheet parser not loaded — refresh and retry.' }); return; }
          const wb = window.XLSX.read(bytes, { type: 'array' });
          const ws = wb.Sheets[wb.SheetNames[0]];
          rows = stockRowsFromGrid(window.XLSX.utils.sheet_to_json(ws, { header: 1, defval: '' }));
        } else {
          rows = stockRowsFromGrid(csvToGrid(new TextDecoder('utf-8').decode(bytes)));
        }
      } catch (_) { setImportStatus({ type: 'error', text: 'Could not read that file.' }); return; }
      if (!rows.length) { setImportStatus({ type: 'error', text: 'No SKU / on-hand columns found in that file.' }); return; }
      setImportStatus({ type: 'busy', text: `Importing ${rows.length} rows into ${region}…` });
      try {
        const res = await fetch('/api/inventory', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
          body: JSON.stringify({ action: 'import-stock', warehouse: region, rows }),
        });
        const d = await res.json().catch(() => ({}));
        if (!res.ok) { setImportStatus({ type: 'error', text: d.error || 'Import failed.' }); return; }
        await loadStock(region).catch(() => {});
        setImportStatus({ type: 'ok', text: `${region}: updated ${d.changed} of ${d.total} SKUs from ${isXlsx ? 'XLSX' : 'CSV'}.` });
      } catch (_) {
        setImportStatus({ type: 'error', text: 'Import failed.' });
      }
    };
    reader.readAsArrayBuffer(file);
  };

  // Export the current region's inventory as a real .xlsx workbook (SheetJS is
  // loaded in demo.html). Falls back to CSV if SheetJS didn't load.
  const exportCSV = () => {
    const cols = ['sku', 'name', 'category', 'region', 'onHand', 'holding', 'afterHold', 'expired', 'shipped', 'shortage'];
    const rows = filtered.map((p) => [p.sku, p.name, p.category, region, p.onHand, p.holding, p.afterHold, p.expired, p.shipped, p.shortage]);
    const base = `gruvgear-${region.toLowerCase()}-inventory`;
    if (window.XLSX) {
      const ws = window.XLSX.utils.aoa_to_sheet([cols, ...rows]);
      const wb = window.XLSX.utils.book_new();
      window.XLSX.utils.book_append_sheet(wb, ws, String(region || 'Inventory').slice(0, 31));
      window.XLSX.writeFile(wb, `${base}.xlsx`);
      return;
    }
    const csv = [cols, ...rows].map((r) => r.map((v) => `"${String(v).replace(/"/g, '""')}"`).join(',')).join('\n');
    const a = document.createElement('a');
    a.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' }));
    a.download = `${base}.csv`;
    a.click();
    URL.revokeObjectURL(a.href);
  };

  return { products, region, setRegion, search, setSearch, category, setCategory, sort, toggleSort, filtered, categories, stats, editorSku, setEditorSku, newOpen, setNewOpen, updateStock, pendingStockChange, confirmStockChange, cancelStockChange, patchProduct, deleteProduct, deleteCategory, deleteColor, addProduct, importCSV, exportCSV, logAudit, importStatus, setImportStatus };
}

// Detect SKU + on-hand columns over a 2D grid (from a CSV or an xlsx sheet) and
// return [{ sku, onHand }]. Recognizes columns by header; falls back to the first
// two columns (which also handles the Shanghai sheet's non-Latin headers).
function stockRowsFromGrid(grid) {
  if (!grid.length) return [];
  const header = grid[0].map((h) => String(h).toLowerCase().replace(/[^a-z0-9]/g, ''));
  const skuIdx = header.findIndex((h) => ['sku', 'productid', 'productidsku', 'item', 'style'].includes(h));
  const qtyIdx = header.findIndex((h) => ['onhand', 'onhandcurrent', 'onstock', 'available', 'qty', 'quantity', 'stock'].includes(h));
  const hasHeader = skuIdx >= 0 || qtyIdx >= 0;
  const body = hasHeader ? grid.slice(1) : grid;
  const si = skuIdx >= 0 ? skuIdx : 0;
  const qi = qtyIdx >= 0 ? qtyIdx : 1;
  return body.map((cells) => {
    const sku = String(cells[si] ?? '').trim();
    const onHand = Number(cells[qi]);
    if (!sku || !Number.isFinite(onHand)) return null;
    return { sku, onHand };
  }).filter(Boolean);
}

// Minimal CSV → 2D grid (quote-aware).
function csvToGrid(text) {
  const split = (line) => {
    const cells = [];
    let cur = '', quoted = false;
    for (let i = 0; i < line.length; i++) {
      const ch = line[i];
      if (ch === '"' && line[i + 1] === '"') { cur += '"'; i++; }
      else if (ch === '"') quoted = !quoted;
      else if (ch === ',' && !quoted) { cells.push(cur.trim()); cur = ''; }
      else cur += ch;
    }
    cells.push(cur.trim());
    return cells;
  };
  return text.split(/\r?\n/).filter((line) => line.trim()).map(split);
}

function parseInventoryCSV(text) {
  const lines = text.split(/\r?\n/).filter((line) => line.trim());
  if (lines.length < 2) return [];
  const split = (line) => {
    const cells = [];
    let cur = '', quoted = false;
    for (let i = 0; i < line.length; i++) {
      const ch = line[i];
      if (ch === '"' && line[i + 1] === '"') { cur += '"'; i++; }
      else if (ch === '"') quoted = !quoted;
      else if (ch === ',' && !quoted) { cells.push(cur.trim()); cur = ''; }
      else cur += ch;
    }
    cells.push(cur.trim());
    return cells;
  };
  const headers = split(lines[0]).map((h) => h.toLowerCase().replace(/[^a-z0-9]/g, ''));
  const pick = (cells, names) => {
    const idx = headers.findIndex((h) => names.includes(h));
    return idx >= 0 ? cells[idx] : '';
  };
  return lines.slice(1).map((line) => {
    const cells = split(line);
    const sku = pick(cells, ['sku', 'productid', 'productidsku']);
    if (!sku) return null;
    return {
      id: sku,
      sku,
      brand: pick(cells, ['brand']) || 'Gruv Gear',
      name: pick(cells, ['name', 'productname']) || sku,
      category: pick(cells, ['category']) || 'Imported',
      color: pick(cells, ['color']) || '-',
      size: pick(cells, ['size']) || '-',
      upc: pick(cells, ['upc', 'itemupc']),
      dimensions: pick(cells, ['dimensions', 'sizein']),
      weight: pick(cells, ['weight', 'itemwt']),
      moq: Number(pick(cells, ['moq', 'moqincrements'])) || 10,
      retail: Number(pick(cells, ['retail', 'retailprice'])) || 0,
      buy: Number(pick(cells, ['buy', 'buyprice', 'wholesale'])) || 0,
      discount: 0.6,
      image: pick(cells, ['image', 'imageurl']) || 'assets/products/image1.png',
      description: pick(cells, ['description', 'productdescription']),
      bullets: [],
      stock: {
        Shanghai: Number(pick(cells, ['shanghai', 'shanghaistock'])) || 0,
        California: Number(pick(cells, ['california', 'californiastock'])) || 0,
        Germany: Number(pick(cells, ['germany', 'germanystock', 'onstock'])) || 0,
      },
    };
  }).filter(Boolean);
}

// Generic editable board used by both the Production Board and Order Dashboard.
// Rows are free-form objects; search matches any string field.
function useBoard(initialRows, makeEmptyRow, audit) {
  const [rows, setRows] = React.useState(initialRows);
  // Re-seed when async initial data (e.g. fetched production rows) arrives.
  React.useEffect(() => { setRows(initialRows); }, [initialRows]);
  const [search, setSearch] = React.useState('');
  const [commentsRow, setCommentsRow] = React.useState(null);

  // Log a board operation to the activity log (status changes + structural ops;
  // free-text field edits are intentionally not logged to avoid keystroke noise).
  const logBoard = (op, row, detail) => {
    if (audit && audit.log) audit.log(`${audit.entity}.${op}`, audit.label && row ? audit.label(row) : (row && row.id), detail);
  };

  const filtered = React.useMemo(() => {
    const q = search.trim().toLowerCase();
    if (!q) return rows;
    return rows.filter((r) => Object.entries(r).some(([k, v]) => k !== 'comments' && String(v).toLowerCase().includes(q)));
  }, [rows, search]);

  const updateRow = (id, key, value) => {
    if (audit && audit.statusKeys && audit.statusKeys.has(key)) logBoard('status', rows.find((r) => r.id === id), `${key} → ${value}`);
    setRows((prev) => prev.map((r) => r.id === id ? { ...r, [key]: value } : r));
  };
  const deleteRow = (id) => {
    logBoard('delete', rows.find((r) => r.id === id), 'Deleted row');
    setRows((prev) => prev.filter((r) => r.id !== id));
  };
  // Archiving flags the row and drops it to the bottom of the list.
  const archiveRow = (id) => {
    const target = rows.find((r) => r.id === id);
    if (!target) return;
    logBoard('archive', target, 'Archived row');
    setRows((prev) => [...prev.filter((r) => r.id !== id), { ...target, archived: true }]);
  };
  const addRow = () => {
    const row = { ...makeEmptyRow(), id: Date.now() };
    logBoard('add', row, 'Added row');
    setRows((prev) => [...prev, row]);
  };
  const addComment = (rowId, text, attachments = [], author) => {
    if (!text.trim() && attachments.length === 0) return;
    logBoard('comment', rows.find((r) => r.id === rowId), text.trim());
    setRows((prev) => prev.map((r) => r.id === rowId ? { ...r, comments: [...r.comments, { author: author || INV_EMPLOYEE.name, text: text.trim(), date: new Date().toLocaleDateString('en-US'), attachments }] } : r));
  };

  return { rows, filtered, search, setSearch, commentsRow, setCommentsRow, updateRow, deleteRow, archiveRow, addRow, addComment };
}

function InvSortLabel({ label, sortKey, state, onSort, color }) {
  const active = state.sort.key === sortKey;
  return (
    <button onClick={() => onSort(sortKey)} style={{
      border: 'none', background: 'transparent', padding: 0, color,
      font: 'inherit', textTransform: 'inherit', letterSpacing: 'inherit',
      cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 4,
    }}>
      {label}<span style={{ opacity: active ? 1 : 0.25 }}>{active && state.sort.dir === 'desc' ? 'v' : '^'}</span>
    </button>
  );
}

function InvRegionTabs({ warehouses, active, onPick, palette, pill, compact }) {
  return (
    <div style={{ display: 'flex', gap: compact ? 6 : pill ? 8 : 0, overflowX: compact ? 'auto' : 'visible', maxWidth: compact ? '100%' : 'none' }}>
      {warehouses.map((w) => {
        const is = active === w.name;
        return (
          <button key={w.id} onClick={() => onPick(w.name)} style={{
            height: compact ? 30 : pill ? 34 : 42, padding: compact ? '0 9px' : pill ? '0 14px' : '0 20px',
            border: pill ? `1px solid ${is ? palette.accent : palette.border}` : 'none',
            borderBottom: pill ? undefined : `2px solid ${is ? palette.accent : 'transparent'}`,
            background: is ? palette.active : 'transparent',
            color: is ? palette.accentText : palette.muted,
            borderRadius: pill ? 999 : 0, fontFamily: palette.font, fontSize: compact ? 12 : 13,
            fontWeight: is ? 600 : 500, cursor: 'pointer',
            whiteSpace: 'nowrap',
          }}>{w.flag} {compact ? w.name : w.name}</button>
        );
      })}
    </div>
  );
}

function InvMetric({ label, value, tone, palette, compact }) {
  return (
    <div style={{ padding: compact ? '8px 9px' : 16, background: palette.metricBg, border: `1px solid ${palette.border}`, borderRadius: palette.radius }}>
      <div style={{ fontSize: compact ? 9 : 11, color: palette.muted, textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: compact ? 4 : 6, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{label}</div>
      <div style={{ fontFamily: palette.mono, fontSize: compact ? 17 : 24, fontWeight: 700, color: tone || palette.text }}>{value}</div>
    </div>
  );
}

function InvStatusPill({ p, palette }) {
  const low = p.afterHold < 25 || p.shortage > 0;
  const text = p.shortage > 0 ? 'Short' : p.holding > 0 ? 'Held' : low ? 'Low' : 'Healthy';
  const bg = p.shortage > 0 ? palette.badBg : p.holding > 0 ? palette.holdBg : low ? palette.warnBg : palette.goodBg;
  const color = p.shortage > 0 ? palette.bad : p.holding > 0 ? palette.hold : low ? palette.warn : palette.good;
  return <span style={{ padding: '3px 8px', borderRadius: 999, background: bg, color, fontSize: 11, fontWeight: 700 }}>{text}</span>;
}

function InvImage({ src, inset = '88%' }) {
  return (
    <div style={{
      width: inset,
      height: inset,
      backgroundImage: `url("${src}")`,
      backgroundSize: 'contain',
      backgroundPosition: 'center',
      backgroundRepeat: 'no-repeat',
    }} />
  );
}

function resizeImageFile(file, maxSize = 1500, quality = 0.9) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = () => reject(reader.error);
    reader.onload = () => {
      const img = new Image();
      img.onerror = () => reject(new Error('Could not load image'));
      img.onload = () => {
        let { width, height } = img;
        if (width > maxSize || height > maxSize) {
          if (width >= height) {
            height = Math.round((height / width) * maxSize);
            width = maxSize;
          } else {
            width = Math.round((width / height) * maxSize);
            height = maxSize;
          }
        }
        const canvas = document.createElement('canvas');
        canvas.width = width;
        canvas.height = height;
        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0, width, height);
        const mime = file.type === 'image/png' ? 'image/png' : 'image/jpeg';
        resolve(canvas.toDataURL(mime, quality));
      };
      img.src = reader.result;
    };
    reader.readAsDataURL(file);
  });
}

function InventoryEditor({ mode, row, region, onClose, onSave, onDelete, canDelete = true, palette, allCategories, allColors, existingSkus, onDeleteCategory, onDeleteColor }) {
  const width = useInventoryViewportWidth();
  const isPhone = width < 760;
  const empty = { sku: '', brand: 'Gruv Gear', name: '', category: '', color: '-', size: '-', upc: '', dimensions: '', weight: '', moq: 1, retail: 0, buy: 0, image: '', stock: { Shanghai: 0, California: 0, Germany: 0 } };
  const [form, setForm] = React.useState(row || empty);
  const [deleteOpen, setDeleteOpen] = React.useState(false);
  const [deleteText, setDeleteText] = React.useState('');
  const [saveConfirmOpen, setSaveConfirmOpen] = React.useState(false);
  const [closeConfirmOpen, setCloseConfirmOpen] = React.useState(false);
  const [pendingCatDelete, setPendingCatDelete] = React.useState(null);
  const [pendingColorDelete, setPendingColorDelete] = React.useState(null);
  const [errors, setErrors] = React.useState({});
  const initialFormRef = React.useRef(row || empty);
  const set = (key, value) => {
    setForm((f) => ({ ...f, [key]: value }));
    if (errors[key]) setErrors((e) => { const n = { ...e }; delete n[key]; return n; });
  };
  const setStock = (name, value) => setForm((f) => ({ ...f, stock: { ...f.stock, [name]: Number(value) || 0 } }));

  const isDirty = React.useMemo(() => JSON.stringify(form) !== JSON.stringify(initialFormRef.current), [form]);

  const validate = () => {
    const e = {};
    if (!form.sku?.trim()) e.sku = 'SKU is required';
    if (!form.name?.trim()) e.name = 'Product name is required';
    if (!form.category?.trim()) e.category = 'Category is required';
    if (mode === 'new' && form.sku && existingSkus?.has(form.sku.trim())) {
      e.sku = `SKU "${form.sku.trim()}" already exists`;
    }
    setErrors(e);
    return Object.keys(e).length === 0;
  };

  const submit = () => {
    if (!validate()) return;
    setSaveConfirmOpen(true);
  };

  const requestClose = () => {
    if (isDirty) setCloseConfirmOpen(true);
    else onClose();
  };

  const uploadImage = async (file) => {
    if (!file) return;
    try {
      const dataUrl = await resizeImageFile(file, 1500, 0.9);
      set('image', dataUrl);
    } catch (err) {
      console.error('Image upload failed', err);
    }
  };

  return (
    <div style={{
      position: isPhone ? 'fixed' : 'absolute', inset: 0, background: 'rgba(16,24,40,.34)',
      display: 'grid', placeItems: isPhone ? 'stretch' : 'center',
      padding: isPhone ? 0 : 18, overflow: 'auto', zIndex: 60,
    }} onClick={(e) => { if (e.target === e.currentTarget) requestClose(); }}>
      <div style={{
        width: isPhone ? '100%' : 560, maxWidth: '100%',
        minHeight: isPhone ? '100dvh' : undefined, maxHeight: isPhone ? 'none' : '86%',
        overflow: 'auto', background: palette.surface, color: palette.text,
        border: isPhone ? 'none' : `1px solid ${palette.border}`,
        borderRadius: isPhone ? 0 : palette.radius + 4,
        boxShadow: '0 24px 80px rgba(0,0,0,.24)', boxSizing: 'border-box',
      }}>
        <div style={{ padding: isPhone ? 14 : 20, borderBottom: `1px solid ${palette.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
          <div>
            <div style={{ fontSize: 18, fontWeight: 700 }}>{mode === 'new' ? 'Add product' : 'Update product'}</div>
            <div style={{ fontSize: 12, color: palette.muted }}>Manual employee inventory control</div>
          </div>
          <button onClick={requestClose} style={{ width: 32, height: 32, borderRadius: 8, border: `1px solid ${palette.border}`, background: palette.bg, cursor: 'pointer' }}>×</button>
        </div>
        <div style={{ padding: isPhone ? 14 : 20, display: 'grid', gridTemplateColumns: isPhone ? '1fr' : '1fr 1fr', gap: isPhone ? 12 : 14 }}>
          <div style={{ gridColumn: '1 / -1', display: 'grid', gridTemplateColumns: isPhone ? '96px minmax(0, 1fr)' : '110px 1fr', gap: 14, alignItems: 'start' }}>
            <label htmlFor="invEditorImage" style={{
              cursor: 'pointer',
              width: isPhone ? 96 : 110, height: isPhone ? 96 : 110,
              border: `1px dashed ${palette.border}`, borderRadius: palette.radius || 8,
              background: palette.bg, display: 'grid', placeItems: 'center',
              overflow: 'hidden', position: 'relative',
            }}
              onMouseOver={(e) => { e.currentTarget.style.borderColor = palette.accent; }}
              onMouseOut={(e) => { e.currentTarget.style.borderColor = palette.border; }}
            >
              {form.image ? (
                <InvImage src={form.image} />
              ) : (
                <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4, color: palette.muted, padding: 8, textAlign: 'center' }}>
                  <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
                    <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
                    <circle cx="12" cy="13" r="4" />
                  </svg>
                  <div style={{ fontSize: 10, fontWeight: 600, letterSpacing: 0.3 }}>Tap to upload</div>
                </div>
              )}
            </label>
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 11, color: palette.muted, marginBottom: 5, textTransform: 'uppercase', letterSpacing: 0.5 }}>Product picture</div>
              <input id="invEditorImage" type="file" accept="image/*" onChange={(e) => uploadImage(e.target.files?.[0])} style={{ display: 'none' }} />
              <label htmlFor="invEditorImage" style={{
                display: 'inline-flex', alignItems: 'center', gap: 6,
                height: 34, padding: '0 12px', borderRadius: 7,
                border: `1px solid ${palette.border}`, background: palette.bg,
                cursor: 'pointer', fontSize: 12, fontWeight: 600, color: palette.text,
              }}>{form.image ? 'Change image' : 'Choose image'}</label>
              <div style={{ marginTop: 6, fontSize: 11, color: palette.muted, lineHeight: 1.4 }}>Auto-resized to max 1500×1500 keeping the original aspect ratio.</div>
            </div>
          </div>
          <InvField label="SKU" value={form.sku} onChange={(v) => set('sku', v)} palette={palette} disabled={mode !== 'new'} required error={errors.sku} />
          <InvField label="Brand" value={form.brand || ''} onChange={(v) => set('brand', v)} palette={palette} />
          <InvSelect label="Category" value={form.category} options={allCategories || []} onChange={(v) => set('category', v)} palette={palette} placeholder="Choose category" required error={errors.category} onDelete={onDeleteCategory ? (opt) => setPendingCatDelete(opt) : undefined} />
          <div style={{ gridColumn: '1 / -1' }}><InvField label="Product name" value={form.name} onChange={(v) => set('name', v)} palette={palette} required error={errors.name} /></div>
          <InvSelect label="Color" value={form.color} options={allColors || []} onChange={(v) => set('color', v)} palette={palette} placeholder="Choose color" onDelete={onDeleteColor ? (opt) => setPendingColorDelete(opt) : undefined} />
          <InvField label="Size" value={form.size} onChange={(v) => set('size', v)} palette={palette} />
          <InvField label="UPC" value={form.upc || ''} onChange={(v) => set('upc', v)} palette={palette} />
          <InvField label="Dimensions" value={form.dimensions || ''} onChange={(v) => set('dimensions', v)} palette={palette} />
          <InvField label="Weight" value={form.weight || ''} onChange={(v) => set('weight', v)} palette={palette} />
          <InvField label="MOQ" type="number" value={form.moq} onChange={(v) => set('moq', Number(v) || 1)} palette={palette} />
          <InvField label="Retail" type="number" value={form.retail} onChange={(v) => set('retail', Number(v) || 0)} palette={palette} />
          {window.WAREHOUSES.map((w) => (
            <InvField key={w.name} label={`${w.name} inventory`} type="number" value={form.stock?.[w.name] || 0} onChange={(v) => setStock(w.name, v)} palette={palette} />
          ))}
        </div>
        {Object.keys(errors).length > 0 && (
          <div style={{ margin: isPhone ? '0 14px 0' : '0 20px 0', padding: '10px 12px', background: palette.badBg, border: `1px solid ${palette.bad}`, borderRadius: 8, color: palette.bad, fontSize: 12, fontWeight: 600 }}>
            Please fix the highlighted fields before saving.
          </div>
        )}
        {mode !== 'new' && canDelete && deleteOpen && (
          <div style={{ margin: isPhone ? '12px 14px 14px' : '12px 20px 20px', padding: 14, border: `1px solid ${palette.bad}`, borderRadius: palette.radius || 8, background: palette.badBg }}>
            <div style={{ fontSize: 13, fontWeight: 800, color: palette.bad, marginBottom: 6 }}>Delete this product?</div>
            <div style={{ fontSize: 12, color: palette.muted, marginBottom: 10 }}>Type DELETE to permanently remove {form.sku} from this inventory dashboard.</div>
            <input value={deleteText} onChange={(e) => setDeleteText(e.target.value)} placeholder="DELETE" style={{ width: '100%', height: 34, border: `1px solid ${palette.bad}`, borderRadius: 7, padding: '0 10px', fontFamily: palette.mono, marginBottom: 10 }} />
            <button disabled={deleteText !== 'DELETE'} onClick={() => onDelete?.()} style={{ ...invButton(palette, true), background: deleteText === 'DELETE' ? palette.bad : palette.border, color: '#fff', cursor: deleteText === 'DELETE' ? 'pointer' : 'not-allowed' }}>Confirm delete</button>
          </div>
        )}
        <div style={{
          padding: isPhone ? 14 : 20, borderTop: `1px solid ${palette.border}`,
          display: 'flex', justifyContent: 'flex-end', gap: 10, flexWrap: 'wrap',
        }}>
          <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', flexWrap: 'wrap' }}>
            {mode !== 'new' && canDelete && <button onClick={() => setDeleteOpen((v) => !v)} style={{ ...invButton(palette, false), color: palette.bad }}>Delete</button>}
            <button onClick={requestClose} style={invButton(palette, false)}>Cancel</button>
            <button onClick={submit} style={invButton(palette, true)}>Save</button>
          </div>
        </div>
      </div>
      {saveConfirmOpen && <InventoryConfirmDialog
        palette={palette}
        title="Save changes?"
        message={`Are you sure you want to save changes to ${form.sku || 'this product'}?`}
        confirmLabel="Save changes"
        cancelLabel="Cancel"
        onConfirm={() => { setSaveConfirmOpen(false); onSave(form); }}
        onCancel={() => setSaveConfirmOpen(false)}
      />}
      {closeConfirmOpen && <InventoryConfirmDialog
        palette={palette}
        title="Discard changes?"
        message="You have unsaved changes. Are you sure you want to close without saving?"
        confirmLabel="Discard"
        cancelLabel="Keep editing"
        onConfirm={() => { setCloseConfirmOpen(false); onClose(); }}
        onCancel={() => setCloseConfirmOpen(false)}
      />}
      {pendingCatDelete && <InventoryConfirmDialog
        palette={palette}
        title="Delete category?"
        message={`Delete the "${pendingCatDelete}" category? Every SKU in it will move to "No Category". This cannot be undone.`}
        confirmLabel="Delete category"
        cancelLabel="Cancel"
        onConfirm={() => { onDeleteCategory?.(pendingCatDelete); if (form.category === pendingCatDelete) set('category', 'No Category'); setPendingCatDelete(null); }}
        onCancel={() => setPendingCatDelete(null)}
      />}
      {pendingColorDelete && <InventoryConfirmDialog
        palette={palette}
        title="Delete color?"
        message={`Delete the "${pendingColorDelete}" color? It will be removed from every SKU that uses it. This cannot be undone.`}
        confirmLabel="Delete color"
        cancelLabel="Cancel"
        onConfirm={() => { onDeleteColor?.(pendingColorDelete); if (form.color === pendingColorDelete) set('color', ''); setPendingColorDelete(null); }}
        onCancel={() => setPendingColorDelete(null)}
      />}
    </div>
  );
}

function InvField({ label, value, onChange, type = 'text', palette, disabled, required, error }) {
  return (
    <label style={{ display: 'block' }}>
      <div style={{ fontSize: 11, color: palette.muted, marginBottom: 5, textTransform: 'uppercase', letterSpacing: 0.5 }}>
        {label}{required && <span style={{ color: palette.bad, marginLeft: 3 }}>*</span>}
      </div>
      <input disabled={disabled} type={type} value={value} onChange={(e) => onChange(e.target.value)} style={{
        width: '100%', height: 38, borderRadius: 8,
        border: `1px solid ${error ? palette.bad : palette.border}`,
        background: disabled ? palette.metricBg : palette.bg, color: palette.text,
        padding: '0 10px', fontFamily: palette.font, outline: 'none', boxSizing: 'border-box',
        boxShadow: error ? `0 0 0 3px ${palette.badBg}` : 'none',
      }} />
      {error && <div style={{ marginTop: 4, fontSize: 11, color: palette.bad }}>{error}</div>}
    </label>
  );
}

function InvSelect({ label, value, options, onChange, palette, placeholder, required, error, onDelete }) {
  const ADD_NEW = '__add_new__';
  const [open, setOpen] = React.useState(false);
  const [showCustom, setShowCustom] = React.useState(false);
  const [customVal, setCustomVal] = React.useState('');
  const ref = React.useRef(null);
  const inputRef = React.useRef(null);
  const allOptions = !value || options.includes(value) ? options : [value, ...options];

  React.useEffect(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [open]);

  const pick = (v) => {
    if (v === ADD_NEW) {
      setOpen(false);
      setShowCustom(true);
      setCustomVal('');
      setTimeout(() => inputRef.current?.focus(), 0);
    } else {
      onChange(v);
      setOpen(false);
    }
  };

  const submitCustom = () => {
    const trimmed = customVal.trim();
    if (trimmed) { onChange(trimmed); setShowCustom(false); }
  };

  return (
    <div style={{ display: 'block' }}>
      <div style={{ fontSize: 11, color: palette.muted, marginBottom: 5, textTransform: 'uppercase', letterSpacing: 0.5 }}>
        {label}{required && <span style={{ color: palette.bad, marginLeft: 3 }}>*</span>}
      </div>
      {showCustom ? (
        <div style={{ display: 'flex', gap: 6 }}>
          <input
            ref={inputRef}
            value={customVal}
            onChange={(e) => setCustomVal(e.target.value)}
            onKeyDown={(e) => { if (e.key === 'Enter') submitCustom(); if (e.key === 'Escape') setShowCustom(false); }}
            placeholder={`New ${label.toLowerCase()}…`}
            style={{ flex: 1, height: 38, border: `1px solid ${palette.border}`, borderRadius: 8, padding: '0 10px', fontFamily: palette.font, outline: 'none', boxSizing: 'border-box', color: palette.text, background: palette.bg, minWidth: 0 }}
          />
          <button onClick={submitCustom} style={{ height: 38, padding: '0 12px', borderRadius: 8, border: 'none', background: palette.accent, color: palette.onAccent, fontFamily: palette.font, fontWeight: 700, cursor: 'pointer', flexShrink: 0 }}>Add</button>
          <button onClick={() => setShowCustom(false)} style={{ height: 38, padding: '0 10px', borderRadius: 8, border: `1px solid ${palette.border}`, background: palette.surface, color: palette.text, fontFamily: palette.font, cursor: 'pointer', flexShrink: 0 }}>✕</button>
        </div>
      ) : (
        <div ref={ref} style={{ position: 'relative' }}>
          <button onClick={() => setOpen((v) => !v)} style={{
            width: '100%', height: 38, padding: '0 10px', fontFamily: palette.font, fontSize: 13,
            color: value ? palette.text : palette.muted,
            border: `1px solid ${error ? palette.bad : palette.border}`,
            borderRadius: 8, background: palette.bg, cursor: 'pointer', outline: 'none', boxSizing: 'border-box',
            display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, textAlign: 'left',
            boxShadow: error ? `0 0 0 3px ${palette.badBg}` : 'none',
          }}>
            <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{value || placeholder || ' '}</span>
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" style={{ flexShrink: 0, transition: 'transform .15s', transform: open ? 'rotate(180deg)' : 'none', color: palette.muted }}>
              <path d="M6 9l6 6 6-6" />
            </svg>
          </button>
          {open && (
            <div style={{
              position: 'absolute', top: '100%', left: 0, right: 0, marginTop: 4,
              background: palette.surface, border: `1px solid ${palette.border}`,
              borderRadius: 8, zIndex: 999, boxShadow: '0 8px 24px rgba(15,23,42,0.16)',
              maxHeight: 220, overflowY: 'auto',
            }}>
              {allOptions.map((opt) => {
                const showDel = onDelete && opt && opt !== 'No Category';
                return (
                  <div key={opt} style={{ display: 'flex', alignItems: 'center', background: opt === value ? palette.active : 'transparent' }}>
                    <button onClick={() => pick(opt)} style={{
                      flex: 1, minWidth: 0, padding: '9px 12px', border: 'none', cursor: 'pointer', textAlign: 'left',
                      background: 'transparent', fontFamily: palette.font, fontSize: 13, color: palette.text,
                      display: 'flex', alignItems: 'center', gap: 8,
                    }}>
                      <span style={{ width: 14, color: palette.accent, flexShrink: 0 }}>{opt === value ? '✓' : ''}</span>
                      <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{opt}</span>
                    </button>
                    {showDel && <button onClick={(e) => { e.stopPropagation(); setOpen(false); onDelete(opt); }} aria-label={`Delete ${opt}`} title={`Delete ${opt}`}
                      onMouseOver={(e) => { e.currentTarget.style.color = palette.text; e.currentTarget.style.background = palette.bg; }}
                      onMouseOut={(e) => { e.currentTarget.style.color = palette.muted; e.currentTarget.style.background = 'transparent'; }}
                      style={{
                      flexShrink: 0, width: 30, height: 30, margin: '0 6px 0 2px', border: 'none', background: 'transparent',
                      color: palette.muted, cursor: 'pointer', fontSize: 17, lineHeight: 1, borderRadius: 6, display: 'grid', placeItems: 'center',
                    }}>×</button>}
                  </div>
                );
              })}
              <button onClick={() => pick(ADD_NEW)} style={{
                width: '100%', padding: '9px 12px', border: 'none', borderTop: `1px solid ${palette.border}`,
                cursor: 'pointer', textAlign: 'left', background: 'transparent',
                fontFamily: palette.font, fontSize: 13, color: palette.accent, fontWeight: 600,
                display: 'flex', alignItems: 'center', gap: 8,
              }}>
                <span style={{ width: 14, flexShrink: 0 }}>+</span>
                Add new…
              </button>
            </div>
          )}
        </div>
      )}
      {error && <div style={{ marginTop: 4, fontSize: 11, color: palette.bad }}>{error}</div>}
    </div>
  );
}

const invButton = (palette, primary) => ({
  height: 36, padding: '0 14px', borderRadius: 8,
  border: primary ? 'none' : `1px solid ${palette.border}`,
  background: primary ? palette.accent : palette.surface,
  color: primary ? palette.onAccent : palette.text,
  fontFamily: palette.font, fontWeight: 700, cursor: 'pointer',
});

function useInventoryViewportWidth() {
  const [width, setWidth] = React.useState(() => window.innerWidth || 1440);
  React.useEffect(() => {
    const onResize = () => setWidth(window.innerWidth || 1440);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  return width;
}

function LedgerInventoryApp({ catalog, warehouses }) {
  const [signedIn, setSignedIn] = React.useState(false);
  const [user, setUser] = React.useState(null); // { role, name }
  const [page, setPage] = React.useState('inventory');
  const state = useInventoryDashboard(catalog, user?.token);
  // Open production = real backordered order units (replaces the demo mocks).
  const [prodRows, setProdRows] = React.useState([]);
  React.useEffect(() => {
    if (!user?.token) return undefined;
    let cancelled = false;
    fetch('/api/inventory?resource=production', { headers: { Authorization: `Bearer ${user.token}` } })
      .then((r) => (r.ok ? r.json() : Promise.reject(new Error('production'))))
      .then((d) => {
        if (cancelled) return;
        setProdRows((d.production || []).map((p) => ({
          ...makeEmptyProduction(),
          id: p.id,
          item: p.product_name || p.sku,
          sku: p.sku,
          poNum: p.qbo_doc_number ? `INV-${p.qbo_doc_number}` : (p.po_number || ''),
          qty: Number(p.qty) || 0,
          factory: '',
          // 'Scheduled' is just the DB default for a fresh backorder — show it
          // blank like the other stages until it actually advances.
          stageProduction: (p.stage && p.stage !== 'Scheduled') ? p.stage : '',
          comments: [{
            author: 'System',
            text: `Backorder from ${p.account || 'reseller'}${p.warehouse ? ' · ' + p.warehouse : ''}`,
            date: p.created_at ? new Date(p.created_at).toLocaleDateString('en-US') : '',
          }],
        })));
      })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [user?.token]);
  const prodState = useBoard(prodRows, makeEmptyProduction, { log: state.logAudit, entity: 'production', label: (r) => r.item || r.sku || String(r.id), statusKeys: PROD_STATUS_KEYS });
  // Order Dashboard = real reseller orders (replaces the demo mocks).
  const [orderRows, setOrderRows] = React.useState([]);
  React.useEffect(() => {
    if (!user?.token) return undefined;
    let cancelled = false;
    fetch('/api/inventory?resource=orders', { headers: { Authorization: `Bearer ${user.token}` } })
      .then((r) => (r.ok ? r.json() : Promise.reject(new Error('orders'))))
      .then((d) => {
        if (cancelled) return;
        setOrderRows((d.orders || []).map((o) => ({
          id: o.id,
          invoiceNum: o.qbo_doc_number ? `INV-${o.qbo_doc_number}` : o.po_number,
          comments: [],
          orderReceived: o.created_at ? String(o.created_at).slice(0, 10) : '',
          customer: o.account || '—',
          salesperson: '',
          owner: '',
          origin: o.warehouse === 'California' ? 'Domestic' : 'International',
          warehouse: o.warehouse || '',
          invoicing: '',
          shipment: '',
          finalStatus: '',
          tracking: '',
        })));
      })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [user?.token]);
  const ordersState = useBoard(orderRows, makeEmptyOrder, { log: state.logAudit, entity: 'order', label: (r) => r.invoiceNum || r.customer || String(r.id), statusKeys: ORDER_STATUS_KEYS });
  const P = {
    bg: L.bg, surface: L.surface, metricBg: '#fbfcfe', border: L.border, text: L.text, muted: L.textDim,
    accent: L.accent, active: L.accentDim, accentText: L.accent, onAccent: '#fff',
    good: L.discount, goodBg: L.discountBg, warn: L.warn, warnBg: L.warnBg, bad: '#b42318', badBg: '#fef3f2', hold: L.accent, holdBg: L.accentDim,
    font: L.font, mono: L.mono, radius: 8,
  };
  // Managers and admins get full product CRUD; plain employees can only adjust
  // on-hand counts and use the production board.
  const canManageProducts = user?.role === 'manager' || user?.role === 'admin';
  if (!signedIn) return <InventoryLogin palette={P} variant="ledger" onLogin={(u) => { setUser(u); setSignedIn(true); }} />;
  if (page === 'production') return <BoardPage state={prodState} palette={P} page={page} onPageChange={setPage} user={user} title="Production Board" subtitle="Track factory POs across every production stage — from purchase order to arrival." columns={PROD_COLUMNS} commentLabel={(r) => `${r.item || ''}${r.sku ? ` / ${r.sku}` : ''}`} rowLabel={(r) => r.item || 'this item'} />;
  if (page === 'orders') return <BoardPage state={ordersState} palette={P} page={page} onPageChange={setPage} user={user} title="Order Dashboard" subtitle="Track customer orders through invoicing, shipment and final status." columns={ORDER_COLUMNS} originFilter commentLabel={(r) => `${r.invoiceNum || ''}${r.customer ? ` / ${r.customer}` : ''}`} rowLabel={(r) => r.invoiceNum || 'this order'} />;
  if (page === 'activity') return <ActivityPage token={user?.token} user={user} palette={P} page={page} onPageChange={setPage} />;
  return <InventoryTableDesign state={state} warehouses={warehouses} palette={P} variant="ledger" title="Inventory Control" subtitle="Employee dashboard for regional availability, active holds, payment deadlines, and shipped deductions." page={page} onPageChange={setPage} canManageProducts={canManageProducts} user={user} />;
}

// Single centered card — the layout is fluid (clamp + max-width), so there is no
// phone breakpoint to track here.
function InventoryLogin({ palette, variant, onLogin }) {
  const [username, setUsername] = React.useState('admin');
  const [password, setPassword] = React.useState('temppass');
  const [empBusy, setEmpBusy] = React.useState(false);
  const [empError, setEmpError] = React.useState('');
  const [captcha, setCaptcha] = React.useState('');
  const [captchaReset, setCaptchaReset] = React.useState(0);
  const titleFont = variant === 'ledger' ? palette.font : palette.serif;

  const submitEmployee = async (e) => {
    e?.preventDefault?.();
    setEmpBusy(true);
    setEmpError('');
    try {
      const res = await fetch('/api/employees/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password, recaptchaToken: captcha }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error || `Login failed (${res.status})`);
      onLogin({ role: data.role, name: data.name, token: data.token });
    } catch (err) {
      setEmpError(err.message);
      setCaptchaReset((n) => n + 1);
      setEmpBusy(false);
    }
  };

  // Card colors only — AuthShell paints its own dark navy ground.
  const authPalette = { surface: palette.surface, border: L.borderStrong, text: palette.text, font: palette.font, mono: palette.mono };
  return (
    <AuthShell palette={authPalette} label="Inventory Control" footer={<SupportLine />}>
      <form onSubmit={submitEmployee}>
        <div style={{ fontFamily: titleFont, fontSize: 20, fontWeight: 700, letterSpacing: -0.4, marginBottom: 6, textAlign: 'center' }}>
          Employee sign in
        </div>
        <div style={{ fontSize: 13, lineHeight: 1.5, color: palette.muted, marginBottom: 24, textAlign: 'center' }}>
          Use the account created by your inventory admin.
        </div>

        <AuthField palette={authPalette} label="Username" value={username} onChange={setUsername} autoComplete="username" />
        <div style={{ height: 16 }} />
        <AuthField palette={authPalette} label="Password" type="password" value={password} onChange={setPassword} autoComplete="current-password" />

        {empError ? (
          <div style={{ marginTop: 12, padding: '8px 12px', borderRadius: 8, background: palette.badBg, color: palette.bad, fontSize: 12 }}>{empError}</div>
        ) : null}

        <label style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 14, minHeight: 24, fontSize: 13, color: L.textMid, cursor: 'pointer' }}>
          <input type="checkbox" defaultChecked style={{ width: 16, height: 16, accentColor: palette.accent }} /> Keep me signed in
        </label>

        <Recaptcha onToken={setCaptcha} resetSignal={captchaReset} />

        <button type="submit" disabled={empBusy || (recaptchaEnabled() && !captcha)} style={{
          ...invButton(palette, true), width: '100%', height: 46, marginTop: 22, borderRadius: 10, fontSize: 15,
          opacity: empBusy || (recaptchaEnabled() && !captcha) ? 0.7 : 1, cursor: empBusy ? 'wait' : 'pointer',
        }}>{empBusy ? 'Signing in…' : 'Enter inventory dashboard'}</button>
      </form>
    </AuthShell>
  );
}

function InventoryTableDesign({ state, warehouses, palette, variant, title, subtitle, page, onPageChange, canManageProducts = true, user }) {
  const width = useInventoryViewportWidth();
  const isPhone = width < 760;
  const isCompact = width < 1180;
  // Managers can add/edit products; only admins can delete them.
  const canDeleteProducts = user?.role === 'admin';
  // Admins and managers reach the admin console from here — there is no separate
  // admin login. Both /api/employees/login and /api/admin/login mint the same
  // emp_<id> token and requireRole() re-reads the role from the DB, so this
  // session is already authorized; admin.jsx just needs it in localStorage.
  // (admin.jsx gates the Employees view on the stored role, so write all three
  // keys — writing only the token leaves an admin stuck on the Resellers view.)
  const canOpenAdmin = user?.role === 'admin' || user?.role === 'manager';
  const openAdminConsole = () => {
    try {
      localStorage.setItem('gruv_admin_token', user?.token || '');
      localStorage.setItem('gruv_admin_role', user?.role || '');
      localStorage.setItem('gruv_admin_name', user?.name || '');
    } catch (_) {}
    window.location.href = '/demo.html?demo=admin';
  };
  const [categoriesOpen, setCategoriesOpen] = React.useState(false);
  const csvInputRef = React.useRef(null);
  const editor = state.editorSku ? state.filtered.find((p) => p.sku === state.editorSku) || state.products.find((p) => p.sku === state.editorSku) : null;
  const allCategories = React.useMemo(() => state.categories.filter((c) => c !== 'All').sort(), [state.categories]);
  const allColors = React.useMemo(() => [...new Set(state.products.map((p) => p.color).filter(Boolean))].sort(), [state.products]);
  const existingSkus = React.useMemo(() => new Set(state.products.map((p) => p.sku)), [state.products]);
  const pagePad = isPhone ? '10px 8px' : variant === 'atelier' ? '30px 42px' : '22px 26px';
  const titleFont = variant === 'ledger' ? palette.font : palette.serif;
  const radius = palette.radius;

  const categoryPanel = (
    <div style={{ height: '100%', background: variant === 'ledger' ? palette.surface : 'transparent', padding: 18, overflow: 'auto' }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
        <div style={{ fontSize: 11, color: palette.muted, textTransform: 'uppercase', letterSpacing: 0.8, fontWeight: 800 }}>Categories</div>
        <button onClick={() => setCategoriesOpen(false)} style={{ width: 30, height: 30, borderRadius: 8, border: `1px solid ${palette.border}`, background: palette.surface, color: palette.muted, cursor: 'pointer', fontSize: 18 }}>x</button>
      </div>
      {state.categories.map((c) => {
        const active = state.category === c;
        return <button key={c} onClick={() => { state.setCategory(c); setCategoriesOpen(false); }} style={{ width: '100%', textAlign: 'left', border: 'none', background: active ? palette.active : 'transparent', color: active ? palette.accentText : palette.text, borderRadius: radius || 0, padding: '9px 10px', cursor: 'pointer', fontFamily: palette.font, fontSize: 12, fontWeight: active ? 700 : 500, marginBottom: 3 }}>{c}</button>;
      })}
      <div style={{ marginTop: 20, padding: 14, border: `1px solid ${palette.border}`, borderRadius: radius, background: palette.metricBg, fontSize: 12, color: palette.muted, lineHeight: 1.6 }}>
        Payment holds expire automatically after the deadline and no longer reduce available inventory. Shipped orders are deducted from on-hand stock.
      </div>
    </div>
  );

  return (
    <div style={{ width: '100%', height: '100%', background: palette.bg, color: palette.text, fontFamily: palette.font, overflow: 'hidden', display: 'flex', flexDirection: 'column', position: 'relative' }}>
      <div style={{ flex: '0 0 auto', minHeight: isPhone ? 48 : 64, background: variant === 'ledger' ? palette.surface : palette.bg, borderBottom: `1px solid ${palette.border}`, display: 'flex', alignItems: 'center', padding: isPhone ? '12px 10px' : '0 26px', gap: isPhone ? 8 : 18, flexWrap: isPhone ? 'wrap' : 'nowrap', rowGap: isPhone ? 10 : 0 }}>
        <BrandLogo size={isPhone ? 26 : 30} radius={variant === 'atelier' ? 0 : 8} />
        <div>
          <div style={{ fontFamily: titleFont, fontSize: isPhone ? 13 : variant === 'ledger' ? 15 : 24, fontWeight: variant === 'ledger' ? 700 : 400, lineHeight: 1 }}>Gruv Gear</div>
          <div style={{ fontSize: isPhone ? 8 : 10, color: palette.muted, fontFamily: palette.mono, letterSpacing: 1.1, textTransform: 'uppercase' }}>Employee inventory</div>
        </div>
        {!isPhone && <div style={{ width: 1, height: 24, background: palette.border }} />}
        {onPageChange && <PageNavTabs page={page} onPageChange={onPageChange} palette={palette} isPhone={isPhone} />}
        {!isPhone && onPageChange && <div style={{ width: 1, height: 24, background: palette.border }} />}
        <InvRegionTabs warehouses={warehouses} active={state.region} onPick={state.setRegion} palette={palette} pill={variant !== 'atelier'} compact={isPhone} />
        <div style={{ flex: 1 }} />
        <input ref={csvInputRef} type="file" accept=".csv,.xlsx,text/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" onChange={(e) => state.importCSV(e.target.files?.[0])} style={{ display: 'none' }} />
        {canOpenAdmin && <button onClick={openAdminConsole} style={{ ...invButton(palette, false), height: isPhone ? 32 : 36, padding: isPhone ? '0 11px' : '0 14px', fontSize: isPhone ? 12 : 13 }}>Admin Dashboard</button>}
        {!isPhone && canManageProducts && <button onClick={() => csvInputRef.current?.click()} style={invButton(palette, false)}>Import Excel</button>}
        {!isPhone && <button onClick={state.exportCSV} style={invButton(palette, false)}>Export Excel</button>}
        {canManageProducts && <button onClick={() => state.setNewOpen(true)} style={{ ...invButton(palette, true), height: isPhone ? 32 : 36, padding: isPhone ? '0 11px' : '0 14px', fontSize: isPhone ? 12 : 13 }}>Add product</button>}
        {!isPhone && <div style={{ textAlign: 'right' }}>
          <div style={{ fontSize: 12, fontWeight: 700 }}>{user?.name || INV_EMPLOYEE.name}</div>
          <div style={{ fontSize: 10, color: palette.muted, fontFamily: palette.mono, textTransform: 'capitalize' }}>{user?.role || INV_EMPLOYEE.role}</div>
        </div>}
      </div>

      {state.importStatus && (
        <div style={{
          padding: isPhone ? '8px 10px' : '10px 26px', display: 'flex', alignItems: 'center', gap: 10,
          background: state.importStatus.type === 'error' ? palette.badBg : state.importStatus.type === 'ok' ? palette.goodBg : palette.active,
          color: state.importStatus.type === 'error' ? palette.bad : state.importStatus.type === 'ok' ? palette.good : palette.accentText,
          borderBottom: `1px solid ${palette.border}`, fontSize: 13, fontWeight: 600,
        }}>
          <span>{state.importStatus.text}</span>
          <div style={{ flex: 1 }} />
          <button onClick={() => state.setImportStatus(null)} style={{ border: 'none', background: 'transparent', color: 'inherit', cursor: 'pointer', fontSize: 16, lineHeight: 1 }} aria-label="Dismiss">×</button>
        </div>
      )}

      <div style={{ padding: pagePad, borderBottom: `1px solid ${palette.border}`, background: variant === 'ledger' ? palette.bg : 'transparent' }}>
        <div style={{ marginBottom: isPhone ? 8 : 14 }}>
          <div>
            <div style={{ fontFamily: titleFont, fontSize: isPhone ? 18 : variant === 'ledger' ? 26 : 48, letterSpacing: 0, lineHeight: 1, fontWeight: variant === 'ledger' ? 700 : 400 }}>{title}</div>
            {!isPhone && <div style={{ marginTop: 6, color: palette.muted, fontSize: 13, maxWidth: 720 }}>{subtitle}</div>}
          </div>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: isPhone ? 'repeat(3, minmax(0, 1fr))' : 'repeat(6, 1fr)', gap: isPhone ? 6 : 12 }}>
          <InvMetric label="SKUs" value={fmtInt(state.stats.skus)} palette={palette} compact={isPhone} />
          <InvMetric label="On hand" value={fmtInt(state.stats.onHand)} palette={palette} compact={isPhone} />
          <InvMetric label="Held" value={fmtInt(state.stats.holding)} tone={palette.hold} palette={palette} compact={isPhone} />
          <InvMetric label="After hold" value={fmtInt(state.stats.afterHold)} tone={palette.good} palette={palette} compact={isPhone} />
          <InvMetric label="Short SKUs" value={fmtInt(state.stats.shortageSkus)} tone={state.stats.shortageSkus ? palette.bad : palette.good} palette={palette} compact={isPhone} />
          <InvMetric label="Expired released" value={fmtInt(state.stats.expired)} tone={palette.warn} palette={palette} compact={isPhone} />
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: isPhone ? 6 : 10, marginTop: isPhone ? 8 : 12, flexWrap: isPhone ? 'wrap' : 'nowrap', justifyContent: 'flex-start' }}>
          <button onClick={() => setCategoriesOpen(true)} style={{ ...invButton(palette, false), height: isPhone ? 34 : 38, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: isPhone ? 12 : 13 }}>
            Category: <span style={{ color: palette.accent, marginLeft: 4 }}>{state.category}</span>
            {state.category !== 'All' && (
              <span
                onClick={e => { e.stopPropagation(); state.setCategory('All'); }}
                aria-label="Clear category filter"
                style={{ marginLeft: 6, fontSize: 15, lineHeight: 1, opacity: 0.55, cursor: 'pointer' }}
              >×</span>
            )}
          </button>
          <div style={{ width: isPhone ? '100%' : 340, maxWidth: '100%', position: 'relative', order: isPhone ? 2 : 0 }}>
            <input value={state.search} onChange={(e) => state.setSearch(e.target.value)} placeholder="Search SKU, product, category" style={{ width: '100%', height: isPhone ? 34 : 38, border: `1px solid ${palette.border}`, borderRadius: radius || 0, background: palette.surface, padding: '0 12px', fontFamily: palette.font, outline: 'none', color: palette.text }} />
          </div>
          <div style={{ fontSize: 12, color: palette.muted, fontFamily: palette.mono, order: isPhone ? 3 : 0 }}>{fmtInt(state.filtered.length)} of {fmtInt(state.products.length)} SKUs</div>
        </div>
      </div>

      <div style={{ flex: 1, display: 'flex', overflow: 'hidden' }}>
        {!isCompact && categoriesOpen && <aside style={{ flex: '0 0 220px', borderRight: `1px solid ${palette.border}`, overflow: 'hidden' }}>{categoryPanel}</aside>}
        {isCompact && categoriesOpen && <InventoryDrawer onClose={() => setCategoriesOpen(false)} width={isPhone ? '86vw' : 280}>{categoryPanel}</InventoryDrawer>}

        <main style={{ flex: 1, overflow: 'auto', background: palette.surface }}>
          {isPhone ? (
            <div style={{ padding: 10, display: 'grid', gap: 10 }}>
              {state.filtered.map((p) => <InventoryMobileCard key={p.sku} p={p} state={state} palette={palette} canManageProducts={canManageProducts} />)}
            </div>
          ) : (
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
            <thead style={{ position: 'sticky', top: 0, background: palette.surface, zIndex: 2 }}>
              <tr style={{ borderBottom: `1px solid ${palette.border}`, color: palette.muted, textTransform: 'uppercase', letterSpacing: 0.5 }}>
                <th style={invTh(52, 'left')}></th>
                <th style={invTh(260, 'left')}><InvSortLabel label="Product" sortKey="name" state={state} onSort={state.toggleSort} color={palette.muted} /></th>
                <th style={invTh(116, 'left')}><InvSortLabel label="SKU" sortKey="sku" state={state} onSort={state.toggleSort} color={palette.muted} /></th>
                <th style={invTh(86, 'right')}><InvSortLabel label="On hand" sortKey="onHand" state={state} onSort={state.toggleSort} color={palette.muted} /></th>
                <th style={invTh(86, 'right')}><InvSortLabel label="Hold" sortKey="holding" state={state} onSort={state.toggleSort} color={palette.muted} /></th>
                <th style={invTh(96, 'right')}><InvSortLabel label="After hold" sortKey="afterHold" state={state} onSort={state.toggleSort} color={palette.muted} /></th>
                <th style={invTh(92, 'right')}><InvSortLabel label="Shortage" sortKey="shortage" state={state} onSort={state.toggleSort} color={palette.muted} /></th>
                <th style={invTh(120, 'left')}><InvSortLabel label="Order hold / deadline" sortKey="holdDeadline" state={state} onSort={state.toggleSort} color={palette.muted} /></th>
                <th style={invTh(90, 'center')}><InvSortLabel label="Status" sortKey="statusLabel" state={state} onSort={state.toggleSort} color={palette.muted} /></th>
                <th style={invTh(150, 'right')}>Actions</th>
              </tr>
            </thead>
            <tbody>
              {state.filtered.map((p, i) => {
                const hold = p.related.find((h) => h.computedStatus === 'holding') || p.related.find((h) => h.computedStatus === 'expired') || p.related.find((h) => h.computedStatus === 'shipped');
                return (
                  <tr key={p.sku} style={{ borderBottom: `1px solid ${palette.border}`, background: p.shortage > 0 ? palette.badBg : i % 2 ? palette.metricBg : palette.surface }}>
                    <td style={{ padding: '9px 8px 9px 18px' }}><div style={{ width: 36, height: 36, background: palette.bg, borderRadius: radius || 0, display: 'grid', placeItems: 'center', overflow: 'hidden' }}><InvImage src={p.image} /></div></td>
                    <td style={{ padding: '9px 10px' }}><div style={{ fontWeight: 700 }}>{p.name}</div><div style={{ color: palette.muted, fontSize: 11 }}>{p.category} / {p.color} / {p.size}</div></td>
                    <td style={{ padding: '9px 10px', fontFamily: palette.mono, color: palette.muted }}>{p.sku}</td>
                    <td style={{ padding: '9px 10px', textAlign: 'right' }}>{canManageProducts
                      ? <InventoryStockInput value={p.onHand} shipped={p.shipped} onCommit={(next) => state.updateStock(p.sku, next)} palette={palette} style={{ width: 72, height: 30 }} />
                      : <span style={{ fontFamily: palette.mono, fontWeight: 800 }}>{fmtInt(p.onHand)}</span>}</td>
                    <td style={{ padding: '9px 10px', textAlign: 'right', fontFamily: palette.mono, color: p.holding ? palette.hold : palette.muted, fontWeight: p.holding ? 800 : 500 }}>{fmtInt(p.holding)}</td>
                    <td style={{ padding: '9px 10px', textAlign: 'right', fontFamily: palette.mono, color: p.afterHold < 0 ? palette.bad : palette.text, fontWeight: 800 }}>{fmtInt(p.afterHold)}</td>
                    <td style={{ padding: '9px 10px', textAlign: 'right', fontFamily: palette.mono, color: p.shortage ? palette.bad : palette.muted }}>{p.shortage ? fmtInt(p.shortage) : '-'}</td>
                    <td style={{ padding: '9px 10px' }}>{hold ? <div style={{ fontFamily: palette.mono, fontSize: 11, fontWeight: 700, color: hold.computedStatus === 'expired' ? palette.warn : palette.text }}>{fmtInt(hold.qty)} in production</div> : <span style={{ color: palette.muted }}>No active production</span>}</td>
                    <td style={{ padding: '9px 10px', textAlign: 'center' }}><InvStatusPill p={p} palette={palette} /></td>
                    <td style={{ padding: '9px 18px 9px 10px', textAlign: 'right' }}>
                      {canManageProducts
                        ? <button onClick={() => state.setEditorSku(p.sku)} style={miniAction(palette)}>Update</button>
                        : <span style={{ color: palette.muted, fontSize: 11 }}>View only</span>}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
          )}
        </main>
      </div>

      {state.pendingStockChange && <InventoryConfirmDialog
        palette={palette}
        title="Change quantity?"
        message={`Are you sure you want to change ${state.pendingStockChange.sku} in ${state.pendingStockChange.region} from ${fmtInt(state.pendingStockChange.currentOnHand)} to ${fmtInt(state.pendingStockChange.nextOnHand)}?`}
        confirmLabel="Change quantity"
        cancelLabel="Cancel"
        onConfirm={state.confirmStockChange}
        onCancel={state.cancelStockChange}
      />}
      {canManageProducts && state.newOpen && <InventoryEditor mode="new" region={state.region} palette={palette} allCategories={allCategories} allColors={allColors} existingSkus={existingSkus} onDeleteCategory={state.deleteCategory} onDeleteColor={state.deleteColor} onClose={() => state.setNewOpen(false)} onSave={(form) => { state.addProduct(form); state.setNewOpen(false); }} />}
      {canManageProducts && editor && <InventoryEditor mode="edit" row={editor} region={state.region} palette={palette} canDelete={canDeleteProducts} allCategories={allCategories} allColors={allColors} existingSkus={existingSkus} onDeleteCategory={state.deleteCategory} onDeleteColor={state.deleteColor} onClose={() => state.setEditorSku(null)} onSave={(form) => { state.patchProduct(editor.sku, form); state.setEditorSku(null); }} onDelete={() => { state.deleteProduct(editor.sku); state.setEditorSku(null); }} />}
    </div>
  );
}

function InventoryDrawer({ width, onClose, children }) {
  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 25, background: 'rgba(15,23,42,0.38)' }} onClick={onClose}>
      <div onClick={(e) => e.stopPropagation()} style={{ position: 'absolute', top: 0, bottom: 0, left: 0, width, maxWidth: '100%', background: L.surface, boxShadow: '18px 0 46px rgba(15,23,42,0.24)' }}>
        {children}
      </div>
    </div>
  );
}

function InventoryConfirmDialog({ palette, title, message, confirmLabel, cancelLabel, onConfirm, onCancel }) {
  const isPhone = useInventoryViewportWidth() < 760;
  return (
    <div style={{ position: isPhone ? 'fixed' : 'absolute', inset: 0, zIndex: 40, background: 'rgba(16,24,40,0.42)', display: 'grid', placeItems: 'center', padding: isPhone ? 16 : 18 }} onClick={onCancel}>
      <div onClick={(e) => e.stopPropagation()} style={{ width: isPhone ? '100%' : 430, maxWidth: isPhone ? 'calc(100vw - 32px)' : '100%', background: palette.surface, border: `1px solid ${palette.border}`, borderRadius: 12, boxShadow: '0 24px 70px rgba(0,0,0,.22)', padding: isPhone ? 18 : 24 }}>
        <div style={{ fontSize: isPhone ? 16 : 18, fontWeight: 800, marginBottom: 8 }}>{title}</div>
        <div style={{ fontSize: 13, color: palette.muted, lineHeight: 1.55, marginBottom: 18 }}>{message}</div>
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10, flexWrap: 'wrap' }}>
          <button onClick={onCancel} style={invButton(palette, false)}>{cancelLabel}</button>
          <button onClick={onConfirm} style={invButton(palette, true)}>{confirmLabel}</button>
        </div>
      </div>
    </div>
  );
}

function InventoryMobileCard({ p, state, palette, canManageProducts = true }) {
  const hold = p.related.find((h) => h.computedStatus === 'holding') || p.related.find((h) => h.computedStatus === 'expired') || p.related.find((h) => h.computedStatus === 'shipped');
  return (
    <div style={{ border: `1px solid ${palette.border}`, borderRadius: 10, background: p.shortage > 0 ? palette.badBg : palette.surface, padding: 14 }}>
      <div style={{ display: 'grid', gridTemplateColumns: '74px minmax(0,1fr)', gap: 14, marginBottom: 14 }}>
        <div style={{ width: 74, height: 74, background: palette.bg, borderRadius: 8, display: 'grid', placeItems: 'center', overflow: 'hidden' }}>
          <InvImage src={p.image} />
        </div>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 15, fontWeight: 800, lineHeight: 1.25, color: palette.accent }}>{p.name}</div>
          <div style={{ fontFamily: palette.mono, fontSize: 12, color: palette.muted, marginTop: 5 }}>{p.sku}</div>
          <div style={{ fontSize: 12, color: palette.muted, marginTop: 3 }}>{p.category}</div>
        </div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 8, alignItems: 'end' }}>
        <InvMiniStat label="On hand" value={fmtInt(p.onHand)} />
        <InvMiniStat label="Held" value={fmtInt(p.holding)} />
        <InvMiniStat label="After hold" value={fmtInt(p.afterHold)} tone={p.afterHold < 0 ? palette.bad : palette.text} />
      </div>
      <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
        <div style={{ fontSize: 12, color: palette.muted }}>
          {hold ? `${fmtInt(hold.qty)} in production` : 'No active production'}
        </div>
        <InvStatusPill p={p} palette={palette} />
      </div>
      <div style={{ marginTop: 12, display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'flex-end' }}>
        {canManageProducts
          ? <InventoryStockInput value={p.onHand} shipped={p.shipped} onCommit={(next) => state.updateStock(p.sku, next)} palette={palette} style={{ width: 90, height: 40, fontSize: 15 }} />
          : <span style={{ fontFamily: palette.mono, fontWeight: 800, fontSize: 15 }}>{fmtInt(p.onHand)} on hand</span>}
        {canManageProducts && <button onClick={() => state.setEditorSku(p.sku)} style={{ ...miniAction(palette), height: 40, padding: '0 14px', fontSize: 13 }}>Update</button>}
      </div>
    </div>
  );
}

function InventoryStockInput({ value, shipped, onCommit, palette, style }) {
  const toDraft = (v) => (v === 0 ? '' : String(v));
  const [draft, setDraft] = React.useState(toDraft(value));
  React.useEffect(() => {
    setDraft(toDraft(value));
  }, [value]);
  const commit = () => {
    const parsed = Math.max(0, Number.parseInt(draft, 10) || 0);
    if (parsed === value) {
      setDraft(toDraft(value));
      return;
    }
    onCommit(parsed + shipped);
  };
  return (
    <input
      type="number"
      step="10"
      min="0"
      inputMode="numeric"
      placeholder="Qty"
      value={draft}
      onChange={(e) => setDraft(e.target.value)}
      onFocus={(e) => { try { e.currentTarget.select(); } catch (err) {} }}
      onBlur={commit}
      onKeyDown={(e) => {
        if (e.key === 'Enter') {
          e.currentTarget.blur();
        } else if (e.key === 'Escape') {
          setDraft(toDraft(value));
          e.currentTarget.blur();
        }
      }}
      style={{
        border: `1px solid ${palette.border}`,
        borderRadius: 7,
        textAlign: 'right',
        padding: '0 10px',
        fontFamily: palette.mono,
        color: palette.text,
        background: '#fff',
        ...style,
      }}
    />
  );
}

function InvMiniStat({ label, value, tone }) {
  return (
    <div>
      <div style={{ fontSize: 10, color: L.textDim, textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 3 }}>{label}</div>
      <div style={{ fontFamily: L.mono, fontSize: 14, fontWeight: 800, color: tone || L.text }}>{value}</div>
    </div>
  );
}

const invTh = (w, align) => ({ padding: '11px 10px', width: w, textAlign: align, whiteSpace: 'nowrap', fontWeight: 800, fontSize: 10 });
const miniAction = (palette) => ({ height: 28, padding: '0 9px', border: `1px solid ${palette.border}`, borderRadius: 6, background: palette.surface, color: palette.text, fontSize: 11, fontWeight: 700, fontFamily: palette.font, cursor: 'pointer' });

function PageNavTabs({ page, onPageChange, palette, isPhone }) {
  const tabs = [
    { key: 'inventory', label: 'Inventory' },
    { key: 'production', label: 'Productions' },
    { key: 'orders', label: 'Orders' },
    { key: 'activity', label: 'Activity' },
  ];
  return (
    <div style={{ display: 'flex', gap: 2, background: palette.bg, border: `1px solid ${palette.border}`, borderRadius: 8, padding: 3 }}>
      {tabs.map(({ key, label }) => {
        const active = page === key;
        return (
          <button key={key} onClick={() => onPageChange(key)} style={{
            height: 28, padding: '0 12px', borderRadius: 6, border: 'none',
            background: active ? palette.surface : 'transparent',
            color: active ? palette.text : palette.muted,
            fontFamily: palette.font, fontWeight: active ? 700 : 500,
            cursor: 'pointer', fontSize: 12, whiteSpace: 'nowrap',
            boxShadow: active ? '0 1px 3px rgba(0,0,0,.08)' : 'none',
          }}>{label}</button>
        );
      })}
    </div>
  );
}

function ProdEditableCell({ value, onChange, palette, style, type = 'text', placeholder = '' }) {
  const [focused, setFocused] = React.useState(false);
  const [hovered, setHovered] = React.useState(false);
  return (
    <div style={{ position: 'relative', width: '100%', minHeight: 26 }}>
      <input
        type={type}
        value={value}
        placeholder={placeholder}
        onChange={(e) => onChange(e.target.value)}
        onFocus={() => setFocused(true)}
        onBlur={() => setFocused(false)}
        onMouseEnter={() => setHovered(true)}
        onMouseLeave={() => setHovered(false)}
        style={{
          border: `1px solid ${focused || hovered ? palette.border : 'transparent'}`,
          borderRadius: 5, padding: '4px 8px',
          fontFamily: palette.font, fontSize: 12,
          color: palette.text,
          background: focused ? palette.surface : 'transparent',
          outline: 'none', boxSizing: 'border-box',
          ...(focused ? {
            position: 'absolute', top: 0, left: 0,
            width: 140,
            zIndex: 20,
            boxShadow: '0 6px 18px rgba(15,23,42,0.14)',
          } : {
            position: 'relative', width: '100%',
          }),
          ...style,
        }}
      />
    </div>
  );
}

function ProdDateCell({ value, onChange, palette }) {
  const [focused, setFocused] = React.useState(false);
  const [hovered, setHovered] = React.useState(false);
  const display = value ? new Date(value + 'T12:00:00').toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }) : '';
  return (
    <input
      type="date"
      value={value || ''}
      onChange={(e) => onChange(e.target.value)}
      onFocus={() => setFocused(true)}
      onBlur={() => setFocused(false)}
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
      style={{
        border: `1px solid ${focused || hovered ? palette.border : 'transparent'}`,
        borderRadius: 5, padding: '4px 6px',
        fontFamily: palette.font, fontSize: 12, color: value ? palette.text : palette.muted,
        background: focused ? palette.surface : 'transparent',
        outline: 'none', cursor: 'pointer', width: '100%', boxSizing: 'border-box',
      }}
    />
  );
}

function ProdStatusSelect({ value, onChange, palette, styles }) {
  const map = styles || PROD_STATUS_STYLES;
  const [open, setOpen] = React.useState(false);
  // No fallback to the first state: an unset value stays gray/blank.
  const s = value && map[value] ? map[value] : STATUS_EMPTY_STYLE;
  const optionBtn = (active) => ({
    display: 'block', width: '100%', textAlign: 'left',
    padding: '8px 12px', border: 'none',
    background: active ? palette.bg : palette.surface,
    cursor: 'pointer', fontFamily: palette.font,
  });
  return (
    <div style={{ position: 'relative', display: 'inline-block' }}>
      <button onClick={() => setOpen((v) => !v)} style={{ ...statusChipStyle(s), border: 'none', cursor: 'pointer' }}>
        {value || STATUS_EMPTY_LABEL}
      </button>
      {open && (
        <>
          <div style={{ position: 'fixed', inset: 0, zIndex: 49 }} onClick={() => setOpen(false)} />
          <div style={{
            position: 'absolute', top: '100%', left: 0, zIndex: 50, marginTop: 4,
            background: palette.surface, border: `1px solid ${palette.border}`,
            borderRadius: 8, boxShadow: '0 8px 24px rgba(0,0,0,.12)', minWidth: 160, overflow: 'hidden',
          }}>
            <button key="__none" onClick={() => { onChange(''); setOpen(false); }} style={optionBtn(!value)}>
              <span style={statusChipStyle(STATUS_EMPTY_STYLE)}>{STATUS_EMPTY_LABEL} None</span>
            </button>
            {Object.keys(map).map((status) => (
              <button key={status} onClick={() => { onChange(status); setOpen(false); }} style={optionBtn(value === status)}>
                <span style={statusChipStyle(map[status])}>{status}</span>
              </button>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

function ProdLeadAvatar({ initials, onChange, palette }) {
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState(initials);
  const bg = LEAD_AVATAR_BG[initials] || '#94a3b8';
  React.useEffect(() => { setDraft(initials); }, [initials]);
  if (editing) {
    return (
      <input
        value={draft} autoFocus maxLength={2}
        onChange={(e) => setDraft(e.target.value.toUpperCase())}
        onBlur={() => { onChange(draft || initials); setEditing(false); }}
        onKeyDown={(e) => {
          if (e.key === 'Enter') { onChange(draft || initials); setEditing(false); }
          if (e.key === 'Escape') { setDraft(initials); setEditing(false); }
        }}
        style={{ width: 32, height: 32, borderRadius: '50%', textAlign: 'center', fontWeight: 700, fontSize: 12, border: `2px solid ${palette.accent}`, fontFamily: palette.mono, background: palette.surface, color: palette.text, outline: 'none' }}
      />
    );
  }
  return (
    <div onClick={() => setEditing(true)} title="Click to edit" style={{
      width: 32, height: 32, borderRadius: '50%', background: bg, color: '#fff',
      display: 'grid', placeItems: 'center', fontWeight: 700, fontSize: 12,
      fontFamily: palette.mono, cursor: 'pointer', userSelect: 'none',
    }}>{initials || '?'}</div>
  );
}

function ProdCommentsBtn({ comments, onOpen }) {
  return (
    <button onClick={onOpen} style={{ position: 'relative', background: 'transparent', border: 'none', cursor: 'pointer', padding: 4, display: 'inline-flex', alignItems: 'center' }}>
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
        <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
      </svg>
      {comments.length > 0 && (
        <span style={{
          position: 'absolute', top: 0, right: 0, width: 16, height: 16, borderRadius: '50%',
          background: '#2e5dff', color: '#fff', fontSize: 9, fontWeight: 800,
          display: 'grid', placeItems: 'center',
        }}>{comments.length}</span>
      )}
    </button>
  );
}

function ProdCommentsModal({ row, onClose, onAdd, palette, label, readOnly }) {
  const isPhone = useInventoryViewportWidth() < 760;
  const [text, setText] = React.useState('');
  const [attachments, setAttachments] = React.useState([]);
  const canSubmit = text.trim().length > 0 || attachments.length > 0;

  const addFiles = (files) => {
    Array.from(files).forEach((file) => {
      const reader = new FileReader();
      reader.onload = () => setAttachments((prev) => [...prev, { name: file.name, url: reader.result }]);
      reader.readAsDataURL(file);
    });
  };

  const submit = () => {
    if (!canSubmit) return;
    onAdd(text, attachments);
    setText('');
    setAttachments([]);
  };

  return (
    <div style={{ position: isPhone ? 'fixed' : 'absolute', inset: 0, zIndex: 50, background: 'rgba(16,24,40,0.42)', display: 'flex', alignItems: isPhone ? 'flex-end' : 'center', justifyContent: 'center', padding: isPhone ? 0 : 18 }} onClick={onClose}>
      <div onClick={(e) => e.stopPropagation()} style={{
        width: isPhone ? '100%' : 480, maxWidth: isPhone ? '100%' : '100%',
        background: palette.surface, border: `1px solid ${palette.border}`,
        borderRadius: isPhone ? '16px 16px 0 0' : 12,
        boxShadow: '0 24px 70px rgba(0,0,0,.22)',
        padding: isPhone ? 16 : 24,
        maxHeight: isPhone ? '85dvh' : 'none',
        display: 'flex', flexDirection: 'column',
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 14, flexShrink: 0 }}>
          <div>
            <div style={{ fontSize: isPhone ? 15 : 16, fontWeight: 800, marginBottom: 3 }}>Comments</div>
            <div style={{ fontSize: 11, color: palette.muted, fontFamily: palette.mono }}>{label || row.item}</div>
          </div>
          <button onClick={onClose} style={{ width: 32, height: 32, borderRadius: 8, border: `1px solid ${palette.border}`, background: palette.bg, cursor: 'pointer', fontSize: 18, flexShrink: 0 }}>×</button>
        </div>
        {row.comments.length === 0 && (
          <div style={{ textAlign: 'center', padding: '16px 0', color: palette.muted, fontSize: 13, flexShrink: 0 }}>No comments yet.</div>
        )}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8, overflowY: 'auto', marginBottom: 14, flex: 1, minHeight: 0 }}>
          {row.comments.map((c, i) => (
            <div key={i} style={{ padding: '10px 12px', background: palette.bg, borderRadius: 8, border: `1px solid ${palette.border}`, flexShrink: 0 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 5, gap: 8 }}>
                <span style={{ fontWeight: 700, fontSize: 12 }}>{c.author}</span>
                <span style={{ fontSize: 11, color: palette.muted, fontFamily: palette.mono, whiteSpace: 'nowrap' }}>{c.date}</span>
              </div>
              {c.text && <div style={{ fontSize: 13, lineHeight: 1.5 }}>{c.text}</div>}
              {c.attachments?.length > 0 && (
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: c.text ? 8 : 0 }}>
                  {c.attachments.map((a, j) => (
                    <a key={j} href={a.url} download={a.name} style={{
                      display: 'inline-flex', alignItems: 'center', gap: 4,
                      padding: '3px 8px', borderRadius: 6, border: `1px solid ${palette.border}`,
                      background: palette.surface, fontSize: 11, color: palette.accent,
                      textDecoration: 'none', fontFamily: palette.mono,
                    }}>&#128206; {a.name}</a>
                  ))}
                </div>
              )}
            </div>
          ))}
        </div>
        {readOnly ? (
          <div style={{ fontSize: 11, color: palette.muted, textAlign: 'center', flexShrink: 0 }}>You have view-only access to comments here.</div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8, flexShrink: 0 }}>
            {attachments.length > 0 && (
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                {attachments.map((a, i) => (
                  <div key={i} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '3px 8px', borderRadius: 6, border: `1px solid ${palette.border}`, background: palette.bg, fontSize: 11, fontFamily: palette.mono }}>
                    &#128206; {a.name}
                    <button onClick={() => setAttachments((prev) => prev.filter((_, j) => j !== i))} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0, marginLeft: 2, color: palette.muted, fontSize: 14, lineHeight: 1 }}>×</button>
                  </div>
                ))}
              </div>
            )}
            <div style={{ display: 'flex', gap: 8 }}>
              <input
                value={text}
                onChange={(e) => setText(e.target.value)}
                placeholder="Add a comment..."
                onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) submit(); }}
                style={{ flex: 1, height: 40, border: `1px solid ${palette.border}`, borderRadius: 8, padding: '0 12px', fontFamily: palette.font, outline: 'none', color: palette.text, background: palette.bg, fontSize: 13, minWidth: 0 }}
              />
              <label title="Attach file" style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 40, height: 40, border: `1px solid ${palette.border}`, borderRadius: 8, background: palette.bg, cursor: 'pointer', flexShrink: 0, fontSize: 16 }}>
                <input type="file" multiple onChange={(e) => addFiles(e.target.files)} style={{ display: 'none' }} />
                &#128206;
              </label>
              <button onClick={submit} disabled={!canSubmit} style={{ ...invButton(palette, true), height: 40, flexShrink: 0, opacity: canSubmit ? 1 : 0.5 }}>Post</button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

// Renders one board cell based on its column descriptor `type`.
// Small free-text note that sits under each status pill (mockup 19JUN2026).
function BoardNoteInput({ value, onChange, palette }) {
  const [hovered, setHovered] = React.useState(false);
  const [focused, setFocused] = React.useState(false);
  return (
    <input
      value={value}
      onChange={(e) => onChange(e.target.value)}
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
      onFocus={() => setFocused(true)}
      onBlur={() => setFocused(false)}
      placeholder="+ note"
      style={{
        marginTop: 4, width: '100%', boxSizing: 'border-box',
        border: `1px solid ${focused || hovered ? palette.border : 'transparent'}`, borderRadius: 4,
        background: focused ? palette.surface : 'transparent', color: palette.muted,
        fontFamily: palette.font, fontSize: 10, padding: '2px 4px', outline: 'none',
      }}
    />
  );
}

// Static (non-editable) rendering of a board cell, used when the signed-in role
// may not edit that column.
function BoardCellReadOnly({ col, row, palette }) {
  const val = row[col.key];
  switch (col.type) {
    case 'namesku':
      return (
        <div>
          <div style={{ fontWeight: 700 }}>{row.item || '—'}</div>
          <div style={{ fontSize: 10, color: palette.muted, fontFamily: palette.mono }}>{row.sku || ''}</div>
        </div>
      );
    case 'status': {
      const map = col.styles || PROD_STATUS_STYLES;
      const s = val && map[val] ? map[val] : STATUS_EMPTY_STYLE;
      const noteKey = col.key + 'Note';
      return (
        <div>
          <span style={statusChipStyle(s)}>{val || STATUS_EMPTY_LABEL}</span>
          {row[noteKey] ? <div style={{ marginTop: 4, fontSize: 10, color: palette.muted }}>{row[noteKey]}</div> : null}
        </div>
      );
    }
    case 'warehouse':
      return <span style={{ fontSize: 12, color: val ? palette.text : palette.muted }}>{val || '—'}</span>;
    case 'date':
      return <span style={{ fontSize: 12, color: val ? palette.text : palette.muted }}>{val ? new Date(val + 'T12:00:00').toLocaleDateString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }) : '—'}</span>;
    case 'number':
      return <span style={{ fontFamily: palette.mono, fontSize: 12 }}>{(val || val === 0) ? String(val) : '—'}</span>;
    case 'leaddays':
      return <span style={{ fontFamily: palette.mono, fontSize: 12 }}>{val ? `${val} days` : '—'}</span>;
    default:
      return <span style={{ fontSize: 12, fontFamily: col.mono ? palette.mono : palette.font, color: val ? palette.text : palette.muted }}>{val || '—'}</span>;
  }
}

function BoardCell({ col, row, state, palette, readOnly }) {
  const set = (v) => state.updateRow(row.id, col.key, v);
  // Comments stay viewable for everyone (the composer inside the modal is what
  // gets gated); computed columns are already static.
  if (col.type === 'comments') {
    return <ProdCommentsBtn comments={row.comments || []} onOpen={() => state.setCommentsRow(row.id)} />;
  }
  if (col.type === 'dayselapsed') {
    const d = boardDaysElapsed(row.depositDate);
    return <span style={{ fontFamily: palette.mono, fontSize: 12, color: palette.muted }}>{d === '' ? '—' : d}</span>;
  }
  if (readOnly) return <BoardCellReadOnly col={col} row={row} palette={palette} />;
  switch (col.type) {
    case 'namesku':
      return (
        <div>
          <ProdEditableCell value={row.item} onChange={(v) => state.updateRow(row.id, 'item', v)} palette={palette} style={{ fontWeight: 700 }} placeholder="Item name" />
          <ProdEditableCell value={row.sku} onChange={(v) => state.updateRow(row.id, 'sku', v)} palette={palette} style={{ fontSize: 10, color: palette.muted, fontFamily: palette.mono }} placeholder="SKU" />
        </div>
      );
    case 'comments':
      return <ProdCommentsBtn comments={row.comments || []} onOpen={() => state.setCommentsRow(row.id)} />;
    case 'warehouse': {
      const names = (typeof window !== 'undefined' && window.WAREHOUSES ? window.WAREHOUSES.map((w) => w.name) : ['Shanghai', 'California', 'Germany']);
      return (
        <select value={row[col.key] || ''} onChange={(e) => set(e.target.value)} style={{
          width: '100%', height: 30, border: `1px solid ${palette.border}`, borderRadius: 6,
          background: palette.bg, color: row[col.key] ? palette.text : palette.muted,
          fontFamily: palette.font, fontSize: 12, padding: '0 6px', outline: 'none', cursor: 'pointer',
        }}>
          <option value="">—</option>
          {names.map((n) => <option key={n} value={n}>{n}</option>)}
        </select>
      );
    }
    case 'status': {
      const noteKey = col.key + 'Note';
      return (
        <div>
          <ProdStatusSelect value={row[col.key]} onChange={set} palette={palette} styles={col.styles} />
          <BoardNoteInput value={row[noteKey] || ''} onChange={(v) => state.updateRow(row.id, noteKey, v)} palette={palette} />
        </div>
      );
    }
    case 'date':
      return <ProdDateCell value={row[col.key]} onChange={set} palette={palette} />;
    case 'number':
      return <ProdEditableCell value={row[col.key] === 0 ? '' : String(row[col.key] ?? '')} onChange={(v) => set(Number(v) || 0)} palette={palette} type="number" placeholder="0" style={{ textAlign: 'right', fontFamily: palette.mono }} />;
    case 'leaddays':
      return (
        <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
          <ProdEditableCell value={row[col.key]} onChange={set} palette={palette} type="number" placeholder="—" style={{ width: 52, fontFamily: palette.mono }} />
          {row[col.key] ? <span style={{ fontSize: 11, color: palette.muted, whiteSpace: 'nowrap' }}>days</span> : null}
        </div>
      );
    case 'dayselapsed': {
      const d = boardDaysElapsed(row.depositDate);
      return <span style={{ fontFamily: palette.mono, fontSize: 12, color: palette.muted }}>{d === '' ? '—' : d}</span>;
    }
    default:
      return <ProdEditableCell value={row[col.key]} onChange={set} palette={palette} placeholder="—" style={col.mono ? { fontFamily: palette.mono } : undefined} />;
  }
}

// Read-only activity/audit feed of operations performed on the dashboard.
function ActivityPage({ token, user, palette, page, onPageChange }) {
  const width = useInventoryViewportWidth();
  const isPhone = width < 760;
  const [entries, setEntries] = React.useState(null);

  const load = React.useCallback(() => {
    if (!token) { setEntries([]); return; }
    fetch('/api/inventory?resource=audit', { headers: { Authorization: `Bearer ${token}` } })
      .then((r) => (r.ok ? r.json() : Promise.reject(new Error('audit'))))
      .then((d) => setEntries(d.audit || []))
      .catch(() => setEntries([]));
  }, [token]);
  React.useEffect(() => { load(); }, [load]);

  const actionColor = (a = '') => {
    if (a.startsWith('stock')) return { bg: palette.active, color: palette.accentText };
    if (a.startsWith('product')) return { bg: palette.goodBg, color: palette.good };
    if (a.startsWith('production')) return { bg: '#fffaeb', color: '#b54708' };
    if (a.startsWith('order')) return { bg: '#eaf0ff', color: '#2e5dff' };
    return { bg: palette.bg, color: palette.muted };
  };
  const fmtTime = (iso) => {
    const d = new Date(iso);
    return isNaN(d.getTime()) ? '' : d.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
  };
  const th = { textAlign: 'left', padding: '9px 12px', fontSize: 11, textTransform: 'uppercase', letterSpacing: 0.5 };

  return (
    <div style={{ width: '100%', height: '100%', background: palette.bg, color: palette.text, fontFamily: palette.font, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
      <div style={{ flex: '0 0 auto', minHeight: isPhone ? 48 : 64, background: palette.surface, borderBottom: `1px solid ${palette.border}`, display: 'flex', alignItems: 'center', padding: isPhone ? '8px' : '0 26px', gap: isPhone ? 8 : 18 }}>
        <BrandLogo size={isPhone ? 26 : 30} radius={8} />
        <div style={{ flexShrink: 0 }}>
          <div style={{ fontSize: isPhone ? 13 : 15, fontWeight: 700, lineHeight: 1 }}>Gruv Gear</div>
          <div style={{ fontSize: isPhone ? 8 : 10, color: palette.muted, fontFamily: palette.mono, letterSpacing: 1.1, textTransform: 'uppercase' }}>Employee inventory</div>
        </div>
        {!isPhone && <div style={{ width: 1, height: 24, background: palette.border }} />}
        <PageNavTabs page={page} onPageChange={onPageChange} palette={palette} isPhone={isPhone} />
        <div style={{ flex: 1 }} />
        <button onClick={load} style={{ ...invButton(palette, false), height: isPhone ? 32 : 36, fontSize: isPhone ? 12 : 13, flexShrink: 0 }}>Refresh</button>
        {!isPhone && (
          <div style={{ textAlign: 'right', flexShrink: 0 }}>
            <div style={{ fontSize: 12, fontWeight: 700 }}>{user?.name || INV_EMPLOYEE.name}</div>
            <div style={{ fontSize: 10, color: palette.muted, fontFamily: palette.mono, textTransform: 'capitalize' }}>{user?.role || INV_EMPLOYEE.role}</div>
          </div>
        )}
      </div>

      <div style={{ padding: isPhone ? '10px 8px' : '20px 26px', borderBottom: `1px solid ${palette.border}` }}>
        <div style={{ fontSize: isPhone ? 18 : 26, fontWeight: 700, lineHeight: 1 }}>Activity Log</div>
        {!isPhone && <div style={{ marginTop: 6, color: palette.muted, fontSize: 13 }}>Every operation performed on the inventory dashboard — who, what, and when.</div>}
      </div>

      <div style={{ flex: 1, overflow: 'auto', padding: isPhone ? '8px' : '14px 26px' }}>
        {entries === null ? (
          <div style={{ padding: 40, textAlign: 'center', color: palette.muted }}>Loading…</div>
        ) : entries.length === 0 ? (
          <div style={{ padding: 40, textAlign: 'center', color: palette.muted, background: palette.surface, border: `1px solid ${palette.border}`, borderRadius: 10 }}>No activity yet. Actions on the dashboard will show up here.</div>
        ) : (
          <div style={{ background: palette.surface, border: `1px solid ${palette.border}`, borderRadius: 10, overflow: 'auto' }}>
            <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13, minWidth: isPhone ? 0 : 700 }}>
              <thead>
                <tr style={{ background: palette.bg, color: palette.muted }}>
                  <th style={th}>When</th>
                  <th style={th}>Who</th>
                  <th style={th}>Action</th>
                  <th style={th}>Target</th>
                  {!isPhone && <th style={th}>Detail</th>}
                  {!isPhone && <th style={th}>Warehouse</th>}
                </tr>
              </thead>
              <tbody>
                {entries.map((e) => {
                  const c = actionColor(e.action);
                  return (
                    <tr key={e.id} style={{ borderTop: `1px solid ${palette.border}` }}>
                      <td style={{ padding: '9px 12px', fontFamily: palette.mono, fontSize: 11, color: palette.muted, whiteSpace: 'nowrap' }}>{fmtTime(e.created_at)}</td>
                      <td style={{ padding: '9px 12px', fontWeight: 600, whiteSpace: 'nowrap' }}>{e.actor || '—'}</td>
                      <td style={{ padding: '9px 12px' }}><span style={{ padding: '2px 8px', borderRadius: 999, background: c.bg, color: c.color, fontFamily: palette.mono, fontSize: 11, fontWeight: 700, whiteSpace: 'nowrap' }}>{e.action}</span></td>
                      <td style={{ padding: '9px 12px', fontFamily: palette.mono, fontSize: 12 }}>{e.target || '—'}</td>
                      {!isPhone && <td style={{ padding: '9px 12px', color: palette.text }}>{e.detail || ''}</td>}
                      {!isPhone && <td style={{ padding: '9px 12px', color: palette.muted }}>{e.warehouse || ''}</td>}
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        )}
      </div>
    </div>
  );
}

// Config-driven editable board shared by the Production Board and Order Dashboard.
function BoardPage({ state, palette, page, onPageChange, user, title, subtitle, columns, commentLabel, rowLabel, originFilter }) {
  const width = useInventoryViewportWidth();
  const isPhone = width < 760;
  // Access control: managers/admins get full CRUD on both boards. Plain employees
  // are view-only on Production and may edit only the status columns on Orders.
  const role = user?.role || 'employee';
  const canManageRows = role === 'manager' || role === 'admin';
  const canComment = canManageRows || page === 'orders';
  const isColEditable = (col) => canManageRows || (page === 'orders' && col.type === 'status');
  const accessNote = canManageRows ? '' : (page === 'orders' ? 'Statuses only' : 'View only');
  const [deleteConfirmId, setDeleteConfirmId] = React.useState(null);
  const [pendingArchiveId, setPendingArchiveId] = React.useState(null);
  const [origin, setOrigin] = React.useState('All');
  const [sort, setSort] = React.useState({ key: null, dir: 'asc' });
  const confirmDelete = () => { state.deleteRow(deleteConfirmId); setDeleteConfirmId(null); };
  const toggleSort = (key) => setSort((s) => ({ key, dir: s.key === key && s.dir === 'asc' ? 'desc' : 'asc' }));

  const rowsToShow = React.useMemo(() => {
    let rows = originFilter && origin !== 'All' ? state.filtered.filter((r) => r.origin === origin) : state.filtered;
    if (sort.key) {
      const sign = sort.dir === 'asc' ? 1 : -1;
      rows = [...rows].sort((a, b) => {
        const av = String(a[sort.key] ?? '').toLowerCase();
        const bv = String(b[sort.key] ?? '').toLowerCase();
        if (av < bv) return -1 * sign;
        if (av > bv) return 1 * sign;
        return 0;
      });
    }
    // Archived rows always sink to the bottom.
    return [...rows.filter((r) => !r.archived), ...rows.filter((r) => r.archived)];
  }, [state.filtered, origin, originFilter, sort]);

  // Merge consecutive columns that share a `group` into one spanning header cell.
  const groupSegments = [];
  columns.forEach((c) => {
    const g = c.group || '';
    const last = groupSegments[groupSegments.length - 1];
    if (last && last.group === g) last.span += 1;
    else groupSegments.push({ group: g, span: 1 });
  });
  const hasGroups = columns.some((c) => c.group);

  // Archived rows drop into their own "Completed / Archived" group below the
  // active list rather than just being grayed out inline.
  const activeRows = rowsToShow.filter((r) => !r.archived);
  const archivedRows = rowsToShow.filter((r) => r.archived);
  const renderRow = (row, i) => (
    <tr key={row.id} style={{ borderBottom: `1px solid ${palette.border}`, background: i % 2 ? palette.metricBg : palette.surface, color: row.archived ? palette.muted : undefined }}>
      {columns.map((c) => (
        <td key={c.key} style={{ padding: '6px 10px', textAlign: c.align || 'left', verticalAlign: 'top' }}>
          <BoardCell col={c} row={row} state={state} palette={palette} readOnly={!isColEditable(c)} />
        </td>
      ))}
      <td style={{ padding: '6px 12px', textAlign: 'center', verticalAlign: 'top' }}>
        {canManageRows ? (
          <div style={{ display: 'inline-flex', gap: 6 }}>
            {!row.archived && (
              <button onClick={() => setPendingArchiveId(row.id)} aria-label="Archive row" title="Archive row" style={{ width: 24, height: 24, borderRadius: 5, border: `1px solid ${palette.border}`, background: 'transparent', color: palette.muted, cursor: 'pointer', display: 'grid', placeItems: 'center' }}>
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="4" rx="1" /><path d="M5 8v11a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8" /><path d="M10 12h4" /></svg>
              </button>
            )}
            <button onClick={() => setDeleteConfirmId(row.id)} aria-label="Delete row" title="Delete row" style={{ width: 24, height: 24, borderRadius: 5, border: `1px solid ${palette.border}`, background: 'transparent', color: palette.muted, cursor: 'pointer', fontSize: 16, display: 'grid', placeItems: 'center', lineHeight: 1 }}>×</button>
          </div>
        ) : (
          <span style={{ color: palette.muted, fontSize: 12 }}>—</span>
        )}
      </td>
    </tr>
  );

  return (
    <div style={{ width: '100%', height: '100%', background: palette.bg, color: palette.text, fontFamily: palette.font, overflow: 'hidden', display: 'flex', flexDirection: 'column', position: 'relative' }}>
      {/* Header */}
      <div style={{ flex: '0 0 auto', minHeight: isPhone ? 48 : 64, background: palette.surface, borderBottom: `1px solid ${palette.border}`, display: 'flex', alignItems: 'center', padding: isPhone ? '8px' : '0 26px', gap: isPhone ? 8 : 18, flexWrap: 'nowrap' }}>
        <BrandLogo size={isPhone ? 26 : 30} radius={8} />
        <div style={{ flexShrink: 0 }}>
          <div style={{ fontSize: isPhone ? 13 : 15, fontWeight: 700, lineHeight: 1 }}>Gruv Gear</div>
          <div style={{ fontSize: isPhone ? 8 : 10, color: palette.muted, fontFamily: palette.mono, letterSpacing: 1.1, textTransform: 'uppercase' }}>Employee inventory</div>
        </div>
        {!isPhone && <div style={{ width: 1, height: 24, background: palette.border, flexShrink: 0 }} />}
        <PageNavTabs page={page} onPageChange={onPageChange} palette={palette} isPhone={isPhone} />
        <div style={{ flex: 1 }} />
        {canManageRows && <button onClick={state.addRow} style={{ ...invButton(palette, true), height: isPhone ? 32 : 36, padding: isPhone ? '0 11px' : '0 14px', fontSize: isPhone ? 12 : 13, flexShrink: 0 }}>+ Add row</button>}
        {!isPhone && (
          <div style={{ textAlign: 'right', flexShrink: 0 }}>
            <div style={{ fontSize: 12, fontWeight: 700 }}>{user?.name || INV_EMPLOYEE.name}</div>
            <div style={{ fontSize: 10, color: palette.muted, fontFamily: palette.mono, textTransform: 'capitalize' }}>{user?.role || INV_EMPLOYEE.role}</div>
          </div>
        )}
      </div>

      {/* Title */}
      <div style={{ padding: isPhone ? '10px 8px' : '20px 26px', borderBottom: `1px solid ${palette.border}`, background: palette.bg }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
          <div style={{ fontSize: isPhone ? 18 : 26, fontWeight: 700, lineHeight: 1 }}>{title}</div>
          {accessNote && (
            <span style={{ padding: '3px 10px', borderRadius: 999, background: palette.metricBg, border: `1px solid ${palette.border}`, color: palette.muted, fontSize: 11, fontWeight: 700, whiteSpace: 'nowrap' }}>{accessNote}</span>
          )}
        </div>
        {!isPhone && subtitle && <div style={{ marginTop: 6, color: palette.muted, fontSize: 13 }}>{subtitle}</div>}
      </div>

      {/* Toolbar */}
      <div style={{ padding: isPhone ? '8px' : '10px 26px', borderBottom: `1px solid ${palette.border}`, background: palette.bg, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        {originFilter && (
          <div style={{ display: 'inline-flex', background: palette.surface, border: `1px solid ${palette.border}`, borderRadius: 8, padding: 3, flexShrink: 0 }}>
            {['All', 'Domestic', 'International'].map((o) => {
              const active = origin === o;
              return (
                <button key={o} onClick={() => setOrigin(o)} style={{
                  height: 28, padding: '0 12px', borderRadius: 6, border: 'none',
                  background: active ? palette.accent : 'transparent', color: active ? palette.onAccent : palette.muted,
                  fontFamily: palette.font, fontWeight: active ? 700 : 500, cursor: 'pointer', fontSize: 12, whiteSpace: 'nowrap',
                }}>{o}</button>
              );
            })}
          </div>
        )}
        <div style={{ flex: 1, minWidth: isPhone ? '100%' : 220, maxWidth: isPhone ? '100%' : 320 }}>
          <input value={state.search} onChange={(e) => state.setSearch(e.target.value)} placeholder="Search…" style={{ width: '100%', height: 34, border: `1px solid ${palette.border}`, borderRadius: palette.radius, background: palette.surface, padding: '0 12px', fontFamily: palette.font, outline: 'none', color: palette.text, fontSize: 13, boxSizing: 'border-box' }} />
        </div>
        <div style={{ fontSize: 12, color: palette.muted, fontFamily: palette.mono, whiteSpace: 'nowrap', flexShrink: 0 }}>{rowsToShow.length} of {state.rows.length} rows</div>
      </div>

      {/* Table */}
      <div style={{ flex: 1, overflow: 'auto', background: palette.surface }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
          <thead style={{ position: 'sticky', top: 0, background: palette.surface, zIndex: 2 }}>
            {hasGroups && (
              <tr style={{ borderBottom: `1px solid ${palette.border}` }}>
                {groupSegments.map((seg, i) => (
                  <th key={i} colSpan={seg.span} style={seg.group ? {
                    padding: '7px 10px', textAlign: 'center', fontSize: 11, fontWeight: 800,
                    color: palette.muted, textTransform: 'uppercase', letterSpacing: 0.6,
                    background: palette.metricBg, borderLeft: `1px solid ${palette.border}`, borderRight: `1px solid ${palette.border}`,
                  } : { background: palette.surface }}>{seg.group}</th>
                ))}
                <th style={{ background: palette.surface }} />
              </tr>
            )}
            <tr style={{ borderBottom: `1px solid ${palette.border}`, color: palette.muted, textTransform: 'uppercase', letterSpacing: 0.5 }}>
              {columns.map((c) => (
                <th key={c.key} style={invTh(c.width, c.align || 'left')}>
                  {c.sortable ? (
                    <button onClick={() => toggleSort(c.key)} style={{ border: 'none', background: 'transparent', padding: 0, font: 'inherit', color: 'inherit', textTransform: 'inherit', letterSpacing: 'inherit', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 3 }}>
                      {c.label}<span style={{ fontSize: 9, opacity: sort.key === c.key ? 1 : 0.35 }}>{sort.key === c.key && sort.dir === 'desc' ? '▼' : '▲'}</span>
                    </button>
                  ) : c.label}
                </th>
              ))}
              <th style={invTh(72, 'center')}>Action</th>
            </tr>
          </thead>
          <tbody>
            {activeRows.map((row, i) => renderRow(row, i))}
            {archivedRows.length > 0 && (
              <tr>
                <td colSpan={columns.length + 1} style={{ padding: '9px 14px', background: palette.metricBg, borderTop: `1px solid ${palette.border}`, borderBottom: `1px solid ${palette.border}`, fontSize: 11, fontWeight: 800, color: palette.muted, textTransform: 'uppercase', letterSpacing: 0.6 }}>
                  Completed / Archived ({archivedRows.length})
                </td>
              </tr>
            )}
            {archivedRows.map((row, i) => renderRow(row, i))}
            {rowsToShow.length === 0 && (
              <tr><td colSpan={columns.length + 1} style={{ padding: '28px 10px', textAlign: 'center', color: palette.muted, fontSize: 13 }}>No rows to show.</td></tr>
            )}
          </tbody>
        </table>
        {canManageRows && (
          <div style={{ padding: '10px 20px' }}>
            <button onClick={state.addRow} style={{ background: 'transparent', border: 'none', color: palette.accent, fontFamily: palette.font, fontSize: 13, fontWeight: 700, cursor: 'pointer' }}>+ Add row</button>
          </div>
        )}
      </div>

      {/* Comments modal */}
      {state.commentsRow !== null && (() => {
        const row = state.rows.find((r) => r.id === state.commentsRow);
        if (!row) return null;
        return (
          <ProdCommentsModal
            row={row} palette={palette}
            label={commentLabel ? commentLabel(row) : undefined}
            readOnly={!canComment}
            onClose={() => state.setCommentsRow(null)}
            onAdd={(text, attachments) => state.addComment(state.commentsRow, text, attachments, user?.name)}
          />
        );
      })()}

      {/* Delete confirmation */}
      {deleteConfirmId !== null && (() => {
        const row = state.rows.find((r) => r.id === deleteConfirmId);
        if (!row) return null;
        return (
          <InventoryConfirmDialog
            palette={palette}
            title="Delete this row?"
            message={`Are you sure you want to remove "${rowLabel ? rowLabel(row) : 'this row'}" from ${title}? This cannot be undone.`}
            confirmLabel="Delete"
            cancelLabel="Cancel"
            onConfirm={confirmDelete}
            onCancel={() => setDeleteConfirmId(null)}
          />
        );
      })()}

      {/* Archive confirmation */}
      {pendingArchiveId !== null && (() => {
        const row = state.rows.find((r) => r.id === pendingArchiveId);
        if (!row) return null;
        return (
          <InventoryConfirmDialog
            palette={palette}
            title="Archive this row?"
            message={`Are you sure you want to archive "${rowLabel ? rowLabel(row) : 'this row'}"? It will move to the bottom of ${title} and be greyed out.`}
            confirmLabel="Archive"
            cancelLabel="Cancel"
            onConfirm={() => { state.archiveRow(pendingArchiveId); setPendingArchiveId(null); }}
            onCancel={() => setPendingArchiveId(null)}
          />
        );
      })()}
    </div>
  );
}
