// Caririaçu Guincho — Vistoria Digital Screen const { useState: useStateV, useEffect: useEffectV } = React; const CHECKLIST_ITEMS = [ 'CD/Mp3','Auto-Falantes','Calotas','Faróis','Alarme','Estepe', 'Macaco','Extintor','Documentos','Tapetes','Chave de Roda','Manual' ]; const FOTOS_CATS = ['Frente','Lateral Esq.','Lateral Dir.','Traseira','Painel','Motor','Outros']; const VehicleView = ({ label, marks, onMark }) => { const [hov, setHov] = useStateV(false); return (
{label}
{ const rect = e.currentTarget.getBoundingClientRect(); const x = Math.round(((e.clientX - rect.left) / rect.width) * 100); const y = Math.round(((e.clientY - rect.top) / rect.height) * 100); onMark(x, y, label); }} onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)} style={{ position: 'relative', cursor: 'crosshair', border: `2px solid ${hov ? '#3B82F6' : '#E2E8F0'}`, borderRadius: 10, background: '#F8FAFC', width: 140, height: 90, overflow: 'hidden', transition: 'border 0.15s' }}> {label === 'Frente' && <> } {label === 'Traseira' && <> } {(label === 'Lat. Esq.' || label === 'Lat. Dir.') && <> } {marks.map((m, i) => (
{i+1}
))} {hov &&
}
); }; const VistoriaScreen = () => { const [vistorias, setVistorias] = useStateV([]); const [loading, setLoading] = useStateV(true); const [tab, setTab] = useStateV(0); const [showForm, setShowForm] = useStateV(false); const [motoristas, setMotoristas] = useStateV([]); // Form state const [tipoVistoria, setTipoVistoria] = useStateV('Entrada'); const [tipoVeiculo, setTipoVeiculo] = useStateV('Carro'); const [placa, setPlaca] = useStateV(''); const [marca, setMarca] = useStateV(''); const [modelo, setModelo] = useStateV(''); const [cor, setCor] = useStateV(''); const [ano, setAno] = useStateV(''); const [km, setKm] = useStateV(''); const [motoristaId, setMotoristaId] = useStateV(''); const [checklist, setChecklist] = useStateV({}); const [combustivel, setCombustivel] = useStateV(50); const [damages, setDamages] = useStateV({ 'Frente': [], 'Traseira': [], 'Lat. Esq.': [], 'Lat. Dir.': [] }); const [signGuin, setSignGuin] = useStateV(null); const [signCli, setSignCli] = useStateV(null); const [nomeGuin, setNomeGuin] = useStateV(''); const [nomeCli, setNomeCli] = useStateV(''); const [formStep, setFormStep] = useStateV(0); const TABS = ['Identificação','Checklist','Mapa de Avarias','Fotos','Assinaturas','Finalizar']; const VEICS = ['Carro','Moto','Caminhão','Utilitário']; const refresh = async () => { setLoading(true); setVistorias(await db.list('vistorias', { order: 'created_at' })); setLoading(false); }; useEffectV(() => { refresh(); db.list('motoristas', { order: 'nome', asc: true }).then(setMotoristas); }, []); const resetForm = () => { setTipoVistoria('Entrada'); setTipoVeiculo('Carro'); setPlaca(''); setMarca(''); setModelo(''); setCor(''); setAno(''); setKm(''); setMotoristaId(''); setChecklist({}); setCombustivel(50); setDamages({ 'Frente': [], 'Traseira': [], 'Lat. Esq.': [], 'Lat. Dir.': [] }); setSignGuin(null); setSignCli(null); setNomeGuin(''); setNomeCli(''); setFormStep(0); }; const addDamage = (x, y, view) => setDamages(d => ({ ...d, [view]: [...(d[view]||[]), { x, y, comment: '' }] })); const totalDamages = Object.values(damages).reduce((s, a) => s + a.length, 0); const salvarVistoria = async () => { if (!placa) { showToast('Informe a placa', 'warning'); return; } const motoristaSel = motoristas.find(m => m.id === parseInt(motoristaId)); const created = await db.insert('vistorias', { tipo: tipoVistoria, tipo_veiculo: tipoVeiculo, placa, marca, modelo, cor, ano, km, veiculo: `${marca} ${modelo} ${cor}`.trim(), motorista_id: motoristaSel?.id || null, motorista_nome: motoristaSel?.nome || null, checklist, combustivel, damages, assinatura_guincheiro: signGuin, assinatura_cliente: signCli, nome_guincheiro: nomeGuin, nome_cliente: nomeCli, status: (signGuin && signCli) ? 'concluida' : 'pendente', }); if (created) { showToast(`Vistoria ${created.numero} salva!`, 'success'); resetForm(); setShowForm(false); refresh(); } }; const filtradas = vistorias.filter(v => tab === 0 || (tab === 1 && v.status === 'pendente') || (tab === 2 && v.status === 'concluida')); if (showForm) return (
{ resetForm(); setShowForm(false); }} style={{ transform: 'rotate(180deg)' }}>

Nova Vistoria

{TABS.map((t, i) => ( ))}
{formStep === 0 && (
setMotoristaId(e.target.value)} options={[{value:'',label:'Selecione...'}, ...motoristas.map(m => ({ value: m.id, label: m.nome }))]} />
{VEICS.map(v => ( ))}
setPlaca(e.target.value)} /> setMarca(e.target.value)} /> setModelo(e.target.value)} /> setCor(e.target.value)} /> setAno(e.target.value)} /> setKm(e.target.value)} />
)} {formStep === 1 && (
{CHECKLIST_ITEMS.map(item => { const checked = checklist[item]; return ( ); })}
50 ? '#10B981' : combustivel > 25 ? '#F59E0B' : '#EF4444', transition: 'width 0.3s, background 0.3s' }} />
setCombustivel(+e.target.value)} style={{ width: '100%', marginTop: 8, accentColor: '#F59E0B' }} />
E1/41/23/4F
)} {formStep === 2 && (
Mapa de Avarias
Clique nas vistas para marcar avarias
{totalDamages > 0 && {totalDamages} avaria{totalDamages > 1 ? 's' : ''}}
{Object.keys(damages).map(view => ( addDamage(x, y, view)} /> ))}
{totalDamages > 0 && (
Avarias Registradas
{Object.entries(damages).map(([view, marks]) => marks.map((m, i) => (
{i+1} {view} — posição ({m.x}%, {m.y}%)
)))}
)}
)} {formStep === 3 && (
Fotos do Veículo
Upload de fotos requer Supabase Storage configurado — em breve. Por enquanto, anexe externamente ao laudo PDF.
{FOTOS_CATS.map(cat => (
{cat}
))}
)} {formStep === 4 && (
Assinaturas Digitais
setNomeGuin(e.target.value)} />
setNomeCli(e.target.value)} />
Data/Hora automática: {new Date().toLocaleString('pt-BR')}
)} {formStep === 5 && (
Vistoria Pronta!
Revise o resumo e salve a vistoria
{[ ['Tipo', tipoVistoria], ['Placa', placa || '—'], ['Veículo', `${marca} ${modelo}`.trim() || '—'], ['Combustível', `${combustivel}%`], ['Avarias', `${totalDamages} marcadas`], ['Motorista', motoristas.find(m => m.id === parseInt(motoristaId))?.nome || '—'], ['Assinatura Guincheiro', signGuin ? '✓ Assinado' : '— Pendente'], ['Assinatura Cliente', signCli ? '✓ Assinado' : '— Pendente'], ].map(([k,v]) => (
{k}
{v}
))}
Salvar Vistoria
)} {formStep < 5 && (
setFormStep(s => Math.max(0, s-1))} disabled={formStep === 0}>← Anterior setFormStep(s => Math.min(5, s+1))}>Próximo →
)}
); return (

Vistoria Digital

Laudos e checklists dos veículos
{ resetForm(); setShowForm(true); }}>Nova Vistoria
{['Todas','Pendentes','Concluídas'].map((t, i) => ( ))}
{loading ? (
Carregando vistorias...
) : filtradas.length === 0 ? (
{vistorias.length === 0 ? 'Nenhuma vistoria registrada' : 'Nenhuma vistoria nesta categoria'}
{vistorias.length === 0 ? 'Clique em "Nova Vistoria" para registrar a primeira' : 'Tente outra aba'}
{vistorias.length === 0 && { resetForm(); setShowForm(true); }} style={{ marginTop: 16 }}>Nova Vistoria}
) : ( {['Nº','Placa','Veículo','Data/Hora','Tipo','Motorista','Status'].map(h => ( ))} {filtradas.map((v, i) => ( e.currentTarget.style.background = '#EFF6FF'} onMouseLeave={e => e.currentTarget.style.background = i % 2 ? '#F8FAFC' : '#fff'}> ))}
{h}
{v.numero} {v.placa || '—'} {v.veiculo || '—'} {new Date(v.created_at).toLocaleString('pt-BR')} {v.tipo} {v.motorista_nome || '—'}
)}
); }; Object.assign(window, { VistoriaScreen });