// === Showcase: AI Analysis of an active quotation (Supabase) ===
function ScreenQuotationDetail({ setRoute, quoteId }) {
const { BRL } = window.SCP;
const [loading, setLoading] = useState(true);
const [loadErr, setLoadErr] = useState(null);
const [Q, setQ] = useState(null);
useEffect(() => {
if (!quoteId) return;
let mounted = true;
setLoading(true);
(async () => {
try {
const raw = await window.scpDb.quotations.getByIdOrCode(quoteId);
if (!mounted) return;
// ----- Transform DB shape → UI shape esperado pelo componente -----
const items = (raw.items || [])
.sort((a, b) => (a.position || 0) - (b.position || 0))
.map(it => ({
id: it.id,
product_id: it.product_id,
name: it.name_snapshot || it.product?.name,
unit: it.unit_snapshot || it.product?.unit,
qty: Number(it.qty),
lastPrice: Number(it.last_price) || 0,
}));
const suppliers = (raw.invited || []).map(i => i.supplier_id);
const bidsMap = {};
(raw.invited || []).forEach(inv => {
bidsMap[inv.supplier_id] = {
_status: inv.status,
_at: inv.responded_at ? new Date(inv.responded_at).toLocaleString('pt-BR', { dateStyle: 'short', timeStyle: 'short' }) : null,
};
});
(raw.bids || []).forEach(b => {
if (!bidsMap[b.supplier_id]) bidsMap[b.supplier_id] = {};
bidsMap[b.supplier_id][b.item_id] = {
price: b.price != null ? Number(b.price) : null,
alt: b.alt_brand,
skip: !!b.skip,
};
});
const supObj = Object.fromEntries(
(raw.invited || []).map(i => [i.supplier_id, {
id: i.supplier_id,
name: i.supplier?.name || '—',
cnpj: i.supplier?.cnpj,
city: i.supplier?.city,
score: i.supplier?.score,
avgDelivery: i.supplier?.avg_delivery,
}])
);
setQ({
id: raw.id,
code: raw.code,
title: raw.title,
status: raw.status,
createdAt: raw.created_at ? new Date(raw.created_at).toLocaleString('pt-BR', { dateStyle: 'short', timeStyle: 'short' }) : '—',
deadline: raw.deadline ? new Date(raw.deadline).toLocaleString('pt-BR', { dateStyle: 'short', timeStyle: 'short' }) : '—',
buyer: raw.created_by_name || window.SCP.COMPANY.owner,
obs: raw.obs,
items,
suppliers,
bids: bidsMap,
_supObj: supObj,
});
} catch (e) {
if (mounted) setLoadErr(e.message || 'Falha ao carregar cotação');
} finally {
if (mounted) setLoading(false);
}
})();
return () => { mounted = false; };
}, [quoteId]);
const responded = useMemo(
() => (Q ? Q.suppliers.filter(s => Q.bids[s]?._status === 'respondida') : []),
[Q]
);
const supObj = Q?._supObj || {};
// For each item, find best price and AI-recommended supplier (heuristic over bids)
const analysis = useMemo(() => {
if (!Q) return [];
return Q.items.map(item => {
const offers = responded
.map(sid => ({ sid, bid: Q.bids[sid]?.[item.id] }))
.filter(o => o.bid && o.bid.price != null && !o.bid.skip);
if (offers.length === 0) return { item, best: null, second: null, allOffers: offers, savings: 0 };
offers.sort((a, b) => a.bid.price - b.bid.price);
const best = offers[0];
const second = offers[1] ?? null;
const savings = (item.lastPrice - best.bid.price) * item.qty;
return { item, best, second, allOffers: offers, savings };
});
}, [Q, responded]);
const totals = useMemo(() => {
let total = 0, lastTotal = 0, savings = 0;
analysis.forEach(a => {
if (a.best) {
total += a.best.bid.price * a.item.qty;
lastTotal += a.item.lastPrice * a.item.qty;
savings += (a.item.lastPrice - a.best.bid.price) * a.item.qty;
}
});
return { total, lastTotal, savings, pct: lastTotal ? savings / lastTotal * 100 : 0 };
}, [analysis]);
// Group suggested by supplier (purchase orders preview)
const bySupplier = useMemo(() => {
const map = {};
analysis.forEach(a => {
if (!a.best) return;
(map[a.best.sid] ||= []).push(a);
});
return map;
}, [analysis]);
const [iaState, setIaState] = useState('idle'); // idle | running | done | error
const [iaResult, setIaResult] = useState(null); // { recommendation, model, tokens_used, duration_ms }
const [iaError, setIaError] = useState(null);
// Carrega análise existente OU dispara nova
useEffect(() => {
if (!Q) return;
let mounted = true;
setIaState('idle');
setIaResult(null);
setIaError(null);
(async () => {
try {
// Tenta pegar análise cacheada primeiro
const cached = await window.scpDb.ai.getLatestAnalysis(Q.id);
if (cached && mounted) {
setIaResult({
recommendation: cached.recommendation,
model: cached.model,
tokens_used: cached.tokens_used,
duration_ms: cached.duration_ms,
created_at: cached.created_at,
});
setIaState('done');
return;
}
// Se não, chama Edge Function
if (mounted) setIaState('running');
const res = await window.scpDb.ai.analyzeQuotation(Q.id);
if (!mounted) return;
setIaResult(res);
setIaState('done');
} catch (e) {
if (mounted) {
setIaError(e.message || String(e));
setIaState('error');
}
}
})();
return () => { mounted = false; };
}, [Q?.id]);
const reanalyze = async () => {
if (!Q) return;
setIaState('running');
setIaError(null);
try {
const res = await window.scpDb.ai.analyzeQuotation(Q.id);
setIaResult(res);
setIaState('done');
window.scpToast('Nova análise concluída', { kind: 'emerald', sub: `${res.tokens_used} tokens · ${res.duration_ms}ms` });
} catch (e) {
setIaError(e.message || String(e));
setIaState('error');
window.scpToast('Erro na análise', { kind: 'coral', sub: e.message });
}
};
const [overrides, setOverrides] = useState({});
const setPick = (itemId, sid) => setOverrides(o => ({ ...o, [itemId]: sid }));
// Pick padrão = IA real se disponível, senão melhor preço (fallback heurístico)
const aiPicks = iaResult?.recommendation?.per_item || {};
const pickFor = (itemId, defaultSid) => overrides[itemId] || aiPicks[itemId] || defaultSid;
const matrixRef = useRef(null);
const [approving, setApproving] = useState(false);
const pendingSuppliers = Q ? Q.suppliers.filter(sid => Q.bids[sid]?._status !== 'respondida') : [];
// === Early returns DEPOIS de todos os hooks (regra do React) ===
if (loading) {
return
Carregando cotação…
;
}
if (loadErr || !Q) {
return
Falha ao carregar cotação.
{loadErr || 'Cotação não encontrada.'}
setRoute('quotations')}>Voltar para cotações
;
}
const handleRemind = () => {
if (pendingSuppliers.length === 0) {
window.scpToast('Todos já responderam', { kind: 'emerald', sub: 'Nenhum lembrete necessário' });
return;
}
const names = pendingSuppliers.map(sid => supObj[sid]?.name).filter(Boolean).join(', ');
window.scpToast(`Lembrete enviado · ${pendingSuppliers.length} fornecedor${pendingSuppliers.length === 1 ? '' : 'es'}`, { kind: 'sky', sub: names });
};
const handleExport = () => {
window.scpToast('Exportação iniciada', { kind: 'sky', sub: `Cotação ${Q.code} · PDF com matriz comparativa` });
};
const handleApprove = async () => {
if (Q.status === 'fechada') {
window.scpToast('Cotação já está fechada', { kind: 'amber' });
return;
}
setApproving(true);
try {
// 1) Cria pedidos por fornecedor (1 pedido para cada grupo de itens vencidos)
const orders = await window.scpDb.orders.createFromQuotation(Q, bySupplier);
// 2) Marca a cotação como fechada + salva total/economia
await window.scpDb.quotations.updateStatus(Q.id, 'fechada');
window.scpToast(`Cotação fechada · ${orders.length} pedidos emitidos`, {
kind: 'emerald',
sub: `${Q.code} · economia de ${BRL(totals.savings)}`,
});
setTimeout(() => setRoute && setRoute('orders'), 700);
} catch (e) {
window.scpToast('Erro ao fechar cotação', { kind: 'coral', sub: e.message });
} finally {
setApproving(false);
}
};
const handleManual = () => {
window.scpToast('Modo manual ativado', { kind: 'sky', sub: 'Clique nas células da matriz para escolher fornecedor por item' });
matrixRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
};
const handleDelete = async () => {
const msg = `Excluir a cotação ${Q.code} (${Q.title})?\n\nIsso vai apagar todos os itens, lances e convites de fornecedores.\nPedidos já emitidos a partir desta cotação continuarão preservados.\n\nAção não pode ser desfeita.`;
if (!confirm(msg)) return;
try {
await window.scpDb.quotations.remove(Q.id);
window.scpToast('Cotação excluída', { kind: 'amber', sub: Q.code });
setRoute('quotations');
} catch (e) {
window.scpToast('Erro ao excluir', { kind: 'coral', sub: e.message });
}
};
return (
{/* Header bar */}
} onClick={() => setRoute('quotations')}>Voltar
{QSTATUS[Q.status]?.label || 'Em Análise'}
{Q.code}
·
Criada {Q.createdAt} · por {Q.buyer}
{Q.title}
{Q.obs}
Itens {Q.items.length}
Respostas {responded.length}/{Q.suppliers.length}
Prazo {Q.deadline}
} onClick={handleDelete} style={{ color: 'oklch(0.82 0.14 25)' }}>Excluir
} onClick={handleRemind}>Cobrar fornecedores
} onClick={handleExport}>Exportar
{/* IA recommendation banner */}
Análise IA · OpenAI ({iaResult?.model || 'gpt-4o'})
{iaState === 'idle' && 'preparando…'}
{iaState === 'running' && `processando ${Q.suppliers.length} fornecedores · ${Q.items.length} itens…`}
{iaState === 'done' && iaResult?.duration_ms && `concluída em ${(iaResult.duration_ms / 1000).toFixed(2)}s · ${iaResult.tokens_used} tokens`}
{iaState === 'done' && !iaResult?.duration_ms && 'análise carregada do cache'}
{iaState === 'error' && 'falha na análise'}
{iaState === 'error' ? (
Não consegui completar a análise. {iaError || 'Erro desconhecido.'}
Verifique se a Edge Function analyze-quotation foi deployada e se o secret OPENAI_API_KEY está configurado.
Tentar novamente
) : iaState !== 'done' ? (
Cruzando lances
Avaliando histórico
Otimizando combinações
Validando prazos
) : (
{iaResult?.recommendation?.reasoning || 'Recomendação gerada com base em preço, confiabilidade e prazo.'}
Valor estimado {BRL(iaResult?.recommendation?.total_value ?? totals.total)}
Economia vs. último ciclo ↓ {BRL(iaResult?.recommendation?.savings_value ?? totals.savings)} ({(iaResult?.recommendation?.savings_pct ?? totals.pct).toFixed(1)}%)
Fornecedores selecionados {Object.keys(iaResult?.recommendation?.supplier_orders || bySupplier).length}
Confiança {iaResult?.recommendation?.confidence ?? 96}%
)}
} disabled={iaState !== 'done' || approving || Q.status === 'fechada'} onClick={handleApprove}>
{approving ? 'Emitindo…' : Q.status === 'fechada' ? 'Cotação fechada' : 'Aprovar e emitir pedidos'}
Ajustar manualmente
{iaState === 'done' && (
}>Re-analisar
)}
{/* Comparative matrix */}
Melhor preço
Sugestão IA
Não trabalha
Sem resposta
Produto
Qtd
Últ. preço
{Q.suppliers.map(sid => {
const s = supObj[sid];
const status = Q.bids[sid]?._status;
return (
{status === 'respondida'
? <>respondida {Q.bids[sid]._at}>
: <>aguardando>}
);
})}
Sugestão
{analysis.map(a => {
const aiPick = a.best?.sid;
const userPick = pickFor(a.item.id, aiPick);
return (
{a.item.name}
{a.item.id} · {a.item.unit}
{a.item.qty}
{BRL(a.item.lastPrice)}
{Q.suppliers.map(sid => {
const bid = Q.bids[sid]?.[a.item.id];
const status = Q.bids[sid]?._status;
const isBest = aiPick === sid;
const isPicked = userPick === sid;
let cell;
if (status === 'pendente') cell = — ;
else if (!bid || bid.skip) cell = não trabalha ;
else cell = (
setPick(a.item.id, sid)}
className={cls('cell price', isBest && 'best', isPicked && !isBest && 'picked')}>
{BRL(bid.price)}
{bid.alt && {bid.alt} }
{isPicked && {isBest ? <> IA> : 'manual'} }
);
return {cell} ;
})}
{a.best ? (
{supObj[userPick].name.split(' ')[0]}
↓ {BRL(a.savings)}
) : aguardando }
);
})}
Total sugerido
{Q.items.reduce((s, i) => s + i.qty, 0)}
{BRL(totals.lastTotal)}
{Q.suppliers.map(sid => {
const sup = supObj[sid];
const status = Q.bids[sid]?._status;
if (status !== 'respondida') return ;
let total = 0;
let count = 0;
Q.items.forEach(it => {
const b = Q.bids[sid][it.id];
if (b && b.price != null && !b.skip) { total += b.price * it.qty; count++; }
});
return (
{BRL(total)}
{count} itens
);
})}
{BRL(totals.total)}
{/* Purchase order preview cards */}
{Object.keys(bySupplier).length} fornecedores · {analysis.filter(a => a.best).length} itens} />
{Object.entries(bySupplier).map(([sid, items]) => {
const sup = supObj[sid];
const total = items.reduce((s, a) => s + a.best.bid.price * a.item.qty, 0);
return (
{sup.name}
{sup.cnpj} · {sup.city}
{items.length} itens
{items.map(a => (
{a.item.name}
{a.item.qty} {a.item.unit}
{BRL(a.best.bid.price * a.item.qty)}
))}
Score do fornecedor
{sup.score}/100
{BRL(total)}
);
})}
);
}
(function() {
if (document.getElementById('scp-qdetail-css')) return;
const s = document.createElement('style'); s.id = 'scp-qdetail-css';
s.textContent = `
.scp-q-head { padding: 22px 24px; display: grid; grid-template-columns: 1.4fr auto; gap: 22px; }
.scp-q-head-titles { margin-top: 6px; }
.scp-q-head-tag { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 12px 0 6px; }
.scp-q-head-tag .scp-q-id { font-size: 12px; color: var(--fg-3); }
.scp-q-head-tag .sep { color: var(--fg-4); }
.scp-q-head-tag .meta { font-size: 12px; color: var(--fg-3); }
.scp-q-head h2 { margin: 4px 0 6px; font-size: 24px; font-weight: 600; letter-spacing: -0.02em; }
.scp-q-head p { margin: 0; color: var(--fg-3); font-size: 13px; line-height: 1.5; }
.scp-q-head-right { display: flex; flex-direction: column; align-items: flex-end; gap: 14px; }
.scp-q-meta { display: flex; gap: 24px; }
.scp-q-meta > div { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.scp-q-meta span { font-size: 11px; color: var(--fg-3); text-transform: uppercase; letter-spacing: .08em; font-weight: 600; }
.scp-q-meta strong { font-size: 18px; font-weight: 600; font-feature-settings: "tnum"; }
.scp-q-meta strong small { font-size: 13px; color: var(--fg-3); font-weight: 500; margin-left: 4px; }
.scp-q-actions { display: flex; gap: 8px; }
/* IA banner */
.scp-ia-banner {
display: grid; grid-template-columns: 56px 1fr auto; gap: 18px;
padding: 18px 22px; align-items: center;
background:
radial-gradient(80% 100% at 0% 0%, oklch(0.30 0.10 160 / .25), transparent 60%),
linear-gradient(180deg, oklch(0.21 0.025 250), oklch(0.19 0.020 250));
border-color: oklch(0.34 0.06 160 / .4);
}
.ia-orb { width: 56px; height: 56px; filter: drop-shadow(0 8px 20px oklch(0.5 0.14 160 / .5)); }
.ia-body { min-width: 0; }
.ia-tag { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: oklch(0.92 0.13 160); font-weight: 600; letter-spacing: .01em; }
.ia-status { color: var(--fg-3); font-weight: 500; font-family: 'JetBrains Mono', monospace; font-size: 11px; }
.ia-status.running::before { content: "●"; color: oklch(0.78 0.16 160); margin-right: 4px; animation: blink 1s infinite; }
@keyframes blink { 50% { opacity: 0.3; } }
.ia-skel { margin-top: 10px; }
.ia-skel .bar { height: 5px; background: oklch(0.28 0.01 250); border-radius: 999px; overflow: hidden; }
.ia-skel .bar > span {
display: block; height: 100%; width: 30%;
background: linear-gradient(90deg, oklch(0.50 0.12 160), oklch(0.78 0.16 160));
animation: slide 1.6s ease-in-out infinite; border-radius: 999px;
}
@keyframes slide { 0% { transform: translateX(-100%); width: 30%; } 50% { width: 60%; } 100% { transform: translateX(280%); width: 30%; } }
.ia-steps { display: flex; gap: 16px; margin-top: 8px; font-size: 11.5px; color: var(--fg-4); }
.ia-steps .on { color: oklch(0.92 0.13 160); }
.ia-steps .on::before { content: "✓ "; color: oklch(0.78 0.16 160); }
.ia-result p { margin: 6px 0 12px; color: var(--fg-2); font-size: 13px; line-height: 1.5; max-width: 580px; }
.ia-result p strong { color: var(--fg); font-weight: 600; }
.ia-numbers { display: grid; grid-template-columns: repeat(4, auto); gap: 24px; }
.ia-numbers > div { display: flex; flex-direction: column; gap: 2px; }
.ia-numbers span { font-size: 11px; color: var(--fg-3); text-transform: uppercase; letter-spacing: .08em; font-weight: 600; }
.ia-numbers strong { font-size: 15px; font-weight: 600; }
.ia-numbers strong.em { color: oklch(0.92 0.13 160); }
.ia-cta { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
/* Matrix */
.scp-matrix-head { padding: 18px 22px; border-bottom: 1px solid var(--border-soft); display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.scp-matrix-legend { display: flex; gap: 14px; font-size: 11.5px; color: var(--fg-3); }
.scp-matrix-legend > span { display: inline-flex; align-items: center; gap: 6px; }
.scp-matrix-legend .dot { width: 8px; height: 8px; border-radius: 3px; }
.scp-matrix-legend .dot.tone-emerald { background: oklch(0.78 0.16 160); }
.scp-matrix-legend .dot.tone-violet { background: oklch(0.74 0.14 285); }
.scp-matrix-legend .dot.tone-muted { background: oklch(0.40 0.012 250); }
.scp-matrix-legend .dot.tone-amber { background: oklch(0.84 0.14 78); }
.scp-matrix-wrap { overflow-x: auto; }
.scp-matrix { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
.scp-matrix thead th {
padding: 12px 14px; background: var(--surface);
font-size: 11px; color: var(--fg-3); text-transform: uppercase; letter-spacing: .08em; font-weight: 600;
text-align: left; border-bottom: 1px solid var(--border-soft); white-space: nowrap;
}
.scp-matrix th.sticky-l, .scp-matrix td.sticky-l {
position: sticky; left: 0; background: var(--surface); z-index: 1;
box-shadow: 1px 0 0 var(--border-soft);
min-width: 240px; max-width: 280px;
}
.scp-matrix tbody td.sticky-l { background: var(--surface); }
.scp-matrix tbody tr:hover td.sticky-l { background: var(--surface-2); }
.scp-matrix tbody tr:hover td { background: var(--surface-2); }
.scp-matrix .supplier-head { min-width: 130px; padding: 10px 14px; vertical-align: bottom; }
.scp-matrix .supplier-head.pending { color: oklch(0.78 0.10 78); }
.scp-matrix .sh-top { display: flex; align-items: center; gap: 7px; text-transform: none; letter-spacing: 0; color: var(--fg); font-size: 12px; font-weight: 600; }
.scp-matrix .sh-top .n { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 110px; }
.scp-matrix .sh-bot { display: flex; align-items: center; gap: 4px; font-size: 10.5px; color: var(--fg-3); text-transform: none; letter-spacing: 0; font-weight: 500; margin-top: 4px; }
.scp-matrix .supplier-head.pending .sh-bot { color: oklch(0.78 0.12 78); }
.scp-matrix tbody td { padding: 10px 14px; border-bottom: 1px solid var(--border-soft); vertical-align: middle; }
.scp-matrix tbody td.cell-td { padding: 6px 8px; }
.scp-matrix tbody td.cell-td.best { background: oklch(0.24 0.06 160 / .18) !important; }
.scp-matrix tbody tr:hover td.cell-td.best { background: oklch(0.28 0.07 160 / .22) !important; }
.scp-matrix .cell {
display: inline-flex; flex-direction: column; align-items: flex-start; gap: 2px;
padding: 6px 10px; border-radius: 8px; min-width: 100px; text-align: left;
transition: background .12s, border-color .12s;
border: 1px solid transparent;
}
.scp-matrix .cell.price { cursor: pointer; }
.scp-matrix .cell .v { font-family: 'JetBrains Mono', monospace; font-weight: 600; color: var(--fg); font-size: 13px; }
.scp-matrix .cell.best { background: oklch(0.32 0.08 160 / .6); border-color: oklch(0.50 0.10 160); }
.scp-matrix .cell.best .v { color: oklch(0.96 0.16 160); }
.scp-matrix .cell.picked { background: oklch(0.30 0.08 285 / .55); border-color: oklch(0.48 0.10 285); }
.scp-matrix .cell.picked .v { color: oklch(0.94 0.13 285); }
.scp-matrix .cell .alt { font-size: 10.5px; color: var(--fg-3); }
.scp-matrix .cell .marker { font-size: 9px; padding: 1px 6px; border-radius: 999px; display: inline-flex; align-items: center; gap: 3px; font-weight: 700; letter-spacing: .04em; margin-top: 3px; }
.scp-matrix .cell .marker.ia { background: oklch(0.45 0.12 160); color: white; }
.scp-matrix .cell .marker.user { background: oklch(0.45 0.12 285); color: white; }
.scp-matrix .cell.skip { color: var(--fg-4); font-size: 11px; padding: 6px 4px; font-style: italic; }
.scp-matrix .cell.pending { color: oklch(0.78 0.10 78); }
.scp-matrix .suggestion { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 120px; }
.scp-matrix .suggestion .save { font-size: 11px; color: oklch(0.86 0.14 160); font-weight: 600; }
.scp-matrix tfoot td {
padding: 14px 14px; background: oklch(0.18 0.012 250);
border-top: 1px solid var(--border-soft);
font-size: 13px; font-weight: 600;
}
.scp-matrix tfoot td.em { color: var(--fg); }
/* Order preview cards */
.scp-q-orders-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; margin-top: 8px; }
.scp-q-order { padding: 16px 18px; }
.scp-q-order .oh { display: flex; align-items: center; gap: 10px; padding-bottom: 12px; border-bottom: 1px solid var(--border-soft); }
.scp-q-order .oh .info { flex: 1; min-width: 0; }
.scp-q-order .oh .info .n { display: block; font-weight: 600; color: var(--fg); font-size: 14px; }
.scp-q-order .oh .info .m { display: block; font-size: 11.5px; color: var(--fg-3); margin-top: 2px; }
.scp-q-order .oitems { display: flex; flex-direction: column; padding: 10px 0; }
.scp-q-order .oi { display: grid; grid-template-columns: 1fr auto auto; gap: 12px; padding: 7px 0; font-size: 12.5px; align-items: center; }
.scp-q-order .oi .n { color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.scp-q-order .oi .q { color: var(--fg-3); font-size: 11px; }
.scp-q-order .oi .v { color: var(--fg-2); font-weight: 600; font-family: 'JetBrains Mono', monospace; }
.scp-q-order .of { display: grid; grid-template-columns: 1fr auto auto; gap: 12px; padding-top: 12px; border-top: 1px solid var(--border-soft); align-items: center; font-size: 12px; color: var(--fg-3); }
.scp-q-order .of .num { font-family: 'JetBrains Mono', monospace; color: var(--fg-2); display: inline-flex; align-items: center; gap: 4px; }
.scp-q-order .of .num svg { color: oklch(0.84 0.14 78); }
.scp-q-order .of .total { font-size: 16px; font-weight: 700; color: oklch(0.92 0.14 160); }
`;
document.head.appendChild(s);
})();
window.ScreenQuotationDetail = ScreenQuotationDetail;