import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { ScrollArea } from "@/components/ui/scroll-area"; import { toast } from 'sonner'; import { Search, Package, Shield, AlertTriangle, Settings, Check, Loader2, Layers } from 'lucide-react'; import { useDebounce } from '@/hooks/useDebounce'; import { Peca } from '@/hooks/usePecas'; import { useComponentesPeca } from '@/hooks/useComponentesPeca'; import { useUserRole } from '@/hooks/useUserRole'; import { supabase } from '@/integrations/supabase/client'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; export interface ItemDisponivel { id: string; marca: string; descricao: string; perfil?: string; tipo: 'peca' | 'componente'; quantidade_disponivel: number; processo_atual_permitido: number; nome_processo?: string; } interface ComponenteItemData { id: string; marca_componente: string; perfil?: string; peso_unitario?: number; quantidade_por_peca?: number; descricao?: string; } interface ApontamentoPecaRecord { id: string; quantidade_produzida: number; data_apontamento?: string; created_at: string; usuario_id?: string; processo?: { nome?: string; ordem?: number } | null; peca?: { marca?: string; of_number?: string } | null; } interface SeletorItensOtimizadoProps { pecasDisponiveis?: ItemDisponivel[]; componentesDisponiveis?: ItemDisponivel[]; itemSelecionado?: ItemDisponivel | null; onItemSelect?: (item: ItemDisponivel) => void; onBatchSelect?: (items: ItemDisponivel[], tipo: 'peca' | 'componente') => Promise; onApontarItemDireto?: (item: ItemDisponivel, quantidade: number) => Promise; loading?: boolean; onSelectPeca?: (peca: PecaWithComponents) => void; onSelectComponente?: (componente: ComponenteItemData) => void; pecaId?: string | null; } interface PecaWithComponents extends Peca { componentes?: ComponenteItemData[]; processo_atual_permitido?: number; } const getPrioridadeColor = (prioridade?: string) => { switch (prioridade?.toLowerCase()) { case 'alta': return 'text-red-600 dark:text-red-400'; case 'média': return 'text-amber-600 dark:text-yellow-400'; case 'baixa': return 'text-emerald-600 dark:text-green-400'; default: return 'text-muted-foreground'; } }; const PecaItem = React.memo(({ peca, onSelect }: { peca: PecaWithComponents; onSelect: (peca: PecaWithComponents) => void }) => { return (
onSelect(peca)} >
{peca.marca}
Prioridade: {peca.prioridade || 'N/A'}
{peca.descricao && (
{peca.descricao}
)}
Qtd Disponível: {peca.quantidadeDisponivel || 0}
); }); PecaItem.displayName = 'PecaItem'; const ComponenteItem = React.memo(({ componente, onSelect }: { componente: ComponenteItemData; onSelect: (componente: ComponenteItemData) => void }) => (
onSelect(componente)} >
{componente.marca_componente}
Perfil: {componente.perfil || 'N/A'}
Peso Unitário: {componente.peso_unitario || 0}kg | Qtd por Peça: {componente.quantidade_por_peca || 1}
{componente.descricao && (
{componente.descricao}
)}
)); ComponenteItem.displayName = 'ComponenteItem'; interface ItemDisponivelCardProps { item: ItemDisponivel; isSelected: boolean; isAdmin?: boolean; onSelect?: (item: ItemDisponivel) => void; onAdminAction?: (item: ItemDisponivel) => void; onApontarItemDireto?: (item: ItemDisponivel, quantidade: number) => Promise; } const ItemDisponivelCard = React.memo(({ item, isSelected, isAdmin, onSelect, onAdminAction, onApontarItemDireto, }: ItemDisponivelCardProps) => { const [qtdValue, setQtdValue] = useState(''); const [isTodas, setIsTodas] = useState(false); const [isLoading, setIsLoading] = useState(false); // Mantém sincronizado se a quantidade disponível mudar ou modo 'todas' estiver ativo useEffect(() => { if (isTodas) { setQtdValue(item.quantidade_disponivel.toString()); } else if (qtdValue) { const parsed = parseInt(qtdValue, 10); if (!isNaN(parsed) && parsed > item.quantidade_disponivel) { setQtdValue(item.quantidade_disponivel.toString()); } } }, [item.quantidade_disponivel, isTodas]); const handleToggleTodas = () => { setIsTodas((prev) => { const novo = !prev; setQtdValue(novo ? item.quantidade_disponivel.toString() : ''); return novo; }); }; const handleQtdChange = (val: string) => { setQtdValue(val); if (isTodas) { setIsTodas(false); } }; const handleExecutarBaixa = async () => { if (!onApontarItemDireto || isLoading) return; const quantidade = isTodas ? item.quantidade_disponivel : parseInt(qtdValue, 10); if (isNaN(quantidade) || quantidade <= 0) { toast.error(`Informe a quantidade para ${item.marca} ou ative 'Todas'`); return; } if (quantidade > item.quantidade_disponivel) { toast.error(`Quantidade não pode ser maior que ${item.quantidade_disponivel} disponível`); return; } setIsLoading(true); try { const sucesso = await onApontarItemDireto(item, quantidade); if (sucesso) { setQtdValue(''); setIsTodas(false); } } catch (error) { console.error('Erro ao executar baixa direta:', error); toast.error('Erro ao registrar apontamento'); } finally { setIsLoading(false); } }; return (
onSelect?.(item)} >
{item.marca} {item.perfil && ( {item.perfil} )}
{item.quantidade_disponivel} disponível
{item.descricao && item.descricao !== item.perfil && (
{item.descricao}
)} {/* Linha com processo e controles rápidos de apontamento */}
Processo: {item.nome_processo || item.processo_atual_permitido}
{onApontarItemDireto && (
e.stopPropagation()} > handleQtdChange(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleExecutarBaixa(); } }} disabled={isTodas || isLoading} className="h-7 w-20 text-xs px-2 text-center font-medium bg-background border-input shadow-none" />
)}
{isAdmin && onAdminAction && ( )}
); }); ItemDisponivelCard.displayName = 'ItemDisponivelCard'; export const SeletorItensOtimizado: React.FC = ({ pecasDisponiveis = [], componentesDisponiveis = [], itemSelecionado, onItemSelect, onBatchSelect, onApontarItemDireto, loading = false, onSelectPeca, onSelectComponente, pecaId }) => { const [searchTerm, setSearchTerm] = useState(''); const [filtroNumeroPeca, setFiltroNumeroPeca] = useState(''); const debouncedSearchTerm = useDebounce(searchTerm, 300); const [pecasFiltradas, setPecasFiltradas] = useState([]); const [loadingPecas, setLoadingPecas] = useState(false); const [loadingComponentes, setLoadingComponentes] = useState(false); const [componentes, setComponentes] = useState([]); const [showComponentes, setShowComponentes] = useState(false); const inputRef = useRef(null); // Estados para funcionalidade administrativa const [showAdminModal, setShowAdminModal] = useState(false); const [selectedPecaForAdmin, setSelectedPecaForAdmin] = useState(null); const [apontamentosPeca, setApontamentosPeca] = useState([]); const [loadingApontamentos, setLoadingApontamentos] = useState(false); const { isAdmin } = useUserRole(); const { fetchComponentesPeca } = useComponentesPeca(); // Função para buscar apontamentos de uma peça específica const buscarApontamentosPeca = useCallback(async (peca: ItemDisponivel) => { setLoadingApontamentos(true); try { const { data, error } = await supabase .from('apontamentos_producao') .select(` id, quantidade_produzida, data_apontamento, created_at, processo:processos_fabricacao!apontamentos_producao_processo_id_fkey(nome, ordem), peca:pecas!apontamentos_producao_peca_id_fkey(marca, of_number) `) .eq('tipo_apontamento', 'peca') .eq('peca_id', peca.id) .order('created_at', { ascending: false }); if (error) { console.error('Erro ao buscar apontamentos da peça:', error); toast.error('Erro ao buscar apontamentos da peça'); return; } setApontamentosPeca(data || []); } catch (error) { console.error('Erro ao buscar apontamentos:', error); toast.error('Erro ao buscar apontamentos'); } finally { setLoadingApontamentos(false); } }, []); // Função para abrir o modal administrativo const handleAdminAction = useCallback(async (peca: ItemDisponivel) => { setSelectedPecaForAdmin(peca); setShowAdminModal(true); await buscarApontamentosPeca(peca); }, [buscarApontamentosPeca]); // Função para forçar exclusão da peça da tela const handleForceRemove = useCallback(async () => { if (!selectedPecaForAdmin) return; try { toast.success(`Peça ${selectedPecaForAdmin.marca} foi removida da exibição`); setShowAdminModal(false); setSelectedPecaForAdmin(null); setApontamentosPeca([]); } catch (error) { console.error('Erro ao remover peça:', error); toast.error('Erro ao remover peça da exibição'); } }, [selectedPecaForAdmin]); const pecasOrdenadas = useMemo(() => { let pecasFiltradas = pecasDisponiveis; if (filtroNumeroPeca) { const termo = filtroNumeroPeca.toLowerCase().trim(); pecasFiltradas = pecasDisponiveis.filter(peca => peca.marca.toLowerCase().includes(termo) || (peca.perfil && peca.perfil.toLowerCase().includes(termo)) || (peca.descricao && peca.descricao.toLowerCase().includes(termo)) ); } return pecasFiltradas.sort((a, b) => a.marca.localeCompare(b.marca, undefined, { numeric: true, sensitivity: 'base' })); }, [pecasDisponiveis, filtroNumeroPeca]); const componentesOrdenados = useMemo(() => { let comps = componentesDisponiveis; if (filtroNumeroPeca) { const termo = filtroNumeroPeca.toLowerCase().trim(); comps = componentesDisponiveis.filter(comp => comp.marca.toLowerCase().includes(termo) || (comp.perfil && comp.perfil.toLowerCase().includes(termo)) || (comp.descricao && comp.descricao.toLowerCase().includes(termo)) ); } return comps.sort((a, b) => a.marca.localeCompare(b.marca, undefined, { numeric: true, sensitivity: 'base' })); }, [componentesDisponiveis, filtroNumeroPeca]); const fetchPecas = useCallback(async (term: string) => { if (!term.trim()) { setPecasFiltradas([]); return; } try { setLoadingPecas(true); } catch (error) { console.error('Erro ao buscar peças:', error); } finally { setLoadingPecas(false); } }, []); const loadComponentes = useCallback(async (pecaId: string) => { try { setLoadingComponentes(true); const componentesData = await fetchComponentesPeca(pecaId); setComponentes(componentesData || []); setShowComponentes(true); } catch (error) { console.error('Erro ao carregar componentes:', error); toast.error('Erro ao carregar componentes da peça'); } finally { setLoadingComponentes(false); } }, [fetchComponentesPeca]); useEffect(() => { if (onSelectPeca) { fetchPecas(debouncedSearchTerm); } }, [debouncedSearchTerm, fetchPecas, onSelectPeca]); useEffect(() => { if (pecaId && onSelectComponente) { loadComponentes(pecaId); } }, [pecaId, onSelectComponente, loadComponentes]); const handlePecaSelect = useCallback((peca: PecaWithComponents) => { if (onSelectPeca) { onSelectPeca(peca); } else if (onItemSelect) { const itemDisponivel: ItemDisponivel = { id: peca.id, marca: peca.marca, descricao: peca.descricao || '', tipo: 'peca', quantidade_disponivel: peca.quantidadeDisponivel || 0, processo_atual_permitido: peca.processo_atual_permitido || 0, nome_processo: (peca as any).nome_processo }; onItemSelect(itemDisponivel); } }, [onSelectPeca, onItemSelect]); const handleComponenteSelect = useCallback((componente: ComponenteItemData) => { if (onSelectComponente) { onSelectComponente(componente); } }, [onSelectComponente]); const handleItemSelect = useCallback((item: ItemDisponivel) => { if (onItemSelect) { onItemSelect(item); } }, [onItemSelect]); const handleBatchApontamento = useCallback(async (tipo: 'peca' | 'componente') => { if (!onBatchSelect) return; try { const items = tipo === 'peca' ? pecasOrdenadas : componentesOrdenados; await onBatchSelect(items, tipo); } catch (error) { console.error('Erro no apontamento em lote:', error); toast.error('Erro ao realizar apontamento em lote'); } }, [onBatchSelect, pecasOrdenadas, componentesOrdenados]); if (onSelectPeca) { return (
setSearchTerm(e.target.value)} ref={inputRef} className="bg-background border-input text-foreground placeholder:text-muted-foreground shadow-sm h-9" />
{loadingPecas ? (
Carregando peças...
) : (
{pecasFiltradas.map((peca) => ( ))} {pecasFiltradas.length === 0 && (
Nenhuma peça encontrada.
)}
)} {showComponentes && (

Componentes da Peça

{loadingComponentes ? (
Carregando componentes...
) : (
{componentes.map((componente) => ( ))} {componentes.length === 0 && (
Nenhum componente cadastrado para esta peça.
)}
)}
)}
); } return ( <>
{loading ? (
Carregando itens disponíveis...
) : ( <> {/* Seção de Peças Disponíveis */} {pecasDisponiveis.length > 0 && (

Peças Disponíveis

setFiltroNumeroPeca(e.target.value)} className="bg-background border-input text-foreground placeholder:text-muted-foreground h-8 w-56 shadow-sm text-xs" /> {isAdmin && ( )}
{pecasOrdenadas.map((item) => ( ))} {pecasOrdenadas.length === 0 && (
{filtroNumeroPeca ? 'Nenhuma peça encontrada com esse filtro.' : 'Nenhuma peça disponível.'}
)}
)} {/* Seção de Componentes Disponíveis */} {componentesDisponiveis.length > 0 && (

Componentes para Corte (Controle de Kit)

{componentesOrdenados.map((item) => ( ))}
)} {/* Mensagem quando não há itens */} {pecasDisponiveis.length === 0 && componentesDisponiveis.length === 0 && (
Nenhum item disponível para este processo.
)} )}
{/* Modal Administrativo */} Ferramenta Administrativa - Forçar Exclusão Verificação de apontamentos e opção de forçar exclusão da peça da exibição atual. {selectedPecaForAdmin && (

Informações da Peça

Marca: {selectedPecaForAdmin.marca}
Descrição: {selectedPecaForAdmin.descricao}
Quantidade Disponível: {selectedPecaForAdmin.quantidade_disponivel}
Processo Atual: {selectedPecaForAdmin.nome_processo || selectedPecaForAdmin.processo_atual_permitido}
{loadingApontamentos ? (
Carregando histórico de apontamentos...
) : (

Histórico de Apontamentos ({apontamentosPeca.length})

{apontamentosPeca.length > 0 ? ( <>
Atenção!

Esta peça possui {apontamentosPeca.length} apontamento(s) registrado(s). Forçar a exclusão irá removê-la da exibição atual, mas não afetará os dados já salvos.

{apontamentosPeca.map((apontamento, index) => (
Processo: {apontamento.processo?.nome || 'N/A'}
Quantidade: {apontamento.quantidade_produzida}
Data: {new Date(apontamento.created_at).toLocaleString('pt-BR')}
Usuário: {apontamento.usuario_id}
))}
) : (
✅ Nenhum apontamento encontrado para esta peça. É seguro removê-la da exibição.
)}
)}
)}
); };