497 lines
16 KiB
TypeScript
497 lines
16 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import { supabase } from '@/integrations/supabase/client';
|
|
import { useAuth } from '@/hooks/useAuth';
|
|
import { toast } from 'sonner';
|
|
|
|
// Função utilitária para retry de requisições
|
|
const retryRequest = async <T,>(
|
|
requestFn: () => Promise<T>,
|
|
maxRetries: number = 3,
|
|
delay: number = 1000
|
|
): Promise<T> => {
|
|
let lastError: Error;
|
|
|
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
try {
|
|
console.log(`🔄 Tentativa ${attempt}/${maxRetries}`);
|
|
return await requestFn();
|
|
} catch (error) {
|
|
lastError = error as Error;
|
|
console.warn(`⚠️ Tentativa ${attempt} falhou:`, error);
|
|
|
|
// Se é o último retry ou não é um erro de rede, não tenta novamente
|
|
if (attempt === maxRetries ||
|
|
(!lastError.message.includes('ERR_ABORTED') &&
|
|
!lastError.message.includes('Failed to fetch') &&
|
|
!lastError.message.includes('aborted'))) {
|
|
throw lastError;
|
|
}
|
|
|
|
// Aguardar antes da próxima tentativa
|
|
await new Promise(resolve => setTimeout(resolve, delay * attempt));
|
|
}
|
|
}
|
|
|
|
throw lastError!;
|
|
};
|
|
|
|
export interface ApontamentoProducao {
|
|
id: string;
|
|
of_number: string;
|
|
peca_id: string;
|
|
componente_id?: string;
|
|
tipo_apontamento: 'peca' | 'componente';
|
|
processo_id: string;
|
|
quantidade_produzida: number;
|
|
data_apontamento: string;
|
|
observacoes?: string;
|
|
created_at: string;
|
|
created_by?: string;
|
|
peca?: {
|
|
marca: string;
|
|
descricao: string;
|
|
peso_unitario: number;
|
|
etapa_fase: string;
|
|
};
|
|
processo?: {
|
|
nome: string;
|
|
ordem: number;
|
|
};
|
|
componente?: {
|
|
marca_componente: string;
|
|
descricao: string;
|
|
peso_unitario: number;
|
|
};
|
|
}
|
|
|
|
export interface ProcessoFabricacao {
|
|
id: string;
|
|
nome: string;
|
|
descricao?: string;
|
|
ordem: number;
|
|
ativo: boolean;
|
|
cor?: string;
|
|
}
|
|
|
|
export const useApontamentosProducao = () => {
|
|
const [apontamentos, setApontamentos] = useState<ApontamentoProducao[]>([]);
|
|
const [processos, setProcessos] = useState<ProcessoFabricacao[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const { user } = useAuth();
|
|
|
|
const fetchApontamentos = useCallback(async () => {
|
|
try {
|
|
console.log('🔍 Iniciando busca COMPLETA de apontamentos...');
|
|
|
|
// Verificar se o usuário está autenticado
|
|
if (!user) {
|
|
console.warn('⚠️ Usuário não autenticado em useApontamentosProducao');
|
|
return;
|
|
}
|
|
|
|
console.log('✅ Usuário autenticado:', user.id);
|
|
|
|
const { count: totalCount, error: countError } = await retryRequest(async () => {
|
|
const result = await supabase
|
|
.from('apontamentos_producao')
|
|
.select('*', { count: 'exact', head: true });
|
|
return result;
|
|
});
|
|
|
|
if (countError) {
|
|
console.error('❌ Erro ao contar registros:', countError);
|
|
console.error('❌ Detalhes do erro:', {
|
|
message: countError.message,
|
|
details: countError.details,
|
|
hint: countError.hint,
|
|
code: countError.code
|
|
});
|
|
toast.error('Erro ao acessar dados: ' + countError.message);
|
|
throw countError;
|
|
}
|
|
|
|
console.log(`📊 TOTAL DE REGISTROS NA TABELA: ${totalCount}`);
|
|
|
|
// SEGUNDA VERIFICAÇÃO: Buscar TODOS os apontamentos sem qualquer limitação
|
|
let allApontamentos: ApontamentoProducao[] = [];
|
|
let pageNumber: number = 0;
|
|
const itemsPerPage: number = 1000; // Buscar em lotes de 1000 para evitar timeout
|
|
let hasMoreData: boolean = true;
|
|
|
|
while (hasMoreData) {
|
|
const startIndex: number = pageNumber * itemsPerPage;
|
|
const endIndex: number = (pageNumber + 1) * itemsPerPage - 1;
|
|
|
|
console.log(`📄 Buscando página ${pageNumber + 1} (registros ${startIndex + 1} a ${endIndex + 1})`);
|
|
|
|
const { data: pageData, error: pageError } = await retryRequest(async () => {
|
|
const result = await supabase
|
|
.from('apontamentos_producao')
|
|
.select(`
|
|
*,
|
|
peca:pecas(marca, descricao, peso_unitario, etapa_fase),
|
|
processo:processos_fabricacao(nome, ordem),
|
|
componente:componentes_peca(marca_componente, descricao, peso_unitario)
|
|
`)
|
|
.range(startIndex, endIndex)
|
|
.order('created_at', { ascending: false });
|
|
return result;
|
|
});
|
|
|
|
if (pageError) {
|
|
console.error('❌ Erro ao buscar página:', pageError);
|
|
throw pageError;
|
|
}
|
|
|
|
if (!pageData || pageData.length === 0) {
|
|
hasMoreData = false;
|
|
break;
|
|
}
|
|
|
|
allApontamentos = [...allApontamentos, ...(pageData as unknown as ApontamentoProducao[])];
|
|
console.log(`✅ Página ${pageNumber + 1}: ${pageData.length} registros carregados. Total acumulado: ${allApontamentos.length}`);
|
|
|
|
// Se retornou menos que o itemsPerPage, chegamos ao fim
|
|
if (pageData.length < itemsPerPage) {
|
|
hasMoreData = false;
|
|
}
|
|
|
|
pageNumber++;
|
|
}
|
|
|
|
console.log(`🎯 BUSCA FINALIZADA: ${allApontamentos.length} de ${totalCount || 0} registros carregados`);
|
|
|
|
// Verificar especificamente OF B118
|
|
const b118Count: number = allApontamentos.filter(apt => apt.of_number === 'B118').length;
|
|
console.log(`🔍 OF B118: ${b118Count} apontamentos encontrados`);
|
|
|
|
// Verificar distribuição por OF
|
|
const ofCounts = allApontamentos.reduce((acc, apt) => {
|
|
const of = apt.of_number || 'SEM_OF';
|
|
acc[of] = (acc[of] || 0) + 1;
|
|
return acc;
|
|
}, {} as Record<string, number>);
|
|
|
|
console.log('📋 Distribuição por OF (primeiros 10):',
|
|
Object.entries(ofCounts)
|
|
.sort(([,a], [,b]) => (b as number) - (a as number))
|
|
.slice(0, 10)
|
|
);
|
|
|
|
// Cast the tipo_apontamento to the correct type
|
|
const typedData = allApontamentos.map(item => ({
|
|
...item,
|
|
tipo_apontamento: (item.tipo_apontamento || 'peca') as 'peca' | 'componente'
|
|
}));
|
|
|
|
setApontamentos(typedData);
|
|
|
|
console.log(`✅ DADOS CARREGADOS COMPLETAMENTE: ${typedData.length} apontamentos`);
|
|
|
|
} catch (error) {
|
|
console.error('❌ ERRO CRÍTICO ao buscar apontamentos:', error);
|
|
|
|
// Diagnóstico detalhado do erro
|
|
if (error instanceof Error) {
|
|
console.error('❌ Tipo de erro:', error.name);
|
|
console.error('❌ Mensagem:', error.message);
|
|
console.error('❌ Stack:', error.stack);
|
|
|
|
// Verificar se é um erro de rede
|
|
if (error.message.includes('ERR_ABORTED') || error.message.includes('aborted')) {
|
|
console.error('🚨 ERRO DE REDE DETECTADO: ERR_ABORTED');
|
|
toast.error('Erro de conexão: Requisição cancelada. Verifique sua conexão com a internet.');
|
|
} else if (error.message.includes('Failed to fetch')) {
|
|
console.error('🚨 ERRO DE FETCH DETECTADO');
|
|
toast.error('Erro de conexão: Falha ao conectar com o servidor.');
|
|
} else {
|
|
toast.error('Erro ao carregar apontamentos: ' + error.message);
|
|
}
|
|
} else {
|
|
console.error('❌ Erro desconhecido:', error);
|
|
toast.error('Erro desconhecido ao carregar apontamentos');
|
|
}
|
|
}
|
|
}, [user]);
|
|
|
|
const fetchProcessos = useCallback(async () => {
|
|
try {
|
|
console.log('Buscando processos...');
|
|
const { data, error } = await retryRequest(async () => {
|
|
const result = await supabase
|
|
.from('processos_fabricacao')
|
|
.select('*')
|
|
.eq('ativo', true)
|
|
.order('ordem');
|
|
return result;
|
|
});
|
|
|
|
if (error) {
|
|
console.error('Erro ao buscar processos:', error);
|
|
throw error;
|
|
}
|
|
|
|
console.log(`Processos carregados: ${data?.length || 0} registros`);
|
|
setProcessos(data || []);
|
|
} catch (error) {
|
|
console.error('Erro ao buscar processos:', error);
|
|
toast.error('Erro ao carregar processos');
|
|
}
|
|
}, []);
|
|
|
|
const buscarQuantidadeProcessada = useCallback(async (
|
|
marcaItem: string,
|
|
processoId: string,
|
|
ofNumber: string,
|
|
tipoItem: 'peca' | 'componente' = 'peca'
|
|
): Promise<number> => {
|
|
try {
|
|
let query = supabase
|
|
.from('apontamentos_producao')
|
|
.select('quantidade_produzida')
|
|
.eq('processo_id', processoId)
|
|
.eq('of_number', ofNumber)
|
|
.eq('tipo_apontamento', tipoItem);
|
|
|
|
if (tipoItem === 'componente') {
|
|
// Para componentes, buscar pela marca_componente
|
|
const { data: componentesData, error: componentesError } = await supabase
|
|
.from('componentes_peca')
|
|
.select('id')
|
|
.eq('marca_componente', marcaItem);
|
|
|
|
if (componentesError || !componentesData?.length) {
|
|
console.log(`Nenhum componente encontrado para marca: ${marcaItem}`);
|
|
return 0;
|
|
}
|
|
|
|
const componenteIds = componentesData.map(c => c.id);
|
|
query = query.in('componente_id', componenteIds);
|
|
} else {
|
|
// Para peças, buscar pela marca da peça
|
|
const { data: pecasData, error: pecasError } = await supabase
|
|
.from('pecas')
|
|
.select('id')
|
|
.eq('marca', marcaItem)
|
|
.eq('of_number', ofNumber);
|
|
|
|
if (pecasError || !pecasData?.length) {
|
|
console.log(`Nenhuma peça encontrada para marca: ${marcaItem} na OF: ${ofNumber}`);
|
|
return 0;
|
|
}
|
|
|
|
const pecaIds = pecasData.map(p => p.id);
|
|
query = query.in('peca_id', pecaIds);
|
|
}
|
|
|
|
const { data, error } = await query;
|
|
|
|
if (error) {
|
|
console.error('Erro ao buscar quantidade processada:', error);
|
|
return 0;
|
|
}
|
|
|
|
const totalProcessado: number = data?.reduce((sum, apt) => sum + apt.quantidade_produzida, 0) || 0;
|
|
|
|
console.log(`Quantidade processada para ${marcaItem} no processo ${processoId}: ${totalProcessado}`);
|
|
|
|
return totalProcessado;
|
|
} catch (error) {
|
|
console.error('Erro ao buscar quantidade processada:', error);
|
|
return 0;
|
|
}
|
|
}, []);
|
|
|
|
const criarApontamento = useCallback(async (apontamento: {
|
|
of_number: string;
|
|
peca_id?: string;
|
|
componente_id?: string;
|
|
tipo_apontamento: 'peca' | 'componente';
|
|
processo_id: string;
|
|
quantidade_produzida: number;
|
|
data_apontamento: string;
|
|
observacoes?: string;
|
|
}) => {
|
|
try {
|
|
if (!user) throw new Error('Usuário não autenticado');
|
|
|
|
console.log('🔄 Criando apontamento:', apontamento);
|
|
|
|
const insertData: any = {
|
|
of_number: apontamento.of_number,
|
|
tipo_apontamento: apontamento.tipo_apontamento,
|
|
processo_id: apontamento.processo_id,
|
|
quantidade_produzida: apontamento.quantidade_produzida,
|
|
data_apontamento: apontamento.data_apontamento,
|
|
observacoes: apontamento.observacoes || null,
|
|
created_by: user.id
|
|
};
|
|
|
|
// Para componentes, precisamos buscar o peca_id da peça pai
|
|
if (apontamento.tipo_apontamento === 'componente' && apontamento.componente_id) {
|
|
// Buscar a peça pai do componente
|
|
const { data: componenteData, error: componenteError } = await supabase
|
|
.from('componentes_peca')
|
|
.select('peca_id')
|
|
.eq('id', apontamento.componente_id)
|
|
.single();
|
|
|
|
if (componenteError || !componenteData) {
|
|
throw new Error('Erro ao buscar dados do componente');
|
|
}
|
|
|
|
insertData.componente_id = apontamento.componente_id;
|
|
insertData.peca_id = componenteData.peca_id; // Usar o peca_id da peça pai
|
|
} else {
|
|
insertData.peca_id = apontamento.peca_id;
|
|
insertData.componente_id = null;
|
|
}
|
|
|
|
// Inserir o apontamento
|
|
const { error } = await supabase
|
|
.from('apontamentos_producao')
|
|
.insert(insertData);
|
|
|
|
if (error) {
|
|
console.error('❌ Erro SQL:', error);
|
|
throw error;
|
|
}
|
|
|
|
console.log('✅ Apontamento criado com sucesso, forçando atualização completa...');
|
|
|
|
// FORÇA ATUALIZAÇÃO COMPLETA DOS DADOS
|
|
await new Promise(resolve => setTimeout(resolve, 500)); // Aguarda 500ms para garantir persistência
|
|
await fetchApontamentos();
|
|
|
|
// Log de verificação
|
|
console.log(`🔍 Verificando apontamento criado para OF ${apontamento.of_number}`);
|
|
|
|
toast.success('Apontamento registrado com sucesso!');
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('❌ Erro ao criar apontamento:', error);
|
|
toast.error('Erro ao registrar apontamento: ' + (error as any).message);
|
|
return { success: false, error };
|
|
}
|
|
}, [user, fetchApontamentos]);
|
|
|
|
const criarProcesso = async (processo: Omit<ProcessoFabricacao, 'id'>) => {
|
|
try {
|
|
if (!user) throw new Error('Usuário não autenticado');
|
|
|
|
const { data, error } = await supabase
|
|
.from('processos_fabricacao')
|
|
.insert({
|
|
...processo,
|
|
created_by: user.id
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
|
|
toast.success('Processo criado com sucesso!');
|
|
await fetchProcessos();
|
|
return { success: true, data };
|
|
} catch (error) {
|
|
console.error('Erro ao criar processo:', error);
|
|
toast.error('Erro ao criar processo');
|
|
return { success: false, error };
|
|
}
|
|
};
|
|
|
|
const atualizarProcesso = async (id: string, processo: Partial<ProcessoFabricacao>) => {
|
|
try {
|
|
const { error } = await supabase
|
|
.from('processos_fabricacao')
|
|
.update(processo)
|
|
.eq('id', id);
|
|
|
|
if (error) throw error;
|
|
|
|
toast.success('Processo atualizado com sucesso!');
|
|
await fetchProcessos();
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Erro ao atualizar processo:', error);
|
|
toast.error('Erro ao atualizar processo');
|
|
return { success: false, error };
|
|
}
|
|
};
|
|
|
|
const exportarModeloCSV = () => {
|
|
const headers = ['of_number', 'marca_peca', 'processo', 'quantidade_produzida', 'data_apontamento', 'observacoes'];
|
|
const exampleData = [
|
|
['B002', 'COLUNA-4', 'Detalhamento', '5', '2025-06-28', 'Exemplo de observação'],
|
|
['B002', 'VIGA-10', 'Corte', '3', '2025-06-28', ''],
|
|
['B003', 'PILAR-2', 'Expedição', '2', '2025-06-28', 'Peça conferida']
|
|
];
|
|
|
|
const csvContent = [headers, ...exampleData]
|
|
.map(row => row.map(cell => `"${cell}"`).join(','))
|
|
.join('\n');
|
|
|
|
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
const link = document.createElement('a');
|
|
const url = URL.createObjectURL(blob);
|
|
link.setAttribute('href', url);
|
|
link.setAttribute('download', 'modelo_apontamento_producao.csv');
|
|
link.style.visibility = 'hidden';
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
|
|
toast.success('Modelo CSV baixado com sucesso!');
|
|
};
|
|
|
|
const refetch = useCallback(async () => {
|
|
console.log('🔄 Executando refetch completo...');
|
|
setLoading(true);
|
|
try {
|
|
await Promise.all([fetchApontamentos(), fetchProcessos()]);
|
|
console.log('✅ Refetch completado com sucesso');
|
|
} catch (error) {
|
|
console.error('❌ Erro durante refetch:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [fetchApontamentos, fetchProcessos]);
|
|
|
|
useEffect(() => {
|
|
if (user) {
|
|
console.log('🚀 Iniciando carregamento de dados para usuário:', user.id);
|
|
setLoading(true);
|
|
|
|
// Adicionar timeout para detectar requisições que ficam pendentes
|
|
const timeoutId = setTimeout(() => {
|
|
console.warn('⚠️ TIMEOUT: Requisições demorando mais que 30 segundos');
|
|
toast.error('Timeout: Requisições demorando muito. Verifique sua conexão.');
|
|
}, 30000);
|
|
|
|
Promise.all([
|
|
fetchApontamentos(),
|
|
fetchProcessos()
|
|
]).finally(() => {
|
|
clearTimeout(timeoutId);
|
|
setLoading(false);
|
|
console.log('✅ Carregamento finalizado');
|
|
});
|
|
} else {
|
|
console.log('❌ Usuário não autenticado, não carregando dados');
|
|
}
|
|
}, [user, fetchApontamentos, fetchProcessos]);
|
|
|
|
return {
|
|
apontamentos,
|
|
processos,
|
|
loading,
|
|
criarApontamento,
|
|
criarProcesso,
|
|
atualizarProcesso,
|
|
exportarModeloCSV,
|
|
buscarQuantidadeProcessada,
|
|
refetch
|
|
};
|
|
};
|