/* ============================================================
   pages_dash.jsx — 成本测算看板 / 主数据同步 / 物料与价格
   ============================================================ */
const { Ico, Btn, Tag, Chip, Card, CardHead, Switch, Avatar, Drawer, Modal, Empty, Bar, Ring, Columns, ColumnsCompare, StackBar, Spark, Donut, Lines, useApp, FlowStepper, MiniSteps, Timeline, SrcDot, FieldCell, DiffTable, CompletenessPanel } = window;
const { useState, useEffect, useMemo, Fragment } = React;

const yuan = (v) => (v == null ? '—' : Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }));
const yuanInt = (v) => (v == null ? '—' : Math.round(v).toLocaleString('zh-CN'));
const wan = (v) => (v >= 10000 ? (v / 10000).toFixed(1) + ' 万' : yuanInt(v));
const pct = (v, d) => (v == null ? '—' : (v * 100).toFixed(d == null ? 1 : d) + '%');

/* ================================================================
   1. 看板
   ================================================================ */
const CAP_COLORS = { material: 'var(--c-material)', labor: 'var(--c-labor)', overhead: 'var(--c-overhead)' };

/* 公式在 M1 上的样例值（看板只读展示） */
function formulaValue(id, c) {
  switch (id) {
    case 'stdQty': return wan(c.std.fin.std) + ' 盒';
    case 'material': return wan(c.material.tax.total);
    case 'wsCash': return wan(c.factors.wsCash.tax);
    case 'wsDepr': return wan(c.factors.wsDepr.tax);
    case 'auxCash': return wan(c.factors.auxCash.tax);
    case 'auxDepr': return wan(c.factors.auxDepr.tax);
    case 'prodCost': return wan(c.prodCost.tax);
    case 'minUnit': return yuan(c.minUnit.box.noTax) + ' 元/盒';
    case 'tentative': return wan(c.prodCost.tentTax);
    default: return '—';
  }
}

function Dashboard() {
  const app = useApp();
  const { role, go, calc, COMPLETENESS, activeMode, setActiveMode, todo, drugs, activeDrug, switchDrug } = app;
  const [period, setPeriod] = useState('Q4');
  const [view, setView] = useState('detail');

  const isFinance = role === 'finance';
  const myTodo = todo.length;
  const declarationTodo = todo.filter((item) => item.kind === 'approve' || item.kind === 'fill');
  const activeDrugRecord = drugs.find((d) => d.id === activeDrug) || drugs[0];
  const product = activeDrugRecord ? activeDrugRecord.product : window.PRODUCT;

  /* 全 6 工况完整度（每工况 7 项） */
  const comp = useMemo(() => {
    let ok = 0, T = 0;
    window.MODES.forEach((m) => (COMPLETENESS[m.key] || []).forEach((x) => { T++; if (x.ok) ok++; }));
    return { ok, T, pct: ok / T * 100, missing: T - ok, unready: window.MODES.filter((m) => (COMPLETENESS[m.key] || []).some((x) => !x.ok)).length };
  }, [COMPLETENESS]);

  const modeRows = window.MODES.map((m) => ({
    m, c: calc[m.key], full: (COMPLETENESS[m.key] || []).every((x) => x.ok),
  }));

  const selectedRow = modeRows.find((row) => row.m.key === activeMode) || modeRows[0];
  const selectedCalc = selectedRow.c;
  const selectedItems = COMPLETENESS[selectedRow.m.key] || [];
  const selectedOk = selectedItems.filter((item) => item.ok).length;
  const selectedMissing = selectedItems.filter((item) => !item.ok);
  const selectedPct = selectedItems.length ? selectedOk / selectedItems.length * 100 : 0;
  const materialTotal = selectedCalc.material.tax.total;
  const wsTotal = selectedCalc.factors.wsCash.tax + selectedCalc.factors.wsDepr.tax;
  const axTotal = selectedCalc.factors.auxCash.tax + selectedCalc.factors.auxDepr.tax;
  const grand = materialTotal + wsTotal + axTotal || 1;
  const tentativeGap = selectedCalc.minUnit.box.tentTax - selectedCalc.minUnit.box.tax;
  const lowestRow = modeRows.reduce((best, row) => !best || row.c.minUnit.box.tax < best.c.minUnit.box.tax ? row : best, null);
  const highestRow = modeRows.reduce((best, row) => !best || row.c.minUnit.box.tax > best.c.minUnit.box.tax ? row : best, null);
  const modeSpread = lowestRow && highestRow ? highestRow.c.minUnit.box.tax - lowestRow.c.minUnit.box.tax : 0;
  const completeModes = modeRows.filter((row) => row.full).length;

  const c1 = selectedCalc;

  const quickActions = [
    { key: 'tasks', icon: 'send', label: '发起测算任务', desc: '下发成本数据收集', roles: ['finance'] },
    { key: 'sync', icon: 'plug', label: '主数据同步', desc: '中台 / SAP / BOM / 价格库', roles: ['finance', 'supply'] },
    { key: 'formula', icon: 'sigma', label: '公式口径', desc: '9 条口径 · 来源标注', roles: ['finance'] },
    { key: 'declarations', icon: 'form', label: '我的填报任务', desc: (role === 'prod' ? '生产车间附表1填报' : '供应部价格确认') + ' · 待办 ' + declarationTodo.length, roles: ['prod', 'supply'] },
    { key: 'forecast', icon: 'calc', label: '成本核算结果', desc: '完整成本明细 · ' + comp.unready + ' 个暂定', roles: ['finance', 'prod', 'supply'] },
  ].filter((a) => a.roles.includes(role));

  return (
    <div className="scroll" data-screen-label="01 成本测算看板">
      {/* 头部 */}
      <div className="sec-title">
        <h2>成本测算看板</h2>
        <span className="sub">{product.name} {product.spec} · 2026 年 {period} · 数据截止 {window.NOW}</span>
        <div className="right">
          <label className="dashboard-filter dashboard-drug-filter">
            <span>测算药品</span>
            <select className="select" value={activeDrug} onChange={(e) => switchDrug(e.target.value)}>
              {drugs.map((drug) => <option key={drug.id} value={drug.id}>{drug.product.name} {drug.product.spec}</option>)}
            </select>
          </label>
          <div className="seg">
            {['Q3', 'Q4'].map((q) => <button key={q} className={period === q ? 'on' : ''} onClick={() => setPeriod(q)}>{q}</button>)}
          </div>
          <Btn icon="download" size="sm">导出测算表</Btn>
        </div>
      </div>

      <div className="drug-context mb12">
        <div className="drug-context-main">
          <span className="drug-context-label">当前测算药品</span>
          <strong>{product.name}</strong>
          {product.spec && <span>{product.spec}</span>}
        </div>
        <div className="drug-context-meta">
          <span>产品编码 <b className="num">{product.id || '—'}</b></span>
          <span>剂型 <b>{product.cat || '—'}</b></span>
          <span>包装 <b>{product.packSpec || '—'}</b></span>
          <span>车间 <b>{product.wsName || '—'}</b></span>
        </div>
        <Btn size="sm" kind="ghost" icon="layers" onClick={() => go('drugs')}>药品管理</Btn>
      </div>

      {/* 快捷入口 */}
      <div className="grid dashboard-actions">
        {quickActions.map((a) => (
          <button className="card" key={a.key} onClick={() => go(a.key)} style={{ padding: '12px 14px', display: 'flex', alignItems: 'center', gap: 11, textAlign: 'left' }}>
            <span style={{ width: 32, height: 32, borderRadius: 8, background: 'var(--primary-soft)', color: 'var(--primary)', display: 'grid', placeItems: 'center', flex: '0 0 auto' }}>
              <Ico name={a.icon} size={16} />
            </span>
            <span style={{ minWidth: 0 }}>
              <span style={{ display: 'block', fontSize: 12.5, fontWeight: 500 }}>{a.label}</span>
              <span className="tiny muted nowrap" style={{ display: 'block', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.desc}</span>
            </span>
          </button>
        ))}
      </div>

      <div className="dashboard-viewbar">
        <div className="dashboard-view-choice">
          <div className="seg" role="tablist" aria-label="看板视图">
            <button role="tab" aria-selected={view === 'detail'} className={view === 'detail' ? 'on' : ''} onClick={() => setView('detail')}>
              当前工况详情 <span className="n">{activeMode}</span>
            </button>
            <button role="tab" aria-selected={view === 'compare'} className={view === 'compare' ? 'on' : ''} onClick={() => setView('compare')}>
              工况对比 <span className="n">{modeRows.length}</span>
            </button>
          </div>
          <span className="tiny muted">{view === 'detail' ? '只显示当前选择工况的数据' : '按同一成本口径横向比较全部工况'}</span>
        </div>
        {view === 'detail' && (
          <label className="dashboard-filter dashboard-mode-filter">
            <span>当前工况</span>
            <select className="select" value={activeMode} onChange={(e) => setActiveMode(e.target.value)}>
              {window.MODES.map((mode) => <option key={mode.key} value={mode.key}>{mode.key} {mode.name}</option>)}
            </select>
          </label>
        )}
      </div>

      {view === 'detail' ? (
        <div className="grid g4 mb12 dashboard-detail-kpis">
          <Card>
            <div className="kpi clickable" onClick={() => go('forecast')}>
              <div className="k-label">{activeMode} 生产成本（含税） <Ico name="info" size={12} style={{ color: 'var(--ink-4)' }} /></div>
              <div className="k-value">{wan(selectedCalc.prodCost.tax)}<small>元</small></div>
              <div className="k-foot"><span className="muted">不含税 {wan(selectedCalc.prodCost.noTax)} 元</span></div>
            </div>
          </Card>
          <Card>
            <div className="kpi">
              <div className="k-label">单位成本（正式价）</div>
              <div className="k-value">{yuan(selectedCalc.minUnit.box.tax)}<small>元/盒</small></div>
              <div className="k-foot"><span className="muted">当前选择工况</span></div>
            </div>
          </Card>
          <Card>
            <div className="kpi">
              <div className="k-label">单位成本（临测价）</div>
              <div className="k-value">{yuan(selectedCalc.minUnit.box.tentTax)}<small>元/盒</small></div>
              <div className="k-foot"><span className="muted">与正式价相差 {tentativeGap >= 0 ? '+' : ''}{yuan(tentativeGap)} 元/盒</span></div>
            </div>
          </Card>
          <Card>
            <div className="kpi">
              <div className="k-label">当前工况数据完整度</div>
              <div className="k-value">{selectedOk}<small>/ {selectedItems.length} 项</small></div>
              <div style={{ marginTop: 9 }}><Bar value={selectedPct} kind={selectedPct === 100 ? 'ok' : 'warn'} /></div>
              <div className="k-foot"><span className="muted">{selectedMissing.length ? '还缺 ' + selectedMissing.length + ' 项数据' : '数据齐备，可正式核算'}</span></div>
            </div>
          </Card>
        </div>
      ) : (
        <div className="grid g4 mb12 dashboard-compare-kpis">
          <Card>
            <div className="kpi">
              <div className="k-label">对比工况</div>
              <div className="k-value">{modeRows.length}<small>个</small></div>
              <div className="k-foot"><span className="muted">同一药品、期间和成本口径</span></div>
            </div>
          </Card>
          <Card>
            <div className="kpi">
              <div className="k-label">最低单位成本（含税）</div>
              <div className="k-value">{yuan(lowestRow.c.minUnit.box.tax)}<small>元/盒</small></div>
              <div className="k-foot"><span className="muted">{lowestRow.m.key} {lowestRow.m.name}</span></div>
            </div>
          </Card>
          <Card>
            <div className="kpi">
              <div className="k-label">最高与最低差额</div>
              <div className="k-value">{yuan(modeSpread)}<small>元/盒</small></div>
              <div className="k-foot"><span className="muted">{highestRow.m.key} 对比 {lowestRow.m.key}</span></div>
            </div>
          </Card>
          <Card>
            <div className="kpi">
              <div className="k-label">可正式核算</div>
              <div className="k-value">{completeModes}<small>/ {modeRows.length} 个工况</small></div>
              <div style={{ marginTop: 9 }}><Bar value={modeRows.length ? completeModes / modeRows.length * 100 : 0} kind={completeModes === modeRows.length ? 'ok' : 'warn'} /></div>
              <div className="k-foot"><span className="muted">{comp.unready ? comp.unready + ' 个工况数据未齐' : '全部工况数据齐备'}</span></div>
            </div>
          </Card>
        </div>
      )}

      {view === 'detail' ? (
        <div className="grid g-hero mb12 dashboard-detail">
          <Card>
            <CardHead title="成本构成" sub={activeMode + ' 当前工况，含税'}
              right={<Btn size="sm" icon="calc" onClick={() => go('forecast')}>查看明细</Btn>} />
            <div className="card-body">
              <div className="row dashboard-cost-breakdown">
                <Donut size={132} thickness={16} centerLabel={wan(grand)} centerSub={activeMode + ' 含税'}
                  parts={[
                    { value: materialTotal, color: CAP_COLORS.material, label: '物料' },
                    { value: wsTotal, color: CAP_COLORS.labor, label: '车间' },
                    { value: axTotal, color: CAP_COLORS.overhead, label: '辅助' },
                  ]} />
                <div className="col" style={{ gap: 13, flex: 1 }}>
                  {[['物料成本', materialTotal, CAP_COLORS.material], ['车间成本（付现+折旧）', wsTotal, CAP_COLORS.labor], ['辅助成本（付现+折旧）', axTotal, CAP_COLORS.overhead]].map(([k, v, c]) => (
                    <div key={k} className="cost-part-row">
                      <div className="row" style={{ gap: 7 }}>
                        <i className="sw" style={{ width: 9, height: 9, borderRadius: 2.5, background: c }} />
                        <span style={{ fontSize: 12.5 }}>{k}</span>
                        <span className="spacer" />
                        <span className="num" style={{ fontSize: 12.5, fontWeight: 500 }}>{wan(v)}</span>
                        <span className="num tiny muted" style={{ width: 48, textAlign: 'right' }}>{(v / grand * 100).toFixed(1)}%</span>
                      </div>
                    </div>
                  ))}
                </div>
              </div>
            </div>
          </Card>

          <Card>
            <CardHead title={activeMode + ' 工况参数'} sub={selectedRow.m.kind} />
            <div className="card-body mode-facts">
              <div className="mode-fact"><span>生产条件</span><b>{selectedRow.m.desc}</b></div>
              <div className="mode-fact"><span>连续生产批次</span><b className="num">{selectedRow.m.batches} 批</b></div>
              <div className="mode-fact"><span>日生产时长</span><b className="num">{selectedRow.m.hpd} 小时</b></div>
              <div className="mode-fact"><span>半成品批理论产量</span><b className="num">{yuanInt(selectedRow.m.stages.semi.batchQty)} 片</b></div>
              <div className="mode-fact"><span>半成品成品率</span><b className="num">{pct(selectedRow.m.stages.semi.yield)}</b></div>
              <div className="mode-fact"><span>产成品批理论产量</span><b className="num">{yuanInt(selectedRow.m.stages.fin.batchQty)} 盒</b></div>
              <div className="mode-fact"><span>产成品成品率</span><b className="num">{pct(selectedRow.m.stages.fin.yield)}</b></div>
              <div className="mode-fact wide"><span>达成说明</span><b>{selectedRow.m.saving || '暂无补充说明'}</b></div>
            </div>
          </Card>
        </div>
      ) : (
        <Card className="mb12 dashboard-comparison-chart">
          <CardHead title="全部工况单位成本比较" sub="正式价与临测价（元/盒，含税）"
            right={<Btn size="sm" icon="calc" onClick={() => go('forecast')}>打开核算结果</Btn>} />
          <div className="card-body">
            <ColumnsCompare data={modeRows.map((r) => ({
              label: r.m.key, sub: r.m.kind,
              a: r.c.minUnit.box.tax, b: r.c.minUnit.box.tentTax,
              color: r.full ? 'var(--primary)' : 'var(--warn)',
            }))} height={230} fmt={(v) => yuan(v)} />
            <div className="legend">
              <span className="li"><i className="sw" style={{ background: 'var(--primary)' }} />正式价格口径</span>
              <span className="li"><i className="sw" style={{ background: 'var(--line-strong)' }} />临测价格口径</span>
              <span className="spacer" />
              {modeRows.some((r) => !r.full) && <span className="li" style={{ color: 'var(--warn)' }}><i className="sw" style={{ background: 'var(--warn)' }} />数据未齐</span>}
            </div>
          </div>
        </Card>
      )}

      {view === 'detail' && <div className="grid dashboard-lower">
        {/* 数据完整度明细 */}
        <Card>
          <CardHead title={activeMode + ' 数据完整度'} sub="当前工况"
            right={selectedMissing.length ? <Tag kind="warn">缺 {selectedMissing.length} 项</Tag> : <Tag kind="ok">数据齐备</Tag>} />
          <div className="card-body">
            <div className="mode-completeness-list">
              {selectedItems.map((item, i) => (
                <div className="mode-completeness-row" key={item.k}>
                  <span className={'mode-check' + (item.ok ? ' ok' : '')}>{item.ok ? <Ico name="check" size={10} /> : null}</span>
                  <span>{item.k}</span>
                  <span className="spacer" />
                  <span className="tiny muted">{item.src}</span>
                  <Tag kind={item.ok ? 'ok' : 'warn'}>{item.ok ? '已齐' : '缺失'}</Tag>
                </div>
              ))}
            </div>
            {selectedMissing.length > 0 && (
              <div className="notice warn mt12">
                <span className="n-ico"><Ico name="alert" size={14} /></span>
                <div>缺少 {selectedMissing.map((item) => item.k).join('、')}，当前结果按暂定值展示。</div>
              </div>
            )}
          </div>
        </Card>

        {/* 待办 */}
        <Card>
          <CardHead title={isFinance ? '当前药品待我处理' : '当前药品填报任务'} sub={'共 ' + myTodo + ' 项'}
            right={<Btn size="sm" onClick={() => go(isFinance ? 'approvals' : 'declarations')}>全部 →</Btn>} />
          <div className="card-body" style={{ paddingTop: 8, paddingBottom: 8 }}>
            {todo.length === 0 && <Empty icon="checkc" title="暂无待办" desc="所有需要你处理的事项都已完成。" />}
            {todo.map((t, i) => {
              if (t.kind === 'approve') return (
                <div key={i} className="row" style={{ gap: 11, padding: '11px 0', borderTop: i ? '1px solid var(--line)' : 'none' }}>
                  <span style={{ width: 28, height: 28, borderRadius: 7, background: 'var(--primary-soft)', color: 'var(--primary)', display: 'grid', placeItems: 'center', flex: '0 0 auto' }}><Ico name="check" size={15} /></span>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12.5, fontWeight: 500 }}>{t.decl.title}</div>
                    <div className="tiny muted">{t.decl.id} · 车间 + 供应部已填报，等待财务复核</div>
                  </div>
                  <Btn size="sm" kind="primary" onClick={() => go('approvals', { focus: t.decl.id })}>去审批</Btn>
                </div>
              );
              if (t.kind === 'diff') return (
                <div key={i} className="row" style={{ gap: 11, padding: '11px 0', borderTop: i ? '1px solid var(--line)' : 'none' }}>
                  <span style={{ width: 28, height: 28, borderRadius: 7, background: 'var(--warn-soft)', color: 'var(--warn)', display: 'grid', placeItems: 'center', flex: '0 0 auto' }}><Ico name="alert" size={15} /></span>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12.5, fontWeight: 500 }}>{t.diff.mid} · {t.diff.field} 存在差异</div>
                    <div className="tiny muted num">本地 {yuan(t.diff.local)} → 外部 {yuan(t.diff.remote)} {t.diff.unit}</div>
                  </div>
                  <Btn size="sm" onClick={() => go('sync')}>去确认</Btn>
                </div>
              );
              if (t.kind === 'fill') return (
                <div key={i} className="row" style={{ gap: 11, padding: '11px 0', borderTop: i ? '1px solid var(--line)' : 'none' }}>
                  <span style={{ width: 28, height: 28, borderRadius: 7, background: 'var(--primary-soft)', color: 'var(--primary)', display: 'grid', placeItems: 'center', flex: '0 0 auto' }}><Ico name="form" size={15} /></span>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12.5, fontWeight: 500 }}>{t.decl.title}</div>
                    <div className="tiny muted">{t.decl.id} · 截止 {t.decl.due} · {t.decl.modes.join(' / ')}</div>
                  </div>
                  <Btn size="sm" kind="primary" onClick={() => go('declarations', { open: t.decl.id })}>去填报</Btn>
                </div>
              );
              return (
                <div key={i} className="row" style={{ gap: 11, padding: '11px 0', borderTop: i ? '1px solid var(--line)' : 'none' }}>
                  <span style={{ width: 28, height: 28, borderRadius: 7, background: 'var(--danger-soft)', color: 'var(--danger)', display: 'grid', placeItems: 'center', flex: '0 0 auto' }}><Ico name="alert" size={15} /></span>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12.5, fontWeight: 500 }}>{t.text}</div>
                    <div className="tiny muted">建议：向对应部门下发补充填报任务</div>
                  </div>
                  <Btn size="sm" onClick={() => go('tasks')}>去处理</Btn>
                </div>
              );
            })}
          </div>
        </Card>
      </div>}

      {view === 'compare' && (
        <Card className="dashboard-comparison-table">
          <CardHead title="工况对比明细" sub="全部工况采用同一药品、期间和成本口径" />
          <div className="tbl-wrap">
            <table className="tbl">
              <thead>
                <tr>
                  <th style={{ width: 110 }}>工况</th>
                  <th>生产条件</th>
                  <th className="r" style={{ width: 130 }}>正式价（元/盒）</th>
                  <th className="r" style={{ width: 130 }}>临测价（元/盒）</th>
                  <th className="r" style={{ width: 100 }}>价差</th>
                  <th className="r" style={{ width: 90 }}>完整度</th>
                  <th style={{ width: 100 }}>核算状态</th>
                  <th className="r" style={{ width: 100 }}>操作</th>
                </tr>
              </thead>
              <tbody>
                {modeRows.map((row) => {
                  const items = COMPLETENESS[row.m.key] || [];
                  const ready = items.filter((item) => item.ok).length;
                  const gap = row.c.minUnit.box.tentTax - row.c.minUnit.box.tax;
                  return (
                    <tr key={row.m.key}>
                      <td><div className="primary-cell num">{row.m.key} {row.m.name}</div><div className="sub-cell">{row.m.kind}</div></td>
                      <td><div>{row.m.desc}</div><div className="sub-cell">{row.m.batches} 批，{row.m.hpd} 小时/天</div></td>
                      <td className="r num">{yuan(row.c.minUnit.box.tax)}</td>
                      <td className="r num">{yuan(row.c.minUnit.box.tentTax)}</td>
                      <td className="r num" style={{ color: gap > 0 ? 'var(--warn)' : 'var(--ok)' }}>{gap >= 0 ? '+' : ''}{yuan(gap)}</td>
                      <td className="r num">{ready}/{items.length}</td>
                      <td>{row.full ? <Tag kind="ok">可正式核算</Tag> : <Tag kind="warn">暂定</Tag>}</td>
                      <td className="r"><Btn size="sm" onClick={() => { setActiveMode(row.m.key); setView('detail'); }}>查看详情</Btn></td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </Card>
      )}

      {view === 'detail' && (
        <Card className="mt12 dashboard-formulas">
          <CardHead title={activeMode + ' 公式口径'} sub="当前工况实时试算"
            right={<span className="tiny muted">9 条口径，2 条锁定</span>} />
          <div className="card-body">
            {window.FORMULAS.map((f, i) => (
              <div className="row formula-row" key={f.id}>
                <span className="tag" style={{ background: f.locked ? 'var(--surface-3)' : 'var(--ok-soft)', color: f.locked ? 'var(--ink-3)' : 'var(--ok)', height: 20, fontSize: 10.5 }}>{f.locked ? '锁定' : '可调'}</span>
                <span className="formula-name">{f.name}</span>
                <span className="mono tiny muted clamp2 formula-expression">{f.expr}</span>
                <span className="num formula-value">{formulaValue(f.id, c1)}</span>
              </div>
            ))}
            <div className="notice flat mt12">
              <span className="n-ico"><Ico name="info" size={14} /></span>
              <div>右侧数值仅对应当前药品的 <b>{activeMode}</b> 工况。公式口径为财务规则，改动需审批留痕。</div>
            </div>
          </div>
        </Card>
      )}
    </div>
  );
}

/* ================================================================
   2. 主数据同步
   ================================================================ */
const SRC_ICONS = { MDM: 'layers', SAP: 'chip', BOM: 'box', PRICE: 'plug' };

function SyncPage() {
  const app = useApp();
  const { sync, syncNow, resolveDiff, toast, role } = app;
  const [tab, setTab] = useState('diff');
  const [busy, setBusy] = useState(null);

  const openDiffs = sync.diffs.filter((d) => d.status === 'open' || d.status === 'stale');
  const canSync = role === 'finance' || role === 'supply';

  const doSync = (src) => {
    setBusy(src);
    setTimeout(() => {
      syncNow(src);
      setBusy(null);
      toast(src + ' 同步完成' + (src === 'PRICE' ? '，发现 2 项差异待确认' : '，无字段变化'), src === 'PRICE' ? 'warn' : 'ok');
    }, 900);
  };

  const resultTag = (r) => r === 'ok' ? <Tag kind="ok" dot>成功</Tag> : r === 'drift' ? <Tag kind="warn" dot>有差异</Tag> : <Tag kind="danger" dot>失败</Tag>;

  return (
    <div className="scroll" data-screen-label="02 主数据同步">
      <div className="sec-title">
        <h2>主数据同步</h2>
        <span className="sub">中台 / SAP / BOM / 物料价格库 → 核算主数据 · 差异需人工确认后才可用于核算</span>
        <div className="right">
          <Btn size="sm" icon="settings">连接配置</Btn>
          {canSync && <Btn size="sm" kind="primary" icon="refresh" onClick={() => doSync('PRICE')}>全部同步</Btn>}
        </div>
      </div>

      {/* 数据源 */}
      <div className="grid g2 mb12">
        {sync.sources.map((s) => (
          <Card key={s.id}>
            <div className="card-body">
              <div className="row" style={{ gap: 12 }}>
                <span style={{ width: 38, height: 38, borderRadius: 10, background: s.status === 'ok' ? 'var(--primary-soft)' : 'var(--warn-soft)', color: s.status === 'ok' ? 'var(--primary)' : 'var(--warn)', display: 'grid', placeItems: 'center', flex: '0 0 auto' }}>
                  <Ico name={SRC_ICONS[s.id] || 'layers'} size={18} />
                </span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div className="row" style={{ gap: 8 }}>
                    <span style={{ fontSize: 13.5, fontWeight: 600 }}>{s.name}</span>
                    {s.status === 'ok' ? <Tag kind="ok" dot>正常</Tag> : <Tag kind="warn" dot>有差异</Tag>}
                  </div>
                  <div className="tiny muted" style={{ marginTop: 3 }}>{s.kind} · {s.mode}</div>
                </div>
                {canSync && <Btn size="sm" icon="refresh" onClick={() => doSync(s.id)} disabled={busy === s.id}>{busy === s.id ? '同步中…' : '立即同步'}</Btn>}
              </div>
              <div className="divider" />
              <div className="grid" style={{ gridTemplateColumns: 'repeat(4,1fr)', gap: 10 }}>
                {[['最近同步', s.last.slice(5)], ['同步对象', s.objects + ' 个'], ['字段数', s.fields + ' 项'], ['耗时', s.dur]].map(([k, v]) => (
                  <div key={k}>
                    <div className="tiny muted">{k}</div>
                    <div className="num" style={{ fontSize: 13, fontWeight: 500, marginTop: 3 }}>{v}</div>
                  </div>
                ))}
              </div>
              <div style={{ marginTop: 12 }}>
                <div className="row tiny muted mb8" style={{ justifyContent: 'space-between' }}>
                  <span>字段同步健康度</span><span className="num">{s.health}%</span>
                </div>
                <Bar value={s.health} kind={s.health >= 95 ? 'ok' : 'warn'} />
              </div>
            </div>
          </Card>
        ))}
      </div>

      {openDiffs.length > 0 && (
        <div className="notice warn mb12">
          <span className="n-ico"><Ico name="alert" size={15} /></span>
          <div>
            有 <b>{openDiffs.length} 项</b>差异尚未确认。为避免用错数据，涉及差异的物料单价在核算中仍使用<b>本地值</b>，
            并在成本核算结果页标记为「存在未确认差异」。<b>采纳</b>远端值后相关工况将立即参与重算。
          </div>
        </div>
      )}

      <div className="grid mb12">
        <Card>
          <div className="card-head plain" style={{ paddingBottom: 0 }}>
            <div className="seg">
              <button className={tab === 'diff' ? 'on' : ''} onClick={() => setTab('diff')}>差异对比 <span className="n">{openDiffs.length}</span></button>
              <button className={tab === 'run' ? 'on' : ''} onClick={() => setTab('run')}>同步历史 <span className="n">{sync.runs.length}</span></button>
              <button className={tab === 'map' ? 'on' : ''} onClick={() => setTab('map')}>字段映射 <span className="n">{sync.mapping.filter((m) => m.on).length}/{sync.mapping.length}</span></button>
            </div>
            <div className="right">
              <Chip icon="filter">仅看待确认</Chip>
              <Btn size="sm" icon="download">导出</Btn>
            </div>
          </div>
          <div style={{ paddingTop: 12 }}>
            {tab === 'diff' && <DiffTable diffs={sync.diffs} onResolve={(id, choice) => {
              resolveDiff(id, choice);
              toast(choice === 'accept' ? '已采纳外部值，相关工况将重新计算' : '已保留本地值，差异标记为待复核', choice === 'accept' ? 'ok' : 'warn');
            }} />}

            {tab === 'run' && (
              <div className="tbl-wrap">
                <table className="tbl">
                  <thead>
                    <tr>
                      <th style={{ width: 130 }}>批次</th>
                      <th style={{ width: 150 }}>开始时间</th>
                      <th style={{ width: 120 }}>数据源</th>
                      <th style={{ width: 110 }}>结果</th>
                      <th className="r" style={{ width: 64 }}>新增</th>
                      <th className="r" style={{ width: 64 }}>更新</th>
                      <th className="r" style={{ width: 74 }}>无变化</th>
                      <th style={{ width: 130 }}>触发人</th>
                      <th>说明</th>
                    </tr>
                  </thead>
                  <tbody>
                    {sync.runs.map((r) => (
                      <tr key={r.id}>
                        <td className="num" style={{ fontSize: 12.5 }}>{r.id}</td>
                        <td className="num tiny">{r.t}</td>
                        <td><span className="row" style={{ gap: 6 }}><SrcDot src={r.src === 'BOM' ? 'bom' : r.src === 'PRICE' ? 'price' : r.src === 'SAP' ? 'sap' : 'mdm'} />{r.src}</span></td>
                        <td>{resultTag(r.result)}</td>
                        <td className="r num">{r.add || '—'}</td>
                        <td className="r num" style={r.upd ? { fontWeight: 600, color: 'var(--warn)' } : {}}>{r.upd || '—'}</td>
                        <td className="r num muted">{r.skip}</td>
                        <td className="tiny">{r.by}</td>
                        <td className="tiny muted">{r.err || (r.upd ? '价格发生变动，已生成差异待确认' : '无字段变化')}</td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            )}

            {tab === 'map' && (
              <div>
                <div className="notice flat" style={{ margin: '0 14px 12px', borderRadius: 8 }}>
                  <span className="n-ico"><Ico name="link" size={14} /></span>
                  <div>本地字段 ← 外部字段的映射关系。关闭映射后该字段不再随同步更新，保留人工维护值（如财务报工单价）。</div>
                </div>
                <div className="tbl-wrap">
                  <table className="tbl">
                    <thead>
                      <tr>
                        <th style={{ width: 170 }}>本地字段</th>
                        <th>外部字段（来源系统）</th>
                        <th style={{ width: 110 }}>数据类型</th>
                        <th style={{ width: 90 }} className="c">主键</th>
                        <th style={{ width: 90 }} className="c">参与同步</th>
                      </tr>
                    </thead>
                    <tbody>
                      {sync.mapping.map((m) => (
                        <tr key={m.local}>
                          <td className="primary-cell">{m.local}</td>
                          <td className="mono tiny" style={{ color: 'var(--ink-2)' }}>{m.remote}</td>
                          <td><span className="tag" style={{ height: 19, fontSize: 10.5 }}>{m.type}</span></td>
                          <td className="c">{m.key ? <Ico name="lock" size={13} style={{ margin: '0 auto', color: 'var(--ink-3)' }} /> : <span className="muted tiny">—</span>}</td>
                          <td className="c">
                            <div style={{ display: 'grid', placeItems: 'center' }}>
                              <Switch on={m.on} onChange={() => toast(m.local + ' 的同步映射已' + (m.on ? '关闭' : '开启'), 'ok')} />
                            </div>
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              </div>
            )}
          </div>
        </Card>
      </div>
    </div>
  );
}

/* ================================================================
   3. 物料与价格
   ================================================================ */
function MaterialsPage() {
  const app = useApp();
  const { toast, role, sync, bomLines, updateBomLine } = app;
  const [q, setQ] = useState('');
  const [cat, setCat] = useState('全部');
  const [stageF, setStageF] = useState('全部');
  const [open, setOpen] = useState(null);

  const cats = ['全部', '原料', '辅料', '包材'];
  const rows = bomLines.filter((m) => {
    if (cat !== '全部' && m.cat !== cat) return false;
    if (stageF !== '全部' && m.stage !== stageF) return false;
    if (q && !(m.id + m.name + m.spec + m.supplier).toLowerCase().includes(q.toLowerCase())) return false;
    return true;
  });

  const syncTag = (m) => {
    const d = sync.diffs.find((x) => x.mid === m.id && x.status !== 'resolved');
    if (!d) return <Tag kind="ok" dot>已同步</Tag>;
    if (d.status === 'stale') return <Tag kind="danger" dot>价格超期</Tag>;
    return <Tag kind="warn" dot>有差异</Tag>;
  };

  const tentCount = bomLines.filter((b) => b.tentativePriceTax != null && Number(b.tentativePriceTax) !== Number(b.priceTax)).length;
  const openDiffCount = sync.diffs.filter((d) => d.status !== 'resolved').length;

  return (
    <div className="scroll tight" data-screen-label="03 物料与价格">
      <div style={{ padding: '18px 18px 0' }}>
        <div className="sec-title">
          <h2>物料与价格</h2>
          <span className="sub">BOM 单耗（万片/万盒） + 物料价格库（含税 / 临测双轨）· 财务可直接改单耗与单价，改完立即重算</span>
          <div className="right">
            <Btn size="sm" icon="download">导出</Btn>
            {role === 'finance' && <Btn size="sm" icon="plus">新增物料</Btn>}
          </div>
        </div>

        <div className="grid g4 mb12">
          <Card><div className="kpi" style={{ padding: '13px 15px' }}>
            <div className="k-label">物料总数</div>
            <div className="k-value" style={{ fontSize: 24 }}>{bomLines.length}<small>项</small></div>
            <div className="k-foot"><span className="muted">原料 1 · 辅料 4 · 包材 4</span></div>
          </div></Card>
          <Card><div className="kpi" style={{ padding: '13px 15px' }}>
            <div className="k-label">价格待复核</div>
            <div className="k-value" style={{ fontSize: 24, color: openDiffCount ? 'var(--warn)' : 'var(--ink)' }}>{openDiffCount}<small>项</small></div>
            <div className="k-foot"><span className="muted">来自物料价格库同步差异</span></div>
          </div></Card>
          <Card><div className="kpi" style={{ padding: '13px 15px' }}>
            <div className="k-label">临测价双轨</div>
            <div className="k-value" style={{ fontSize: 24 }}>{tentCount}<small>项</small></div>
            <div className="k-foot"><span className="muted">临测价 ≠ 正式价的物料</span></div>
          </div></Card>
          <Card><div className="kpi" style={{ padding: '13px 15px' }}>
            <div className="k-label">供应部已确认</div>
            <div className="k-value" style={{ fontSize: 24, color: 'var(--ok)' }}>{bomLines.filter((b) => b.confirmed).length}<small>项</small></div>
            <div className="k-foot"><span className="muted">最近确认 2026-09-28 · 供应部</span></div>
          </div></Card>
        </div>
      </div>

      <div style={{ padding: '0 18px 18px' }}>
        <Card>
          <div className="tbl-toolbar">
            <div style={{ position: 'relative', width: 230 }}>
              <Ico name="search" size={14} style={{ position: 'absolute', left: 9, top: 9, color: 'var(--ink-4)' }} />
              <input className="input" style={{ paddingLeft: 28 }} placeholder="搜索编码 / 名称 / 供应商" value={q} onChange={(e) => setQ(e.target.value)} />
            </div>
            <div className="divider v" />
            {cats.map((c) => <Chip key={c} on={cat === c} onClick={() => setCat(c)}>{c}</Chip>)}
            <div className="spacer" />
            <div className="seg">
              {[['全部', '全部阶段'], ['semi', '半成品（片）'], ['fin', '产成品（盒）']].map(([k, l]) => <button key={k} className={stageF === k ? 'on' : ''} onClick={() => setStageF(k)}>{l}</button>)}
            </div>
            <Btn size="sm" icon="sliders">列设置</Btn>
          </div>

          <div className="tbl-wrap">
            <table className="tbl">
              <thead>
                <tr>
                  <th style={{ width: 104 }}>物料编码</th>
                  <th>物料名称 / 规格</th>
                  <th style={{ width: 76 }}>分类</th>
                  <th style={{ width: 104 }}>投料阶段</th>
                  <th className="r" style={{ width: 104 }}>单耗</th>
                  <th style={{ width: 118 }}>供应商</th>
                  <th style={{ width: 100 }} className="r">含税单价</th>
                  <th style={{ width: 100 }} className="r">临测含税价</th>
                  <th style={{ width: 64 }} className="r">税率</th>
                  <th style={{ width: 92 }}>确认状态</th>
                  <th style={{ width: 100 }}>同步状态</th>
                  <th style={{ width: 62 }} className="r">操作</th>
                </tr>
              </thead>
              <tbody>
                {rows.map((m) => {
                  const hasTent = m.tentativePriceTax != null && Number(m.tentativePriceTax) !== Number(m.priceTax);
                  return (
                    <tr key={m.id} className="clickable" onClick={() => setOpen(m.id)}>
                      <td className="num" style={{ fontWeight: 500, fontSize: 12.5 }}>{m.id}</td>
                      <td>
                        <div className="primary-cell">{m.name}</div>
                        <div className="sub-cell mono">{m.spec}</div>
                      </td>
                      <td><span className="tag" style={{ height: 19, fontSize: 10.5 }}>{m.cat}</span></td>
                      <td><span className="tag" style={{ height: 19, fontSize: 10.5, background: m.stage === 'semi' ? 'var(--primary-soft)' : 'var(--surface-3)', color: m.stage === 'semi' ? 'var(--primary)' : 'var(--ink-3)' }}>{m.stage === 'semi' ? '半成品（片）' : '产成品（盒）'}</span></td>
                      <td className="r" onClick={(e) => e.stopPropagation()}>
                        {role === 'finance'
                          ? <RateInput value={m.qty} onCommit={(n) => updateBomLine(m.id, { qty: n })} />
                          : <span className="num tiny">{m.qty}</span>}
                        <div className="tiny muted">/万{m.stage === 'semi' ? '片' : '盒'}（{m.unit}）</div>
                      </td>
                      <td className="tiny">{m.supplier}</td>
                      <td className="r" onClick={(e) => e.stopPropagation()}>
                        {role === 'finance'
                          ? <RateInput value={m.priceTax} onCommit={(n) => updateBomLine(m.id, { priceTax: n })} />
                          : <span className="num" style={{ fontWeight: 500 }}>{yuan(m.priceTax)}</span>}
                      </td>
                      <td className="r" onClick={(e) => e.stopPropagation()} style={{ color: hasTent ? 'var(--warn-deep, #8a5a00)' : 'var(--ink-3)' }}>
                        {role === 'finance'
                          ? <RateInput value={m.tentativePriceTax} onCommit={(n) => updateBomLine(m.id, { tentativePriceTax: n })} />
                          : <span className="num">{m.tentativePriceTax != null ? yuan(m.tentativePriceTax) : '—'}</span>}
                      </td>
                      <td className="r" onClick={(e) => e.stopPropagation()}>
                        {role === 'finance'
                          ? <RateInput value={Math.round(m.taxRate * 1000) / 10} step="1" onCommit={(n) => updateBomLine(m.id, { taxRate: n / 100 })} />
                          : <span className="num tiny">{Math.round(m.taxRate * 100)}%</span>}
                      </td>
                      <td>{m.confirmed ? <Tag kind="ok" dot>已确认</Tag> : <Tag kind="warn" dot>待确认</Tag>}</td>
                      <td>{syncTag(m)}</td>
                      <td className="r">
                        <button className="btn sm ghost" onClick={(e) => { e.stopPropagation(); setOpen(m.id); }}>详情</button>
                      </td>
                    </tr>
                  );
                })}
                {rows.length === 0 && <tr><td colSpan={12}><Empty icon="search" title="没有匹配的物料" desc="调整筛选条件或清空搜索关键字。" /></td></tr>}
              </tbody>
            </table>
          </div>
          <div className="tbl-foot">
            <span>共 {rows.length} 项</span>
            <span className="muted">·</span>
            <span className="muted">含税单价取自物料价格库，临测价由供应部临测人员维护（双轨口径）</span>
            <div className="pager">
              <button><Ico name="left" size={14} /></button>
              <button className="on">1</button>
              <button><Ico name="right" size={14} /></button>
            </div>
          </div>
        </Card>
      </div>

      {open && <MaterialDrawer mid={open} onClose={() => setOpen(null)} />}
    </div>
  );
}

function MaterialDrawer({ mid, onClose }) {
  const app = useApp();
  const m = window.bomLineById(mid);
  const [tab, setTab] = useState('info');
  const diff = app.sync.diffs.find((d) => d.mid === mid && d.status !== 'resolved');
  const priceHistory = [
    { t: '2026-06', v: m.priceTax * 0.96 }, { t: '2026-07', v: m.priceTax * 0.99 },
    { t: '2026-08', v: m.priceTax * 1.02 }, { t: '2026-09', v: m.priceTax },
  ];
  const usages = window.MODES.map((mm) => {
    const c = window.computeMode(mm);
    const line = c.material.tax.lines.find((l) => l.id === mid);
    return { m: mm, line };
  });
  const relatedAudit = app.audit.filter((a) => (a.refTitle || '').indexOf(mid) >= 0 || (a.changes || []).some((c) => (c.k || '').indexOf(mid) >= 0));

  return (
    <Drawer wide title={m.name}
      meta={<Fragment>
        <span className="num">{m.id}</span>
        <span className="muted">·</span><span>{m.spec}</span>
        <span className="muted">·</span><span className="srcline"><SrcDot src="price" />物料价格库同步</span>
      </Fragment>}
      onClose={onClose}
      footer={<Fragment>
        <Btn size="sm" icon="history">变更记录</Btn>
        <div className="spacer" />
        <Btn size="sm" onClick={onClose}>关闭</Btn>
        {app.role === 'finance' && <Btn size="sm" kind="primary" icon="refresh">重新拉取</Btn>}
      </Fragment>}>

      {diff && (
        <div className={'notice ' + (diff.status === 'stale' ? 'danger' : 'warn')} style={{ marginBottom: 14 }}>
          <span className="n-ico"><Ico name="alert" size={15} /></span>
          <div>
            {diff.status === 'stale'
              ? <Fragment>价格已 <b>{diff.staleDays} 天</b>未更新，超过 15 天阈值。当前核算使用该值，建议向供应部发起复核。</Fragment>
              : <Fragment>价格库最新值为 <b className="num">{yuan(diff.remote)} {diff.unit}</b>，本地为 <b className="num">{yuan(diff.local)}</b>（{diff.from}）。差异未确认前核算仍使用本地值。</Fragment>}
          </div>
        </div>
      )}

      <div className="seg mb12">
        {[['info', '基本信息'], ['price', '价格与税率'], ['usage', '工况投料'], ['audit', '变更留痕']].map(([k, l]) => (
          <button key={k} className={tab === k ? 'on' : ''} onClick={() => setTab(k)}>{l}</button>
        ))}
      </div>

      {tab === 'info' && (
        <Card><div className="card-body">
          <dl className="kv" style={{ gridTemplateColumns: '126px 1fr' }}>
            <dt>物料编码</dt><dd className="num">{m.id}</dd>
            <dt>物料名称</dt><dd>{m.name}</dd>
            <dt>规格型号</dt><dd className="mono" style={{ fontSize: 12 }}>{m.spec}</dd>
            <dt>计量单位</dt><dd>{m.unit}</dd>
            <dt>物料分类</dt><dd>{m.cat}</dd>
            <dt>投料阶段</dt><dd>{m.stage === 'semi' ? '半成品（按 万片 投料）' : '产成品（按 万盒 投料）'}</dd>
            <dt>单耗</dt><dd className="num">{m.qty} {m.unit}/万{m.stage === 'semi' ? '片' : '盒'}</dd>
            <dt>默认供应商</dt><dd>{m.supplier}</dd>
            <dt>临测供应商</dt><dd>{m.tentativeSupplier}</dd>
            <dt>数据来源</dt><dd><span className="srcline"><SrcDot src="price" />物料价格库（每日 02:00 同步）· 单耗 <SrcDot src="bom" />BOM 系统</span></dd>
            <dt>价格确认</dt><dd>{m.confirmed ? m.confirmBy + ' · ' + m.confirmAt : '待确认'}</dd>
          </dl>
        </div></Card>
      )}

      {tab === 'price' && (
        <div className="col" style={{ gap: 12 }}>
          <Card>
            <CardHead title="采购单价趋势" sub="近 4 个月物料价格库含税价" />
            <div className="card-body">
              <Lines height={150} fmt={(v) => Math.round(v * 100) / 100}
                labels={priceHistory.map((h) => h.t.replace('2026-', ''))}
                series={[{ data: priceHistory.map((h) => h.v), color: 'var(--primary)' }]} />
            </div>
          </Card>
          <Card>
            <CardHead title="当前采购参数" />
            <div className="card-body">
              <div className="grid g3">
                {[['含税采购单价', yuan(m.priceTax) + ' 元/' + m.unit], ['不含税单价', yuan(window.notaxPrice(m.priceTax, m.taxRate)) + ' 元/' + m.unit], ['发票税率', Math.round(m.taxRate * 100) + '%'],
                  ['临测含税价', m.tentativePriceTax != null ? yuan(m.tentativePriceTax) + ' 元/' + m.unit : '—'], ['价格日期', m.priceAt], ['确认时间', m.confirmAt || '—']].map(([k, v]) => (
                  <div key={k}><div className="tiny muted">{k}</div><div className="num" style={{ fontSize: 16, fontWeight: 600, marginTop: 4 }}>{v}</div></div>
                ))}
              </div>
              <div className="notice flat mt12">
                <span className="n-ico"><Ico name="info" size={14} /></span>
                <div>单价由物料价格库同步维护。若需临时调整（如锁价合同），由供应部在测算单中提交并附凭据，财务审批后生效；临测价用于临测价格双轨测算。</div>
              </div>
            </div>
          </Card>
        </div>
      )}

      {tab === 'usage' && (
        <Card>
          <CardHead title="工况投料量与成本" sub={'6 工况 × 投料（含税）· 单耗 ' + m.qty + ' ' + m.unit + '/万' + (m.stage === 'semi' ? '片' : '盒')} />
          <div className="tbl-wrap">
            <table className="tbl">
              <thead><tr><th>工况</th><th style={{ width: 150 }} className="r">投料量</th><th style={{ width: 140 }} className="r">行成本（含税）</th><th style={{ width: 120 }} className="r">单价</th></tr></thead>
              <tbody>
                {usages.map(({ m2, line }) => (
                  <tr key={m2.key}>
                    <td>
                      <div className="primary-cell">{m2.key} {m2.name}</div>
                      <div className="sub-cell">{m2.desc} · {m2.kind}</div>
                    </td>
                    <td className="r num">{Math.round(line.qty * 1000) / 1000} {m.unit}</td>
                    <td className="r num" style={{ fontWeight: 500 }}>{yuan(line.costTax)}</td>
                    <td className="r num tiny muted">{yuan(line.priceTax)} 元/{m.unit}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </Card>
      )}

      {tab === 'audit' && (
        <Card><div className="card-body">
          {relatedAudit.length === 0
            ? <Empty icon="history" title="暂无该物料的变更记录" desc="价格同步、申报复核等变动会在此自动留痕。" />
            : <Timeline events={relatedAudit.map((a) => ({ t: a.t, act: a.act, who: a.who, dept: a.dept, ok: a.ok, note: a.note, changes: a.changes }))} />}
        </div></Card>
      )}
    </Drawer>
  );
}

Object.assign(window, { Dashboard, SyncPage, MaterialsPage, MaterialDrawer, yuan, yuanInt, wan, pct, CAP_COLORS });

/* ================================================================
   4. 药品管理：切换 / 新增 / 导入车间数据
   导入按新都《附表1-向车间收集基础数据》的列名识别（CSV）。
   ================================================================ */

/* 解析附表1：按列名定位，把同一药品的半成品、产成品行配成工况。
   入参是二维数组（首行起，含表头），CSV 和 Excel 都先转成这个格式再进来。 */
function parseAnnex1(rows) {
  if (!rows || !rows.length) return { error: '文件是空的' };
  const norm = (v) => String(v == null ? '' : v).replace(/\s/g, '');
  let headerIdx = -1;
  for (let i = 0; i < rows.length; i++) {
    const line = rows[i].map(norm).join(',');
    if (line.indexOf('产品名称') >= 0 && line.indexOf('工艺阶段') >= 0) { headerIdx = i; break; }
  }
  if (headerIdx < 0) return { error: '没有找到附表1的表头（需要含「产品名称」「工艺阶段」列）。' };
  const head = rows[headerIdx].map(norm);
  const col = (name) => head.indexOf(norm(name));
  const need = ['产品名称', '工艺阶段', '批理论产量', '成品率'];
  for (let i = 0; i < need.length; i++) { if (col(need[i]) < 0) return { error: '缺少列：' + need[i] }; }
  const num = (row, name) => { const i = col(name); if (i < 0) return null; const n = Number(row[i]); return row[i] === '' || row[i] == null || isNaN(n) ? null : n; };
  const cell = (row, name) => { const i = col(name); return i < 0 ? '' : String(row[i] == null ? '' : row[i]).trim(); };

  const groups = {};
  const order = [];
  for (let r = headerIdx + 1; r < rows.length; r++) {
    const row = rows[r];
    const name = cell(row, '产品名称');
    if (!name) continue;
    if (!groups[name]) { groups[name] = []; order.push(name); }
    groups[name].push(row);
  }

  const drugs = order.map((name) => {
    const rows = groups[name];
    const first = rows[0];
    const product = {
      id: cell(first, '产品编码') || name, base: cell(first, '基地代码'), name: name, spec: cell(first, '产品规格'),
      cat: cell(first, '类型'), packSpec: cell(first, '装量规格'), packQty: num(first, '件装量') || 0,
      unit: cell(first, '计量单位') || '盒', minUnit: '片', minUnitFactor: num(first, '最小单位折算系数') || 1,
      make: cell(first, '制造分类') || '自制', wsCode: cell(first, '生产车间'), wsName: cell(first, '生产车间描述'), wsDesc: cell(first, '生产车间描述'),
    };
    const modes = [];
    let cur = null;
    rows.forEach((row) => {
      const sk = cell(row, '工艺阶段').indexOf('产成品') >= 0 ? 'fin' : 'semi';
      if (!cur || cur.stages[sk].batchQty) {
        cur = window.blankMode('M' + (modes.length + 1), '模式' + (modes.length + 1));
        cur.batches = num(row, '日产出批次数') || 1;
        modes.push(cur);
      }
      const st = cur.stages[sk];
      st.batchQty = num(row, '批理论产量') || 0;
      st.sample = num(row, '标准单批抽留样');
      st.sampleTimes = num(row, '标准抽样次数') || 1;
      const yld = num(row, '成品率');
      st.yield = yld == null ? 1 : yld;
      st.hours = num(row, '生产时长') || 0;
      st.dailyLabor = num(row, '日总人工(小时）') || num(row, '日总人工') || 0;
      st.water = num(row, '单批次水 （立方米）') || num(row, '单批次水') || 0;
      st.elec = num(row, '单批次电 （千瓦时）') || num(row, '单批次电') || 0;
      st.steam = num(row, '单批次汽（吨）') || num(row, '单批次汽') || 0;
    });
    return { product: product, modes: modes, bom: [] };
  });
  return { drugs: drugs };
}

function DrugsPage() {
  const app = useApp();
  const { drugs, activeDrug, switchDrug, addDrug, importDrug, toast, role } = app;
  const [open, setOpen] = useState(false);
  const [form, setForm] = useState({ id: '', name: '', spec: '', cat: '片剂', minUnitFactor: 10, wsName: '' });
  const [importMsg, setImportMsg] = useState(null);
  const canEdit = role === 'finance';
  const set = (k, v) => setForm(Object.assign({}, form, { [k]: v }));

  const onFile = (e) => {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    const isExcel = /\.xlsx?$/i.test(file.name);
    if (isExcel && !window.XLSX) { toast('Excel 解析库未加载，请检查网络后刷新，或先把附表1另存为 CSV 再导入', 'warn'); e.target.value = ''; return; }
    const reader = new FileReader();
    reader.onload = () => {
      let rows;
      if (isExcel) {
        const wb = window.XLSX.read(reader.result, { type: 'array' });
        let picked = null;
        wb.SheetNames.forEach((sn) => {
          if (picked) return;
          const grid = window.XLSX.utils.sheet_to_json(wb.Sheets[sn], { header: 1, raw: false, defval: '' });
          const hit = grid.some((r) => r.map((c) => String(c).replace(/\s/g, '')).join(',').indexOf('产品名称') >= 0);
          if (hit) picked = grid;
        });
        rows = picked || [];
      } else {
        rows = String(reader.result).replace(/\r/g, '').split('\n').filter((l) => l.trim()).map((l) => l.split(',').map((c) => c.trim()));
      }
      const res = parseAnnex1(rows);
      if (res.error) { setImportMsg(res.error); toast(res.error, 'warn'); return; }
      res.drugs.forEach((d) => importDrug(d));
      const last = res.drugs[res.drugs.length - 1].product.name;
      setImportMsg('已导入 ' + res.drugs.length + ' 个药品，并切换到「' + last + '」。物料 BOM 需在「物料与价格」页另行维护。');
      toast('导入完成', 'ok');
    };
    if (isExcel) reader.readAsArrayBuffer(file); else reader.readAsText(file, 'utf-8');
    e.target.value = '';
  };

  const submit = () => {
    if (!form.name) { toast('请填写产品名称', 'warn'); return; }
    addDrug(form);
    setOpen(false);
    toast('已新增药品并切换为当前测算对象', 'ok');
    setForm({ id: '', name: '', spec: '', cat: '片剂', minUnitFactor: 10, wsName: '' });
  };

  return (
    <div className="scroll" data-screen-label="药品管理">
      <div className="sec-title">
        <h2>药品管理</h2>
        <span className="sub">切换当前测算药品 · 新增药品 · 导入车间填报数据（附表1）</span>
        <div className="right">
          {canEdit && <Btn size="sm" icon="plus" kind="primary" onClick={() => setOpen(true)}>新增药品</Btn>}
        </div>
      </div>

      <Card className="mb12">
        <CardHead title="导入车间数据" sub="按新都《附表1-向车间收集基础数据》的列名识别" />
        <div className="card-body">
          <div className="row" style={{ gap: 12, alignItems: 'center' }}>
            <input type="file" accept=".xlsx,.xls,.csv,text/csv" onChange={onFile} disabled={!canEdit} />
            <span className="tiny muted">直接选附表1的 Excel，或另存为 CSV 再选</span>
          </div>
          {importMsg && <div className="notice flat mt12"><span className="n-ico"><Ico name="info" size={14} /></span><div className="tiny">{importMsg}</div></div>}
          <div className="tiny muted mt12">识别的列：产品编码、产品名称、产品规格、类型、装量规格、工艺阶段、批理论产量、日产出批次数、标准单批抽留样、标准抽样次数、成品率、生产时长、日总人工、单批次水、单批次电、单批次汽。同一药品的半成品、产成品行会自动配成一个工况。</div>
        </div>
      </Card>

      <Card>
        <CardHead title="药品清单" sub={'共 ' + drugs.length + ' 个 · 当前测算对象高亮'} />
        <div className="tbl-wrap">
          <table className="tbl">
            <thead>
              <tr>
                <th style={{ width: 130 }}>产品编码</th>
                <th>产品名称 / 规格</th>
                <th style={{ width: 90 }}>类型</th>
                <th style={{ width: 120 }}>生产车间</th>
                <th className="r" style={{ width: 80 }}>工况数</th>
                <th className="r" style={{ width: 80 }}>物料数</th>
                <th style={{ width: 110 }}>状态</th>
                <th style={{ width: 90 }} className="r">操作</th>
              </tr>
            </thead>
            <tbody>
              {drugs.map((d) => {
                const on = d.id === activeDrug;
                return (
                  <tr key={d.id} style={on ? { background: 'var(--primary-soft)' } : null}>
                    <td className="num" style={{ fontWeight: 500 }}>{d.product.id}</td>
                    <td>
                      <div className="primary-cell">{d.product.name}</div>
                      <div className="sub-cell">{d.product.spec} · {d.product.packSpec}</div>
                    </td>
                    <td className="tiny">{d.product.cat}</td>
                    <td className="tiny">{d.product.wsName}</td>
                    <td className="r num">{d.modes.length}</td>
                    <td className="r num">{d.bom.length}</td>
                    <td>{on ? <Tag kind="primary" dot>当前测算</Tag> : <Tag>未启用</Tag>}</td>
                    <td className="r">{on ? null : <Btn size="sm" onClick={() => { switchDrug(d.id); toast('已切换到「' + d.product.name + '」', 'ok'); }}>切换</Btn>}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </Card>

      {open && (
        <Modal title="新增药品" onClose={() => setOpen(false)}
          footer={<Fragment><Btn size="sm" onClick={() => setOpen(false)}>取消</Btn><Btn size="sm" kind="primary" onClick={submit}>新增并切换</Btn></Fragment>}>
          <div className="grid g2" style={{ gap: 12 }}>
            {[['name', '产品名称'], ['id', '产品编码（可空，自动生成）'], ['spec', '产品规格'], ['cat', '类型'], ['packSpec', '装量规格'], ['wsName', '生产车间']].map(([k, label]) => (
              <div key={k}>
                <div className="tiny muted" style={{ marginBottom: 6 }}>{label}</div>
                <input className="input" value={form[k] || ''} onChange={(e) => set(k, e.target.value)} />
              </div>
            ))}
            <div>
              <div className="tiny muted" style={{ marginBottom: 6 }}>最小单位折算系数（片 → 盒）</div>
              <input className="input num" type="number" value={form.minUnitFactor} onChange={(e) => set('minUnitFactor', e.target.value)} />
            </div>
          </div>
          <div className="notice flat mt12">
            <span className="n-ico"><Ico name="info" size={14} /></span>
            <div className="tiny">新增后会生成一个空白工况（模式1），车间产量、成品率、水电气等请在「成本测算单」里填写，物料在「物料与价格」里维护。也可以直接用上方的导入，从附表1一次性带入车间数据。</div>
          </div>
        </Modal>
      )}
    </div>
  );
}

Object.assign(window, { Dashboard, SyncPage, MaterialsPage, MaterialDrawer, DrugsPage, parseAnnex1, yuan, yuanInt, wan, pct, CAP_COLORS });
