import React, { useState, useEffect, useMemo } from 'react'; import { Truck, Plus, ListChecks, ChevronRight, Check, Package, User, FileCheck, AlertCircle, Eye, Download, Share2, Trash2, FileText, CheckCircle2, Calendar, Weight } from 'lucide-react'; import jsPDF from 'jspdf'; import autoTable from 'jspdf-autotable'; import { OFAtiva } from '@/hooks/useOFsAtivas'; import { useAuth } from '@/hooks/useAuth'; import { supabase } from '@/integrations/supabase/client'; import { smartAudio } from '@/utils/smartAudio'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { toast } from 'sonner'; interface SmartEmbarqueFlowProps { obra: OFAtiva; onVoltarHub: () => void; } interface RomaneioAberto { id: string; numero_romaneio: string; nome_motorista?: string; tipo_transporte?: string; frete_tipo?: string; peso_total_romaneio?: number; status?: string; created_at?: string; data_romaneio?: string; } interface ItemRomaneioCarregado { id: string; romaneio_id: string; peca_id: string; marca: string; descricao?: string; fase?: string; quantidade_expedida: number; peso_unitario: number; peso_total: number; created_at?: string; } interface PecaPronta { id: string; marca: string; descricao: string; etapa_fase: string; quantidade: number; peso_unitario: number; quantidadeJaExpedida: number; saldoDisponivel: number; } type SubTelaEmbarque = | 'menu' | 'novo_romaneio' | 'selecionar_romaneio' | 'carregando_pecas' | 'conferencia_romaneio'; export const SmartEmbarqueFlow: React.FC = ({ obra, onVoltarHub }) => { const { user } = useAuth(); // Subtelas do fluxo de embarque const [subTela, setSubTela] = useState('menu'); // Romaneio ativo para carregamento ou conferência const [romaneioAtivo, setRomaneioAtivo] = useState(null); // Lista de romaneios da OF const [romaneios, setRomaneios] = useState([]); const [loadingRomaneios, setLoadingRomaneios] = useState(false); // Itens carregados no romaneio ativo const [itensRomaneio, setItensRomaneio] = useState([]); const [loadingItensRomaneio, setLoadingItensRomaneio] = useState(false); const [removendoItemId, setRemovendoItemId] = useState(null); const [gerandoPDF, setGerandoPDF] = useState(false); // Formulário de Novo Romaneio Rápido const [nomeMotorista, setNomeMotorista] = useState(''); const [tipoTransporte, setTipoTransporte] = useState< 'caminhao_pequeno' | 'caminhao_trucado' | 'caminhao_munck' | 'carreta_12m' >('caminhao_trucado'); const [freteTipo, setFreteTipo] = useState<'proprio' | 'terceiros'>('proprio'); const [criandoRomaneio, setCriandoRomaneio] = useState(false); // Lista de peças prontas da OF e seus saldos de embarque const [pecasProntas, setPecasProntas] = useState([]); const [loadingPecas, setLoadingPecas] = useState(false); // Peça selecionada para adicionar ao caminhão const [pecaEmbarque, setPecaEmbarque] = useState(null); const [qtdEmbarcar, setQtdEmbarcar] = useState(1); const [salvandoItem, setSalvandoItem] = useState(false); // Formatadores de Transporte e Frete const formatTransporte = (tipo?: string) => { const mapa: Record = { carro: 'Carro', utilitario: 'Utilitário', caminhao_pequeno: 'Caminhão Pequeno (Toco)', caminhao_trucado: 'Caminhão Trucado', caminhao_munck: 'Caminhão Munck', carreta_12m: 'Carreta 12m', carreta_15m: 'Carreta 15m', especial: 'Especial / Prancha', }; return (tipo && mapa[tipo]) || tipo || 'Caminhão Padrão'; }; const formatFrete = (tipo?: string) => { return tipo === 'proprio' ? 'Próprio' : 'Terceiros / Contratado'; }; // Carregar Romaneios Abertos da OF const carregarRomaneios = React.useCallback(async () => { try { setLoadingRomaneios(true); const { data, error } = await supabase .from('romaneios_expedicao') .select('*') .eq('of_number', obra.of_number) .neq('status', 'Entregue') .order('created_at', { ascending: false }); if (error) throw error; setRomaneios(data || []); } catch (e) { console.error('Erro ao buscar romaneios:', e); } finally { setLoadingRomaneios(false); } }, [obra.of_number]); // Carregar Peças com Saldo para Embarque const carregarPecasParaEmbarque = React.useCallback(async () => { try { setLoadingPecas(true); const [resPecas, resItensExpedidos] = await Promise.all([ supabase .from('pecas') .select('id, marca, descricao, etapa_fase, quantidade, peso_unitario') .eq('of_number', obra.of_number), supabase .from('itens_romaneio_pecas') .select('peca_id, quantidade_expedida'), ]); const mapaExpedidas = new Map(); if (resItensExpedidos.data) { resItensExpedidos.data.forEach((item) => { const atual = mapaExpedidas.get(item.peca_id) || 0; mapaExpedidas.set(item.peca_id, atual + Number(item.quantidade_expedida || 0)); }); } if (resPecas.data) { const formatadas: PecaPronta[] = resPecas.data.map((p) => { const jaExp = mapaExpedidas.get(p.id) || 0; const saldo = Math.max(0, p.quantidade - jaExp); return { ...p, quantidadeJaExpedida: jaExp, saldoDisponivel: saldo, }; }); // Ordenação natural das marcas para localização rápida formatadas.sort((a, b) => a.marca.localeCompare(b.marca, undefined, { numeric: true, sensitivity: 'base' }) ); setPecasProntas(formatadas); } } catch (e) { console.error('Erro ao carregar peças para embarque:', e); } finally { setLoadingPecas(false); } }, [obra.of_number]); // Carregar itens colocados dentro de um romaneio específico const carregarItensDoRomaneio = React.useCallback(async (romaneioId: string) => { try { setLoadingItensRomaneio(true); const { data, error } = await supabase .from('itens_romaneio_pecas') .select('*') .eq('romaneio_id', romaneioId) .order('created_at', { ascending: true }); if (error) throw error; const lista = data || []; // Ordenar marcas naturalmente lista.sort((a, b) => (a.marca || '').localeCompare(b.marca || '', undefined, { numeric: true, sensitivity: 'base' }) ); setItensRomaneio(lista); } catch (e) { console.error('Erro ao buscar itens do romaneio:', e); toast.error('Erro ao carregar itens do caminhão'); } finally { setLoadingItensRomaneio(false); } }, []); useEffect(() => { carregarRomaneios(); carregarPecasParaEmbarque(); }, [carregarRomaneios, carregarPecasParaEmbarque]); // Sincronizar itens quando o romaneio ativo mudar useEffect(() => { if (romaneioAtivo?.id) { carregarItensDoRomaneio(romaneioAtivo.id); } }, [romaneioAtivo?.id, carregarItensDoRomaneio]); // Criar Novo Romaneio const handleCriarRomaneio = async () => { if (!user) return; try { setCriandoRomaneio(true); const hoje = new Date().toISOString().split('T')[0]; // Gerar número de romaneio sequencial RO-XX const seq = (romaneios.length + 1).toString().padStart(2, '0'); const numRomaneio = `RO${obra.of_number.replace(/\D/g, '')}-${seq}`; const novo = { of_number: obra.of_number, numero_romaneio: numRomaneio, data_romaneio: hoje, data_criacao: hoje, revisao: 0, prioridade: 'Normal' as const, status: 'Em planejamento' as const, tipo_transporte: tipoTransporte, frete_tipo: freteTipo, nome_motorista: nomeMotorista || 'Motorista a definir', peso_total_romaneio: 0, created_by: user.id, }; const { data, error } = await supabase .from('romaneios_expedicao') .insert(novo) .select() .single(); if (error) throw error; smartAudio.playSuccess(); toast.success(`Romaneio ${numRomaneio} criado!`); setRomaneioAtivo(data); setItensRomaneio([]); setSubTela('carregando_pecas'); carregarRomaneios(); } catch (e: any) { smartAudio.playAlert(); console.error('Erro ao criar romaneio:', e); toast.error('Erro ao criar romaneio: ' + (e?.message || 'Falha de conexão')); } finally { setCriandoRomaneio(false); } }; // Abrir Tela de Conferência para qualquer romaneio const handleAbrirConferencia = (r: RomaneioAberto) => { smartAudio.playClick(); setRomaneioAtivo(r); carregarItensDoRomaneio(r.id); setSubTela('conferencia_romaneio'); }; // Incluir Peça no Romaneio const handleIncluirPeca = async () => { if (!romaneioAtivo || !pecaEmbarque || qtdEmbarcar <= 0) return; try { setSalvandoItem(true); const pesoTotalItem = qtdEmbarcar * (pecaEmbarque.peso_unitario || 0); const novoItem = { romaneio_id: romaneioAtivo.id, peca_id: pecaEmbarque.id, marca: pecaEmbarque.marca, descricao: pecaEmbarque.descricao, fase: pecaEmbarque.etapa_fase || 'Geral', quantidade_expedida: qtdEmbarcar, peso_unitario: pecaEmbarque.peso_unitario || 0, peso_total: pesoTotalItem, quantidade_faltante: Math.max(0, pecaEmbarque.saldoDisponivel - qtdEmbarcar), }; const { error } = await supabase.from('itens_romaneio_pecas').insert(novoItem); if (error) throw error; // Atualizar o peso total no cabeçalho do romaneio const novoPesoRomaneio = (romaneioAtivo.peso_total_romaneio || 0) + pesoTotalItem; await supabase .from('romaneios_expedicao') .update({ peso_total_romaneio: novoPesoRomaneio }) .eq('id', romaneioAtivo.id); smartAudio.playSuccess(); toast.success(`Adicionado: ${qtdEmbarcar}x ${pecaEmbarque.marca} ao caminhão!`); // Atualizar estado local e listas setRomaneioAtivo((prev) => (prev ? { ...prev, peso_total_romaneio: novoPesoRomaneio } : null)); setPecaEmbarque(null); await Promise.all([ carregarPecasParaEmbarque(), carregarItensDoRomaneio(romaneioAtivo.id), ]); } catch (e: any) { smartAudio.playAlert(); console.error('Erro ao adicionar peça ao romaneio:', e); toast.error('Erro ao incluir no caminhão: ' + (e?.message || '')); } finally { setSalvandoItem(false); } }; // Remover / Estornar Item do Romaneio const handleRemoverItemRomaneio = async (item: ItemRomaneioCarregado) => { if (!romaneioAtivo) return; try { setRemovendoItemId(item.id); const { error } = await supabase .from('itens_romaneio_pecas') .delete() .eq('id', item.id); if (error) throw error; // Recalcular peso total do romaneio const novoPeso = Math.max(0, (romaneioAtivo.peso_total_romaneio || 0) - item.peso_total); await supabase .from('romaneios_expedicao') .update({ peso_total_romaneio: novoPeso }) .eq('id', romaneioAtivo.id); setRomaneioAtivo((prev) => (prev ? { ...prev, peso_total_romaneio: novoPeso } : null)); smartAudio.playSuccess(); toast.success(`Peça ${item.marca} removida do romaneio.`); // Atualizar dados await Promise.all([ carregarItensDoRomaneio(romaneioAtivo.id), carregarPecasParaEmbarque(), carregarRomaneios(), ]); } catch (e: any) { smartAudio.playAlert(); toast.error('Erro ao remover item: ' + (e?.message || '')); } finally { setRemovendoItemId(null); } }; // Finalizar / Concluir Romaneio Oficialmente const handleConcluirRomaneioOficial = async (novoStatus: 'Confirmado' | 'Expedido' = 'Expedido') => { if (!romaneioAtivo) return; try { const { error } = await supabase .from('romaneios_expedicao') .update({ status: novoStatus }) .eq('id', romaneioAtivo.id); if (error) throw error; smartAudio.playSuccess(); toast.success(`Romaneio ${romaneioAtivo.numero_romaneio} concluído com sucesso (${novoStatus})!`); await carregarRomaneios(); setRomaneioAtivo(null); setSubTela('menu'); } catch (e: any) { smartAudio.playAlert(); toast.error('Erro ao finalizar romaneio: ' + (e?.message || '')); } }; // ───────────────────────────────────────────────────────────── // GERAÇÃO DE PDF OFICIAL DO ROMANEIO (jsPDF + autoTable) // ───────────────────────────────────────────────────────────── const gerarRomaneioPDF = (acao: 'download' | 'whatsapp') => { if (!romaneioAtivo) return; try { setGerandoPDF(true); const doc = new jsPDF(); const dataFormatada = new Date().toLocaleDateString('pt-BR'); const horaFormatada = new Date().toLocaleTimeString('pt-BR'); // 1. TOPO LIMPO CORPORATIVO (FUNDO BRANCO, AZUL MARINHO & VERDE - ZERO DESPERDÍCIO DE TONER) doc.setFont('helvetica', 'bold'); doc.setFontSize(16); doc.setTextColor(15, 43, 92); // Azul Marinho Profundo doc.text(`ROMANEIO DE EXPEDIÇÃO & CARGA`, 14, 16); // Status em Verde no canto superior direito doc.setFontSize(9); doc.setFont('helvetica', 'bold'); doc.setTextColor(5, 150, 105); // Verde Esmeralda doc.text(`STATUS: ${(romaneioAtivo.status || 'EXPEDIDO').toUpperCase()}`, 150, 16); // Metadados da Obra em Preto / Grafite doc.setFont('helvetica', 'normal'); doc.setFontSize(9.5); doc.setTextColor(31, 41, 55); doc.text(`OF: ${obra.of_number} | Cliente: ${obra.cliente || 'Industrial'}`, 14, 23); doc.text( `Romaneio: ${romaneioAtivo.numero_romaneio} | Emissão: ${dataFormatada} às ${horaFormatada}`, 14, 29 ); // Linha divisória de topo fina e elegante: Azul Marinho com terminal em Verde doc.setDrawColor(30, 58, 138); // Azul Marinho doc.setLineWidth(1.2); doc.line(14, 33, 165, 33); doc.setDrawColor(5, 150, 105); // Verde Esmeralda doc.setLineWidth(1.2); doc.line(165, 33, 196, 33); // 2. CAIXA DE DADOS DO TRANSPORTE (Fundo Branco, Borda Fina, Barra Lateral Azul Marinho) const boxTransporteY = 37; doc.setFillColor(30, 58, 138); // Barra lateral azul marinho doc.roundedRect(14, boxTransporteY, 3, 19, 1, 1, 'F'); doc.setDrawColor(203, 213, 225); // Borda cinza suave doc.setLineWidth(0.4); doc.roundedRect(17, boxTransporteY, 179, 19, 1.5, 1.5, 'S'); doc.setFont('helvetica', 'bold'); doc.setFontSize(8.5); doc.setTextColor(15, 43, 92); // Azul Marinho doc.text(`DADOS DO TRANSPORTE:`, 22, boxTransporteY + 6); doc.setFont('helvetica', 'normal'); doc.setFontSize(8.5); doc.setTextColor(31, 41, 55); // Preto / Grafite doc.text(`Motorista: ${romaneioAtivo.nome_motorista || 'A definir'}`, 22, boxTransporteY + 13); doc.text(`Veículo: ${formatTransporte(romaneioAtivo.tipo_transporte)}`, 90, boxTransporteY + 13); doc.text(`Frete: ${formatFrete(romaneioAtivo.frete_tipo)}`, 155, boxTransporteY + 13); // 3. TABELA DE PEÇAS CARREGADAS (Fundo Branco, Linhas Finas, Marca em Azul Marinho, Totais em Verde) const tableRows = itensRomaneio.map((item, idx) => [ (idx + 1).toString(), item.marca, item.fase || '1', item.descricao || 'Peça Estrutural', item.quantidade_expedida.toString(), `${Number(item.peso_unitario || 0).toFixed(1)} kg`, `${Number(item.peso_total || 0).toFixed(1)} kg`, ]); const totalPecas = itensRomaneio.reduce( (acc, it) => acc + Number(it.quantidade_expedida || 0), 0 ); const totalKg = itensRomaneio.reduce( (acc, it) => acc + Number(it.peso_total || 0), 0 ); autoTable(doc, { startY: 61, head: [['#', 'Marca', 'Fase', 'Descrição da Peça', 'Qtd', 'Peso Unit.', 'Peso Total']], body: tableRows, theme: 'plain', headStyles: { fillColor: [248, 250, 252], // Fundo branco/gelo suave textColor: [15, 43, 92], // Azul Marinho fontStyle: 'bold', lineWidth: 0.4, lineColor: [30, 58, 138], // Linha azul marinho }, styles: { fontSize: 8.5, textColor: [31, 41, 55], // Preto nítido cellPadding: 2.5, lineWidth: 0.1, lineColor: [226, 232, 240], // Linha cinza clara }, columnStyles: { 0: { cellWidth: 10, textColor: [100, 116, 139] }, 1: { cellWidth: 26, fontStyle: 'bold', textColor: [15, 43, 92] }, // Marca em Azul Marinho 2: { cellWidth: 18, halign: 'center' }, 3: { cellWidth: 65 }, 4: { cellWidth: 15, halign: 'center', fontStyle: 'bold' }, 5: { cellWidth: 24, halign: 'right' }, 6: { cellWidth: 24, halign: 'right', fontStyle: 'bold', textColor: [22, 101, 52] }, // Peso em Verde }, }); const finalY = (doc as any).lastAutoTable?.finalY || 120; // 4. RESUMO E TOTAIS (Fundo Branco, Borda e Barra Lateral em Verde Esmeralda) const boxTotaisY = finalY + 5; doc.setFillColor(5, 150, 105); // Verde doc.roundedRect(14, boxTotaisY, 3, 16, 1, 1, 'F'); doc.setDrawColor(5, 150, 105); // Borda Verde doc.setLineWidth(0.4); doc.roundedRect(17, boxTotaisY, 179, 16, 1.5, 1.5, 'S'); doc.setFont('helvetica', 'bold'); doc.setFontSize(8.5); doc.setTextColor(22, 101, 52); // Verde Escuro doc.text(`TOTAIS DO ROMANEIO:`, 22, boxTotaisY + 6); doc.setFont('helvetica', 'bold'); doc.setFontSize(9.5); doc.setTextColor(15, 23, 42); // Preto doc.text( `Marcas: ${itensRomaneio.length} | Total de Peças: ${totalPecas} un | Peso Total: ${totalKg.toFixed(1)} kg`, 22, boxTotaisY + 12 ); // 5. CAMPOS DE ASSINATURA (Linhas Finas) const posYAssinaturas = boxTotaisY + 28; if (posYAssinaturas < 265) { doc.setDrawColor(148, 163, 184); doc.setLineWidth(0.4); // Assinatura Fábrica doc.line(18, posYAssinaturas, 95, posYAssinaturas); doc.setFontSize(8); doc.setFont('helvetica', 'normal'); doc.setTextColor(71, 85, 105); doc.text('Expedido por (Fábrica / Almoxarifado)', 22, posYAssinaturas + 5); // Assinatura Motorista doc.line(115, posYAssinaturas, 192, posYAssinaturas); doc.text('Recebido por (Motorista / Transportador)', 120, posYAssinaturas + 5); // Assinatura Canteiro const posYObra = posYAssinaturas + 16; doc.line(65, posYObra, 145, posYObra); doc.text('Recebido na Obra (Canteiro / Montagem)', 72, posYObra + 5); } const nomeArquivo = `Romaneio_${romaneioAtivo.numero_romaneio}.pdf`; if (acao === 'download') { doc.save(nomeArquivo); smartAudio.playSuccess(); toast.success('Romaneio em PDF gerado e baixado!'); } else if (acao === 'whatsapp') { smartAudio.playClick(); const textoMsg = encodeURIComponent( `*TRACKSTEEL - ROMANEIO DE EXPEDIÇÃO*\n` + `*Romaneio:* ${romaneioAtivo.numero_romaneio}\n` + `*Obra:* OF ${obra.of_number} - ${obra.cliente || ''}\n` + `*Motorista:* ${romaneioAtivo.nome_motorista || 'A definir'}\n` + `*Veículo:* ${formatTransporte(romaneioAtivo.tipo_transporte)}\n` + `*Carga:* ${itensRomaneio.length} marcas, ${totalPecas} peças (${totalKg.toFixed(1)} kg)\n` + `*Status:* ${romaneioAtivo.status || 'Expedido'}\n\n` + `_Conferência gerada via Modo Smart TrackSteel._` ); window.open(`https://wa.me/?text=${textoMsg}`, '_blank'); } } catch (e: any) { smartAudio.playAlert(); console.error('Erro ao gerar PDF do romaneio:', e); toast.error('Erro ao gerar PDF: ' + (e?.message || '')); } finally { setGerandoPDF(false); } }; // ───────────────────────────────────────────────────────────── // RENDER: SUBTELA 'MENU' // ───────────────────────────────────────────────────────────── if (subTela === 'menu') { return (
ETAPA 2 — EXPEDIÇÃO

Apontar Embarque & Romaneio

Carregue caminhões com as peças prontas no pátio ou confira romaneios

{/* Opção 1: Criar Novo Romaneio */} {/* Opção 2: Romaneios em Aberto (Carga ou Conferência) */}
); } // ───────────────────────────────────────────────────────────── // RENDER: SUBTELA 'NOVO_ROMANEIO' (FORMULÁRIO TOUCH RÁPIDO) // ───────────────────────────────────────────────────────────── if (subTela === 'novo_romaneio') { return (
NOVO CARREGAMENTO

Dados do Veículo & Frete

{/* Nome / Placa do Motorista */}
setNomeMotorista(e.target.value)} placeholder="Ex: João Silva - Placa ABC-1234" className="h-14 pl-11 bg-slate-800 border-slate-700 text-slate-100 text-base font-bold rounded-2xl" />
{/* Tipo de Veículo */}
{[ { id: 'caminhao_pequeno', label: 'Toco / 3/4' }, { id: 'caminhao_trucado', label: 'Trucado' }, { id: 'caminhao_munck', label: 'Munck' }, { id: 'carreta_12m', label: 'Carreta 12m' }, ].map((tipo) => ( ))}
{/* Tipo de Frete */}
); } // ───────────────────────────────────────────────────────────── // RENDER: SUBTELA 'SELECIONAR_ROMANEIO' (LISTAGEM COM CARGA E CONFERÊNCIA) // ───────────────────────────────────────────────────────────── if (subTela === 'selecionar_romaneio') { return (
EXPEDIÇÃO DA OBRA

Romaneios de Carga

Selecione para carregar peças ou abrir conferência e PDF

{loadingRomaneios ? (
Buscando romaneios...
) : romaneios.length === 0 ? (

Nenhum romaneio aberto

Crie um novo romaneio para iniciar a expedição

) : ( romaneios.map((r) => (
{r.numero_romaneio} {r.status || 'Aberto'}
{r.nome_motorista || 'Motorista padrão'}
Veículo: {formatTransporte(r.tipo_transporte)} | Peso:{' '} {(r.peso_total_romaneio || 0).toLocaleString('pt-BR')} kg
{/* Botões de Ação Direta no Card */}
)) )}
); } // ───────────────────────────────────────────────────────────── // RENDER: SUBTELA 'CARREGANDO_PECAS' (CARGA DE PEÇAS NO CAMINHÃO) // ───────────────────────────────────────────────────────────── if (subTela === 'carregando_pecas' && romaneioAtivo) { return (
{/* Banner do Romaneio Ativo com Atalho para Conferência */}
CAMINHÃO ATIVO {romaneioAtivo.numero_romaneio}
Motorista: {romaneioAtivo.nome_motorista} | Peso:{' '} {(romaneioAtivo.peso_total_romaneio || 0).toLocaleString('pt-BR')} kg {itensRomaneio.length > 0 && ( ({itensRomaneio.length} marcas na carga) )}
{/* Modal/Seção de Quantidade para o Item Selecionado */} {pecaEmbarque ? (
{pecaEmbarque.marca} Saldo Disponível: {pecaEmbarque.saldoDisponivel} un
{qtdEmbarcar}
) : null} {/* Lista de Peças Prontas para Embarcar */}
Peças com Saldo para Embarque
{loadingPecas ? (
Carregando peças prontas...
) : pecasProntas.filter((p) => p.saldoDisponivel > 0).length === 0 ? (

Nenhuma peça com saldo de embarque

Todas as peças prontas já foram embarcadas em romaneios anteriores

) : ( pecasProntas .filter((p) => p.saldoDisponivel > 0) .map((p) => (
{p.marca} {p.etapa_fase || 'Estrutural'}
Saldo p/ Embarque:{' '} {p.saldoDisponivel} un
)) )}
{/* Botão Finalizador: abre a tela de conferência & PDF */}
); } // ───────────────────────────────────────────────────────────── // RENDER: SUBTELA 'CONFERENCIA_ROMANEIO' (CONFERÊNCIA TOUCH & PDF) // ───────────────────────────────────────────────────────────── if (subTela === 'conferencia_romaneio' && romaneioAtivo) { const totalPecasCarregadas = itensRomaneio.reduce( (sum, item) => sum + Number(item.quantidade_expedida || 0), 0 ); const pesoTotalCarregado = itensRomaneio.reduce( (sum, item) => sum + Number(item.peso_total || 0), 0 ); return (
{/* Cabeçalho da Conferência */}
CONFERÊNCIA DE EMBARQUE

{romaneioAtivo.numero_romaneio}

OF {obra.of_number} — {obra.cliente || 'Industrial'} {romaneioAtivo.status || 'Em conferência'}
{/* KPIs em Grid Rápido */}
Marcas {itensRomaneio.length}
Peças {totalPecasCarregadas} un
Peso Carga {pesoTotalCarregado.toLocaleString('pt-BR', { maximumFractionDigits: 1 })} kg
{/* Card de Dados do Transporte */}
Motorista: {romaneioAtivo.nome_motorista || 'Não informado'}
Veículo: {formatTransporte(romaneioAtivo.tipo_transporte)}
Tipo de Frete: {formatFrete(romaneioAtivo.frete_tipo)}
{/* Lista de Peças Colocadas no Romaneio para Conferência */}
Peças Colocadas no Caminhão ({itensRomaneio.length}) Toque na lixeira para estornar
{loadingItensRomaneio ? (
Carregando itens do romaneio...
) : itensRomaneio.length === 0 ? (

Nenhuma peça colocada neste romaneio ainda

Adicione peças prontas no caminhão para gerar o romaneio

) : ( itensRomaneio.map((item, idx) => (
#{idx + 1}
{item.marca} {item.fase || '1'}
{item.descricao && (
{item.descricao}
)}
Peso Total: {item.peso_total.toFixed(1)} kg ({item.peso_unitario.toFixed(1)} kg/un)
{item.quantidade_expedida} un
{/* Botão de Excluir / Estornar Item */}
)) )}
{/* Botões de Ação na Base: Imprimir PDF, Compartilhar e Concluir */}
{/* Botão Principal: Imprimir / Baixar PDF Oficial */} {/* Linha com WhatsApp e Adicionar Mais Peças */}
{/* Botão de Concluir Romaneio Oficialmente */} {/* Voltar ao Menu */}
); } return null; };