function ScreenQuotations({ openQuotation, setRoute }) { const { BRL } = window.SCP; const [filter, setFilter] = useState('todas'); const [q, setQ] = useState(''); const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); const [loadErr, setLoadErr] = useState(null); useEffect(() => { let mounted = true; (async () => { try { const data = await window.scpDb.quotations.list(); if (mounted) setRows(data); } catch (e) { if (mounted) setLoadErr(e.message || 'Falha ao carregar cotações'); } finally { if (mounted) setLoading(false); } })(); return () => { mounted = false; }; }, []); const counts = useMemo(() => { const c = { todas: rows.length, aberta: 0, analise: 0, fechada: 0, cancelada: 0 }; rows.forEach(x => { if (c[x.status] !== undefined) c[x.status]++; }); return c; }, [rows]); const filtered = rows.filter(x => (filter === 'todas' || x.status === filter) && (!q || ((x.title || '') + ' ' + (x.code || '')).toLowerCase().includes(q.toLowerCase())) ); const kpis = useMemo(() => { const emAndamento = counts.aberta + counts.analise; const fechadas = counts.fechada; const totalInvited = rows.reduce((s, r) => s + (r.suppliersCount || 0), 0); const totalResponded = rows.reduce((s, r) => s + (r.respondedCount || 0), 0); const taxa = totalInvited ? Math.round(100 * totalResponded / totalInvited) : 0; const fechadasComTempo = rows.filter(r => r.status === 'fechada' && r.closed_at && r.created_at); const avgDias = fechadasComTempo.length ? fechadasComTempo.reduce((s, r) => s + (new Date(r.closed_at) - new Date(r.created_at)) / 86400000, 0) / fechadasComTempo.length : 0; return { emAndamento, fechadas, taxa, avgDias }; }, [rows, counts]); return (
{/* KPI row */}
} delta="ao vivo" deltaTone="emerald"/> }/> }/> }/>
{loadErr && ( Falha ao carregar cotações:{' '} {loadErr} )}
{[['todas','Todas'],['aberta','Abertas'],['analise','Em Análise'],['fechada','Fechadas'],['cancelada','Canceladas']].map(([k, l]) => ( ))}
} placeholder="Buscar por número ou título…" value={q} onChange={e => setQ(e.target.value)} /> } title="Filtros"/> } title="Exportar"/>
{loading && ( )} {!loading && filtered.length === 0 && ( )} {!loading && filtered.map(x => { const pct = x.suppliersCount ? Math.round(100 * x.respondedCount / x.suppliersCount) : 0; const status = QSTATUS[x.status] || { tone: 'fg', label: x.status }; const deadline = x.deadline ? new Date(x.deadline).toLocaleDateString('pt-BR') : '—'; const created = x.created_at ? new Date(x.created_at).toLocaleDateString('pt-BR') : '—'; return ( openQuotation(x.id)}> ); })}
Cotação Status Progresso Itens Prazo Valor final Economia
Carregando cotações…
{rows.length === 0 ? 'Nenhuma cotação criada ainda.' : 'Nenhuma cotação com este filtro.'}
e.stopPropagation()}>
{x.title}
{x.code} · criada {created}
{status.label}
{x.respondedCount}/{x.suppliersCount}
{x.itemsCount} {deadline} {x.total_value ? BRL(x.total_value) : '—'} {x.saving_value ? '↓ ' + BRL(x.saving_value) : '—'} e.stopPropagation()}> } size="sm" />
{!loading && (
{filtered.length} cotaç{filtered.length === 1 ? 'ão' : 'ões'}
)}
); } (function() { if (document.getElementById('scp-quotelist-css')) return; const s = document.createElement('style'); s.id = 'scp-quotelist-css'; s.textContent = ` .scp-list-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 18px 22px; border-bottom: 1px solid var(--border-soft); flex-wrap: wrap; } .scp-list-head .scp-input { width: 280px; } .scp-list-foot { display: flex; justify-content: space-between; align-items: center; padding: 14px 22px; border-top: 1px solid var(--border-soft); font-size: 12px; color: var(--fg-3); } .scp-tab-count { font-size: 10px; padding: 1px 6px; border-radius: 999px; background: var(--surface-3); color: var(--fg-3); font-weight: 600; margin-left: 4px; } .scp-tabs button.active .scp-tab-count { background: oklch(0.30 0.06 160 / .5); color: oklch(0.92 0.14 160); } .scp-cb { width: 16px; height: 16px; accent-color: oklch(0.74 0.16 160); cursor: pointer; } .scp-progress { width: 100px; height: 6px; background: var(--surface-2); border-radius: 999px; overflow: hidden; } .scp-progress > span { display: block; height: 100%; background: linear-gradient(90deg, oklch(0.78 0.16 160), oklch(0.65 0.14 230)); border-radius: 999px; } `; document.head.appendChild(s); })(); window.ScreenQuotations = ScreenQuotations;