Adiciona percentual de progresso e data do ultimo apontamento no relatorio de pecas por processo

This commit is contained in:
2026-09-22 10:45:08 +00:00
parent 9574cdb3cb
commit 3a3e230ce7
4 changed files with 312 additions and 67 deletions
@@ -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<RelatorioPecasProcessoModalPr
const [selectedFase, setSelectedFase] = useState('todas');
const { ofs, loading: ofsLoading } = useOFs();
const { pecasComStatus, resumoProcessos, loading } = useRelatorioPecasProcesso(selectedOF);
const { dashboardData } = useDashboardProducaoOtimizado(selectedOF);
// Filtrar peças por fase selecionada
const pecasFiltradas = selectedFase === 'todas'
@@ -37,6 +39,33 @@ export const RelatorioPecasProcessoModal: React.FC<RelatorioPecasProcessoModalPr
pesoTotalExpedicao: pecasFiltradas.filter(p => 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<RelatorioPecasProcessoModalPr
<RelatorioPecasProcessoPDF
pecasComStatus={pecasFiltradas}
estatisticas={estatisticasFiltradas}
percentuais={percentuais}
selectedOF={selectedOF}
selectedFase={selectedFase}
/>
<RelatorioPecasProcessoPrint
pecasComStatus={pecasFiltradas}
estatisticas={estatisticasFiltradas}
percentuais={percentuais}
selectedOF={selectedOF}
selectedFase={selectedFase}
/>
@@ -115,7 +146,7 @@ export const RelatorioPecasProcessoModal: React.FC<RelatorioPecasProcessoModalPr
Preview - OF {selectedOF} {selectedFase !== 'todas' ? `- Fase ${selectedFase}` : ''}
</h3>
{/* Estatísticas */}
{/* Estatísticas com Percentual de Progresso */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6">
<div className="bg-muted/50 p-3 rounded text-center">
<p className="text-sm text-muted-foreground">Total de Peças</p>
@@ -123,19 +154,39 @@ export const RelatorioPecasProcessoModal: React.FC<RelatorioPecasProcessoModalPr
</div>
<div className="bg-muted/50 p-3 rounded text-center">
<p className="text-sm text-muted-foreground">Peso Corte</p>
<p className="text-xl font-bold text-card-foreground">{estatisticasFiltradas.pesoTotalCorte.toFixed(0)} kg</p>
<p className="text-xl font-bold text-card-foreground">
{estatisticasFiltradas.pesoTotalCorte.toFixed(0)} kg{' '}
<span className="text-sm font-semibold text-blue-600 dark:text-blue-400">
({percentuais.corte.toFixed(1)}%)
</span>
</p>
</div>
<div className="bg-muted/50 p-3 rounded text-center">
<p className="text-sm text-muted-foreground">Peso Solda</p>
<p className="text-xl font-bold text-card-foreground">{estatisticasFiltradas.pesoTotalSolda.toFixed(0)} kg</p>
<p className="text-xl font-bold text-card-foreground">
{estatisticasFiltradas.pesoTotalSolda.toFixed(0)} kg{' '}
<span className="text-sm font-semibold text-blue-600 dark:text-blue-400">
({percentuais.solda.toFixed(1)}%)
</span>
</p>
</div>
<div className="bg-muted/50 p-3 rounded text-center">
<p className="text-sm text-muted-foreground">Peso Pintura</p>
<p className="text-xl font-bold text-card-foreground">{estatisticasFiltradas.pesoTotalPintura.toFixed(0)} kg</p>
<p className="text-xl font-bold text-card-foreground">
{estatisticasFiltradas.pesoTotalPintura.toFixed(0)} kg{' '}
<span className="text-sm font-semibold text-blue-600 dark:text-blue-400">
({percentuais.pintura.toFixed(1)}%)
</span>
</p>
</div>
<div className="bg-muted/50 p-3 rounded text-center">
<p className="text-sm text-muted-foreground">Peso Expedição</p>
<p className="text-xl font-bold text-card-foreground">{estatisticasFiltradas.pesoTotalExpedicao.toFixed(0)} kg</p>
<p className="text-xl font-bold text-card-foreground">
{estatisticasFiltradas.pesoTotalExpedicao.toFixed(0)} kg{' '}
<span className="text-sm font-semibold text-blue-600 dark:text-blue-400">
({percentuais.expedicao.toFixed(1)}%)
</span>
</p>
</div>
</div>
@@ -161,21 +212,67 @@ export const RelatorioPecasProcessoModal: React.FC<RelatorioPecasProcessoModalPr
<tr key={`${peca.id}-${index}`} className="hover:bg-muted/20">
<td className="border border-border p-2 text-xs text-card-foreground">{peca.of_number}</td>
<td className="border border-border p-2 text-xs text-card-foreground">{peca.etapa_fase}</td>
<td className="border border-border p-2 text-xs text-card-foreground">{peca.marca}</td>
<td className="border border-border p-2 text-xs text-card-foreground font-semibold">{peca.marca}</td>
<td className="border border-border p-2 text-xs text-center text-card-foreground">{peca.quantidade}</td>
<td className="border border-border p-2 text-xs text-right text-card-foreground">{peca.peso_unitario.toFixed(2)} kg</td>
<td className="border border-border p-2 text-xs text-right text-card-foreground">{peca.peso_total.toFixed(2)} kg</td>
{/* Corte com check e data */}
<td className="border border-border p-2 text-xs text-center text-card-foreground">
{peca.processos.corte ? '✓' : ''}
{peca.processos.corte ? (
<div className="flex flex-col items-center justify-center leading-tight">
<span className="text-emerald-600 dark:text-emerald-400 font-bold text-sm">✓</span>
{peca.datasProcessos?.corte && (
<span className="text-[10px] text-muted-foreground font-medium">
{peca.datasProcessos.corte}
</span>
)}
</div>
) : ''}
</td>
{/* Solda com check e data */}
<td className="border border-border p-2 text-xs text-center text-card-foreground">
{!peca.tem_componentes ? 'S/M' : (peca.processos.solda ? '✓' : '')}
{!peca.tem_componentes ? (
<span className="text-purple-600 dark:text-purple-400 font-semibold">S/M</span>
) : peca.processos.solda ? (
<div className="flex flex-col items-center justify-center leading-tight">
<span className="text-emerald-600 dark:text-emerald-400 font-bold text-sm">✓</span>
{peca.datasProcessos?.solda && (
<span className="text-[10px] text-muted-foreground font-medium">
{peca.datasProcessos.solda}
</span>
)}
</div>
) : ''}
</td>
{/* Pintura com check e data */}
<td className="border border-border p-2 text-xs text-center text-card-foreground">
{peca.processos.pintura ? '✓' : ''}
{peca.processos.pintura ? (
<div className="flex flex-col items-center justify-center leading-tight">
<span className="text-emerald-600 dark:text-emerald-400 font-bold text-sm">✓</span>
{peca.datasProcessos?.pintura && (
<span className="text-[10px] text-muted-foreground font-medium">
{peca.datasProcessos.pintura}
</span>
)}
</div>
) : ''}
</td>
{/* Expedição com check e data */}
<td className="border border-border p-2 text-xs text-center text-card-foreground">
{peca.processos.expedicao ? '✓' : ''}
{peca.processos.expedicao ? (
<div className="flex flex-col items-center justify-center leading-tight">
<span className="text-emerald-600 dark:text-emerald-400 font-bold text-sm">✓</span>
{peca.datasProcessos?.expedicao && (
<span className="text-[10px] text-muted-foreground font-medium">
{peca.datasProcessos.expedicao}
</span>
)}
</div>
) : ''}
</td>
</tr>
))}
@@ -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<RelatorioPecasProcessoPDFProps> = ({
pecasComStatus,
estatisticas,
percentuais,
selectedOF,
selectedFase
}) => {
@@ -47,38 +54,53 @@ export const RelatorioPecasProcessoPDF: React.FC<RelatorioPecasProcessoPDFProps>
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<RelatorioPecasProcessoPDFProps>
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
@@ -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<RelatorioPecasProcessoPrintProps> = ({
pecasComStatus,
estatisticas,
percentuais,
selectedOF,
selectedFase
}) => {
@@ -128,10 +135,10 @@ export const RelatorioPecasProcessoPrint: React.FC<RelatorioPecasProcessoPrintPr
<section style="margin-bottom: 12px;">
<div style="display: flex; flex-wrap: wrap; justify-content: space-between; font-size: 11px; color: #4b5563; padding: 6px 12px; border: 1px solid #e5e7eb; border-radius: 6px; background-color: #f9fafb;" class="summary-info">
<div><strong>Total Peças:</strong> ${estatisticas.totalPecas}</div>
<div><strong>Corte:</strong> ${estatisticas.pesoTotalCorte.toFixed(0)} kg</div>
<div><strong>Solda:</strong> ${estatisticas.pesoTotalSolda.toFixed(0)} kg</div>
<div><strong>Pintura:</strong> ${estatisticas.pesoTotalPintura.toFixed(0)} kg</div>
<div><strong>Expedição:</strong> ${estatisticas.pesoTotalExpedicao.toFixed(0)} kg</div>
<div><strong>Corte:</strong> ${estatisticas.pesoTotalCorte.toFixed(0)} kg <span style="color: #2563eb; font-weight: bold;">(${(percentuais?.corte ?? 0).toFixed(1)}%)</span></div>
<div><strong>Solda:</strong> ${estatisticas.pesoTotalSolda.toFixed(0)} kg <span style="color: #2563eb; font-weight: bold;">(${(percentuais?.solda ?? 0).toFixed(1)}%)</span></div>
<div><strong>Pintura:</strong> ${estatisticas.pesoTotalPintura.toFixed(0)} kg <span style="color: #2563eb; font-weight: bold;">(${(percentuais?.pintura ?? 0).toFixed(1)}%)</span></div>
<div><strong>Expedição:</strong> ${estatisticas.pesoTotalExpedicao.toFixed(0)} kg <span style="color: #2563eb; font-weight: bold;">(${(percentuais?.expedicao ?? 0).toFixed(1)}%)</span></div>
</div>
</section>
@@ -157,14 +164,34 @@ export const RelatorioPecasProcessoPrint: React.FC<RelatorioPecasProcessoPrintPr
<tr>
<td>${peca.of_number}</td>
<td>${peca.etapa_fase}</td>
<td>${peca.marca}</td>
<td style="font-weight: 600;">${peca.marca}</td>
<td>${peca.quantidade}</td>
<td class="text-right">${peca.peso_unitario.toFixed(2)} kg</td>
<td class="text-right">${peca.peso_total.toFixed(2)} kg</td>
<td>${peca.processos.corte ? 'OK' : ''}</td>
<td>${!peca.tem_componentes ? 'S/M' : (peca.processos.solda ? 'OK' : '')}</td>
<td>${peca.processos.pintura ? 'OK' : ''}</td>
<td>${peca.processos.expedicao ? 'OK' : ''}</td>
<td>
${peca.processos.corte ? `
<div style="font-weight: bold; color: #16a34a; font-size: 9pt; line-height: 1;">✓</div>
${peca.datasProcessos?.corte ? `<div style="font-size: 6.5pt; color: #6b7280; margin-top: 1px;">${peca.datasProcessos.corte}</div>` : ''}
` : ''}
</td>
<td>
${!peca.tem_componentes ? '<span style="color: #9333ea; font-weight: 600;">S/M</span>' : (peca.processos.solda ? `
<div style="font-weight: bold; color: #16a34a; font-size: 9pt; line-height: 1;">✓</div>
${peca.datasProcessos?.solda ? `<div style="font-size: 6.5pt; color: #6b7280; margin-top: 1px;">${peca.datasProcessos.solda}</div>` : ''}
` : '')}
</td>
<td>
${peca.processos.pintura ? `
<div style="font-weight: bold; color: #16a34a; font-size: 9pt; line-height: 1;">✓</div>
${peca.datasProcessos?.pintura ? `<div style="font-size: 6.5pt; color: #6b7280; margin-top: 1px;">${peca.datasProcessos.pintura}</div>` : ''}
` : ''}
</td>
<td>
${peca.processos.expedicao ? `
<div style="font-weight: bold; color: #16a34a; font-size: 9pt; line-height: 1;">✓</div>
${peca.datasProcessos?.expedicao ? `<div style="font-size: 6.5pt; color: #6b7280; margin-top: 1px;">${peca.datasProcessos.expedicao}</div>` : ''}
` : ''}
</td>
</tr>
`).join('')}
</tbody>