modified the skeleton MUI
parent
e47161acd1
commit
40d8046617
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import { Box, Skeleton } from '@mui/material';
|
||||
|
||||
const ChartSkeleton = ({ count = 1 }) => {
|
||||
return (
|
||||
<>
|
||||
{Array.from({ length: count }).map((_, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
sx={{
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
padding: '20px',
|
||||
mb: 3,
|
||||
height: '400px'
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Skeleton variant="text" width="40%" height={30} />
|
||||
<Skeleton variant="text" width="20%" height={30} />
|
||||
</Box>
|
||||
<Skeleton variant="rectangular" width="100%" height="80%" />
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2, gap: 2 }}>
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<Skeleton key={i} variant="rounded" width={80} height={36} />
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChartSkeleton;
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Line, ResponsiveContainer, ReferenceArea } from 'recharts';
|
||||
import { Skeleton } from '@mui/material';
|
||||
import { HOUR, DAY } from './constants';
|
||||
|
||||
const TIME_FORMATS = {
|
||||
LONG: 'dd.MM HH:mm', // Для диапазона > 24 часов
|
||||
MEDIUM: 'HH:mm', // Для диапазона > 1 часа
|
||||
|
|
@ -51,7 +53,6 @@ const LineChartComponent = ({
|
|||
? Object.keys(displayData[0]).filter(k => !['timestamp', 'time', 'fullTime'].includes(k))
|
||||
: [];
|
||||
|
||||
// Функция для определения оптимального формата времени в зависимости от диапазона
|
||||
const getTimeFormat = () => {
|
||||
if (!data.length) return TIME_FORMATS.SHORT;
|
||||
|
||||
|
|
@ -77,7 +78,6 @@ const LineChartComponent = ({
|
|||
const handleMouseDown = (e) => {
|
||||
if (!e) return;
|
||||
|
||||
// Получаем индекс точки по координатам
|
||||
const activeIndex = e.activeTooltipIndex;
|
||||
if (activeIndex === undefined || activeIndex < 0 || activeIndex >= data.length) return;
|
||||
|
||||
|
|
@ -113,7 +113,6 @@ const LineChartComponent = ({
|
|||
const startIndex = Math.min(selectionArea.startIndex, selectionArea.endIndex);
|
||||
const endIndex = Math.max(selectionArea.startIndex, selectionArea.endIndex);
|
||||
|
||||
// Нормализуем индексы к диапазону [0, 1] для родительского компонента
|
||||
const normalizedStart = startIndex / (data.length - 1);
|
||||
const normalizedEnd = endIndex / (data.length - 1);
|
||||
|
||||
|
|
@ -152,7 +151,18 @@ const LineChartComponent = ({
|
|||
};
|
||||
|
||||
if (!data.length) {
|
||||
return <div style={{ padding: '20px', textAlign: 'center' }}>Нет данных для отображения</div>;
|
||||
return (
|
||||
<Box sx={{
|
||||
position: 'relative',
|
||||
height: '400px',
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
padding: '20px'
|
||||
}}>
|
||||
<Skeleton variant="text" width="60%" height={30} sx={{ mb: 2 }} />
|
||||
<Skeleton variant="rectangular" width="100%" height="calc(100% - 50px)" />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,13 +1,23 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Skeleton from '@mui/material/Skeleton';
|
||||
|
||||
const LazyChartBatchRenderer = ({ charts }) => {
|
||||
const [visibleIndices, setVisibleIndices] = useState(new Set());
|
||||
const placeholderRefs = useRef([]);
|
||||
const observerRef = useRef(null);
|
||||
const cleanupTimeoutRef = useRef(null);
|
||||
|
||||
const ChartSkeleton = () => (
|
||||
<Box sx={{ backgroundColor: '#fff', borderRadius: '8px', padding: '20px', marginBottom: '20px', position: 'relative', height: '500px' }}>
|
||||
<Box sx={{
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
padding: '20px',
|
||||
marginBottom: '20px',
|
||||
position: 'relative',
|
||||
height: '400px',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<Box sx={{ position: 'absolute', right: '20px', top: '20px' }}>
|
||||
<Skeleton variant="circular" width={16} height={16} />
|
||||
</Box>
|
||||
|
|
@ -15,46 +25,124 @@ const LazyChartBatchRenderer = ({ charts }) => {
|
|||
<Skeleton variant="text" width="40%" height={30} />
|
||||
<Skeleton variant="text" width="30%" height={30} />
|
||||
</Box>
|
||||
<Skeleton variant="rectangular" width="100%" height={300} />
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
width="100%"
|
||||
height="300px"
|
||||
sx={{
|
||||
transform: 'none',
|
||||
animation: 'none'
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2, gap: 2 }}>
|
||||
{[1, 2, 3, 4].map((_, i) => (
|
||||
<Skeleton key={i} variant="rounded" width={80} height={36} />
|
||||
<Skeleton
|
||||
key={i}
|
||||
variant="rounded"
|
||||
width={80}
|
||||
height={36}
|
||||
sx={{
|
||||
transform: 'none',
|
||||
animation: 'none'
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
setVisibleIndices((prev) => {
|
||||
const updated = new Set(prev);
|
||||
entries.forEach((entry) => {
|
||||
const index = parseInt(entry.target.dataset.index, 10);
|
||||
if (entry.isIntersecting) {
|
||||
updated.add(index);
|
||||
} else {
|
||||
updated.delete(index);
|
||||
|
||||
const isElementFarFromViewport = useCallback((element) => {
|
||||
if (!element) return true;
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
const buffer = window.innerHeight * 1.5;
|
||||
|
||||
|
||||
return rect.bottom < -buffer || rect.top > window.innerHeight + buffer;
|
||||
}, []);
|
||||
|
||||
|
||||
const updateVisibleIndices = useCallback(() => {
|
||||
const newVisibleIndices = new Set();
|
||||
|
||||
placeholderRefs.current.forEach((ref, index) => {
|
||||
if (ref && !isElementFarFromViewport(ref)) {
|
||||
newVisibleIndices.add(index);
|
||||
}
|
||||
});
|
||||
return updated;
|
||||
|
||||
setVisibleIndices(prev => {
|
||||
if (newVisibleIndices.size === prev.size &&
|
||||
Array.from(newVisibleIndices).every(i => prev.has(i))) {
|
||||
return prev;
|
||||
}
|
||||
return newVisibleIndices;
|
||||
});
|
||||
}, [isElementFarFromViewport]);
|
||||
|
||||
useEffect(() => {
|
||||
observerRef.current = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
updateVisibleIndices();
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
root: null,
|
||||
rootMargin: '200px',
|
||||
threshold: 0.1,
|
||||
rootMargin: '500px 0px',
|
||||
threshold: 0.01
|
||||
}
|
||||
);
|
||||
|
||||
placeholderRefs.current.forEach((ref) => {
|
||||
if (ref) observer.observe(ref);
|
||||
placeholderRefs.current.forEach(ref => {
|
||||
if (ref) observerRef.current.observe(ref);
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
const handleScroll = () => {
|
||||
if (cleanupTimeoutRef.current) {
|
||||
clearTimeout(cleanupTimeoutRef.current);
|
||||
}
|
||||
|
||||
cleanupTimeoutRef.current = setTimeout(() => {
|
||||
updateVisibleIndices();
|
||||
|
||||
setVisibleIndices(prev => {
|
||||
const updated = new Set(prev);
|
||||
let changed = false;
|
||||
|
||||
placeholderRefs.current.forEach((ref, index) => {
|
||||
if (ref && isElementFarFromViewport(ref) && prev.has(index)) {
|
||||
updated.delete(index);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
return changed ? updated : prev;
|
||||
});
|
||||
}, 150);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
window.addEventListener('resize', updateVisibleIndices, { passive: true });
|
||||
|
||||
updateVisibleIndices();
|
||||
|
||||
return () => {
|
||||
if (cleanupTimeoutRef.current) clearTimeout(cleanupTimeoutRef.current);
|
||||
if (observerRef.current) observerRef.current.disconnect();
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
window.removeEventListener('resize', updateVisibleIndices);
|
||||
};
|
||||
}, [updateVisibleIndices]);
|
||||
|
||||
const shouldShowChart = (index) => {
|
||||
return visibleIndices.has(index) ||
|
||||
visibleIndices.has(index - 1) ||
|
||||
visibleIndices.has(index + 1);
|
||||
};
|
||||
}, [charts]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -63,12 +151,17 @@ const LazyChartBatchRenderer = ({ charts }) => {
|
|||
key={index}
|
||||
ref={(el) => (placeholderRefs.current[index] = el)}
|
||||
data-index={index}
|
||||
style={{
|
||||
minHeight: '400px',
|
||||
marginBottom: '20px',
|
||||
transition: 'opacity 0.3s ease',
|
||||
}}
|
||||
>
|
||||
{visibleIndices.has(index) ? chart : <ChartSkeleton />}
|
||||
{shouldShowChart(index) ? chart : <ChartSkeleton />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LazyChartBatchRenderer;
|
||||
export default React.memo(LazyChartBatchRenderer);
|
||||
Loading…
Reference in New Issue