import React, { useState, useEffect, useMemo } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Card, CardContent } from '@/components/ui/card'; import { Checkbox } from '@/components/ui/checkbox'; import { toast } from 'sonner'; import { Package, Plus, Wrench, Expand, AlertTriangle } from 'lucide-react'; import { RomaneioExpedicao } from '@/hooks/useRomaneios'; import { useProcessosFabricacao } from '@/hooks/useProcessosFabricacao'; import { usePecas } from '@/hooks/usePecas'; import { useMobileResponsive } from '@/hooks/useMobileResponsive'; import { supabase } from '@/integrations/supabase/client'; import { PecasRomaneioExpandido } from './PecasRomaneioExpandido'; interface ItensRomaneioModalProps { romaneio: RomaneioExpedicao; isOpen: boolean; onClose: () => void; } interface PecaDisponivel { id: string; marca: string; descricao: string; perfil_principal?: string | null; etapa_fase: string; quantidade_total_peca: number; quantidade_ja_expedida: number; quantidade_disponivel: number; peso_unitario: number; comprimento?: number | null; prioridade: string; } interface ItemRomaneioPeca { id: string; peca_id: string; quantidade: number; peso_unitario: number; peso_total: number; comprimento?: number | null; marca: string; descricao: string; etapa_fase: string; prioridade: string; } interface ItemRomaneioInsumo { id: string; nome: string; quantidade: number; unidade: string; } export const ItensRomaneioModal: React.FC = ({ romaneio, isOpen, onClose }) => { const { isMobile, isTablet } = useMobileResponsive(); const { data: processos } = useProcessosFabricacao(); const { pecas: todasPecas } = usePecas(); // Estados para filtros const [processoSelecionado, setProcessoSelecionado] = useState('Pintura/Galv'); const [faseFilter, setFaseFilter] = useState('Todas as fases'); const [marcaFilter, setMarcaFilter] = useState(''); const [pecaDisponivel, setPecaDisponivel] = useState(''); const [quantidadePeca, setQuantidadePeca] = useState(''); // Estados para dados dinâmicos const [pecasDisponiveis, setPecasDisponiveis] = useState([]); const [fasesDisponiveis, setFasesDisponiveis] = useState([]); const [marcasDisponiveis, setMarcasDisponiveis] = useState([]); const [loading, setLoading] = useState(false); // Estados para itens do romaneio const [itensPeca, setItensPeca] = useState([]); const [itensInsumo, setItensInsumo] = useState([]); const [loadingItens, setLoadingItens] = useState(false); // Estados para insumos const [nomeInsumo, setNomeInsumo] = useState(''); const [quantidadeInsumo, setQuantidadeInsumo] = useState(''); const [unidadeInsumo, setUnidadeInsumo] = useState(''); // Estados para adicionar todas as peças const [adicionarTodas, setAdicionarTodas] = useState(false); // Novo estado para o modal expandido const [showExpandedModal, setShowExpandedModal] = useState(false); // Buscar peças disponíveis baseadas no processo selecionado const buscarPecasDisponiveis = async (processo: string) => { if (!romaneio.of_number) return; setLoading(true); try { // Buscar peças que estão no processo selecionado e disponíveis para expedição const { data: apontamentosData, error } = await supabase .from('apontamentos_producao') .select(` peca_id, componente_id, tipo_apontamento, quantidade_produzida, processo_id, processo:processos_fabricacao!inner!apontamentos_producao_processo_id_fkey(nome), componente:componentes_peca!apontamentos_producao_componente_id_fkey(id, quantidade_por_peca), peca:pecas!inner!apontamentos_producao_peca_id_fkey( id, marca, descricao, perfil_principal, etapa_fase, quantidade, peso_unitario, comprimento, prioridade, of_number, tem_componentes ) `) .eq('peca.of_number', romaneio.of_number) .eq('processo.nome', processo); if (error) { console.error('Erro ao buscar peças disponíveis:', error); return; } // 2. Buscar quanto dessa OF já foi embalado em TODOS os romaneios da OF const { data: expedidosData, error: expError } = await supabase .from('itens_romaneio_pecas') .select(` peca_id, quantidade_expedida, romaneio:romaneios_expedicao!inner!itens_romaneio_pecas_romaneio_id_fkey(of_number) `) .eq('romaneio.of_number', romaneio.of_number); const expedidosMap = new Map(); if (!expError && expedidosData) { // eslint-disable-next-line @typescript-eslint/no-explicit-any (expedidosData as any[]).forEach((item: any) => { const pid = item.peca_id; const qtd = Number(item.quantidade_expedida) || 0; const current = expedidosMap.get(pid) || 0; expedidosMap.set(pid, current + qtd); }); } // 3. Agrupar por peça principal (NUNCA somar unidades de subcomponentes na peça principal) const pecasAgrupadas = new Map< string, { peca: any; qtdPecaDireta: number; componentesCortados: Map; } >(); // eslint-disable-next-line @typescript-eslint/no-explicit-any (apontamentosData as any[])?.forEach((apontamento: any) => { const pecaObj = apontamento.peca; const pecaId = pecaObj?.id; if (!pecaId) return; if (!pecasAgrupadas.has(pecaId)) { pecasAgrupadas.set(pecaId, { peca: pecaObj, qtdPecaDireta: 0, componentesCortados: new Map() }); } const grupo = pecasAgrupadas.get(pecaId)!; const quantidade = Number(apontamento.quantidade_produzida) || 0; const isComponente = apontamento.tipo_apontamento === 'componente' || Boolean(apontamento.componente_id); if (isComponente) { const compId = apontamento.componente_id || 'comp_default'; const porPeca = Math.max(1, Number(apontamento.componente?.quantidade_por_peca) || 1); const atualComp = grupo.componentesCortados.get(compId)?.cortado || 0; grupo.componentesCortados.set(compId, { cortado: atualComp + quantidade, porPeca }); } else { grupo.qtdPecaDireta += quantidade; } }); // 4. Calcular o saldo Real da peça principal (Produzido da Peça Principal - Expedido em Romaneios) const pecasDisponiveisFinal: PecaDisponivel[] = []; pecasAgrupadas.forEach((grupo, pecaId) => { const pecaObj = grupo.peca; const qtdTotalPeca = Math.max(1, Number(pecaObj.quantidade) || 1); let qtdProduzidaPecaPrincipal = 0; if (grupo.qtdPecaDireta > 0) { // Apontamento direto da peça principal (ou kit sincronizado) qtdProduzidaPecaPrincipal = Math.min(qtdTotalPeca, grupo.qtdPecaDireta); } else if (grupo.componentesCortados.size > 0) { // Caso haja apenas apontamentos de subcomponentes no processo (ex: Corte antes da soldagem), // converte para a quantidade equivalente da peça principal (limitada à quantidade total da peça na OF) let maxPecasEquivalentes = 0; grupo.componentesCortados.forEach(({ cortado, porPeca }) => { const equivalentes = Math.floor(cortado / porPeca); if (equivalentes > maxPecasEquivalentes) { maxPecasEquivalentes = equivalentes; } }); qtdProduzidaPecaPrincipal = Math.min( qtdTotalPeca, maxPecasEquivalentes > 0 ? maxPecasEquivalentes : qtdTotalPeca ); } const qtdExpedida = expedidosMap.get(pecaId) || 0; const saldoReal = Math.min( qtdTotalPeca - qtdExpedida, qtdProduzidaPecaPrincipal - qtdExpedida ); if (saldoReal > 0) { pecasDisponiveisFinal.push({ id: pecaObj.id, marca: pecaObj.marca, descricao: pecaObj.descricao || '', perfil_principal: pecaObj.perfil_principal || null, etapa_fase: pecaObj.etapa_fase || '', quantidade_total_peca: qtdTotalPeca, quantidade_ja_expedida: qtdExpedida, quantidade_disponivel: saldoReal, peso_unitario: Number(pecaObj.peso_unitario) || 0, comprimento: pecaObj.comprimento || null, prioridade: pecaObj.prioridade || 'P4' }); } }); setPecasDisponiveis(pecasDisponiveisFinal); // Extrair fases e marcas únicas const fasesUnicas = [...new Set(pecasDisponiveisFinal.map(p => p.etapa_fase).filter(Boolean))]; const marcasUnicas = [...new Set(pecasDisponiveisFinal.map(p => p.marca).filter(Boolean))]; setFasesDisponiveis(fasesUnicas); setMarcasDisponiveis(marcasUnicas); } catch (error) { console.error('Erro ao buscar peças disponíveis:', error); toast.error('Erro ao carregar peças disponíveis'); } finally { setLoading(false); } }; // Carregar itens existentes do romaneio const carregarItensRomaneio = async () => { setLoadingItens(true); try { // Carregar peças do romaneio - usando a tabela correta const { data: pecasData, error: pecasError } = await supabase .from('itens_romaneio_pecas') .select(` *, pecas!inner(marca, descricao, etapa_fase, peso_unitario, comprimento, prioridade) `) .eq('romaneio_id', romaneio.id); if (pecasError) { console.error('Erro ao carregar peças do romaneio:', pecasError); } else if (pecasData) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const itensPecaFormatados = pecasData.map((item: any) => ({ id: item.id, peca_id: item.peca_id, quantidade: item.quantidade_expedida, peso_unitario: item.peso_unitario, peso_total: item.peso_total, comprimento: item.pecas?.comprimento || item.comprimento || null, marca: item.marca, descricao: item.descricao || '', etapa_fase: item.fase || '', prioridade: 'P4' })); setItensPeca(itensPecaFormatados); } // Carregar insumos do romaneio - usando a tabela correta const { data: insumosData, error: insumosError } = await supabase .from('itens_romaneio_insumos') .select('*') .eq('romaneio_id', romaneio.id); if (insumosError) { console.error('Erro ao carregar insumos do romaneio:', insumosError); } else if (insumosData) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const itensInsumoFormatados = insumosData.map((item: any) => ({ id: item.id, nome: item.descricao, quantidade: item.quantidade_expedida, unidade: item.unidade })); setItensInsumo(itensInsumoFormatados); } } catch (error) { console.error('Erro ao carregar itens do romaneio:', error); toast.error('Erro ao carregar itens do romaneio'); } finally { setLoadingItens(false); } }; // Carregar peças quando processo selecionado mudar useEffect(() => { if (processoSelecionado && isOpen) { buscarPecasDisponiveis(processoSelecionado); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [processoSelecionado, isOpen, romaneio.of_number]); // Definir processo padrão quando abrir o modal useEffect(() => { if (isOpen) { setProcessoSelecionado('Pintura/Galv'); setFaseFilter('Todas as fases'); setMarcaFilter(''); setPecaDisponivel(''); setQuantidadePeca(''); setAdicionarTodas(false); carregarItensRomaneio(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen]); // Filtrar peças baseado nos filtros selecionados const pecasFiltradas = useMemo(() => { let filtradas = [...pecasDisponiveis]; if (faseFilter !== 'Todas as fases') { filtradas = filtradas.filter(p => p.etapa_fase === faseFilter); } if (marcaFilter.trim()) { filtradas = filtradas.filter(p => p.marca.toLowerCase().includes(marcaFilter.toLowerCase()) ); } return filtradas.sort((a, b) => { const aNum = a.marca.match(/\d+/); const bNum = b.marca.match(/\d+/); if (aNum && bNum) { return parseInt(aNum[0]) - parseInt(bNum[0]); } return a.marca.localeCompare(b.marca, undefined, { numeric: true, sensitivity: 'base' }); }); }, [pecasDisponiveis, faseFilter, marcaFilter]); const handleAdicionarPeca = async () => { const limiteVeiculo = romaneio.comprimento_maximo_veiculo || 12000; if (adicionarTodas) { // Verificar se há peças que excedem a capacidade do veículo const pecasExcedentes = pecasFiltradas.filter(p => p.comprimento && p.comprimento > limiteVeiculo); if (pecasExcedentes.length > 0) { const listaMarcas = pecasExcedentes.map(p => `${p.marca} (${p.comprimento}mm)`).join(', '); const confirmar = window.confirm( `⚠️ ALERTA DE CAPACIDADE DO VEÍCULO:\n\n` + `Atenção: ${pecasExcedentes.length} peça(s) excedem o limite de ${limiteVeiculo} mm (${(limiteVeiculo / 1000).toFixed(1)}m) do veículo:\n` + `${listaMarcas}\n\n` + `Essas peças são maiores que o espaço de carga do veículo selecionado.\n\n` + `Deseja realmente incluí-las no romaneio?` ); if (!confirmar) { toast.warning('Operação cancelada para evitar carregar peças que não cabem no caminhão.'); return; } } // Adicionar todas as peças filtradas com suas quantidades disponíveis for (const peca of pecasFiltradas) { try { const { error } = await supabase .from('itens_romaneio_pecas') .insert({ romaneio_id: romaneio.id, peca_id: peca.id, quantidade_expedida: peca.quantidade_disponivel, peso_unitario: peca.peso_unitario, peso_total: peca.quantidade_disponivel * peca.peso_unitario, comprimento: peca.comprimento || null, marca: peca.marca, descricao: peca.descricao, fase: peca.etapa_fase }); if (error) throw error; } catch (error) { console.error('Erro ao adicionar peça:', error); toast.error(`Erro ao adicionar peça ${peca.marca}`); } } toast.success('Todas as peças foram adicionadas ao romaneio'); } else { // Adicionar peça individual if (!pecaDisponivel || !quantidadePeca) { toast.error('Selecione uma peça e informe a quantidade'); return; } const pecaSelecionada = pecasDisponiveis.find(p => p.id === pecaDisponivel); if (!pecaSelecionada) { toast.error('Peça não encontrada'); return; } // Validação de comprimento do veículo if (pecaSelecionada.comprimento && pecaSelecionada.comprimento > limiteVeiculo) { const confirmar = window.confirm( `⚠️ ALERTA DE CAPACIDADE DO VEÍCULO:\n\n` + `A peça "${pecaSelecionada.marca}" tem comprimento de ${pecaSelecionada.comprimento} mm (${(pecaSelecionada.comprimento / 1000).toFixed(1)}m), ` + `que ULTRAPASSA o comprimento máximo suportado pelo veículo (${limiteVeiculo} mm / ${(limiteVeiculo / 1000).toFixed(1)}m)!\n\n` + `Essa peça NÃO caberá na carroceria do transporte selecionado.\n\n` + `Deseja realmente adicionar esta peça mesmo com essa advertência?` ); if (!confirmar) { toast.warning(`Inclusão da peça ${pecaSelecionada.marca} (${pecaSelecionada.comprimento}mm) cancelada por restrição dimensional.`); return; } } try { const quantidade = parseInt(quantidadePeca); if (quantidade > pecaSelecionada.quantidade_disponivel) { toast.error(`Quantidade não autorizada! Saldo disponível é ${pecaSelecionada.quantidade_disponivel}`); return; } const { error } = await supabase .from('itens_romaneio_pecas') .insert({ romaneio_id: romaneio.id, peca_id: pecaDisponivel, quantidade_expedida: quantidade, peso_unitario: pecaSelecionada.peso_unitario, peso_total: quantidade * pecaSelecionada.peso_unitario, comprimento: pecaSelecionada.comprimento || null, marca: pecaSelecionada.marca, descricao: pecaSelecionada.descricao, fase: pecaSelecionada.etapa_fase }); if (error) throw error; toast.success('Peça adicionada ao romaneio'); } catch (error) { console.error('Erro ao adicionar peça:', error); toast.error('Erro ao adicionar peça ao romaneio'); } } // Recarregar itens e limpar formulário carregarItensRomaneio(); if (processoSelecionado) { buscarPecasDisponiveis(processoSelecionado); } setPecaDisponivel(''); setQuantidadePeca(''); setAdicionarTodas(false); }; const handleAdicionarInsumo = async () => { if (!nomeInsumo || !quantidadeInsumo || !unidadeInsumo) { toast.error('Preencha todos os campos do insumo'); return; } try { const { error } = await supabase .from('itens_romaneio_insumos') .insert({ romaneio_id: romaneio.id, descricao: nomeInsumo, quantidade_expedida: parseFloat(quantidadeInsumo), unidade: unidadeInsumo, tipo_insumo: 'Manual' }); if (error) throw error; toast.success('Insumo adicionado ao romaneio'); setNomeInsumo(''); setQuantidadeInsumo(''); setUnidadeInsumo(''); carregarItensRomaneio(); } catch (error) { console.error('Erro ao adicionar insumo:', error); toast.error('Erro ao adicionar insumo ao romaneio'); } }; const handleRemoverItemPeca = async (itemId: string) => { try { const { error } = await supabase .from('itens_romaneio_pecas') .delete() .eq('id', itemId); if (error) throw error; toast.success('Item removido do romaneio'); carregarItensRomaneio(); if (processoSelecionado) { buscarPecasDisponiveis(processoSelecionado); } } catch (error) { console.error('Erro ao remover item:', error); toast.error('Erro ao remover item do romaneio'); } }; const handleRemoverItemInsumo = async (itemId: string) => { try { const { error } = await supabase .from('itens_romaneio_insumos') .delete() .eq('id', itemId); if (error) throw error; toast.success('Insumo removido do romaneio'); carregarItensRomaneio(); } catch (error) { console.error('Erro ao remover insumo:', error); toast.error('Erro ao remover insumo do romaneio'); } }; const getPrioridadeColor = (prioridade: string) => { switch (prioridade) { case 'P1': return 'bg-red-100 text-red-800'; case 'P2': return 'bg-orange-100 text-orange-800'; case 'P3': return 'bg-blue-100 text-blue-800'; default: return 'bg-gray-100 text-gray-800'; } }; const qtdTotalPecas = itensPeca.reduce((total, item) => total + item.quantidade, 0); const pesoTotalPecas = itensPeca.reduce((total, item) => total + item.peso_total, 0); const maiorComprimentoPecas = itensPeca.reduce( (max, item) => Math.max(max, Number(item.comprimento) || 0), 0 ); return ( <> Itens do Romaneio {romaneio.numero_romaneio} - OF: {romaneio.of_number}
Peças Insumos {/* Seção Adicionar Peça */}

Adicionar Peça

{/* Filtro de Processo */}
{/* Filtros dinâmicos */}
setMarcaFilter(e.target.value)} placeholder="Digite a marca da peça..." className={isMobile ? 'h-10' : 'h-11'} />
{/* Opção para adicionar todas as peças */}
setAdicionarTodas(checked as boolean)} className="mt-1 shrink-0" />
{!adicionarTodas && ( <> {/* Seletor de peça disponível */}
p.id === pecaDisponivel)?.quantidade_disponivel || 1} value={quantidadePeca} onChange={(e) => setQuantidadePeca(e.target.value)} placeholder="Quantidade" className={isMobile ? 'h-10' : 'h-11'} />
{/* Alerta de dimensão se a peça exceder a capacidade do veículo */} {(() => { const pSel = pecasDisponiveis.find(p => p.id === pecaDisponivel); const limite = romaneio.comprimento_maximo_veiculo || 12000; if (pSel?.comprimento && pSel.comprimento > limite) { return (
ALERTA DE DIMENSÃO: Peça não cabe no veículo ({limite}mm)

A peça {pSel.marca} possui {pSel.comprimento} mm ({(pSel.comprimento / 1000).toFixed(1)}m) e ULTRAPASSA o comprimento máximo de carregamento deste veículo ({(limite / 1000).toFixed(1)}m).

); } return null; })()} )}
{/* Seção de resumo das peças no romaneio */}

Peças no Romaneio

Qtd Total: {qtdTotalPecas} Peso Total: {pesoTotalPecas.toFixed(2)} kg Maior Comprimento: {maiorComprimentoPecas > 0 ? `${maiorComprimentoPecas} mm` : '-'}
{/* Seção Adicionar Insumo */}

Adicionar Insumo

setNomeInsumo(e.target.value)} placeholder="Nome do insumo" className={isMobile ? 'h-10' : 'h-11'} />
setQuantidadeInsumo(e.target.value)} placeholder="Quantidade" className={isMobile ? 'h-10' : 'h-11'} />
setUnidadeInsumo(e.target.value)} placeholder="Ex: kg, m, unid" className={isMobile ? 'h-10' : 'h-11'} />
{/* Lista de insumos no romaneio */}

Insumos no Romaneio

{loadingItens ? (
Carregando...
) : itensInsumo.length === 0 ? (
Nenhum insumo adicionado
) : (
Nome
{!isMobile &&
Quantidade
} {!isMobile &&
Unidade
}
{isMobile ? 'Qtd/Unid/Ações' : 'Ações'}
{itensInsumo.map((item) => (
{item.nome}
{isMobile ? (
{item.quantidade} {item.unidade}
) : ( <>
{item.quantidade}
{item.unidade}
)}
))}
)}
{/* Modal expandido para peças */} setShowExpandedModal(false)} romaneio={romaneio} onRefresh={carregarItensRomaneio} /> ); };