Files
tracksteel_app/src/hooks/useApontamentosProducaoOtimizado.tsx
T

417 lines
12 KiB
TypeScript

import { useState, useCallback, useRef, useEffect } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
import { OptimizedCache } from '@/utils/OptimizedCache';
interface ItemDisponivel {
id: string;
tipo: 'peca' | 'componente';
marca: string;
descricao: string;
peso_unitario: number;
quantidade_total: number;
quantidade_produzida: number;
quantidade_disponivel: number;
peca_pai?: string;
}
interface ItensDisponiveis {
pecasDisponiveis: ItemDisponivel[];
componentesDisponiveis: ItemDisponivel[];
}
interface ProcessoFabricacao {
id: string;
nome: string;
ordem: number;
cor?: string;
ativo: boolean;
}
interface ResultadoValidacao {
valido: boolean;
motivo?: string;
proximoProcesso?: ProcessoFabricacao;
}
interface ApontamentoHistorico {
id: string;
of_number: string;
processo_id: string;
peca_id?: string;
componente_id?: string;
quantidade_produzida: number;
data_apontamento: string;
tipo_apontamento: 'peca' | 'componente';
created_at: string;
}
export const useApontamentosProducaoOtimizado = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [processosOrdenados, setProcessosOrdenados] = useState<ProcessoFabricacao[]>([]);
// Cache refs para performance
const historicoCache = useRef<Map<string, ApontamentoHistorico[]>>(new Map());
const itensCache = useRef<Map<string, ItensDisponiveis>>(new Map());
const processosCache = useRef<Map<string, ProcessoFabricacao[]>>(new Map());
// Função para buscar itens disponíveis otimizada
const calcularItensDisponiveis = useCallback(async (ofNumber?: string, processoId?: string): Promise<ItensDisponiveis> => {
if (!ofNumber) {
return {
pecasDisponiveis: [],
componentesDisponiveis: []
};
}
const cacheKey = `itens_${ofNumber}_${processoId || 'all'}`;
try {
// Verificar cache primeiro
const cached = await OptimizedCache.get(cacheKey, 'ITENS_DISPONIVEIS');
if (cached) {
console.log('📦 Cache HIT - Itens disponíveis:', cacheKey);
return {
pecasDisponiveis: [],
componentesDisponiveis: []
};
}
console.log('🔄 Calculando itens disponíveis para OF:', ofNumber, 'Processo:', processoId);
const startTime = performance.now();
// Buscar apontamentos existentes
const { data: apontamentos } = await supabase
.from('apontamentos_producao')
.select('*')
.eq('of_number', ofNumber);
// Buscar peças da OF
const { data: pecas } = await supabase
.from('pecas')
.select('*')
.eq('of_number', ofNumber);
// Buscar componentes das peças
const { data: componentes } = await supabase
.from('componentes_peca')
.select(`
*,
peca:pecas!inner(of_number, marca)
`)
.eq('peca.of_number', ofNumber);
const resultado: ItensDisponiveis = {
pecasDisponiveis: [],
componentesDisponiveis: []
};
// Calcular peças disponíveis
if (pecas) {
pecas.forEach(peca => {
const apontamentosPeca = apontamentos?.filter(a =>
a.peca_id === peca.id && a.tipo_apontamento === 'peca'
) || [];
const quantidadeProduzida = apontamentosPeca.reduce((sum, a) => sum + (a.quantidade_produzida || 0), 0);
const quantidadeDisponivel = Math.max(0, (peca.quantidade || 0) - quantidadeProduzida);
if (quantidadeDisponivel > 0) {
resultado.pecasDisponiveis.push({
id: peca.id,
tipo: 'peca',
marca: peca.marca || '',
descricao: peca.descricao || '',
peso_unitario: peca.peso_unitario || 0,
quantidade_total: peca.quantidade || 0,
quantidade_produzida: quantidadeProduzida,
quantidade_disponivel: quantidadeDisponivel
});
}
});
}
// Calcular componentes disponíveis
if (componentes) {
componentes.forEach(componente => {
const apontamentosComponente = apontamentos?.filter(a =>
a.componente_id === componente.id && a.tipo_apontamento === 'componente'
) || [];
const quantidadeProduzida = apontamentosComponente.reduce((sum, a) => sum + (a.quantidade_produzida || 0), 0);
// Quantidade total considerando a peça pai
const pecaPai = pecas?.find(p => p.id === componente.peca_id);
const quantidadeTotal = (componente.quantidade_por_peca || 1) * (pecaPai?.quantidade || 0);
const quantidadeDisponivel = Math.max(0, quantidadeTotal - quantidadeProduzida);
if (quantidadeDisponivel > 0) {
resultado.componentesDisponiveis.push({
id: componente.id,
tipo: 'componente',
marca: componente.marca_componente || '',
descricao: componente.descricao || '',
peso_unitario: componente.peso_unitario || 0,
quantidade_total: quantidadeTotal,
quantidade_produzida: quantidadeProduzida,
quantidade_disponivel: quantidadeDisponivel,
peca_pai: pecaPai?.marca
});
}
});
}
// Armazenar no cache
OptimizedCache.set(cacheKey, resultado, 'ITENS_DISPONIVEIS');
const duration = performance.now() - startTime;
console.log(`⚡ Itens calculados em ${duration.toFixed(1)}ms:`, {
pecas: resultado.pecasDisponiveis.length,
componentes: resultado.componentesDisponiveis.length
});
return resultado;
} catch (error) {
console.error('❌ Erro ao calcular itens disponíveis:', error);
return {
pecasDisponiveis: [],
componentesDisponiveis: []
};
}
}, []);
// Função para buscar histórico de apontamentos
const carregarHistoricoOF = useCallback(async (ofNumber: string): Promise<ApontamentoHistorico[]> => {
if (!ofNumber) return [];
const cacheKey = `historico_${ofNumber}`;
try {
// Verificar cache primeiro
const cached = await OptimizedCache.get<ApontamentoHistorico[]>(cacheKey, 'HISTORICO_OF');
if (cached) {
console.log('📦 Cache HIT - Histórico OF:', ofNumber);
return cached;
}
console.log('🔄 Carregando histórico da OF:', ofNumber);
const { data: apontamentos } = await supabase
.from('apontamentos_producao')
.select(`
*,
peca:pecas!apontamentos_producao_peca_id_fkey(marca, descricao),
processo:processos_fabricacao!apontamentos_producao_processo_id_fkey(nome, ordem),
componente:componentes_peca!apontamentos_producao_componente_id_fkey(marca_componente, descricao)
`)
.eq('of_number', ofNumber)
.order('created_at', { ascending: false });
const historico: ApontamentoHistorico[] = (apontamentos || []).map(a => ({
id: a.id,
of_number: a.of_number,
processo_id: a.processo_id,
peca_id: a.peca_id,
componente_id: a.componente_id,
quantidade_produzida: a.quantidade_produzida,
data_apontamento: a.data_apontamento,
tipo_apontamento: (a.tipo_apontamento as 'peca' | 'componente') || 'peca',
created_at: a.created_at
}));
// Armazenar no cache
OptimizedCache.set(cacheKey, historico, 'HISTORICO_OF');
console.log(`📚 Histórico carregado: ${historico.length} apontamentos`);
return historico;
} catch (error) {
console.error('❌ Erro ao carregar histórico:', error);
return [];
}
}, []);
// Função para validar sequência de processos
const validarSequencia = useCallback(async (
ofNumber: string,
processoId: string,
tipoItem: 'peca' | 'componente',
itemId: string
): Promise<ResultadoValidacao> => {
const cacheKey = `validacao_${ofNumber}_${processoId}_${tipoItem}_${itemId}`;
try {
// Verificar cache primeiro
const cached = await OptimizedCache.get<ResultadoValidacao>(cacheKey, 'PROCESSOS');
if (cached) {
return cached;
}
// Se não há cache, retornar resultado padrão válido
const resultado: ResultadoValidacao = {
valido: true,
motivo: undefined
};
// Cache temporário (validações podem mudar rapidamente)
if (Math.random() > 0.1) { // 90% chance de cachear
OptimizedCache.set(cacheKey, resultado, 'PROCESSOS');
}
return resultado;
} catch (error) {
console.error('❌ Erro na validação de sequência:', error);
return {
valido: false,
motivo: 'Erro interno na validação'
};
}
}, []);
// Buscar processos ordenados
const carregarProcessos = useCallback(async () => {
const cacheKey = 'processos_ativos';
try {
// Verificar cache primeiro
const cached = await OptimizedCache.get<ProcessoFabricacao[]>(cacheKey, 'PROCESSOS');
if (cached) {
setProcessosOrdenados(cached);
return cached;
}
const { data: processos } = await supabase
.from('processos_fabricacao')
.select('*')
.eq('ativo', true)
.order('ordem');
const processosLista = processos || [];
// Armazenar no cache
OptimizedCache.set(cacheKey, processosLista, 'PROCESSOS');
setProcessosOrdenados(processosLista);
return processosLista;
} catch (error) {
console.error('❌ Erro ao carregar processos:', error);
setError('Erro ao carregar processos de fabricação');
return [];
}
}, []);
// Invalidar caches específicos
const invalidarCache = useCallback((ofNumber?: string) => {
if (ofNumber) {
// Invalidar caches específicos da OF
OptimizedCache.invalidatePattern(`_${ofNumber}_`);
OptimizedCache.invalidatePattern(`historico_${ofNumber}`);
OptimizedCache.invalidatePattern(`itens_${ofNumber}`);
} else {
// Limpar todos os caches
OptimizedCache.clear();
OptimizedCache.clear();
OptimizedCache.clear();
}
// Limpar cache refs
historicoCache.current.clear();
itensCache.current.clear();
processosCache.current.clear();
}, []);
// Limpar caches ao desmontar
useEffect(() => {
return () => {
invalidarCache();
};
}, [invalidarCache]);
// Função para obter estatísticas de performance
const obterEstatisticas = useCallback(() => {
try {
const cacheStats = OptimizedCache.getStats();
const performanceStats = OptimizedCache.getStats();
const memoryStats = OptimizedCache.getStats();
return {
cache: {
entries: cacheStats.memoryEntries,
storageEntries: cacheStats.localStorageEntries,
totalSize: cacheStats.totalSize
},
performance: {
totalOperations: performanceStats.memoryEntries,
averageTime: performanceStats.localStorageEntries,
cacheHitRate: performanceStats.totalSize
},
memory: {
historico: historicoCache.current.size,
itens: itensCache.current.size,
processos: processosCache.current.size
}
};
} catch (error) {
console.error('Erro ao obter estatísticas:', error);
return null;
}
}, []);
// Função para pré-carregar dados críticos
const precarregarDados = useCallback(async (ofNumber: string) => {
if (!ofNumber) return;
try {
setLoading(true);
// Pré-carregar em paralelo
const promises = [
carregarProcessos(),
carregarHistoricoOF(ofNumber),
calcularItensDisponiveis(ofNumber)
];
await Promise.all(promises);
} catch (error) {
console.error('❌ Erro no pré-carregamento:', error);
setError('Erro ao pré-carregar dados');
} finally {
setLoading(false);
}
}, [carregarProcessos, carregarHistoricoOF, calcularItensDisponiveis]);
// Carregar processos na inicialização
useEffect(() => {
carregarProcessos();
}, [carregarProcessos]);
return {
// Estados
loading,
error,
processosOrdenados,
// Funções principais
calcularItensDisponiveis,
carregarHistoricoOF,
validarSequencia,
carregarProcessos,
// Utilitários
invalidarCache,
precarregarDados,
obterEstatisticas,
// Limpeza
limparCache: invalidarCache
};
};
export default useApontamentosProducaoOtimizado;