Deploy Inicial do SteelCheck com Docker Build Automatizado
This commit is contained in:
@@ -118,20 +118,20 @@ export const ReportDisplay: React.FC<ReportDisplayProps> = ({ report }) => {
|
|||||||
<section className="grid grid-cols-1 md:grid-cols-12 gap-5 mb-5">
|
<section className="grid grid-cols-1 md:grid-cols-12 gap-5 mb-5">
|
||||||
|
|
||||||
{/* Propriedades Mecânicas (Esquerda) */}
|
{/* Propriedades Mecânicas (Esquerda) */}
|
||||||
<div className="col-span-1 md:col-span-7 flex flex-col">
|
<div className="col-span-1 md:col-span-6 flex flex-col">
|
||||||
<h3 className="text-[10px] uppercase font-bold tracking-wider text-slate-400 dark:text-slate-500 mb-2.5 flex items-center gap-1.5">
|
<h3 className="text-[10px] uppercase font-bold tracking-wider text-slate-400 dark:text-slate-500 mb-2.5 flex items-center gap-1.5">
|
||||||
<span className="w-1.5 h-1.5 bg-slate-400 dark:bg-slate-500 rounded-full"></span>
|
<span className="w-1.5 h-1.5 bg-slate-400 dark:bg-slate-500 rounded-full"></span>
|
||||||
2. Propriedades Mecânicas
|
2. Propriedades Mecânicas
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex-grow border border-slate-100 dark:border-slate-800 rounded-xl overflow-hidden bg-white dark:bg-slate-900 shadow-sm flex flex-col justify-between">
|
<div className="flex-grow border border-slate-100 dark:border-slate-800 rounded-xl overflow-hidden bg-white dark:bg-slate-900 shadow-sm flex flex-col justify-between">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-hidden">
|
||||||
<table className="w-full text-left border-collapse text-xs">
|
<table className="w-full table-fixed text-left border-collapse text-xs">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-slate-50 dark:bg-slate-800/50 border-b border-slate-100 dark:border-slate-800 text-[9px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">
|
<tr className="bg-slate-50 dark:bg-slate-800/50 border-b border-slate-100 dark:border-slate-800 text-[9px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">
|
||||||
<th className="py-2.5 px-3">Propriedade</th>
|
<th className="py-2.5 px-2 w-[35%] truncate">Propriedade</th>
|
||||||
<th className="py-2.5 px-2">Certificado</th>
|
<th className="py-2.5 px-1 w-[25%] truncate">Certif.</th>
|
||||||
<th className="py-2.5 px-2 text-center">% Rel.</th>
|
<th className="py-2.5 px-1 w-[15%] text-center truncate">% Rel.</th>
|
||||||
<th className="py-2.5 px-3 text-right">Status</th>
|
<th className="py-2.5 px-1 w-[25%] text-right truncate">Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-slate-50 dark:divide-slate-800/50">
|
<tbody className="divide-y divide-slate-50 dark:divide-slate-800/50">
|
||||||
@@ -140,17 +140,17 @@ export const ReportDisplay: React.FC<ReportDisplayProps> = ({ report }) => {
|
|||||||
const diffColor = isPositive ? 'text-emerald-600 dark:text-emerald-400' : 'text-rose-600 dark:text-rose-400';
|
const diffColor = isPositive ? 'text-emerald-600 dark:text-emerald-400' : 'text-rose-600 dark:text-rose-400';
|
||||||
return (
|
return (
|
||||||
<tr key={i}>
|
<tr key={i}>
|
||||||
<td className="py-2.5 px-3">
|
<td className="py-2.5 px-2 truncate">
|
||||||
<span className="font-semibold block text-slate-800 dark:text-slate-200">{item.property}</span>
|
<span className="font-semibold block text-slate-800 dark:text-slate-200" title={item.property}>{item.property}</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2.5 px-2 font-mono font-bold text-slate-700 dark:text-slate-300">{item.certificate}</td>
|
<td className="py-2.5 px-1 font-mono font-bold text-slate-700 dark:text-slate-300 truncate" title={item.certificate}>{item.certificate}</td>
|
||||||
<td className="py-2.5 px-2 text-center">
|
<td className="py-2.5 px-1 text-center truncate">
|
||||||
{item.differencePercentage ? (
|
{item.differencePercentage ? (
|
||||||
<span className={`text-[9px] font-bold ${diffColor}`}>{item.differencePercentage}</span>
|
<span className={`text-[9px] font-bold ${diffColor}`}>{item.differencePercentage}</span>
|
||||||
) : <span className="text-slate-300 dark:text-slate-600">-</span>}
|
) : <span className="text-slate-300 dark:text-slate-600">-</span>}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2.5 px-3 text-right">
|
<td className="py-2.5 px-1 text-right">
|
||||||
<span className={`inline-flex items-center gap-1 text-[10px] font-bold px-2 py-0.5 rounded ${statusClass(item.status)}`}>
|
<span className={`inline-flex items-center gap-1 text-[9px] font-bold px-1.5 py-0.5 rounded ${statusClass(item.status)}`}>
|
||||||
{statusIcon(item.status)} {item.status}
|
{statusIcon(item.status)} {item.status}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -172,30 +172,30 @@ export const ReportDisplay: React.FC<ReportDisplayProps> = ({ report }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Composição Química (Direita) */}
|
{/* Composição Química (Direita) */}
|
||||||
<div className="col-span-1 md:col-span-5 flex flex-col">
|
<div className="col-span-1 md:col-span-6 flex flex-col">
|
||||||
<h3 className="text-[10px] uppercase font-bold tracking-wider text-slate-400 dark:text-slate-500 mb-2.5 flex items-center gap-1.5">
|
<h3 className="text-[10px] uppercase font-bold tracking-wider text-slate-400 dark:text-slate-500 mb-2.5 flex items-center gap-1.5">
|
||||||
<span className="w-1.5 h-1.5 bg-slate-400 dark:bg-slate-500 rounded-full"></span>
|
<span className="w-1.5 h-1.5 bg-slate-400 dark:bg-slate-500 rounded-full"></span>
|
||||||
3. Composição Química (%)
|
3. Composição Química (%)
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex-grow border border-slate-100 dark:border-slate-800 rounded-xl overflow-hidden bg-white dark:bg-slate-900 shadow-sm flex flex-col justify-start">
|
<div className="flex-grow border border-slate-100 dark:border-slate-800 rounded-xl overflow-hidden bg-white dark:bg-slate-900 shadow-sm flex flex-col justify-start">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-hidden">
|
||||||
<table className="w-full text-left border-collapse text-xs">
|
<table className="w-full table-fixed text-left border-collapse text-xs">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-slate-50 dark:bg-slate-800/50 border-b border-slate-100 dark:border-slate-800 text-[9px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">
|
<tr className="bg-slate-50 dark:bg-slate-800/50 border-b border-slate-100 dark:border-slate-800 text-[9px] text-slate-400 dark:text-slate-500 font-bold uppercase tracking-wider">
|
||||||
<th className="py-2 px-3">Elem.</th>
|
<th className="py-2 px-2 w-[15%] truncate">Elem.</th>
|
||||||
<th className="py-2 px-2">Norma de Referência</th>
|
<th className="py-2 px-1 w-[40%] truncate">Norma Referência</th>
|
||||||
<th className="py-2 px-2">Valor Certificado</th>
|
<th className="py-2 px-1 w-[25%] truncate">Certificado</th>
|
||||||
<th className="py-2 px-3 text-right">Status</th>
|
<th className="py-2 px-1 w-[20%] text-right truncate">Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-slate-50 dark:divide-slate-800/50 font-mono">
|
<tbody className="divide-y divide-slate-50 dark:divide-slate-800/50 font-mono">
|
||||||
{compliance.chemical.map((item, i) => (
|
{compliance.chemical.map((item, i) => (
|
||||||
<tr key={i}>
|
<tr key={i}>
|
||||||
<td className="py-2 px-3 font-sans font-semibold text-slate-800 dark:text-slate-200">{item.element}</td>
|
<td className="py-2 px-2 font-sans font-semibold text-slate-800 dark:text-slate-200 truncate" title={item.element}>{item.element}</td>
|
||||||
<td className="py-2 px-2 text-slate-400 dark:text-slate-500 text-[10px]">{item.norm}</td>
|
<td className="py-2 px-1 text-slate-400 dark:text-slate-500 text-[9px] leading-tight truncate" title={item.norm}>{item.norm}</td>
|
||||||
<td className="py-2 px-2 font-bold text-slate-700 dark:text-slate-300">{item.certificate}</td>
|
<td className="py-2 px-1 font-bold text-slate-700 dark:text-slate-300 truncate" title={item.certificate}>{item.certificate}</td>
|
||||||
<td className="py-2 px-3 text-right">
|
<td className="py-2 px-1 text-right">
|
||||||
<span className={`inline-flex items-center text-[10px] font-bold px-1.5 py-0.5 rounded ${statusClass(item.status)}`}>
|
<span className={`inline-flex items-center text-[9px] font-bold px-1.5 py-0.5 rounded ${statusClass(item.status)}`}>
|
||||||
{item.status}
|
{item.status}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
+27
-280
@@ -1,291 +1,38 @@
|
|||||||
import { jsPDF } from 'jspdf';
|
import { jsPDF } from 'jspdf';
|
||||||
import type { ReportData } from '../types';
|
import html2canvas from 'html2canvas';
|
||||||
|
|
||||||
const COLORS = {
|
export const exportAsPdf = async (elementId: string, fileName: string, action: 'preview' | 'download'): Promise<void> => {
|
||||||
primary: '#1e40af',
|
const element = document.getElementById(elementId);
|
||||||
secondary: '#6366f1',
|
|
||||||
success: '#059669',
|
|
||||||
danger: '#dc2626',
|
|
||||||
dark: '#1e293b',
|
|
||||||
gray: '#64748b',
|
|
||||||
light: '#f8fafc',
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDate = (dateStr: string): string => {
|
if (!element) {
|
||||||
return dateStr || 'N/A';
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusColor = (status: string): string => {
|
|
||||||
if (status === 'CONFORME' || status === 'OK') return COLORS.success;
|
|
||||||
if (status === 'NÃO CONFORME' || status === 'FALHA') return COLORS.danger;
|
|
||||||
return COLORS.gray;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const generatePdfReport = (report: ReportData): jsPDF => {
|
|
||||||
const doc = new jsPDF({
|
|
||||||
orientation: 'portrait',
|
|
||||||
unit: 'mm',
|
|
||||||
format: 'a4',
|
|
||||||
});
|
|
||||||
|
|
||||||
const pageWidth = doc.internal.pageSize.getWidth();
|
|
||||||
const pageHeight = doc.internal.pageSize.getHeight();
|
|
||||||
const margin = 15;
|
|
||||||
let y = margin;
|
|
||||||
|
|
||||||
const addHeader = () => {
|
|
||||||
doc.setFillColor(COLORS.primary);
|
|
||||||
doc.rect(0, 0, pageWidth, 30, 'F');
|
|
||||||
|
|
||||||
doc.setTextColor(255, 255, 255);
|
|
||||||
doc.setFontSize(20);
|
|
||||||
doc.setFont('helvetica', 'bold');
|
|
||||||
doc.text('SteelCheck', margin, 18);
|
|
||||||
|
|
||||||
doc.setFontSize(10);
|
|
||||||
doc.setFont('helvetica', 'normal');
|
|
||||||
doc.text('Relatório de Análise Técnica de Qualidade', margin, 25);
|
|
||||||
|
|
||||||
y = 40;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addFooter = (pageNum: number) => {
|
|
||||||
doc.setFillColor(COLORS.light);
|
|
||||||
doc.rect(0, pageHeight - 15, pageWidth, 15, 'F');
|
|
||||||
|
|
||||||
doc.setFontSize(8);
|
|
||||||
doc.setTextColor(COLORS.gray);
|
|
||||||
doc.text(`Página ${pageNum}`, pageWidth / 2, pageHeight - 8, { align: 'center' });
|
|
||||||
doc.text('SteelCheck - Análise assistida por IA', margin, pageHeight - 8);
|
|
||||||
};
|
|
||||||
|
|
||||||
addHeader();
|
|
||||||
|
|
||||||
doc.setTextColor(COLORS.dark);
|
|
||||||
doc.setFontSize(16);
|
|
||||||
doc.setFont('helvetica', 'bold');
|
|
||||||
doc.text('Relatório Técnico de Qualidade', pageWidth / 2, y, { align: 'center' });
|
|
||||||
y += 8;
|
|
||||||
|
|
||||||
doc.setFontSize(10);
|
|
||||||
doc.setFont('helvetica', 'normal');
|
|
||||||
doc.setTextColor(COLORS.gray);
|
|
||||||
doc.text('Análise de conformidade normativa assistida por IA', pageWidth / 2, y, { align: 'center' });
|
|
||||||
y += 12;
|
|
||||||
|
|
||||||
doc.setFillColor(COLORS.light);
|
|
||||||
doc.roundedRect(margin, y, pageWidth - 2 * margin, 25, 3, 3, 'F');
|
|
||||||
y += 5;
|
|
||||||
|
|
||||||
doc.setFontSize(12);
|
|
||||||
doc.setFont('helvetica', 'bold');
|
|
||||||
doc.setTextColor(COLORS.primary);
|
|
||||||
doc.text('Grau de Confiança da Análise', pageWidth / 2, y, { align: 'center' });
|
|
||||||
y += 8;
|
|
||||||
|
|
||||||
doc.setFontSize(36);
|
|
||||||
doc.setTextColor(COLORS.secondary);
|
|
||||||
doc.text(`${report.confidence}%`, pageWidth / 2, y + 5, { align: 'center' });
|
|
||||||
y += 20;
|
|
||||||
|
|
||||||
const sectionTitle = (title: string) => {
|
|
||||||
y += 5;
|
|
||||||
doc.setFillColor(COLORS.primary);
|
|
||||||
doc.rect(margin, y, 3, 10, 'F');
|
|
||||||
|
|
||||||
doc.setFontSize(12);
|
|
||||||
doc.setFont('helvetica', 'bold');
|
|
||||||
doc.setTextColor(COLORS.dark);
|
|
||||||
doc.text(title, margin + 8, y + 7);
|
|
||||||
|
|
||||||
doc.setDrawColor(200, 200, 200);
|
|
||||||
doc.line(margin + 8, y + 12, pageWidth - margin, y + 12);
|
|
||||||
y += 15;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addKeyValue = (key: string, value: string) => {
|
|
||||||
doc.setFontSize(9);
|
|
||||||
doc.setFont('helvetica', 'bold');
|
|
||||||
doc.setTextColor(COLORS.gray);
|
|
||||||
doc.text(key.toUpperCase(), margin, y);
|
|
||||||
|
|
||||||
doc.setFont('helvetica', 'normal');
|
|
||||||
doc.setTextColor(COLORS.dark);
|
|
||||||
const valueLines = doc.splitTextToSize(value || 'N/A', pageWidth - 2 * margin - 40);
|
|
||||||
doc.text(valueLines, margin + 40, y);
|
|
||||||
y += 6 * (valueLines.length || 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
sectionTitle('1. Dados de Identificação');
|
|
||||||
|
|
||||||
const ident = report.identification;
|
|
||||||
addKeyValue('Produto', ident.product);
|
|
||||||
addKeyValue('Norma(s)', ident.standards);
|
|
||||||
addKeyValue('Fabricante', ident.manufacturer);
|
|
||||||
addKeyValue('Nr. Certificado', ident.certificateNumber);
|
|
||||||
addKeyValue('Data', ident.certificateDate);
|
|
||||||
addKeyValue('Lotes', ident.batches);
|
|
||||||
addKeyValue('Corridas (Heats)', ident.heats);
|
|
||||||
addKeyValue('Quantidade', ident.quantity);
|
|
||||||
|
|
||||||
sectionTitle('2. Verificação de Conformidade');
|
|
||||||
|
|
||||||
const complianceStatusColor = report.compliance.status === 'CONFORME' ? COLORS.success : COLORS.danger;
|
|
||||||
doc.setFillColor(complianceStatusColor);
|
|
||||||
doc.roundedRect(pageWidth - margin - 40, y - 12, 35, 10, 2, 2, 'F');
|
|
||||||
doc.setTextColor(255, 255, 255);
|
|
||||||
doc.setFontSize(10);
|
|
||||||
doc.setFont('helvetica', 'bold');
|
|
||||||
doc.text(report.compliance.status, pageWidth - margin - 22, y - 6, { align: 'center' });
|
|
||||||
|
|
||||||
const addTable = (title: string, items: { property?: string; element?: string; test?: string; norm: string; certificate: string; status: string }[]) => {
|
|
||||||
doc.setFontSize(10);
|
|
||||||
doc.setFont('helvetica', 'bold');
|
|
||||||
doc.setTextColor(COLORS.dark);
|
|
||||||
doc.text(title, margin, y);
|
|
||||||
y += 6;
|
|
||||||
|
|
||||||
const colWidths = [45, 40, 40, 25];
|
|
||||||
const colX = [margin, margin + 45, margin + 85, margin + 125];
|
|
||||||
|
|
||||||
doc.setFillColor(240, 240, 240);
|
|
||||||
doc.rect(margin, y, pageWidth - 2 * margin, 8, 'F');
|
|
||||||
|
|
||||||
doc.setFontSize(8);
|
|
||||||
doc.setTextColor(COLORS.gray);
|
|
||||||
doc.text('Propriedade/Norma', colX[0] + 10, y + 5);
|
|
||||||
doc.text('Valor Norma', colX[1] + 5, y + 5);
|
|
||||||
doc.text('Valor Certificado', colX[2] + 5, y + 5);
|
|
||||||
doc.text('Status', colX[3] + 8, y + 5);
|
|
||||||
y += 8;
|
|
||||||
|
|
||||||
items.forEach((item) => {
|
|
||||||
const label = item.property || item.element || item.test || '';
|
|
||||||
|
|
||||||
doc.setFont('helvetica', 'normal');
|
|
||||||
doc.setTextColor(COLORS.dark);
|
|
||||||
doc.setFontSize(8);
|
|
||||||
|
|
||||||
const labelLines = doc.splitTextToSize(label, colWidths[0] - 5);
|
|
||||||
const normLines = doc.splitTextToSize(item.norm, colWidths[1] - 5);
|
|
||||||
const certLines = doc.splitTextToSize(item.certificate, colWidths[2] - 5);
|
|
||||||
|
|
||||||
const rowHeight = Math.max(labelLines.length, normLines.length, certLines.length) * 4 + 4;
|
|
||||||
|
|
||||||
doc.text(labelLines, colX[0], y + 4);
|
|
||||||
doc.text(normLines, colX[1], y + 4);
|
|
||||||
doc.text(certLines, colX[2], y + 4);
|
|
||||||
|
|
||||||
const statusColor = item.status === 'OK' ? COLORS.success : COLORS.danger;
|
|
||||||
doc.setFillColor(statusColor);
|
|
||||||
doc.roundedRect(colX[3], y, 20, 6, 1, 1, 'F');
|
|
||||||
doc.setTextColor(255, 255, 255);
|
|
||||||
doc.setFontSize(7);
|
|
||||||
doc.setFont('helvetica', 'bold');
|
|
||||||
doc.text(item.status, colX[3] + 10, y + 4, { align: 'center' });
|
|
||||||
|
|
||||||
y += rowHeight;
|
|
||||||
});
|
|
||||||
|
|
||||||
y += 5;
|
|
||||||
};
|
|
||||||
|
|
||||||
addTable('Propriedades Mecânicas', report.compliance.mechanical);
|
|
||||||
addTable('Composição Química (%)', report.compliance.chemical);
|
|
||||||
|
|
||||||
if (report.compliance.otherTests.length > 0) {
|
|
||||||
addTable('Outros Testes', report.compliance.otherTests);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (report.compliance.status === 'CONFORME' && report.overPerformance.length > 0) {
|
|
||||||
sectionTitle('3. Destaques de Desempenho');
|
|
||||||
|
|
||||||
doc.setFontSize(9);
|
|
||||||
doc.setTextColor(COLORS.gray);
|
|
||||||
doc.text('Este material excede os requisitos mínimos normativos:', margin, y);
|
|
||||||
y += 6;
|
|
||||||
|
|
||||||
report.overPerformance.forEach((item) => {
|
|
||||||
doc.setFillColor(240, 255, 240);
|
|
||||||
doc.roundedRect(margin, y - 3, pageWidth - 2 * margin, 12, 2, 2, 'F');
|
|
||||||
|
|
||||||
doc.setFont('helvetica', 'bold');
|
|
||||||
doc.setTextColor(COLORS.dark);
|
|
||||||
doc.text(item.property, margin + 5, y + 2);
|
|
||||||
|
|
||||||
doc.setTextColor(COLORS.success);
|
|
||||||
doc.text(item.value, margin + 5, y + 8);
|
|
||||||
|
|
||||||
y += 15;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (y > pageHeight - 50) {
|
|
||||||
doc.addPage();
|
|
||||||
y = margin;
|
|
||||||
addHeader();
|
|
||||||
}
|
|
||||||
|
|
||||||
sectionTitle('4. Normas Equivalentes');
|
|
||||||
|
|
||||||
doc.setFontSize(9);
|
|
||||||
doc.setTextColor(COLORS.gray);
|
|
||||||
doc.text(`Equivalências internacionais para ${report.identification.standards}:`, margin, y);
|
|
||||||
y += 8;
|
|
||||||
|
|
||||||
const boxWidth = 40;
|
|
||||||
const boxHeight = 15;
|
|
||||||
const gap = 5;
|
|
||||||
let x = margin;
|
|
||||||
|
|
||||||
report.equivalents.forEach((item) => {
|
|
||||||
if (x + boxWidth > pageWidth - margin) {
|
|
||||||
x = margin;
|
|
||||||
y += boxHeight + gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.setFillColor(248, 248, 255);
|
|
||||||
doc.roundedRect(x, y, boxWidth, boxHeight, 2, 2, 'F');
|
|
||||||
|
|
||||||
doc.setFontSize(7);
|
|
||||||
doc.setTextColor(COLORS.gray);
|
|
||||||
doc.text(item.system, x + 2, y + 4);
|
|
||||||
|
|
||||||
doc.setFontSize(9);
|
|
||||||
doc.setFont('helvetica', 'bold');
|
|
||||||
doc.setTextColor(COLORS.dark);
|
|
||||||
doc.text(item.norm, x + 2, y + 10);
|
|
||||||
|
|
||||||
x += boxWidth + gap;
|
|
||||||
});
|
|
||||||
|
|
||||||
const totalPages = doc.getNumberOfPages();
|
|
||||||
for (let i = 1; i <= totalPages; i++) {
|
|
||||||
doc.setPage(i);
|
|
||||||
addFooter(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const exportAsPdf = (elementId: string, fileName: string, action: 'preview' | 'download'): void => {
|
|
||||||
console.log('Using native jsPDF for better quality PDF generation');
|
|
||||||
|
|
||||||
const hiddenDiv = document.getElementById(elementId);
|
|
||||||
if (!hiddenDiv) {
|
|
||||||
console.error(`Element with id ${elementId} not found.`);
|
console.error(`Element with id ${elementId} not found.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const jsonData = hiddenDiv.getAttribute('data-report');
|
// We temporarily make the element visible to html2canvas if it's hidden by CSS
|
||||||
if (!jsonData) {
|
// by cloning it or just capturing it directly (html2canvas can capture off-screen if it's positioned absolute)
|
||||||
console.error('Report data not found in element');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const report: ReportData = JSON.parse(jsonData);
|
const canvas = await html2canvas(element, {
|
||||||
const pdf = generatePdfReport(report);
|
scale: 2, // Higher resolution
|
||||||
|
useCORS: true,
|
||||||
|
logging: false,
|
||||||
|
backgroundColor: '#ffffff' // Ensure white background
|
||||||
|
});
|
||||||
|
|
||||||
|
const imgData = canvas.toDataURL('image/jpeg', 0.95);
|
||||||
|
|
||||||
|
// A4 dimensions in mm: 210 x 297
|
||||||
|
const pdf = new jsPDF({
|
||||||
|
orientation: 'portrait',
|
||||||
|
unit: 'mm',
|
||||||
|
format: 'a4',
|
||||||
|
});
|
||||||
|
|
||||||
|
const pdfWidth = pdf.internal.pageSize.getWidth();
|
||||||
|
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
|
||||||
|
|
||||||
|
pdf.addImage(imgData, 'JPEG', 0, 0, pdfWidth, pdfHeight);
|
||||||
|
|
||||||
if (action === 'download') {
|
if (action === 'download') {
|
||||||
pdf.save(`${fileName}.pdf`);
|
pdf.save(`${fileName}.pdf`);
|
||||||
@@ -296,6 +43,6 @@ export const exportAsPdf = (elementId: string, fileName: string, action: 'previe
|
|||||||
URL.revokeObjectURL(pdfUrl);
|
URL.revokeObjectURL(pdfUrl);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error generating PDF:', error);
|
console.error('Error generating PDF with html2canvas:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user