187 lines
8.8 KiB
TypeScript
187 lines
8.8 KiB
TypeScript
import React, { useRef, useLayoutEffect } from 'react';
|
||
import { format, isValid } from 'date-fns';
|
||
import type { Project } from '../../types';
|
||
import '../../styles/reports.css';
|
||
|
||
interface AnalyticalReportProps {
|
||
project: Project;
|
||
logoUrl?: string;
|
||
}
|
||
|
||
const ProgressFill: React.FC<{ progress: number }> = ({ progress }) => {
|
||
const fillRef = useRef<HTMLDivElement>(null);
|
||
useLayoutEffect(() => {
|
||
if (fillRef.current) {
|
||
fillRef.current.style.setProperty('--progress', `${progress}%`);
|
||
}
|
||
}, [progress]);
|
||
return <div ref={fillRef} className="evol-fill" />;
|
||
};
|
||
|
||
export const AnalyticalReport: React.FC<AnalyticalReportProps> = ({ project, logoUrl }) => {
|
||
const inspections = project.inspections || [];
|
||
const sumWeight = inspections.reduce((acc, curr) => acc + (curr.weightKg || 0), 0);
|
||
const totalWeight = project.weightKg || 0;
|
||
const progress = totalWeight > 0 ? Math.min(Math.round((sumWeight / totalWeight) * 100), 100) : 0;
|
||
|
||
// Período
|
||
const startDate = project.startDate ? new Date(project.startDate) : null;
|
||
const endDate = project.endDate ? new Date(project.endDate) : null;
|
||
const periodStr = (startDate && isValid(startDate) && endDate && isValid(endDate))
|
||
? `${format(startDate, 'MM/yyyy')} – ${format(endDate, 'MM/yyyy')}`
|
||
: '--/----';
|
||
|
||
return (
|
||
<div className="report-container print:block hidden" id="analytical-report">
|
||
<header className="report-header">
|
||
<div className="brand">
|
||
{logoUrl ? (
|
||
<img src={logoUrl} alt="Logo" className="brand-logo" />
|
||
) : (
|
||
<div className="logo-placeholder"></div>
|
||
)}
|
||
</div>
|
||
<div className="text-center">
|
||
<div className="brand-title">RELATÓRIO ANALÍTICO DE OBRA</div>
|
||
<div className="brand-subtitle">
|
||
Detalhamento de Inspeções, Aplicativos e Esquemas de Pintura
|
||
</div>
|
||
</div>
|
||
<div className="meta">
|
||
<div><strong>Data:</strong> {format(new Date(), 'dd/MM/yyyy')}</div>
|
||
<div><strong>Obra:</strong> {project.name.toUpperCase()}</div>
|
||
</div>
|
||
</header>
|
||
|
||
<section className="summary">
|
||
<div className="summary-item">
|
||
<div className="summary-label">Evolução Real</div>
|
||
<div className="summary-value">{progress}%</div>
|
||
<div className="evol-bar">
|
||
<ProgressFill progress={progress} />
|
||
</div>
|
||
</div>
|
||
<div className="summary-item">
|
||
<div className="summary-label">Peso Medido (kgf)</div>
|
||
<div className="summary-value">{sumWeight.toLocaleString('pt-BR')}</div>
|
||
<div className="summary-sub">de {totalWeight.toLocaleString('pt-BR')} total</div>
|
||
</div>
|
||
<div className="summary-item">
|
||
<div className="summary-label">Responsável</div>
|
||
<div className="summary-value text-11pt">{project.technician || '________________'}</div>
|
||
<div className="summary-sub">Técnico Encarregado</div>
|
||
</div>
|
||
<div className="summary-item">
|
||
<div className="summary-label">Período de Obra</div>
|
||
<div className="summary-value text-11pt">{periodStr}</div>
|
||
<div className="summary-sub">Cronograma Previsto</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div className="section-title">
|
||
<h2>ESQUEMA DE PINTURA REQUERIDO</h2>
|
||
<span>Especificação técnica por demão</span>
|
||
</div>
|
||
<table className="table">
|
||
<thead>
|
||
<tr>
|
||
<th className="w-20">Etapa</th>
|
||
<th className="w-40">Produto</th>
|
||
<th className="w-20">Cor</th>
|
||
<th className="w-20">EPS (μm)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{project.paintingSchemes?.map((s, idx) => (
|
||
<tr key={idx}>
|
||
<td className="font-bold uppercase">{s.coat || s.type}</td>
|
||
<td>{s.name.toUpperCase()}</td>
|
||
<td>{s.color || '--'}</td>
|
||
<td className="font-bold">{s.epsMin}-{s.epsMax} μm</td>
|
||
</tr>
|
||
))}
|
||
{(!project.paintingSchemes || project.paintingSchemes.length === 0) && (
|
||
<tr><td colSpan={4} className="text-center p-10mm text-gray-muted">Nenhum esquema definido</td></tr>
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
|
||
<div className="grid-2col">
|
||
<div>
|
||
<div className="section-title">
|
||
<h2>INSPEÇÕES REALIZADAS</h2>
|
||
</div>
|
||
<table className="table">
|
||
<thead>
|
||
<tr>
|
||
<th className="w-25">Data</th>
|
||
<th className="w-40">Peça / Área</th>
|
||
<th className="w-20">Peso</th>
|
||
<th className="w-15">Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{inspections.slice(0, 15).map((insp, idx) => (
|
||
<tr key={idx}>
|
||
<td>{insp.date ? format(new Date(insp.date), 'dd/MM/yy') : '--'}</td>
|
||
<td className="uppercase font-medium text-8pt">{insp.pieceDescription}</td>
|
||
<td className="font-bold">{insp.weightKg?.toLocaleString('pt-BR')}</td>
|
||
<td>
|
||
<span className={`badge ${insp.appearance === 'approved' ? 'badge-ok' : 'badge-err'}`}>
|
||
{insp.appearance === 'approved' ? 'OK' : 'REJ'}
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
{inspections.length === 0 && (
|
||
<tr><td colSpan={4} className="text-center p-5mm text-gray-muted">Sem registros</td></tr>
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div>
|
||
<div className="section-title">
|
||
<h2>REGISTROS DE APLICAÇÃO</h2>
|
||
</div>
|
||
<table className="table">
|
||
<thead>
|
||
<tr>
|
||
<th className="w-25">Data</th>
|
||
<th className="w-35">Etapa</th>
|
||
<th className="w-20">EPS Seca</th>
|
||
<th className="w-20">Pintor</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{project.applicationRecords?.slice(0, 15).map((record, idx) => (
|
||
<tr key={idx}>
|
||
<td>{record.date ? format(new Date(record.date), 'dd/MM/yy') : '--'}</td>
|
||
<td className="uppercase font-medium text-8pt">{record.coatStage}</td>
|
||
<td className="font-bold">{record.dryThicknessCalc || '--'} μm</td>
|
||
<td className="uppercase text-7pt">{record.operator?.split(' ')[0]}</td>
|
||
</tr>
|
||
))}
|
||
{(!project.applicationRecords || project.applicationRecords.length === 0) && (
|
||
<tr><td colSpan={4} className="text-center p-5mm text-gray-muted">Sem registros</td></tr>
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<footer className="report-footer">
|
||
<div className="system-title">
|
||
SteelPaint - Gestão de Pintura Industrial
|
||
</div>
|
||
<div>
|
||
Gerado em {format(new Date(), 'dd/MM/yyyy')} às {format(new Date(), 'HH:mm')}h
|
||
</div>
|
||
<div className="sig-group">
|
||
<div className="sig-line">Responsável Qualidade<span></span></div>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
);
|
||
};
|