Files
GPI/src/client/components/reports/GeneralProjectReport.tsx

175 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
};