refactor(FE): export charts with hidden html renderer and adding package html-to-image

This commit is contained in:
randy-ar
2026-01-18 18:57:10 +07:00
parent c576933ba2
commit a9c22d778b
4 changed files with 768 additions and 44 deletions
@@ -8,12 +8,13 @@ import SelectInput, {
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import useSWR from 'swr';
import { DashboardApi } from '@/services/api/dashboard';
import { useFormik } from 'formik';
import { ProjectFlockApi } from '@/services/api/production';
import { KandangApi, LocationApi } from '@/services/api/master-data';
import { generateDashboardPDF } from '@/components/pages/dashboard/export/DashboardPDF';
import {
DashboardFilterType,
@@ -21,6 +22,9 @@ import {
} from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema';
import DashboardLineChart from '@/components/pages/dashboard/chart/DashboardLineChart';
import DashboardLineChartSkeleton from '@/components/pages/dashboard/skeleton/DashboardLineChartSkeleton';
import DashboardAllCharts, {
DashboardAllChartsRef,
} from '@/components/pages/dashboard/chart/DashboardAllCharts';
import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput';
import {
DashboardFilter,
@@ -31,7 +35,6 @@ import { isResponseSuccess } from '@/lib/api-helper';
import AlertErrorList from '@/components/helper/form/FormErrors';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import ButtonFilter from '@/components/helper/ButtonFilter';
import toast from 'react-hot-toast';
import Dropdown from '@/components/Dropdown';
import Menu from '@/components/menu/Menu';
import MenuItem from '@/components/menu/MenuItem';
@@ -55,6 +58,9 @@ const DashboardProduction = () => {
const [endpointUrl, setEndpointUrl] = useState('/dashboards');
const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>([]);
const [exporting, setExporting] = useState(false);
const statsRef = useRef<HTMLDivElement>(null);
const chartRef = useRef<HTMLDivElement>(null);
const allChartsRef = useRef<DashboardAllChartsRef>(null);
// ===== FETCH DATA =====
const {
@@ -160,21 +166,15 @@ const DashboardProduction = () => {
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
// ===== Export PDF =====
const handleExportPDF = () => {
setExporting(true);
const handleExportPDF = async () => {
await generateDashboardPDF({
filterValues: formik.values,
statsRef,
allChartsRef,
setExporting,
});
};
// Wait for state to render, then trigger print
useEffect(() => {
if (exporting) {
const timer = setTimeout(() => {
window.print();
setExporting(false);
}, 100);
return () => clearTimeout(timer);
}
}, [exporting]);
if (isLoadingDashboardProductionData) {
return (
<div className='w-full min-h-screen flex items-center justify-center'>
@@ -219,34 +219,71 @@ const DashboardProduction = () => {
</div>
{/* Dashboard Stats */}
<DashboardStats data={dashboardProductionData?.statistics_data ?? []} />
<div ref={statsRef}>
<DashboardStats
data={dashboardProductionData?.statistics_data ?? []}
/>
</div>
{/* Use DashboardLineChart component or skeleton */}
{isLoadingDashboardProductionData ? (
<DashboardLineChartSkeleton />
) : dashboardProductionData &&
dashboardProductionData.charts &&
Object.keys(dashboardProductionData.charts).length > 0 ? (
<DashboardLineChart
analysisMode={
isResponseSuccess(dashboardProductionResponse)
? dashboardProductionResponse.meta
? (
dashboardProductionResponse.meta as unknown as DashboardMeta
).filters?.analysis_mode
<div ref={chartRef}>
{isLoadingDashboardProductionData ? (
<DashboardLineChartSkeleton />
) : dashboardProductionData &&
dashboardProductionData.charts &&
Object.keys(dashboardProductionData.charts).length > 0 ? (
<DashboardLineChart
analysisMode={
isResponseSuccess(dashboardProductionResponse)
? dashboardProductionResponse.meta
? (
dashboardProductionResponse.meta as unknown as DashboardMeta
).filters?.analysis_mode
: analysisMode
: analysisMode
: analysisMode
}
data={dashboardProductionData}
/>
) : (
<DashboardLineChartSkeleton
meta={
isResponseSuccess(dashboardProductionResponse)
? (dashboardProductionResponse.meta as unknown as DashboardMeta)
: undefined
}
/>
}
data={dashboardProductionData}
selectedKandang={
analysisMode === 'OVERVIEW'
? (formik.values.kandang as OptionType)
: undefined
}
/>
) : (
<DashboardLineChartSkeleton
meta={
isResponseSuccess(dashboardProductionResponse)
? (dashboardProductionResponse.meta as unknown as DashboardMeta)
: undefined
}
/>
)}
</div>
{/* Hidden container for all charts (used for PDF export in OVERVIEW mode) */}
{dashboardProductionData && (
<div
style={{
position: 'absolute',
left: '-9999px',
top: 0,
width: '1200px', // Fixed width for consistent PDF rendering
}}
>
<DashboardAllCharts
ref={allChartsRef}
data={dashboardProductionData}
analysisMode={
isResponseSuccess(dashboardProductionResponse)
? dashboardProductionResponse.meta
? (
dashboardProductionResponse.meta as unknown as DashboardMeta
).filters?.analysis_mode
: analysisMode
: analysisMode
}
/>
</div>
)}
</section>