feat(performance): optimize resource loading and initialization

- Added resource hints in index.html for improved loading performance, including preconnect and dns-prefetch links.
- Implemented preload for critical JSON resources to enhance initial load times.
- Optimized Google Analytics and HubSpot script loading to defer execution and reduce blocking during initial render.
- Refactored app initialization in App.tsx to defer non-critical operations, improving perceived performance.
- Introduced lazy loading for chart components and TinyMCE editor to minimize initial bundle size and enhance user experience.
- Enhanced Vite configuration for optimized chunking strategy and improved caching with shorter hash lengths.
This commit is contained in:
chamikaJ
2025-07-07 12:41:23 +05:30
parent 26b47aac53
commit aa1fb1c6f5
7 changed files with 583 additions and 177 deletions

View File

@@ -1,5 +1,4 @@
import React, { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
import { Bar } from 'react-chartjs-2';
import React, { useEffect, useState, forwardRef, useImperativeHandle, lazy, Suspense } from 'react';
import {
Chart as ChartJS,
CategoryScale,
@@ -20,7 +19,34 @@ import { IRPTTimeProject } from '@/types/reporting/reporting.types';
import { Empty, Spin } from 'antd';
import logger from '@/utils/errorLogger';
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ChartDataLabels);
// Lazy load the Bar chart component
const LazyBarChart = lazy(() =>
import('react-chartjs-2').then(module => ({ default: module.Bar }))
);
// Chart loading fallback
const ChartLoadingFallback = () => (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '400px',
background: '#fafafa',
borderRadius: '8px',
border: '1px solid #f0f0f0'
}}>
<Spin size="large" />
</div>
);
// Register Chart.js components only when needed
let isChartJSRegistered = false;
const registerChartJS = () => {
if (!isChartJSRegistered) {
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ChartDataLabels);
isChartJSRegistered = true;
}
};
const BAR_THICKNESS = 40;
const STROKE_WIDTH = 4;
@@ -36,6 +62,7 @@ const ProjectTimeSheetChart = forwardRef<ProjectTimeSheetChartRef>((_, ref) => {
const { t } = useTranslation('time-report');
const [jsonData, setJsonData] = useState<IRPTTimeProject[]>([]);
const [loading, setLoading] = useState(false);
const [chartReady, setChartReady] = useState(false);
const chartRef = React.useRef<ChartJS<'bar', string[], unknown>>(null);
const themeMode = useAppSelector(state => state.themeReducer.mode);
@@ -51,6 +78,21 @@ const ProjectTimeSheetChart = forwardRef<ProjectTimeSheetChartRef>((_, ref) => {
} = useAppSelector(state => state.timeReportsOverviewReducer);
const { duration, dateRange } = useAppSelector(state => state.reportingReducer);
// Initialize chart when component mounts
useEffect(() => {
const initChart = () => {
registerChartJS();
setChartReady(true);
};
// Use requestIdleCallback to defer chart initialization
if ('requestIdleCallback' in window) {
requestIdleCallback(initChart, { timeout: 1000 });
} else {
setTimeout(initChart, 500);
}
}, []);
const handleBarClick = (event: any, elements: any) => {
if (elements.length > 0) {
const elementIndex = elements[0].index;
@@ -158,7 +200,7 @@ const ProjectTimeSheetChart = forwardRef<ProjectTimeSheetChartRef>((_, ref) => {
};
useEffect(() => {
if (!loadingTeams && !loadingProjects && !loadingCategories) {
if (!loadingTeams && !loadingProjects && !loadingCategories && chartReady) {
setLoading(true);
fetchChartData().finally(() => {
setLoading(false);
@@ -175,6 +217,7 @@ const ProjectTimeSheetChart = forwardRef<ProjectTimeSheetChartRef>((_, ref) => {
loadingTeams,
loadingProjects,
loadingCategories,
chartReady,
]);
const exportChart = () => {
@@ -200,8 +243,8 @@ const ProjectTimeSheetChart = forwardRef<ProjectTimeSheetChartRef>((_, ref) => {
// Create download link
const link = document.createElement('a');
link.download = 'project-time-sheet.png';
link.href = tempCanvas.toDataURL('image/png');
link.download = 'project-time-sheet-chart.png';
link.href = tempCanvas.toDataURL();
link.click();
}
};
@@ -210,25 +253,35 @@ const ProjectTimeSheetChart = forwardRef<ProjectTimeSheetChartRef>((_, ref) => {
exportChart,
}));
// if (loading) {
// return (
// <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
// <Spin />
// </div>
// );
// }
if (loading) {
return (
<div style={{ minHeight: MIN_HEIGHT }}>
<Spin size="large" style={{ display: 'block', margin: '100px auto' }} />
</div>
);
}
if (!Array.isArray(jsonData) || jsonData.length === 0) {
return (
<div style={{ minHeight: MIN_HEIGHT }}>
<Empty description={t('noDataAvailable')} />
</div>
);
}
const chartHeight = jsonData.length * (BAR_THICKNESS + 10) + 100;
const containerHeight = Math.max(chartHeight, 400);
return (
<div>
<div
style={{
maxWidth: `calc(100vw - ${SIDEBAR_WIDTH}px)`,
minWidth: 'calc(100vw - 260px)',
minHeight: MIN_HEIGHT,
height: `${60 * data.labels.length}px`,
}}
>
<Bar data={data} options={options} ref={chartRef} />
<div style={{ minHeight: MIN_HEIGHT }}>
<div style={{ height: `${containerHeight}px`, width: '100%' }}>
{chartReady ? (
<Suspense fallback={<ChartLoadingFallback />}>
<LazyBarChart data={data} options={options} ref={chartRef} />
</Suspense>
) : (
<ChartLoadingFallback />
)}
</div>
<ProjectTimeLogDrawer />
</div>