🚀 Initial commit: Versão atual do TrackSteel APP
This commit is contained in:
@@ -0,0 +1,504 @@
|
||||
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
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
||||
if (authError) {
|
||||
console.error('❌ Erro de autenticação:', authError);
|
||||
toast.error('Erro de autenticação: ' + authError.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
console.warn('⚠️ Usuário não autenticado');
|
||||
toast.error('Usuário não autenticado');
|
||||
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: any[] = [];
|
||||
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];
|
||||
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');
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
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
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user