175 lines
8.5 KiB
TypeScript
175 lines
8.5 KiB
TypeScript
import React, { useRef, useLayoutEffect } from 'react';
|
||
import { format, min, max, isValid } from 'date-fns';
|
||
import type { Project, Inspection } from '../../types';
|
||
import '../../styles/reports.css';
|
||
|
||
interface GeneralProjectReportProps {
|
||
projects: Project[];
|
||
inspections?: Inspection[];
|
||
title: string;
|
||
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 GeneralProjectReport: React.FC<GeneralProjectReportProps> = ({ projects, inspections = [], title, logoUrl }) => {
|
||
// Cálculos globais
|
||
const totalWeight = projects.reduce((acc, p) => acc + (p.weightKg || 0), 0);
|
||
|
||
const calculateProjectProgress = (project: Project) => {
|
||
const projectInspections = inspections.filter(i => i.projectId === project.id);
|
||
const sumWeight = projectInspections.reduce((acc, curr) => acc + (curr.weightKg || 0), 0);
|
||
const totalW = project.weightKg || 0;
|
||
return totalW > 0 ? Math.min((sumWeight / totalW) * 100, 100) : 0;
|
||
};
|
||
|
||
const avgProgress = projects.length > 0
|
||
? projects.reduce((acc, p) => acc + calculateProjectProgress(p), 0) / projects.length
|
||
: 0;
|
||
|
||
// Período (Min/Max das datas)
|
||
const allDates = projects.flatMap(p => [
|
||
p.startDate ? new Date(p.startDate) : null,
|
||
p.endDate ? new Date(p.endDate) : null
|
||
]).filter((d): d is Date => d !== null && isValid(d));
|
||
|
||
const periodStart = allDates.length > 0 ? format(min(allDates), 'MM/yyyy') : '--/----';
|
||
const periodEnd = allDates.length > 0 ? format(max(allDates), 'MM/yyyy') : '--/----';
|
||
|
||
return (
|
||
<div className="report-container print:block hidden" id="general-project-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">{title.toUpperCase()}</div>
|
||
<div className="brand-subtitle">
|
||
Obras / Projetos – Situação de produção e pintura
|
||
</div>
|
||
</div>
|
||
<div className="meta">
|
||
<div><strong>Data:</strong> {format(new Date(), 'dd/MM/yyyy')}</div>
|
||
<div><strong>Responsável:</strong> ________________</div>
|
||
</div>
|
||
</header>
|
||
|
||
<section className="summary">
|
||
<div className="summary-item">
|
||
<div className="summary-label">Total de obras</div>
|
||
<div className="summary-value">{projects.length}</div>
|
||
<div className="summary-sub">Listadas neste relatório</div>
|
||
</div>
|
||
<div className="summary-item">
|
||
<div className="summary-label">Peso total (kgf)</div>
|
||
<div className="summary-value">{totalWeight.toLocaleString('pt-BR')}</div>
|
||
<div className="summary-sub">Soma dos projetos</div>
|
||
</div>
|
||
<div className="summary-item">
|
||
<div className="summary-label">Evolução média</div>
|
||
<div className="summary-value">{avgProgress.toFixed(1).replace('.', ',')}%</div>
|
||
<div className="summary-sub">Estimativa geral</div>
|
||
</div>
|
||
<div className="summary-item">
|
||
<div className="summary-label">Período</div>
|
||
<div className="summary-value">{periodStart} – {periodEnd}</div>
|
||
<div className="summary-sub">Previsão de execução</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div className="section-title">
|
||
<h2>OBRAS / PROJETOS</h2>
|
||
<span>Visão geral por cronograma, peso e sistema de pintura</span>
|
||
</div>
|
||
|
||
<table className="table">
|
||
<thead>
|
||
<tr>
|
||
<th className="col-obra">Obra / Projeto</th>
|
||
<th className="col-evol">Evol.</th>
|
||
<th className="col-cron">Cronograma</th>
|
||
<th className="col-peso text-center">Peso (kgf)</th>
|
||
<th className="col-tinta">Tinta</th>
|
||
<th className="col-cor">Cor</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{projects.map((project) => {
|
||
const progress = calculateProjectProgress(project);
|
||
const schemes = project.paintingSchemes || [];
|
||
|
||
return (
|
||
<tr key={project.id}>
|
||
<td className="col-obra">
|
||
<div className="obra-nome">{project.name.toUpperCase()}</div>
|
||
<div className="obra-cliente">Cliente: {project.client}</div>
|
||
<div className="obra-cliente">Gestor: {project.technician || '--'}</div>
|
||
</td>
|
||
<td className="col-evol">
|
||
<div className="font-bold">{Math.round(progress)}%</div>
|
||
<div className="evol-bar">
|
||
<ProgressFill progress={progress} />
|
||
</div>
|
||
</td>
|
||
<td className="col-cron">
|
||
<div className="cron">
|
||
<strong>Início:</strong> {project.startDate ? format(new Date(project.startDate), 'dd/MM/yyyy') : '--/--/----'}<br />
|
||
<strong>Término:</strong> {project.endDate ? format(new Date(project.endDate), 'dd/MM/yyyy') : '--/--/----'}
|
||
</div>
|
||
</td>
|
||
<td className="col-peso text-center">
|
||
<div className="font-bold">
|
||
{(project.weightKg || 0).toLocaleString('pt-BR')}
|
||
<span className="block text-[7pt] text-gray-400 font-normal">Est. total</span>
|
||
</div>
|
||
</td>
|
||
<td className="col-tinta">
|
||
<div className="tinta">
|
||
{schemes.length > 0 ? schemes.slice(0, 2).map((s, idx) => (
|
||
<React.Fragment key={idx}>
|
||
<strong>{s.name.toUpperCase()}</strong>
|
||
{s.coat || s.type || 'Esquema'}{idx < schemes.length - 1 && <br />}
|
||
</React.Fragment>
|
||
)) : <span className="text-gray-400 italic">Sem esquema</span>}
|
||
</div>
|
||
</td>
|
||
<td className="col-cor">
|
||
<div className="text-[8pt] leading-tight">
|
||
{schemes.length > 0 ? schemes.slice(0, 2).map((s, idx) => (
|
||
<div key={idx}>{s.color || '-'}</div>
|
||
)) : '-'}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
);
|
||
})}
|
||
</tbody>
|
||
</table>
|
||
|
||
<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>
|
||
);
|
||
};
|