diff --git a/src/components/producao/RelatorioPecasProcessoModal.tsx b/src/components/producao/RelatorioPecasProcessoModal.tsx index f6f7139..83bd376 100644 --- a/src/components/producao/RelatorioPecasProcessoModal.tsx +++ b/src/components/producao/RelatorioPecasProcessoModal.tsx @@ -1,11 +1,12 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { X, FileText, Printer } from 'lucide-react'; import { useOFs } from '@/hooks/useOFs'; import { useRelatorioPecasProcesso, PecaComStatus } from '@/hooks/useRelatorioPecasProcesso'; +import { useDashboardProducaoOtimizado } from '@/hooks/useDashboardProducaoOtimizado'; import { RelatorioPecasProcessoPDF } from './RelatorioPecasProcessoPDF'; import { RelatorioPecasProcessoPrint } from './RelatorioPecasProcessoPrint'; @@ -22,6 +23,7 @@ export const RelatorioPecasProcessoModal: React.FC p.processos.expedicao).reduce((sum, p) => sum + p.peso_total, 0) }; + // Calcular percentual de progresso conforme índices da tela de Dashboard de Produção + const percentuais = useMemo(() => { + if (selectedFase === 'todas' && dashboardData?.processos && dashboardData.processos.length > 0) { + const pCorte = dashboardData.processos.find(p => p.nome.toLowerCase().includes('corte'))?.progressoReal ?? 0; + const pSolda = dashboardData.processos.find(p => p.nome.toLowerCase().includes('solda'))?.progressoReal ?? 0; + const pPintura = dashboardData.processos.find(p => p.nome.toLowerCase().includes('pint') || p.nome.toLowerCase().includes('galv'))?.progressoReal ?? 0; + const pExp = dashboardData.processos.find(p => p.nome.toLowerCase().includes('exped'))?.progressoReal ?? 0; + + return { + corte: pCorte, + solda: pSolda, + pintura: pPintura, + expedicao: pExp + }; + } + + const pesoTotalGeral = pecasFiltradas.reduce((sum, p) => sum + p.peso_total, 0); + const pesoTotalSoldavel = pecasFiltradas.filter(p => p.tem_componentes).reduce((sum, p) => sum + p.peso_total, 0) || pesoTotalGeral; + + return { + corte: pesoTotalGeral > 0 ? (estatisticasFiltradas.pesoTotalCorte / pesoTotalGeral) * 100 : 0, + solda: pesoTotalSoldavel > 0 ? (estatisticasFiltradas.pesoTotalSolda / pesoTotalSoldavel) * 100 : 0, + pintura: pesoTotalGeral > 0 ? (estatisticasFiltradas.pesoTotalPintura / pesoTotalGeral) * 100 : 0, + expedicao: pesoTotalGeral > 0 ? (estatisticasFiltradas.pesoTotalExpedicao / pesoTotalGeral) * 100 : 0, + }; + }, [selectedFase, dashboardData, pecasFiltradas, estatisticasFiltradas]); + // Obter fases únicas das peças const fasesUnicas = [...new Set(pecasComStatus.map(peca => peca.etapa_fase))].sort((a, b) => { const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); @@ -95,12 +124,14 @@ export const RelatorioPecasProcessoModal: React.FC @@ -115,7 +146,7 @@ export const RelatorioPecasProcessoModal: React.FC - {/* Estatísticas */} + {/* Estatísticas com Percentual de Progresso */}

Total de Peças

@@ -123,19 +154,39 @@ export const RelatorioPecasProcessoModal: React.FC

Peso Corte

-

{estatisticasFiltradas.pesoTotalCorte.toFixed(0)} kg

+

+ {estatisticasFiltradas.pesoTotalCorte.toFixed(0)} kg{' '} + + ({percentuais.corte.toFixed(1)}%) + +

Peso Solda

-

{estatisticasFiltradas.pesoTotalSolda.toFixed(0)} kg

+

+ {estatisticasFiltradas.pesoTotalSolda.toFixed(0)} kg{' '} + + ({percentuais.solda.toFixed(1)}%) + +

Peso Pintura

-

{estatisticasFiltradas.pesoTotalPintura.toFixed(0)} kg

+

+ {estatisticasFiltradas.pesoTotalPintura.toFixed(0)} kg{' '} + + ({percentuais.pintura.toFixed(1)}%) + +

Peso Expedição

-

{estatisticasFiltradas.pesoTotalExpedicao.toFixed(0)} kg

+

+ {estatisticasFiltradas.pesoTotalExpedicao.toFixed(0)} kg{' '} + + ({percentuais.expedicao.toFixed(1)}%) + +

@@ -161,21 +212,67 @@ export const RelatorioPecasProcessoModal: React.FC {peca.of_number} {peca.etapa_fase} - {peca.marca} + {peca.marca} {peca.quantidade} {peca.peso_unitario.toFixed(2)} kg {peca.peso_total.toFixed(2)} kg + + {/* Corte com check e data */} - {peca.processos.corte ? '✓' : ''} + {peca.processos.corte ? ( +
+ ✓ + {peca.datasProcessos?.corte && ( + + {peca.datasProcessos.corte} + + )} +
+ ) : ''} + + {/* Solda com check e data */} - {!peca.tem_componentes ? 'S/M' : (peca.processos.solda ? '✓' : '')} + {!peca.tem_componentes ? ( + S/M + ) : peca.processos.solda ? ( +
+ ✓ + {peca.datasProcessos?.solda && ( + + {peca.datasProcessos.solda} + + )} +
+ ) : ''} + + {/* Pintura com check e data */} - {peca.processos.pintura ? '✓' : ''} + {peca.processos.pintura ? ( +
+ ✓ + {peca.datasProcessos?.pintura && ( + + {peca.datasProcessos.pintura} + + )} +
+ ) : ''} + + {/* Expedição com check e data */} - {peca.processos.expedicao ? '✓' : ''} + {peca.processos.expedicao ? ( +
+ ✓ + {peca.datasProcessos?.expedicao && ( + + {peca.datasProcessos.expedicao} + + )} +
+ ) : ''} ))} diff --git a/src/components/producao/RelatorioPecasProcessoPDF.tsx b/src/components/producao/RelatorioPecasProcessoPDF.tsx index 0fca05f..3a35606 100644 --- a/src/components/producao/RelatorioPecasProcessoPDF.tsx +++ b/src/components/producao/RelatorioPecasProcessoPDF.tsx @@ -16,6 +16,12 @@ interface RelatorioPecasProcessoPDFProps { pesoTotalPintura: number; pesoTotalExpedicao: number; }; + percentuais?: { + corte: number; + solda: number; + pintura: number; + expedicao: number; + }; selectedOF: string; selectedFase: string; } @@ -23,6 +29,7 @@ interface RelatorioPecasProcessoPDFProps { export const RelatorioPecasProcessoPDF: React.FC = ({ pecasComStatus, estatisticas, + percentuais, selectedOF, selectedFase }) => { @@ -47,38 +54,53 @@ export const RelatorioPecasProcessoPDF: React.FC doc.setFont('helvetica', 'normal'); doc.text(`OF: ${selectedOF}${faseText} | Gerado em: ${new Date().toLocaleString('pt-BR')}`, 14, 23); - // Resumo por Processo em linha mais compacta - let yPos = 32; - doc.setFontSize(9); + // Resumo por Processo em box estilizado e alinhado + let yPos = 29; + doc.setFillColor(248, 250, 252); + doc.roundedRect(14, yPos, 182, 10, 1, 1, 'F'); + doc.setDrawColor(226, 232, 240); + doc.setLineWidth(0.3); + doc.roundedRect(14, yPos, 182, 10, 1, 1, 'S'); + + doc.setFontSize(7.5); doc.setFont('helvetica', 'bold'); - doc.text(`Total Peças:`, 14, yPos); - doc.setFont('helvetica', 'normal'); - doc.text(`${estatisticas.totalPecas}`, 36, yPos); + doc.setTextColor(71, 85, 105); + doc.text('Total Peças:', 17, yPos + 6.5); + doc.setTextColor(15, 23, 42); + doc.text(`${estatisticas.totalPecas}`, 33, yPos + 6.5); - doc.setFont('helvetica', 'bold'); - doc.text(`Corte:`, 50, yPos); - doc.setFont('helvetica', 'normal'); - doc.text(`${estatisticas.pesoTotalCorte.toFixed(0)} kg`, 62, yPos); + doc.setTextColor(71, 85, 105); + doc.text('Corte:', 44, yPos + 6.5); + doc.setTextColor(15, 43, 92); + doc.text(`${estatisticas.pesoTotalCorte.toFixed(0)} kg`, 54, yPos + 6.5); + doc.setTextColor(37, 99, 235); + doc.text(`(${(percentuais?.corte ?? 0).toFixed(1)}%)`, 70, yPos + 6.5); - doc.setFont('helvetica', 'bold'); - doc.text(`Solda:`, 90, yPos); - doc.setFont('helvetica', 'normal'); - doc.text(`${estatisticas.pesoTotalSolda.toFixed(0)} kg`, 102, yPos); + doc.setTextColor(71, 85, 105); + doc.text('Solda:', 86, yPos + 6.5); + doc.setTextColor(15, 43, 92); + doc.text(`${estatisticas.pesoTotalSolda.toFixed(0)} kg`, 96, yPos + 6.5); + doc.setTextColor(37, 99, 235); + doc.text(`(${(percentuais?.solda ?? 0).toFixed(1)}%)`, 112, yPos + 6.5); - doc.setFont('helvetica', 'bold'); - doc.text(`Pintura:`, 130, yPos); - doc.setFont('helvetica', 'normal'); - doc.text(`${estatisticas.pesoTotalPintura.toFixed(0)} kg`, 145, yPos); + doc.setTextColor(71, 85, 105); + doc.text('Pintura:', 127, yPos + 6.5); + doc.setTextColor(15, 43, 92); + doc.text(`${estatisticas.pesoTotalPintura.toFixed(0)} kg`, 138, yPos + 6.5); + doc.setTextColor(37, 99, 235); + doc.text(`(${(percentuais?.pintura ?? 0).toFixed(1)}%)`, 150, yPos + 6.5); - doc.setFont('helvetica', 'bold'); - doc.text(`Expedição:`, 175, yPos); - doc.setFont('helvetica', 'normal'); - doc.text(`${estatisticas.pesoTotalExpedicao.toFixed(0)} kg`, 192, yPos); + doc.setTextColor(71, 85, 105); + doc.text('Exp.:', 165, yPos + 6.5); + doc.setTextColor(15, 43, 92); + doc.text(`${estatisticas.pesoTotalExpedicao.toFixed(0)} kg`, 173, yPos + 6.5); + doc.setTextColor(37, 99, 235); + doc.text(`(${(percentuais?.expedicao ?? 0).toFixed(1)}%)`, 185, yPos + 6.5); - yPos = 38; // Ajusta yPos para o início da tabela + yPos = 42; // Ajusta yPos para o início da tabela - // Preparar dados para a tabela + // Preparar dados para a tabela com indicação de data abaixo do check const tableData = pecasComStatus.map(peca => [ peca.of_number, peca.etapa_fase, @@ -86,10 +108,10 @@ export const RelatorioPecasProcessoPDF: React.FC peca.quantidade.toString(), `${peca.peso_unitario.toFixed(2)} kg`, `${peca.peso_total.toFixed(2)} kg`, - peca.processos.corte ? 'OK' : '', - !peca.tem_componentes ? 'S/M' : (peca.processos.solda ? 'OK' : ''), - peca.processos.pintura ? 'OK' : '', - peca.processos.expedicao ? 'OK' : '' + peca.processos.corte ? (peca.datasProcessos?.corte ? `✓\n${peca.datasProcessos.corte}` : '✓') : '', + !peca.tem_componentes ? 'S/M' : (peca.processos.solda ? (peca.datasProcessos?.solda ? `✓\n${peca.datasProcessos.solda}` : '✓') : ''), + peca.processos.pintura ? (peca.datasProcessos?.pintura ? `✓\n${peca.datasProcessos.pintura}` : '✓') : '', + peca.processos.expedicao ? (peca.datasProcessos?.expedicao ? `✓\n${peca.datasProcessos.expedicao}` : '✓') : '' ]); // Cabeçalhos da tabela diff --git a/src/components/producao/RelatorioPecasProcessoPrint.tsx b/src/components/producao/RelatorioPecasProcessoPrint.tsx index eb77ac8..c79a91e 100644 --- a/src/components/producao/RelatorioPecasProcessoPrint.tsx +++ b/src/components/producao/RelatorioPecasProcessoPrint.tsx @@ -14,6 +14,12 @@ interface RelatorioPecasProcessoPrintProps { pesoTotalPintura: number; pesoTotalExpedicao: number; }; + percentuais?: { + corte: number; + solda: number; + pintura: number; + expedicao: number; + }; selectedOF: string; selectedFase: string; } @@ -21,6 +27,7 @@ interface RelatorioPecasProcessoPrintProps { export const RelatorioPecasProcessoPrint: React.FC = ({ pecasComStatus, estatisticas, + percentuais, selectedOF, selectedFase }) => { @@ -128,10 +135,10 @@ export const RelatorioPecasProcessoPrint: React.FC
Total Peças: ${estatisticas.totalPecas}
-
Corte: ${estatisticas.pesoTotalCorte.toFixed(0)} kg
-
Solda: ${estatisticas.pesoTotalSolda.toFixed(0)} kg
-
Pintura: ${estatisticas.pesoTotalPintura.toFixed(0)} kg
-
Expedição: ${estatisticas.pesoTotalExpedicao.toFixed(0)} kg
+
Corte: ${estatisticas.pesoTotalCorte.toFixed(0)} kg (${(percentuais?.corte ?? 0).toFixed(1)}%)
+
Solda: ${estatisticas.pesoTotalSolda.toFixed(0)} kg (${(percentuais?.solda ?? 0).toFixed(1)}%)
+
Pintura: ${estatisticas.pesoTotalPintura.toFixed(0)} kg (${(percentuais?.pintura ?? 0).toFixed(1)}%)
+
Expedição: ${estatisticas.pesoTotalExpedicao.toFixed(0)} kg (${(percentuais?.expedicao ?? 0).toFixed(1)}%)
@@ -157,14 +164,34 @@ export const RelatorioPecasProcessoPrint: React.FC ${peca.of_number} ${peca.etapa_fase} - ${peca.marca} + ${peca.marca} ${peca.quantidade} ${peca.peso_unitario.toFixed(2)} kg ${peca.peso_total.toFixed(2)} kg - ${peca.processos.corte ? 'OK' : ''} - ${!peca.tem_componentes ? 'S/M' : (peca.processos.solda ? 'OK' : '')} - ${peca.processos.pintura ? 'OK' : ''} - ${peca.processos.expedicao ? 'OK' : ''} + + ${peca.processos.corte ? ` +
✓
+ ${peca.datasProcessos?.corte ? `
${peca.datasProcessos.corte}
` : ''} + ` : ''} + + + ${!peca.tem_componentes ? 'S/M' : (peca.processos.solda ? ` +
✓
+ ${peca.datasProcessos?.solda ? `
${peca.datasProcessos.solda}
` : ''} + ` : '')} + + + ${peca.processos.pintura ? ` +
✓
+ ${peca.datasProcessos?.pintura ? `
${peca.datasProcessos.pintura}
` : ''} + ` : ''} + + + ${peca.processos.expedicao ? ` +
✓
+ ${peca.datasProcessos?.expedicao ? `
${peca.datasProcessos.expedicao}
` : ''} + ` : ''} + `).join('')} diff --git a/src/hooks/useRelatorioPecasProcesso.tsx b/src/hooks/useRelatorioPecasProcesso.tsx index 0379fa9..180cafd 100644 --- a/src/hooks/useRelatorioPecasProcesso.tsx +++ b/src/hooks/useRelatorioPecasProcesso.tsx @@ -18,6 +18,12 @@ export interface PecaComStatus { pintura: boolean; expedicao: boolean; }; + datasProcessos?: { + corte?: string | null; + solda?: string | null; + pintura?: string | null; + expedicao?: string | null; + }; } export interface ResumoProcesso { @@ -26,6 +32,29 @@ export interface ResumoProcesso { quantidadePecas: number; } +// Formata data ISO ou YYYY-MM-DD para 'dd/mm' +export const formatarDiaMes = (dataStr?: string | null): string => { + if (!dataStr) return ''; + try { + const raw = dataStr.split('T')[0]; + const partes = raw.split('-'); + if (partes.length === 3) { + const dia = partes[2].padStart(2, '0'); + const mes = partes[1].padStart(2, '0'); + return `${dia}/${mes}`; + } + const d = new Date(dataStr); + if (!isNaN(d.getTime())) { + const dia = d.getDate().toString().padStart(2, '0'); + const mes = (d.getMonth() + 1).toString().padStart(2, '0'); + return `${dia}/${mes}`; + } + } catch (e) { + // fallback + } + return ''; +}; + // Função para ordenação numérica natural const naturalSort = (a: string, b: string): number => { const collator = new Intl.Collator(undefined, { @@ -86,39 +115,103 @@ export const useRelatorioPecasProcesso = (ofNumber: string) => { const processosMap = new Map(processosData?.map(p => [p.nome, p.id]) || []); console.log('Processos mapeados:', Object.fromEntries(processosMap)); - // Buscar apontamentos para todas as peças da OF + // Buscar apontamentos para todas as peças da OF incluindo data_apontamento e created_at const pecaIds = pecasData.map(p => p.id); - const { data: apontamentosData, error: apontamentosError } = await supabase - .from('apontamentos_producao') - .select(` - peca_id, - processo_id, - quantidade_produzida, - processos_fabricacao!apontamentos_producao_processo_id_fkey(nome) - `) - .eq('of_number', ofNumber) - .in('peca_id', pecaIds); + const [resApontamentos, resRomaneios] = await Promise.all([ + supabase + .from('apontamentos_producao') + .select(` + peca_id, + processo_id, + quantidade_produzida, + data_apontamento, + created_at, + processos_fabricacao!apontamentos_producao_processo_id_fkey(nome) + `) + .eq('of_number', ofNumber) + .in('peca_id', pecaIds), - if (apontamentosError) { - console.error('Erro ao buscar apontamentos:', apontamentosError); - throw apontamentosError; + // Buscar romaneios de expedição para garantir datas e status de expedição + supabase + .from('itens_romaneio_pecas') + .select(` + marca, + peca_id, + quantidade_expedida, + created_at, + romaneios_expedicao!inner(of_number, data_saida, data_emissao, created_at) + `) + .eq('romaneios_expedicao.of_number', ofNumber) + ]); + + if (resApontamentos.error) { + console.error('Erro ao buscar apontamentos:', resApontamentos.error); + throw resApontamentos.error; } - console.log('Apontamentos encontrados:', apontamentosData?.length || 0); + const apontamentosData = resApontamentos.data || []; + const romaneiosData = resRomaneios.data || []; - // Mapear status dos processos por peça + console.log('Apontamentos encontrados:', apontamentosData.length); + + // Mapear status dos processos por peça e guardar a ÚLTIMA data apontada de cada processo const statusPorPeca = new Map>(); + const datasPorPeca = new Map(); - apontamentosData?.forEach(apt => { + apontamentosData.forEach(apt => { if (!statusPorPeca.has(apt.peca_id)) { statusPorPeca.set(apt.peca_id, new Set()); } - statusPorPeca.get(apt.peca_id)!.add(apt.processos_fabricacao.nome); + const nomeProc = apt.processos_fabricacao?.nome || ''; + statusPorPeca.get(apt.peca_id)!.add(nomeProc); + + if (!datasPorPeca.has(apt.peca_id)) { + datasPorPeca.set(apt.peca_id, { corte: '', solda: '', pintura: '', expedicao: '' }); + } + const datas = datasPorPeca.get(apt.peca_id)!; + const dataApt = apt.data_apontamento || apt.created_at || ''; + const nomeLower = nomeProc.toLowerCase(); + + if (dataApt) { + if (nomeLower.includes('corte')) { + if (!datas.corte || dataApt > datas.corte) datas.corte = dataApt; + } else if (nomeLower.includes('solda')) { + if (!datas.solda || dataApt > datas.solda) datas.solda = dataApt; + } else if (nomeLower.includes('pint') || nomeLower.includes('galv')) { + if (!datas.pintura || dataApt > datas.pintura) datas.pintura = dataApt; + } else if (nomeLower.includes('exped')) { + if (!datas.expedicao || dataApt > datas.expedicao) datas.expedicao = dataApt; + } + } }); - // Construir array de peças com status + // Adicionar informações vindas dos Romaneios de Expedição + romaneiosData.forEach((item: any) => { + const pecaAlvo = pecasData.find(p => p.id === item.peca_id || p.marca === item.marca); + if (pecaAlvo) { + if (!statusPorPeca.has(pecaAlvo.id)) { + statusPorPeca.set(pecaAlvo.id, new Set()); + } + statusPorPeca.get(pecaAlvo.id)!.add('Expedicao'); + + if (!datasPorPeca.has(pecaAlvo.id)) { + datasPorPeca.set(pecaAlvo.id, { corte: '', solda: '', pintura: '', expedicao: '' }); + } + const datas = datasPorPeca.get(pecaAlvo.id)!; + const dataExp = item.romaneios_expedicao?.data_saida || + item.romaneios_expedicao?.data_emissao || + item.romaneios_expedicao?.created_at || + item.created_at || ''; + if (dataExp && (!datas.expedicao || dataExp > datas.expedicao)) { + datas.expedicao = dataExp; + } + } + }); + + // Construir array de peças com status e última data apontada const pecasComStatusFormatadas: PecaComStatus[] = pecasData.map(peca => { const processosRealizados = statusPorPeca.get(peca.id) || new Set(); + const datas = datasPorPeca.get(peca.id) || { corte: '', solda: '', pintura: '', expedicao: '' }; return { id: peca.id, @@ -136,6 +229,12 @@ export const useRelatorioPecasProcesso = (ofNumber: string) => { processosRealizados.has('Pintura/Galv') || processosRealizados.has('Pintura'), expedicao: processosRealizados.has('Expedicao') + }, + datasProcessos: { + corte: formatarDiaMes(datas.corte), + solda: formatarDiaMes(datas.solda), + pintura: formatarDiaMes(datas.pintura), + expedicao: formatarDiaMes(datas.expedicao) } }; });