Files
tracksteel_app/src/hooks/useDashboardProducaoOtimizado.tsx
T

627 lines
27 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
export interface ProcessoConsolidado {
processo_id: string;
processo_nome: string;
processo_cor: string;
data_apontamento: string;
peso_acumulado: number;
}
export interface DateRange {
data_inicio_grafico: string;
data_fim_grafico: string;
}
export interface DashboardProcesso {
id: string;
nome: string;
pesoTotal: number;
pesoFabricado: number;
progressoReal: number;
progressoEsperado: number;
status: 'verde' | 'amarelo' | 'vermelho' | 'azul';
dadosGrafico: {
data: string;
planejado: number;
realizado: number;
}[];
}
export interface DashboardDataOtimizado {
of: string;
progressoGeral: number;
pesoTotalFabricado: number;
tonelagem: number;
processos: DashboardProcesso[];
dadosConsolidados: ProcessoConsolidado[];
dateRange: DateRange;
}
export const useDashboardProducaoOtimizado = (ofNumber: string) => {
const [dashboardData, setDashboardData] = useState<DashboardDataOtimizado | null>(null);
const [loading, setLoading] = useState(false);
const fetchDashboardData = useCallback(async () => {
if (!ofNumber) {
setDashboardData(null);
return;
}
setLoading(true);
try {
console.log('Buscando dados do dashboard para OF:', ofNumber);
// 1. Buscar dados da OF para obter o peso total planejado
const { data: ofData, error: ofError } = await supabase
.from('ordens_fabricacao')
.select('*')
.eq('num_of', ofNumber)
.single();
if (ofError) {
console.error('Erro ao buscar OF:', ofError);
throw ofError;
}
console.log('Dados da OF encontrados:', ofData);
// 2. Buscar ficha técnica para obter peso total se não estiver na OF
let pesoTotalPlanejado = ofData.peso_total || 0;
if (!pesoTotalPlanejado) {
const { data: fichaTecnica, error: fichaError } = await supabase
.from('ficha_tecnica_contratos')
.select('quantidade')
.eq('of_number', ofNumber)
.maybeSingle();
if (!fichaError && fichaTecnica?.quantidade) {
pesoTotalPlanejado = fichaTecnica.quantidade;
}
}
// 3. Buscar peças cadastradas para calcular peso total e peso soldável (peças com componentes)
const { data: pecasData, error: pecasError } = await supabase
.from('pecas')
.select('id, peso_unitario, quantidade, tem_componentes')
.eq('of_number', ofNumber);
if (pecasError) {
console.error('Erro ao buscar peças:', pecasError);
throw pecasError;
}
const pesoBrutoPecas = (pecasData || []).reduce((total, peca) => {
const pesoUnitario = peca.peso_unitario || 0;
const quantidade = peca.quantidade || 0;
return total + (pesoUnitario * quantidade);
}, 0);
// Peso soldável: apenas peças que possuem componentes (peças soltas não vão para solda)
let pesoTotalSoldavel = (pecasData || []).filter(p => p.tem_componentes === true).reduce((total, peca) => {
const pesoUnitario = peca.peso_unitario || 0;
const quantidade = peca.quantidade || 0;
return total + (pesoUnitario * quantidade);
}, 0);
if (!pesoTotalPlanejado) {
pesoTotalPlanejado = pesoBrutoPecas;
}
// CORREÇÃO AUTOMÁTICA: Evita que erros de digitação (ex: digitar 1.856 ao invés de 1856 kg na OF)
// causem porcentagens irreais (ex: 100000%). Compara com a soma real das peças.
if (pesoTotalPlanejado > 0 && pesoBrutoPecas > 0) {
if (pesoBrutoPecas > pesoTotalPlanejado * 50) {
console.log(`Corrigindo erro de digitação de peso da OF. Planejado original: ${pesoTotalPlanejado} kg, Soma das peças: ${pesoBrutoPecas} kg`);
if (pesoBrutoPecas <= pesoTotalPlanejado * 3000 && pesoBrutoPecas >= pesoTotalPlanejado * 500) {
pesoTotalPlanejado = pesoTotalPlanejado * 1000;
} else {
pesoTotalPlanejado = pesoBrutoPecas;
}
}
}
// Se todas as peças têm componentes ou nenhuma tem flag marcada, fallback para o peso total
if (pesoTotalSoldavel <= 0 && pesoTotalPlanejado > 0) {
pesoTotalSoldavel = pesoTotalPlanejado;
}
console.log('Peso total planejado (BASE DE TODOS OS CÁLCULOS):', pesoTotalPlanejado, 'kg');
console.log('Peso total soldável (apenas peças com componentes):', pesoTotalSoldavel, 'kg');
// 4. Buscar apontamentos da OF com joins corretos
const { data: apontamentosData, error: apontamentosError } = await supabase
.from('apontamentos_producao')
.select(`
*,
peca:pecas!apontamentos_producao_peca_id_fkey(peso_unitario, marca),
processo:processos_fabricacao!apontamentos_producao_processo_id_fkey(nome, ordem, cor),
componente:componentes_peca!apontamentos_producao_componente_id_fkey(peso_unitario, marca_componente)
`)
.eq('of_number', ofNumber);
if (apontamentosError) {
console.error('Erro ao buscar apontamentos:', apontamentosError);
throw apontamentosError;
}
console.log('Apontamentos encontrados:', apontamentosData);
// 5. Calcular peso total fabricado:
// - Para peças com componentes (tem_componentes = true): são finalizadas na SOLDA
// - Para peças soltas (tem_componentes = false): são finalizadas no CORTE (não passam por solda)
let pesoTotalFabricado = 0;
const processoSolda = await supabase
.from('processos_fabricacao')
.select('id')
.ilike('nome', '%solda%')
.single();
const processoCorte = await supabase
.from('processos_fabricacao')
.select('id')
.ilike('nome', '%corte%')
.single();
const processoSoldaId = processoSolda.data?.id;
const processoCorteId = processoCorte.data?.id;
const pecasInfoMap = new Map((pecasData || []).map(p => [p.id, p]));
// 1. Somar peças com componentes apontadas na Solda (apenas tipo peca)
if (processoSoldaId) {
const apontamentosSolda = apontamentosData.filter(a => a.processo_id === processoSoldaId && a.tipo_apontamento === 'peca');
apontamentosSolda.forEach(apontamento => {
const pesoUnitario = Number(apontamento.peca?.peso_unitario || 0);
const pesoApontamento = pesoUnitario * Number(apontamento.quantidade_produzida || 0);
pesoTotalFabricado += pesoApontamento;
});
}
// 2. Somar peças SOLTAS (sem componentes) apontadas no Corte
if (processoCorteId) {
const apontamentosCorte = apontamentosData.filter(a => a.processo_id === processoCorteId);
apontamentosCorte.forEach(apontamento => {
if (apontamento.tipo_apontamento === 'peca' && apontamento.peca_id) {
const peca = pecasInfoMap.get(apontamento.peca_id);
const temComponentes = peca?.tem_componentes === true;
if (!temComponentes && apontamento.peca?.peso_unitario) {
const pesoUnitario = Number(apontamento.peca.peso_unitario);
const pesoApontamento = pesoUnitario * Number(apontamento.quantidade_produzida || 0);
pesoTotalFabricado += pesoApontamento;
}
}
});
}
// Evita que ultrapasse o planejado por arredondamento
if (pesoTotalPlanejado > 0) {
pesoTotalFabricado = Math.min(pesoTotalFabricado, pesoTotalPlanejado);
}
console.log('Peso total fabricado (Solda p/ compostas + Corte p/ soltas):', pesoTotalFabricado, 'kg');
// 6. Calcular progresso geral - será recalculado após processar todos os processos
let progressoGeral = 0;
console.log(`PROGRESSO GERAL: ${pesoTotalFabricado} kg / ${pesoTotalPlanejado} kg = ${progressoGeral.toFixed(2)}%`);
// 7. Buscar range de datas (opcional, ignora erro se a RPC não existir no Supabase)
let dateRangeData = null;
try {
const { data, error } = await supabase
.rpc('get_dashboard_date_range', { of_number_param: ofNumber });
if (!error) dateRangeData = data;
} catch (e) {
// Ignora caso RPC não exista
}
// 8. Buscar dados consolidados (opcional, ignora erro se a RPC não existir no Supabase)
let consolidatedData = null;
try {
const { data, error } = await supabase
.rpc('get_dashboard_consolidated_data', { of_number_param: ofNumber });
if (!error) consolidatedData = data;
} catch (e) {
// Ignora caso RPC não exista
}
// 9. Buscar processos
const { data: processosData, error: processosError } = await supabase
.from('processos_fabricacao')
.select('*')
.eq('ativo', true)
.order('ordem');
if (processosError) {
console.error('Erro ao buscar processos:', processosError);
throw processosError;
}
// 10. Buscar cronograma da OF para calcular progresso esperado correto
const { data: cronogramaRaw } = await supabase
.from('cronogramas_of')
.select(`
id,
defasagem_solda,
processos_cronograma:processos_cronograma(
nome_processo,
data_inicio,
data_fim
)
`)
.eq('of_id', ofData.id)
.maybeSingle();
const cronogramaOf = cronogramaRaw as any;
console.log('Cronograma da OF encontrado:', cronogramaOf);
// Função auxiliar para calcular progresso esperado baseado no cronograma
const calcularProgressoEsperado = (nomeProcesso: string): number => {
if (!cronogramaOf?.processos_cronograma || cronogramaOf.processos_cronograma.length === 0) {
console.log(`Cronograma não encontrado para processo ${nomeProcesso}`);
return 0;
}
const hoje = new Date();
let processoCronograma;
// Mapear nomes dos processos para os do cronograma
if (nomeProcesso.toLowerCase().includes('corte') || nomeProcesso.toLowerCase().includes('solda')) {
processoCronograma = cronogramaOf.processos_cronograma.find((p: any) =>
p.nome_processo.toLowerCase().includes('fabricação') ||
p.nome_processo.toLowerCase().includes('fabricacao')
);
} else if (nomeProcesso.toLowerCase().includes('pint') || nomeProcesso.toLowerCase().includes('galv') ||
nomeProcesso.toLowerCase().includes('expedição') || nomeProcesso.toLowerCase().includes('expedicao') ||
nomeProcesso.toLowerCase().includes('montagem')) {
// Usar datas do processo "Instalação" (incluindo "montagem")
processoCronograma = cronogramaOf.processos_cronograma.find((p: any) =>
p.nome_processo.toLowerCase().includes('instalação') ||
p.nome_processo.toLowerCase().includes('instalacao')
);
} else {
// Buscar processo com nome exato ou similar
processoCronograma = cronogramaOf.processos_cronograma.find((p: any) =>
p.nome_processo.toLowerCase().includes(nomeProcesso.toLowerCase()) ||
nomeProcesso.toLowerCase().includes(p.nome_processo.toLowerCase())
);
}
if (!processoCronograma) {
console.log(`Processo ${nomeProcesso} não encontrado no cronograma`);
return 0;
}
const dataInicio = new Date(processoCronograma.data_inicio + 'T00:00:00');
const dataFim = new Date(processoCronograma.data_fim + 'T23:59:59');
console.log(`Processo ${nomeProcesso} - Inicio: ${dataInicio.toDateString()}, Fim: ${dataFim.toDateString()}, Hoje: ${hoje.toDateString()}`);
// Se ainda não chegou na data de início, progresso esperado é 0%
if (hoje < dataInicio) {
console.log(`Data atual (${hoje.toDateString()}) é menor que data de início (${dataInicio.toDateString()}) - Progresso esperado: 0%`);
return 0;
}
const MS_PER_DAY = 1000 * 60 * 60 * 24;
const diasTotais = Math.max(1, Math.floor((dataFim.getTime() - dataInicio.getTime()) / MS_PER_DAY) + 1);
const diasDecorridos = Math.floor((hoje.getTime() - dataInicio.getTime()) / MS_PER_DAY) + 1;
// Calcular percentual baseado nos dias
let percentualEsperado = (diasDecorridos / diasTotais) * 100;
// Se passou da data fim, manter em 100%
if (hoje > dataFim) {
percentualEsperado = 100;
console.log(`Data atual passou da data fim - Progresso esperado mantido em: 100%`);
} else {
console.log(`Processo ${nomeProcesso}: ${diasDecorridos}/${diasTotais} dias = ${percentualEsperado.toFixed(2)}%`);
}
// Se for o processo de Solda, aplicar o fator de defasagem configurado em relação ao Corte
if (nomeProcesso.toLowerCase().includes('solda')) {
const defasagem = cronogramaOf?.defasagem_solda !== undefined && cronogramaOf?.defasagem_solda !== null
? Number(cronogramaOf.defasagem_solda)
: 10;
if (percentualEsperado <= defasagem) {
console.log(`Processo Solda: Fabricação esperada (${percentualEsperado.toFixed(2)}%) <= defasagem (${defasagem}%) -> Progresso esperado Solda: 0%`);
return 0;
}
if (hoje > dataFim) {
return 100;
}
// Escalonamento suave proporcional: de defasagem% até 100%
const progressoSolda = ((percentualEsperado - defasagem) / (100 - defasagem)) * 100;
console.log(`Processo Solda: Fabricação (${percentualEsperado.toFixed(2)}%) com defasagem (${defasagem}%) -> Solda esperada: ${progressoSolda.toFixed(2)}%`);
return Math.min(100, Math.max(0, progressoSolda));
}
return Math.min(100, Math.max(0, percentualEsperado));
};
// 11. Processar dados por processo individual
const processos: DashboardProcesso[] = await Promise.all(processosData.map(async (processo) => {
const apontamentosProcesso = apontamentosData.filter(a => a.processo_id === processo.id);
let pesoFabricadoProcesso = 0;
apontamentosProcesso.forEach(a => {
// Apenas apontamentos de peça mestre somam peso no processo para evitar dupla contagem com subcomponentes
if (a.tipo_apontamento === 'peca' && a.peca?.peso_unitario) {
pesoFabricadoProcesso += Number(a.peca.peso_unitario) * Number(a.quantidade_produzida);
}
});
const isProcessoSolda = processo.nome.toLowerCase().includes('solda');
const pesoBaseProcesso = isProcessoSolda && pesoTotalSoldavel > 0 ? pesoTotalSoldavel : pesoTotalPlanejado;
const progressoReal = pesoBaseProcesso > 0 ? (pesoFabricadoProcesso / pesoBaseProcesso) * 100 : 0;
console.log(`PROCESSO ${processo.nome}: ${pesoFabricadoProcesso} kg fabricado / ${pesoBaseProcesso} kg planejado (base ${isProcessoSolda ? 'soldável' : 'total'}) = ${progressoReal.toFixed(2)}%`);
// Calcular progresso esperado usando as datas do cronograma
const progressoEsperado = calcularProgressoEsperado(processo.nome);
// Determinar status baseado na comparação entre progresso real e esperado
let status: 'verde' | 'amarelo' | 'vermelho' | 'azul';
// Tolerância de conclusão: 99.5% ou mais é considerado 100% concluído (evita erros por dízimas em somas de peso)
const isConcluido = progressoReal >= 99.5;
if (isConcluido) {
// Se já concluiu 100%:
// Se concluiu antes do prazo final (progressoEsperado < 100), fica azul (Adiantado), senão verde (Concluído)
if (progressoEsperado < 100) {
status = 'azul'; // Adiantado
} else {
status = 'verde'; // Concluído no prazo
}
} else if (progressoEsperado >= 100) {
// Passou da data fim e ainda não concluiu: Atrasado
status = 'vermelho';
} else if (progressoEsperado > 0) {
// Comparar progresso real com esperado
if (progressoReal > progressoEsperado) {
status = 'azul'; // Adiantado
} else {
const percentualDoEsperado = (progressoReal / progressoEsperado) * 100;
if (percentualDoEsperado < 60) {
status = 'vermelho'; // Muito atrasado
} else if (percentualDoEsperado < 80) {
status = 'amarelo'; // Atenção
} else {
status = 'verde'; // No prazo
}
}
} else {
// Se não há progresso esperado ainda (antes da data de início), usar verde
status = 'verde';
}
console.log(`STATUS ${processo.nome}: ${progressoReal.toFixed(2)}% real vs ${progressoEsperado.toFixed(2)}% esperado -> ${status}`);
// Gerar dados do gráfico usando as datas específicas do cronograma do processo
const dadosGrafico = [];
// Buscar as datas específicas deste processo no cronograma
let dataInicioProcesso: Date | null = null;
let dataFimProcesso: Date | null = null;
if (cronogramaOf?.processos_cronograma) {
let processoCronograma;
// Mapear nomes dos processos para os do cronograma
if (processo.nome.toLowerCase().includes('corte') || processo.nome.toLowerCase().includes('solda')) {
processoCronograma = cronogramaOf.processos_cronograma.find((p: any) =>
p.nome_processo.toLowerCase().includes('fabricação') ||
p.nome_processo.toLowerCase().includes('fabricacao')
);
} else if (processo.nome.toLowerCase().includes('pint') || processo.nome.toLowerCase().includes('galv') ||
processo.nome.toLowerCase().includes('expedição') || processo.nome.toLowerCase().includes('expedicao') ||
processo.nome.toLowerCase().includes('montagem')) {
processoCronograma = cronogramaOf.processos_cronograma.find((p: any) =>
p.nome_processo.toLowerCase().includes('instalação') ||
p.nome_processo.toLowerCase().includes('instalacao')
);
} else {
processoCronograma = cronogramaOf.processos_cronograma.find((p: any) =>
p.nome_processo.toLowerCase().includes(processo.nome.toLowerCase()) ||
processo.nome.toLowerCase().includes(p.nome_processo.toLowerCase())
);
}
if (processoCronograma) {
dataInicioProcesso = new Date(processoCronograma.data_inicio + 'T00:00:00');
dataFimProcesso = new Date(processoCronograma.data_fim + 'T23:59:59');
}
}
// Se não há cronograma específico, usar as datas da OF como fallback
if (!dataInicioProcesso || !dataFimProcesso) {
dataInicioProcesso = ofData.data_abertura ? new Date(ofData.data_abertura + 'T00:00:00') : new Date();
dataFimProcesso = ofData.data_prazo ? new Date(ofData.data_prazo + 'T23:59:59') : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
}
// Verificar se há apontamentos fora do período planejado e ajustar o range se necessário
let dataInicioGrafico = dataInicioProcesso;
let dataFimGrafico = dataFimProcesso;
if (apontamentosProcesso.length > 0) {
const datasMaisAntigas = apontamentosProcesso.map(a => new Date(a.data_apontamento + 'T00:00:00'));
const datasMaisRecentes = apontamentosProcesso.map(a => new Date(a.data_apontamento + 'T23:59:59'));
const menorDataApontamento = new Date(Math.min(...datasMaisAntigas.map(d => d.getTime())));
const maiorDataApontamento = new Date(Math.max(...datasMaisRecentes.map(d => d.getTime())));
// Estender o período se houver apontamentos fora do planejado
if (menorDataApontamento < dataInicioGrafico) {
dataInicioGrafico = menorDataApontamento;
}
if (maiorDataApontamento > dataFimGrafico) {
dataFimGrafico = maiorDataApontamento;
}
}
const MS_PER_DAY = 1000 * 60 * 60 * 24;
const diasTotaisProcesso = Math.max(1, Math.floor((dataFimProcesso.getTime() - dataInicioProcesso.getTime()) / MS_PER_DAY) + 1);
const diasRange = Math.floor((dataFimGrafico.getTime() - dataInicioGrafico.getTime()) / MS_PER_DAY);
for (let i = 0; i <= diasRange; i++) {
const data = new Date(dataInicioGrafico);
data.setDate(data.getDate() + i);
const dataStr = data.toISOString().split('T')[0];
// Calcular progresso planejado baseado no cronograma específico do processo (percentual dos dias)
let planejado = 0;
if (data >= dataInicioProcesso && data <= dataFimProcesso) {
const diasDecorridos = Math.floor((Math.min(data.getTime(), dataFimProcesso.getTime()) - dataInicioProcesso.getTime()) / MS_PER_DAY) + 1;
const percentualDias = diasDecorridos / diasTotaisProcesso;
planejado = percentualDias * pesoBaseProcesso;
} else if (data > dataFimProcesso) {
// Após o fim planejado, manter o peso base do processo
planejado = pesoBaseProcesso;
}
// Antes do início planejado, o planejado fica 0
// Calcular progresso real até esta data - soma dos pesos apontados
const apontamentosAteData = apontamentosProcesso.filter(a => {
if (!a.data_apontamento) return false;
const dataAptStr = typeof a.data_apontamento === 'string' ? a.data_apontamento.substring(0, 10) : '';
return dataAptStr <= dataStr;
});
let realizado = 0;
apontamentosAteData.forEach(a => {
// Apenas peças mestres computam peso realizado no gráfico
if (a.tipo_apontamento === 'peca' && a.peca?.peso_unitario) {
realizado += Number(a.peca.peso_unitario) * Number(a.quantidade_produzida);
}
});
dadosGrafico.push({
data: dataStr,
planejado,
realizado
});
}
return {
id: processo.id,
nome: processo.nome,
pesoTotal: pesoBaseProcesso, // Peso alvo do processo (soldável para solda, total para os demais)
pesoFabricado: pesoFabricadoProcesso,
progressoReal,
progressoEsperado,
status,
dadosGrafico
};
}));
// Calcular progresso geral como média dos progressos reais, excluindo "Aceite/DB" e "Concluído"
const processosParaMedia = processos.filter(p =>
!p.nome.toLowerCase().includes('aceite') &&
!p.nome.toLowerCase().includes('db') &&
!p.nome.toLowerCase().includes('concluído') &&
!p.nome.toLowerCase().includes('concluido')
);
if (processosParaMedia.length > 0) {
const somaProgressos = processosParaMedia.reduce((soma, processo) => soma + processo.progressoReal, 0);
progressoGeral = somaProgressos / processosParaMedia.length;
} else {
progressoGeral = 0;
}
// Atualizar progresso real e esperado do processo "Concluído" com o progresso geral
processos.forEach(processo => {
if (processo.nome.toLowerCase().includes('concluído') || processo.nome.toLowerCase().includes('concluido')) {
processo.progressoReal = progressoGeral;
// Calcular progresso esperado do processo "Concluído" como média dos outros processos
// (exceto Aceite/DB e o próprio Concluído)
const processosParaMediaEsperada = processos.filter(p =>
!p.nome.toLowerCase().includes('aceite') &&
!p.nome.toLowerCase().includes('db') &&
!p.nome.toLowerCase().includes('concluído') &&
!p.nome.toLowerCase().includes('concluido')
);
if (processosParaMediaEsperada.length > 0) {
const somaProgressosEsperados = processosParaMediaEsperada.reduce((soma, proc) => soma + proc.progressoEsperado, 0);
processo.progressoEsperado = somaProgressosEsperados / processosParaMediaEsperada.length;
} else {
processo.progressoEsperado = 0;
}
// Recalcular status do processo "Concluído"
if (processo.progressoReal >= 99.5) {
processo.status = 'verde'; // Concluído
} else if (processo.progressoEsperado >= 100) {
processo.status = 'vermelho'; // Atrasado (prazo expirou e ainda não finalizou)
} else if (processo.progressoEsperado > 0) {
if (processo.progressoReal > processo.progressoEsperado) {
processo.status = 'azul'; // Adiantado
} else {
const percentualDoEsperado = (processo.progressoReal / processo.progressoEsperado) * 100;
if (percentualDoEsperado < 60) {
processo.status = 'vermelho'; // Atrasado
} else if (percentualDoEsperado < 80) {
processo.status = 'amarelo'; // Atenção
} else {
processo.status = 'verde'; // No prazo
}
}
}
console.log(`PROCESSO CONCLUÍDO atualizado: progresso real = ${progressoGeral.toFixed(2)}%, esperado = ${processo.progressoEsperado.toFixed(2)}%, status = ${processo.status}`);
}
});
console.log(`PROGRESSO GERAL CORRIGIDO: Média de ${processosParaMedia.length} processos (excluindo Aceite/DB e Concluído) = ${progressoGeral.toFixed(2)}%`);
console.log('Processos incluídos na média:', processosParaMedia.map(p => `${p.nome}: ${p.progressoReal.toFixed(1)}%`).join(', '));
const result = {
of: ofNumber,
progressoGeral,
pesoTotalFabricado,
tonelagem: pesoTotalPlanejado, // SEMPRE o peso total da OF/ficha técnica
processos,
dadosConsolidados: consolidatedData || [],
dateRange: dateRangeData?.[0] || { data_inicio_grafico: '', data_fim_grafico: '' }
};
console.log('=== RESULTADO FINAL DO DASHBOARD ===');
console.log('OF:', ofNumber);
console.log('Peso total planejado (base):', pesoTotalPlanejado, 'kg =', (pesoTotalPlanejado / 1000).toFixed(2), 't');
console.log('Peso total fabricado:', pesoTotalFabricado, 'kg =', (pesoTotalFabricado / 1000).toFixed(2), 't');
console.log('Progresso geral:', progressoGeral.toFixed(2) + '%');
console.log('=====================================');
setDashboardData(result);
} catch (error) {
console.error('Erro ao buscar dados do dashboard:', error);
toast.error('Erro ao carregar dados do dashboard');
} finally {
setLoading(false);
}
}, [ofNumber]);
useEffect(() => {
fetchDashboardData();
}, [fetchDashboardData]);
return {
dashboardData,
loading,
refetch: fetchDashboardData
};
};