mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
264 lines
7.6 KiB
TypeScript
264 lines
7.6 KiB
TypeScript
import jsPDF from 'jspdf';
|
|
import { toPng } from 'html-to-image';
|
|
import toast from 'react-hot-toast';
|
|
import { formatDate } from '@/lib/helper';
|
|
import { DashboardFilterType } from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema';
|
|
import { DashboardAllChartsRef } from '@/components/pages/dashboard/chart/DashboardAllCharts';
|
|
|
|
interface DashboardPDFExportParams {
|
|
filterValues: DashboardFilterType;
|
|
statsRef: React.RefObject<HTMLDivElement | null>;
|
|
allChartsRef: React.RefObject<DashboardAllChartsRef | null>;
|
|
setExporting: (value: boolean) => void;
|
|
}
|
|
|
|
export const generateDashboardPDF = async ({
|
|
filterValues,
|
|
statsRef,
|
|
allChartsRef,
|
|
setExporting,
|
|
}: DashboardPDFExportParams): Promise<void> => {
|
|
try {
|
|
setExporting(true);
|
|
toast.loading('Generating PDF...', { id: 'export-pdf' });
|
|
|
|
// Wait for DOM to update
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
|
|
const pdf = new jsPDF('p', 'mm', 'a4');
|
|
const pageWidth = pdf.internal.pageSize.getWidth();
|
|
const pageHeight = pdf.internal.pageSize.getHeight();
|
|
const margin = 10;
|
|
let yPosition = margin;
|
|
|
|
// Add title
|
|
pdf.setFontSize(16);
|
|
pdf.setFont('helvetica', 'bold');
|
|
pdf.text('Dashboard Produksi', margin, yPosition);
|
|
yPosition += 10;
|
|
|
|
// Add filter information (horizontal layout)
|
|
pdf.setFontSize(6);
|
|
pdf.setFont('helvetica', 'normal');
|
|
|
|
const filterItems: string[] = [];
|
|
|
|
// Period
|
|
if (filterValues.startDate || filterValues.endDate) {
|
|
const periodText = `Periode: ${
|
|
filterValues.startDate
|
|
? formatDate(filterValues.startDate, 'DD MMM YYYY')
|
|
: '-'
|
|
} s.d ${
|
|
filterValues.endDate
|
|
? formatDate(filterValues.endDate, 'DD MMM YYYY')
|
|
: '-'
|
|
}`;
|
|
filterItems.push(periodText);
|
|
}
|
|
|
|
// Analysis Mode
|
|
const analysisModeText = `Analysis Mode: ${
|
|
filterValues.analysisMode === 'OVERVIEW'
|
|
? 'Performance Overview'
|
|
: 'Performance Comparison'
|
|
}`;
|
|
filterItems.push(analysisModeText);
|
|
|
|
// Comparison Type (only for COMPARISON mode)
|
|
if (
|
|
filterValues.analysisMode === 'COMPARISON' &&
|
|
filterValues.comparisonType
|
|
) {
|
|
const comparisonTypeLabel =
|
|
filterValues.comparisonType === 'FARM'
|
|
? 'Farm'
|
|
: filterValues.comparisonType === 'FLOCK'
|
|
? 'Flock'
|
|
: filterValues.comparisonType === 'KANDANG'
|
|
? 'Kandang'
|
|
: filterValues.comparisonType;
|
|
filterItems.push(`Compared By: ${comparisonTypeLabel}`);
|
|
}
|
|
|
|
// Farm
|
|
if (filterValues.location) {
|
|
const locationText = Array.isArray(filterValues.location)
|
|
? filterValues.location.map((loc) => loc.label).join(', ')
|
|
: filterValues.location.label;
|
|
filterItems.push(`Farm: ${locationText || '-'}`);
|
|
}
|
|
|
|
// Flock
|
|
if (
|
|
filterValues.flock &&
|
|
(Array.isArray(filterValues.flock)
|
|
? filterValues.flock.length > 0
|
|
: filterValues.flock)
|
|
) {
|
|
const flockText = Array.isArray(filterValues.flock)
|
|
? filterValues.flock.map((f) => f.label).join(', ')
|
|
: filterValues.flock.label;
|
|
filterItems.push(`Flock: ${flockText || '-'}`);
|
|
}
|
|
|
|
// Kandang
|
|
if (
|
|
filterValues.kandang &&
|
|
(Array.isArray(filterValues.kandang)
|
|
? filterValues.kandang.length > 0
|
|
: filterValues.kandang)
|
|
) {
|
|
const kandangText = Array.isArray(filterValues.kandang)
|
|
? filterValues.kandang.map((k) => k.label).join(', ')
|
|
: filterValues.kandang.label;
|
|
filterItems.push(`Kandang: ${kandangText || '-'}`);
|
|
}
|
|
|
|
// Generated timestamp
|
|
filterItems.push(`Dicetak: ${formatDate(new Date(), 'DD MMM YYYY HH:mm')}`);
|
|
|
|
// Render filter items horizontally with word wrap and gray background
|
|
const maxWidth = pageWidth - 2 * margin;
|
|
let currentLine = '';
|
|
const lines: string[] = [];
|
|
|
|
// First pass: calculate all lines
|
|
filterItems.forEach((item, index) => {
|
|
const separator = index > 0 ? ' | ' : '';
|
|
const testLine = currentLine + separator + item;
|
|
const testWidth = pdf.getTextWidth(testLine);
|
|
|
|
if (testWidth > maxWidth && currentLine !== '') {
|
|
lines.push(currentLine);
|
|
currentLine = item;
|
|
} else {
|
|
currentLine = testLine;
|
|
}
|
|
});
|
|
|
|
// Add last line
|
|
if (currentLine) {
|
|
lines.push(currentLine);
|
|
}
|
|
|
|
// Calculate background dimensions
|
|
const lineHeight = 3;
|
|
const padding = 1;
|
|
const backgroundHeight = lines.length * lineHeight + padding * 2;
|
|
|
|
// Draw gray background
|
|
pdf.setFillColor(240, 240, 240); // Light gray (RGB: 240, 240, 240)
|
|
pdf.rect(
|
|
margin - padding,
|
|
yPosition - padding - 2,
|
|
pageWidth - 2 * margin + padding * 2,
|
|
backgroundHeight,
|
|
'F'
|
|
);
|
|
|
|
// Render text on top of background
|
|
lines.forEach((line, index) => {
|
|
pdf.text(line, margin, yPosition);
|
|
if (index < lines.length - 1) {
|
|
yPosition += lineHeight;
|
|
}
|
|
});
|
|
|
|
yPosition += 10;
|
|
|
|
// Capture and add stats if available
|
|
if (statsRef.current) {
|
|
const statsImage = await toPng(statsRef.current, {
|
|
quality: 1,
|
|
pixelRatio: 2,
|
|
});
|
|
const statsImgProps = pdf.getImageProperties(statsImage);
|
|
const statsWidth = pageWidth - 2 * margin;
|
|
const statsHeight =
|
|
(statsImgProps.height * statsWidth) / statsImgProps.width;
|
|
|
|
// Check if we need a new page
|
|
if (yPosition + statsHeight > pageHeight - margin) {
|
|
pdf.addPage();
|
|
yPosition = margin;
|
|
}
|
|
|
|
pdf.addImage(
|
|
statsImage,
|
|
'PNG',
|
|
margin,
|
|
yPosition,
|
|
statsWidth,
|
|
statsHeight
|
|
);
|
|
yPosition += statsHeight + 10;
|
|
}
|
|
|
|
if (allChartsRef.current) {
|
|
// Get all individual chart refs
|
|
const chartRefs = allChartsRef.current.getChartRefs();
|
|
|
|
// Capture each chart separately and add to PDF
|
|
for (let i = 0; i < chartRefs.length; i++) {
|
|
const { ref: chartElement, label } = chartRefs[i];
|
|
|
|
if (chartElement) {
|
|
// Add chart title
|
|
pdf.setFontSize(12);
|
|
pdf.setFont('helvetica', 'bold');
|
|
|
|
const chartImage = await toPng(chartElement, {
|
|
quality: 1,
|
|
pixelRatio: 2,
|
|
});
|
|
const chartImgProps = pdf.getImageProperties(chartImage);
|
|
const chartWidth = pageWidth - 2 * margin;
|
|
const chartHeight =
|
|
(chartImgProps.height * chartWidth) / chartImgProps.width;
|
|
|
|
// Calculate total height needed (title + spacing + chart)
|
|
const titleHeight = 10;
|
|
const totalHeight = titleHeight + chartHeight;
|
|
|
|
// Check if chart fits on current page
|
|
if (yPosition + totalHeight > pageHeight - margin) {
|
|
pdf.addPage();
|
|
yPosition = margin;
|
|
}
|
|
|
|
// Add title
|
|
pdf.text(label, margin, yPosition);
|
|
yPosition += titleHeight;
|
|
|
|
// Add chart image
|
|
pdf.addImage(
|
|
chartImage,
|
|
'PNG',
|
|
margin,
|
|
yPosition,
|
|
chartWidth,
|
|
chartHeight
|
|
);
|
|
|
|
// Update yPosition for next chart (add spacing between charts)
|
|
yPosition += chartHeight + 10;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save the PDF
|
|
const fileName = `dashboard-production-${new Date().toISOString().split('T')[0]}.pdf`;
|
|
pdf.save(fileName);
|
|
|
|
toast.success('PDF exported successfully!', { id: 'export-pdf' });
|
|
} catch (error) {
|
|
console.error('Error generating PDF:', error);
|
|
toast.error('Failed to export PDF. Please try again.', {
|
|
id: 'export-pdf',
|
|
});
|
|
} finally {
|
|
setExporting(false);
|
|
}
|
|
};
|