fix: correcao no carregamento reativo de pecas e componentes no apontamento de producao

This commit is contained in:
2026-08-21 16:32:13 +00:00
parent 046639321b
commit 36d545d36d
+97 -180
View File
@@ -13,7 +13,6 @@ import { usePecas } from '@/hooks/usePecas';
import { useOFs } from '@/hooks/useOFs'; import { useOFs } from '@/hooks/useOFs';
import { useComponentesAgrupados } from '@/hooks/useComponentesAgrupados'; import { useComponentesAgrupados } from '@/hooks/useComponentesAgrupados';
import { SeletorItensOtimizado } from './SeletorItensOtimizado'; import { SeletorItensOtimizado } from './SeletorItensOtimizado';
import { useApontamentosValidacao } from '@/hooks/useApontamentosValidacao';
import { toast } from 'sonner'; import { toast } from 'sonner';
interface ItemDisponivel { interface ItemDisponivel {
@@ -25,7 +24,7 @@ interface ItemDisponivel {
processo_atual_permitido: number; processo_atual_permitido: number;
} }
// Cache para manter seleções do usuário // Cache local para manter seleções básicas do formulário
const formCache = { const formCache = {
of_number: '', of_number: '',
fase: '', fase: '',
@@ -33,13 +32,6 @@ const formCache = {
data_apontamento: new Date().toISOString().split('T')[0] data_apontamento: new Date().toISOString().split('T')[0]
}; };
// Cache para itens já processados
const itensCache = new Map<string, {
pecasDisponiveis: ItemDisponivel[];
componentesDisponiveis: ItemDisponivel[];
timestamp: number;
}>();
export const ApontamentoForm = () => { export const ApontamentoForm = () => {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
of_number: formCache.of_number || '', of_number: formCache.of_number || '',
@@ -52,55 +44,40 @@ export const ApontamentoForm = () => {
}); });
const [itemSelecionado, setItemSelecionado] = useState<ItemDisponivel | null>(null); const [itemSelecionado, setItemSelecionado] = useState<ItemDisponivel | null>(null);
const [itensDisponiveis, setItensDisponiveis] = useState<{
pecasDisponiveis: ItemDisponivel[];
componentesDisponiveis: ItemDisponivel[];
}>({ pecasDisponiveis: [], componentesDisponiveis: [] });
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [cacheValido, setCacheValido] = useState(false); const [cacheValido, setCacheValido] = useState(false);
const [loadingItens, setLoadingItens] = useState(false);
const [isProcessingItems, setIsProcessingItems] = useState(false);
const { criarApontamento, refetch, processos } = useApontamentosProducao(); const { criarApontamento, refetch, processos, apontamentos, loading: loadingApontamentos } = useApontamentosProducao();
const { pecas } = usePecas(); const { pecas, loading: loadingPecas } = usePecas();
const { ofs } = useOFs(); const { ofs } = useOFs();
const { componentesAgrupados } = useComponentesAgrupados(formData.of_number, formData.fase); const { componentesAgrupados, loading: loadingComponentes } = useComponentesAgrupados(formData.of_number, formData.fase, pecas);
const {
validarSequenciaProcessos,
precarregarDados,
limparCache: limparCacheValidacao
} = useApontamentosValidacao();
// Buscar fases únicas da OF selecionada // Buscar fases únicas da OF selecionada
const fasesDisponiveis = useMemo(() => const fasesDisponiveis = useMemo(() => {
if (!formData.of_number || !pecas.length) return [];
return Array.from(
new Set(
pecas pecas
.filter(peca => peca.of_number === formData.of_number) .filter(peca => peca.of_number === formData.of_number)
.map(peca => peca.etapa_fase) .map(peca => peca.etapa_fase)
.filter((fase, index, array) => fase && array.indexOf(fase) === index) .filter(Boolean)
.sort(), )
[pecas, formData.of_number] ).sort();
); }, [pecas, formData.of_number]);
// Peças filtradas - memoizado para evite recálculos // Processo selecionado atualmente
const filteredPecas = useMemo(() => const processoSelecionado = useMemo(() => {
pecas.filter(peca => return processos.find(p => p.id === formData.processo_id) || null;
peca.of_number === formData.of_number && }, [processos, formData.processo_id]);
peca.etapa_fase === formData.fase &&
!peca.tem_componentes
),
[pecas, formData.of_number, formData.fase]
);
// Chave única para cache
const cacheKey = useMemo(() =>
`${formData.of_number}_${formData.fase}_${formData.processo_id}`,
[formData.of_number, formData.fase, formData.processo_id]
);
// Salvar cache quando seleções básicas mudam // Salvar cache quando seleções básicas mudam
const updateCache = useCallback((updates: Partial<typeof formData>) => { const updateCache = useCallback((updates: Partial<typeof formData>) => {
Object.assign(formCache, updates); Object.assign(formCache, updates);
try {
localStorage.setItem('apontamento_cache', JSON.stringify(formCache)); localStorage.setItem('apontamento_cache', JSON.stringify(formCache));
} catch (e) {
console.warn('Erro ao salvar cache:', e);
}
}, []); }, []);
// Carregar cache inicial // Carregar cache inicial
@@ -124,120 +101,87 @@ export const ApontamentoForm = () => {
} }
}, []); }, []);
// Função para processar itens com cache // Cálculo reativo de peças e componentes disponíveis para o processo selecionado
const processarItensDisponiveis = useCallback(async () => { const itensDisponiveis = useMemo(() => {
const { of_number, fase, processo_id } = formData; const { of_number, fase, processo_id } = formData;
if (!of_number || !fase || !processo_id) { if (!of_number || !fase || !processo_id || !pecas.length) {
console.log('⚠️ Campos obrigatórios faltando para carregar itens'); return { pecasDisponiveis: [], componentesDisponiveis: [] };
setItensDisponiveis({ pecasDisponiveis: [], componentesDisponiveis: [] });
return;
} }
// Verificar se já temos no cache (válido por 30 segundos) const ordemProcesso = processoSelecionado?.ordem || 1;
const cached = itensCache.get(cacheKey);
const now = Date.now();
if (cached && (now - cached.timestamp) < 30000) {
console.log('📦 Usando itens do cache');
setItensDisponiveis(cached);
return;
}
// Aguardar dados das peças e componentes // 1. Peças da OF e Fase selecionadas
if (filteredPecas.length === 0 && componentesAgrupados.length === 0) { const pecasDaFase = pecas.filter(
console.log('⏳ Aguardando dados de peças e componentes...'); p => p.of_number === of_number && p.etapa_fase === fase
return; );
}
if (isProcessingItems) { const pecasDisponiveis: ItemDisponivel[] = [];
console.log('🔄 Já processando itens, aguardando...');
return;
}
console.log('\n🚀 === PROCESSANDO ITENS COM CACHE ==='); pecasDaFase.forEach(peca => {
console.log(`📋 OF: ${of_number}, Fase: ${fase}, Processo: ${processo_id}`); // Calcular quanto já foi apontado desta peça neste processo
const totalApontado = apontamentos
.filter(a => a.tipo_apontamento === 'peca' && a.peca_id === peca.id && a.processo_id === processo_id)
.reduce((sum, a) => sum + (Number(a.quantidade_produzida) || 0), 0);
setIsProcessingItems(true); const saldoDisponivel = Math.max(0, (Number(peca.quantidade) || 0) - totalApontado);
setLoadingItens(true);
try { if (saldoDisponivel > 0) {
// Calcular itens disponíveis baseado nos dados existentes pecasDisponiveis.push({
console.log('🧮 Calculando itens disponíveis...');
const itens = {
pecasDisponiveis: filteredPecas.map(peca => ({
id: peca.id, id: peca.id,
marca: peca.marca, marca: peca.marca,
descricao: peca.descricao, descricao: peca.descricao || '',
tipo: 'peca' as const, tipo: 'peca',
quantidade_disponivel: peca.quantidade, quantidade_disponivel: saldoDisponivel,
processo_atual_permitido: 1 processo_atual_permitido: ordemProcesso
})), });
componentesDisponiveis: componentesAgrupados.map(comp => ({ }
id: comp.componente_ids[0] || '', // Use primeiro ID do array });
// 2. Componentes da OF e Fase selecionadas
const componentesDisponiveis: ItemDisponivel[] = [];
if (componentesAgrupados && componentesAgrupados.length > 0) {
componentesAgrupados.forEach(comp => {
// Calcular quanto já foi apontado deste componente neste processo
const totalApontadoComp = apontamentos
.filter(a => a.tipo_apontamento === 'componente' && comp.componente_ids.includes(a.componente_id || '') && a.processo_id === processo_id)
.reduce((sum, a) => sum + (Number(a.quantidade_produzida) || 0), 0);
const saldoComp = Math.max(0, (Number(comp.quantidade_total) || 0) - totalApontadoComp);
if (saldoComp > 0) {
componentesDisponiveis.push({
id: comp.componente_ids[0] || '',
marca: comp.marca_componente, marca: comp.marca_componente,
descricao: comp.descricao || '', descricao: comp.descricao || comp.perfil || '',
tipo: 'componente' as const, tipo: 'componente',
quantidade_disponivel: comp.quantidade_total, quantidade_disponivel: saldoComp,
processo_atual_permitido: 1 processo_atual_permitido: ordemProcesso
}))
};
console.log('✅ Itens calculados:', {
pecas: itens.pecasDisponiveis.length,
componentes: itens.componentesDisponiveis.length
}); });
}
// 4. Salvar no cache
itensCache.set(cacheKey, {
...itens,
timestamp: now
}); });
// 5. Atualizar estado
setItensDisponiveis(itens);
} catch (error) {
console.error('❌ Erro ao processar itens:', error);
setItensDisponiveis({ pecasDisponiveis: [], componentesDisponiveis: [] });
} finally {
setLoadingItens(false);
setIsProcessingItems(false);
} }
}, [
formData.of_number,
formData.fase,
formData.processo_id
]);
// Callback para atualizar dados return { pecasDisponiveis, componentesDisponiveis };
const updateData = useCallback(() => { }, [formData.of_number, formData.fase, formData.processo_id, pecas, apontamentos, processoSelecionado, componentesAgrupados]);
// 4. Atualizar dados para nova seleção
console.log('✅ Dados atualizados para nova seleção de OF/processo');
}, []);
// Efeito controlado para carregar itens // Sincronizar item selecionado caso não exista mais na lista disponível
useEffect(() => { useEffect(() => {
if (formData.of_number && formData.fase && formData.processo_id) { if (itemSelecionado) {
// Usar timeout para evitar chamadas excessivas const listaAtual = itemSelecionado.tipo === 'peca'
const timeoutId = setTimeout(() => { ? itensDisponiveis.pecasDisponiveis
processarItensDisponiveis(); : itensDisponiveis.componentesDisponiveis;
}, 300);
return () => clearTimeout(timeoutId); const itemAindaExiste = listaAtual.find(i => i.id === itemSelecionado.id);
} else { if (!itemAindaExiste) {
setItensDisponiveis({ pecasDisponiveis: [], componentesDisponiveis: [] });
}
}, [formData.of_number, formData.fase, formData.processo_id]);
// Reset do item selecionado quando dados mudam
useEffect(() => {
setItemSelecionado(null); setItemSelecionado(null);
setFormData(prev => ({ setFormData(prev => ({ ...prev, quantidade_produzida: '', todas_disponiveis: false }));
...prev, } else if (itemAindaExiste.quantidade_disponivel !== itemSelecionado.quantidade_disponivel) {
quantidade_produzida: '', setItemSelecionado(itemAindaExiste);
todas_disponiveis: false }
})); }
}, [formData.of_number, formData.fase, formData.processo_id]); }, [itensDisponiveis, itemSelecionado]);
// Auto-preenchimento da quantidade quando "todas disponíveis" é marcado // Auto-preenchimento da quantidade quando "todas disponíveis" é marcado
useEffect(() => { useEffect(() => {
@@ -259,9 +203,9 @@ export const ApontamentoForm = () => {
const tipoTexto = tipo === 'peca' ? 'peças' : 'componentes'; const tipoTexto = tipo === 'peca' ? 'peças' : 'componentes';
const confirmacao = window.confirm( const confirmacao = window.confirm(
`Deseja registrar ${totalItens} ${tipoTexto} com suas respectivas quantidades totais?\n\n` + `Deseja registrar ${totalItens} ${tipoTexto} com suas respectivas quantidades totais disponíveis?\n\n` +
`Total de itens: ${totalItens}\n` + `Total de itens: ${totalItens}\n` +
`Processo: ${formData.processo_id || 'N/A'}` `Processo: ${processoSelecionado?.nome || 'N/A'}`
); );
if (!confirmacao) return; if (!confirmacao) return;
@@ -303,11 +247,8 @@ export const ApontamentoForm = () => {
if (sucessos > 0) { if (sucessos > 0) {
toast.success(`${sucessos} ${tipoTexto} registradas com sucesso!${erros > 0 ? ` (${erros} com erro)` : ''}`); toast.success(`${sucessos} ${tipoTexto} registradas com sucesso!${erros > 0 ? ` (${erros} com erro)` : ''}`);
await refetch();
await Promise.all([ resetFormForNewEntry();
refetch(),
resetFormForNewEntry()
]);
} else { } else {
toast.error(`Erro ao registrar ${tipoTexto} em lote`); toast.error(`Erro ao registrar ${tipoTexto} em lote`);
} }
@@ -331,9 +272,6 @@ export const ApontamentoForm = () => {
setFormData(prev => ({ ...prev, ...updates })); setFormData(prev => ({ ...prev, ...updates }));
updateCache({ of_number: ofNumber, fase: '', processo_id: '' }); updateCache({ of_number: ofNumber, fase: '', processo_id: '' });
setItemSelecionado(null); setItemSelecionado(null);
setItensDisponiveis({ pecasDisponiveis: [], componentesDisponiveis: [] });
// Limpar cache relacionado
itensCache.clear();
}; };
const handleFaseChange = (fase: string) => { const handleFaseChange = (fase: string) => {
@@ -346,9 +284,6 @@ export const ApontamentoForm = () => {
setFormData(prev => ({ ...prev, ...updates })); setFormData(prev => ({ ...prev, ...updates }));
updateCache({ fase: fase, processo_id: '' }); updateCache({ fase: fase, processo_id: '' });
setItemSelecionado(null); setItemSelecionado(null);
setItensDisponiveis({ pecasDisponiveis: [], componentesDisponiveis: [] });
// Limpar cache relacionado
itensCache.clear();
}; };
const handleProcessoChange = (processoId: string) => { const handleProcessoChange = (processoId: string) => {
@@ -360,7 +295,6 @@ export const ApontamentoForm = () => {
setFormData(prev => ({ ...prev, ...updates })); setFormData(prev => ({ ...prev, ...updates }));
updateCache({ processo_id: processoId }); updateCache({ processo_id: processoId });
setItemSelecionado(null); setItemSelecionado(null);
setItensDisponiveis({ pecasDisponiveis: [], componentesDisponiveis: [] });
}; };
const handleItemSelect = (item: ItemDisponivel) => { const handleItemSelect = (item: ItemDisponivel) => {
@@ -387,10 +321,7 @@ export const ApontamentoForm = () => {
})); }));
}; };
// Função para resetar form e atualizar dados const resetFormForNewEntry = () => {
const resetFormForNewEntry = async () => {
console.log('🔄 Resetando formulário e limpando cache...');
setItemSelecionado(null); setItemSelecionado(null);
setFormData(prev => ({ setFormData(prev => ({
...prev, ...prev,
@@ -398,9 +329,6 @@ export const ApontamentoForm = () => {
observacoes: '', observacoes: '',
todas_disponiveis: false todas_disponiveis: false
})); }));
// Limpar cache e forçar recarregamento
itensCache.clear();
}; };
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
@@ -413,7 +341,7 @@ export const ApontamentoForm = () => {
const quantidade = parseInt(formData.quantidade_produzida); const quantidade = parseInt(formData.quantidade_produzida);
if (quantidade <= 0 || quantidade > itemSelecionado.quantidade_disponivel) { if (isNaN(quantidade) || quantidade <= 0 || quantidade > itemSelecionado.quantidade_disponivel) {
toast.error('Quantidade inválida'); toast.error('Quantidade inválida');
return; return;
} }
@@ -421,9 +349,6 @@ export const ApontamentoForm = () => {
setSaving(true); setSaving(true);
try { try {
// Validação básica - pode ser expandida depois
console.log('✅ Validação de sequência aprovada');
const apontamentoData: any = { const apontamentoData: any = {
of_number: formData.of_number, of_number: formData.of_number,
tipo_apontamento: itemSelecionado.tipo, tipo_apontamento: itemSelecionado.tipo,
@@ -443,11 +368,8 @@ export const ApontamentoForm = () => {
if (result.success) { if (result.success) {
toast.success('Apontamento registrado com sucesso!'); toast.success('Apontamento registrado com sucesso!');
await refetch();
await Promise.all([ resetFormForNewEntry();
refetch(),
resetFormForNewEntry()
]);
} }
} catch (error) { } catch (error) {
console.error('Erro no submit:', error); console.error('Erro no submit:', error);
@@ -475,13 +397,11 @@ export const ApontamentoForm = () => {
todas_disponiveis: false todas_disponiveis: false
}); });
setItemSelecionado(null); setItemSelecionado(null);
setItensDisponiveis({ pecasDisponiveis: [], componentesDisponiveis: [] });
setCacheValido(false); setCacheValido(false);
itensCache.clear();
toast.success('Cache limpo com sucesso!'); toast.success('Cache limpo com sucesso!');
}; };
const processoSelecionado = null; const isLoadingItens = loadingPecas || loadingApontamentos || loadingComponentes;
return ( return (
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
@@ -583,15 +503,12 @@ export const ApontamentoForm = () => {
<Alert> <Alert>
<Info className="h-4 w-4" /> <Info className="h-4 w-4" />
<AlertDescription> <AlertDescription>
{processoSelecionado.ordem === 1 {`Processo: ${processoSelecionado.ordem}. ${processoSelecionado.nome}. Selecione as peças ou componentes com saldo pendente para apontar.`}
? `Processo inicial: ${processoSelecionado.nome}. Todos os itens estão disponíveis.`
: `Processo ${processoSelecionado.ordem}: ${processoSelecionado.nome}. Apenas itens que passaram pelos processos anteriores estão disponíveis.`
}
</AlertDescription> </AlertDescription>
</Alert> </Alert>
)} )}
{/* Seletor de itens otimizado com funcionalidade de lote - agora com scroll */} {/* Seletor de itens otimizado com funcionalidade de lote */}
{formData.processo_id && ( {formData.processo_id && (
<div className="max-h-96 overflow-y-auto"> <div className="max-h-96 overflow-y-auto">
<SeletorItensOtimizado <SeletorItensOtimizado
@@ -600,7 +517,7 @@ export const ApontamentoForm = () => {
itemSelecionado={itemSelecionado} itemSelecionado={itemSelecionado}
onItemSelect={handleItemSelect} onItemSelect={handleItemSelect}
onBatchSelect={handleBatchSelect} onBatchSelect={handleBatchSelect}
loading={loadingItens || isProcessingItems} loading={isLoadingItens}
/> />
</div> </div>
)} )}
@@ -660,7 +577,7 @@ export const ApontamentoForm = () => {
</h4> </h4>
{!itemSelecionado ? ( {!itemSelecionado ? (
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
Selecione um item para ver as informações ou use os checkboxes para registro em lote Selecione um item para ver as informações ou use os botões para registro em lote
</div> </div>
) : ( ) : (
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
@@ -668,7 +585,7 @@ export const ApontamentoForm = () => {
<div><strong>Marca:</strong> {itemSelecionado.marca}</div> <div><strong>Marca:</strong> {itemSelecionado.marca}</div>
<div><strong>OF:</strong> {formData.of_number}</div> <div><strong>OF:</strong> {formData.of_number}</div>
<div><strong>Fase:</strong> {formData.fase}</div> <div><strong>Fase:</strong> {formData.fase}</div>
<div><strong>Processo:</strong> {processoSelecionado?.nome || 'N/A'}</div> <div><strong>Processo:</strong> {processoSelecionado ? `${processoSelecionado.ordem}. ${processoSelecionado.nome}` : 'N/A'}</div>
<div><strong>Descrição:</strong> {itemSelecionado.descricao || 'N/A'}</div> <div><strong>Descrição:</strong> {itemSelecionado.descricao || 'N/A'}</div>
<div><strong>Quantidade Disponível:</strong> {itemSelecionado.quantidade_disponivel} unidades</div> <div><strong>Quantidade Disponível:</strong> {itemSelecionado.quantidade_disponivel} unidades</div>
</div> </div>
@@ -676,7 +593,7 @@ export const ApontamentoForm = () => {
</CardContent> </CardContent>
</Card> </Card>
{/* Botões movidos para baixo do card de informações */} {/* Botões de ação */}
<div className="flex justify-end space-x-2"> <div className="flex justify-end space-x-2">
{formData.of_number && formData.fase && formData.processo_id && ( {formData.of_number && formData.fase && formData.processo_id && (
<Button <Button
@@ -690,7 +607,7 @@ export const ApontamentoForm = () => {
)} )}
<Button <Button
type="submit" type="submit"
disabled={saving || !itemSelecionado || !formData.processo_id || loadingItens || isProcessingItems} disabled={saving || !itemSelecionado || !formData.processo_id || isLoadingItens}
className="min-w-32" className="min-w-32"
> >
{saving ? 'Salvando...' : 'Registrar Apontamento'} {saving ? 'Salvando...' : 'Registrar Apontamento'}