diff --git a/package.json b/package.json index f803480..d107aba 100755 --- a/package.json +++ b/package.json @@ -28,7 +28,9 @@ "vite-plugin-svgr": "^4.3.0", "react-scripts": "^5.0.1", "socket.io-client": "^4.8.1", - "antd": "^5.24.7" + "antd": "^5.24.7", + "react-window": "1.8.11", + "react-virtualized-auto-sizer": "1.0.26" }, "devDependencies": { "@eslint/js": "^9.17.0", diff --git a/src/Components/Layout/SettingsComponents/MetricRangeEditor.jsx b/src/Components/Layout/SettingsComponents/MetricRangeEditor.jsx index 62b4471..d9135b6 100644 --- a/src/Components/Layout/SettingsComponents/MetricRangeEditor.jsx +++ b/src/Components/Layout/SettingsComponents/MetricRangeEditor.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { TextField, Box, Typography, IconButton, Divider, CircularProgress, Alert, Collapse, Tooltip, Button @@ -7,6 +7,81 @@ import DeleteIcon from '@mui/icons-material/Delete'; import AddIcon from '@mui/icons-material/Add'; import SearchIcon from '@mui/icons-material/Search'; import axios from 'axios'; +import { VariableSizeList as List } from 'react-window'; +import AutoSizer from 'react-virtualized-auto-sizer'; + +const MetricItem = React.memo(({ metric, index, updateRange, addRange, deleteRange }) => { + return ( + + + {metric.name} + + + {metric.ranges.map((r, j) => ( + *': { flex: 1 } + }} + > + updateRange(index, j, 'min', e.target.value)} + size="small" + variant="standard" + /> + updateRange(index, j, 'max', e.target.value)} + size="small" + variant="standard" + /> + updateRange(index, j, 'status', e.target.value)} + size="small" + variant="standard" + /> + + deleteRange(index, j)} + size="small" + sx={{ flex: 'none' }} + > + + + + + ))} + + + + ); +}); const MetricRangeEditor = ({ onSave }) => { const [ranges, setRanges] = useState([]); @@ -17,7 +92,6 @@ const MetricRangeEditor = ({ onSave }) => { const [hasChanges, setHasChanges] = useState(false); const [success, setSuccess] = useState(false); - // Загрузка данных const loadRanges = useCallback(async () => { try { setLoading(true); @@ -41,31 +115,53 @@ const MetricRangeEditor = ({ onSave }) => { loadRanges(); }, [loadRanges]); - // Обновление диапазона - const updateRange = (metricIndex, rangeIndex, field, value) => { - const newRanges = [...ranges]; - newRanges[metricIndex].ranges[rangeIndex][field] = Number(value); - setRanges(newRanges); + const updateRange = useCallback((metricIndex, rangeIndex, field, value) => { + setRanges(prev => { + const newRanges = [...prev]; + newRanges[metricIndex] = { + ...newRanges[metricIndex], + ranges: [...newRanges[metricIndex].ranges] + }; + newRanges[metricIndex].ranges[rangeIndex] = { + ...newRanges[metricIndex].ranges[rangeIndex], + [field]: Number(value) + }; + return newRanges; + }); setHasChanges(true); + }, []); + + const getItemSize = (index) => { + const baseHeight = 80; + const rangeCount = filtered[index].ranges.length; + return baseHeight + rangeCount * 56 + 40; }; - // Добавление диапазона - const addRange = (metricIndex) => { - const newRanges = [...ranges]; - newRanges[metricIndex].ranges.push({ min: 0, max: 100, status: 1 }); - setRanges(newRanges); + const addRange = useCallback((metricIndex) => { + setRanges(prev => { + const newRanges = [...prev]; + newRanges[metricIndex] = { + ...newRanges[metricIndex], + ranges: [...newRanges[metricIndex].ranges, { min: 0, max: 100, status: 1 }] + }; + return newRanges; + }); setHasChanges(true); - }; + }, []); - // Удаление диапазона - const deleteRange = (metricIndex, rangeIndex) => { - const newRanges = [...ranges]; - newRanges[metricIndex].ranges.splice(rangeIndex, 1); - setRanges(newRanges); + const deleteRange = useCallback((metricIndex, rangeIndex) => { + setRanges(prev => { + const newRanges = [...prev]; + newRanges[metricIndex] = { + ...newRanges[metricIndex], + ranges: newRanges[metricIndex].ranges.filter((_, i) => i !== rangeIndex) + }; + return newRanges; + }); setHasChanges(true); - }; + }, []); - const saveChanges = async () => { + const saveChanges = useCallback(async () => { try { setLoading(true); await axios.post(`${import.meta.env.VITE_BACK_URL}/api/ranges/update`, ranges); @@ -87,10 +183,9 @@ const MetricRangeEditor = ({ onSave }) => { } finally { setLoading(false); } - }; + }, [ranges, onSave]); - // Добавление новой метрики - const addNewMetric = () => { + const addNewMetric = useCallback(() => { if (!newMetricName.trim()) { setError('Введите название метрики'); return; @@ -99,18 +194,26 @@ const MetricRangeEditor = ({ onSave }) => { setError('Метрика с таким именем уже существует'); return; } - setRanges([...ranges, { + setRanges(prev => [...prev, { name: newMetricName, ranges: [{ min: 0, max: 100, status: 1 }] }]); setNewMetricName(''); setHasChanges(true); setError(null); - }; + }, [newMetricName, ranges]); - const filtered = filter - ? ranges.filter(r => r.name.toLowerCase().includes(filter.toLowerCase())) - : ranges; + const filtered = useMemo(() => { + return filter + ? ranges.filter(r => r.name.toLowerCase().includes(filter.toLowerCase())) + : ranges; + }, [filter, ranges]); + + useEffect(() => { + if (onSave) { + onSave({ hasChanges, saveChanges }); + } + }, [hasChanges, onSave, saveChanges]); return ( @@ -166,76 +269,30 @@ const MetricRangeEditor = ({ onSave }) => { - {filtered.map((metric, i) => ( - - - {metric.name} - - - {metric.ranges.map((r, j) => ( - *': { flex: 1 } - }} + + + {({ height, width }) => ( + - updateRange(i, j, 'min', e.target.value)} - size="small" - /> - updateRange(i, j, 'max', e.target.value)} - size="small" - /> - updateRange(i, j, 'status', e.target.value)} - size="small" - /> - - deleteRange(i, j)} - size="small" - sx={{ flex: 'none' }} - > - - - - - ))} - - - - ))} + {({ index, style }) => ( + + + + )} + + )} + + {filtered.length === 0 && ( @@ -244,15 +301,8 @@ const MetricRangeEditor = ({ onSave }) => { )} )} - - {/* Передаем состояние изменений в родительский компонент */} - {useEffect(() => { - if (onSave) { - onSave({ hasChanges, saveChanges }); - } - }, [hasChanges, onSave])} ); }; -export default MetricRangeEditor; \ No newline at end of file +export default React.memo(MetricRangeEditor); \ No newline at end of file