Compare commits

..

No commits in common. "c99b6add4765da992a61f426181d157ee90b471b" and "9f8d0072c229379e45bc8e0ddcbd935355ca6bee" have entirely different histories.

2 changed files with 106 additions and 158 deletions

View File

@ -28,9 +28,7 @@
"vite-plugin-svgr": "^4.3.0",
"react-scripts": "^5.0.1",
"socket.io-client": "^4.8.1",
"antd": "^5.24.7",
"react-window": "1.8.11",
"react-virtualized-auto-sizer": "1.0.26"
"antd": "^5.24.7"
},
"devDependencies": {
"@eslint/js": "^9.17.0",

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import {
TextField, Box, Typography, IconButton, Divider,
CircularProgress, Alert, Collapse, Tooltip, Button
@ -7,81 +7,6 @@ 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 (
<Box sx={{
mb: 3,
p: 2,
border: '1px solid',
borderColor: 'divider',
borderRadius: 2,
backgroundColor: 'background.paper'
}}>
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
{metric.name}
</Typography>
{metric.ranges.map((r, j) => (
<Box
key={j}
sx={{
display: 'flex',
gap: 2,
alignItems: 'center',
mt: 1,
'& > *': { flex: 1 }
}}
>
<TextField
label="Минимум"
type="number"
value={r.min}
onChange={(e) => updateRange(index, j, 'min', e.target.value)}
size="small"
variant="standard"
/>
<TextField
label="Максимум"
type="number"
value={r.max}
onChange={(e) => updateRange(index, j, 'max', e.target.value)}
size="small"
variant="standard"
/>
<TextField
label="Статус"
type="number"
value={r.status}
onChange={(e) => updateRange(index, j, 'status', e.target.value)}
size="small"
variant="standard"
/>
<Tooltip title="Удалить диапазон">
<IconButton
onClick={() => deleteRange(index, j)}
size="small"
sx={{ flex: 'none' }}
>
<DeleteIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
))}
<Button
onClick={() => addRange(index)}
startIcon={<AddIcon />}
size="small"
sx={{ mt: 1 }}
>
Добавить диапазон
</Button>
</Box>
);
});
const MetricRangeEditor = ({ onSave }) => {
const [ranges, setRanges] = useState([]);
@ -92,6 +17,7 @@ const MetricRangeEditor = ({ onSave }) => {
const [hasChanges, setHasChanges] = useState(false);
const [success, setSuccess] = useState(false);
// Загрузка данных
const loadRanges = useCallback(async () => {
try {
setLoading(true);
@ -115,53 +41,31 @@ const MetricRangeEditor = ({ onSave }) => {
loadRanges();
}, [loadRanges]);
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;
});
// Обновление диапазона
const updateRange = (metricIndex, rangeIndex, field, value) => {
const newRanges = [...ranges];
newRanges[metricIndex].ranges[rangeIndex][field] = Number(value);
setRanges(newRanges);
setHasChanges(true);
}, []);
const getItemSize = (index) => {
const baseHeight = 80;
const rangeCount = filtered[index].ranges.length;
return baseHeight + rangeCount * 56 + 40;
};
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;
});
// Добавление диапазона
const addRange = (metricIndex) => {
const newRanges = [...ranges];
newRanges[metricIndex].ranges.push({ min: 0, max: 100, status: 1 });
setRanges(newRanges);
setHasChanges(true);
}, []);
};
const deleteRange = useCallback((metricIndex, rangeIndex) => {
setRanges(prev => {
const newRanges = [...prev];
newRanges[metricIndex] = {
...newRanges[metricIndex],
ranges: newRanges[metricIndex].ranges.filter((_, i) => i !== rangeIndex)
};
return newRanges;
});
// Удаление диапазона
const deleteRange = (metricIndex, rangeIndex) => {
const newRanges = [...ranges];
newRanges[metricIndex].ranges.splice(rangeIndex, 1);
setRanges(newRanges);
setHasChanges(true);
}, []);
};
const saveChanges = useCallback(async () => {
const saveChanges = async () => {
try {
setLoading(true);
await axios.post(`${import.meta.env.VITE_BACK_URL}/api/ranges/update`, ranges);
@ -183,9 +87,10 @@ const MetricRangeEditor = ({ onSave }) => {
} finally {
setLoading(false);
}
}, [ranges, onSave]);
};
const addNewMetric = useCallback(() => {
// Добавление новой метрики
const addNewMetric = () => {
if (!newMetricName.trim()) {
setError('Введите название метрики');
return;
@ -194,26 +99,18 @@ const MetricRangeEditor = ({ onSave }) => {
setError('Метрика с таким именем уже существует');
return;
}
setRanges(prev => [...prev, {
setRanges([...ranges, {
name: newMetricName,
ranges: [{ min: 0, max: 100, status: 1 }]
}]);
setNewMetricName('');
setHasChanges(true);
setError(null);
}, [newMetricName, 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]);
const filtered = filter
? ranges.filter(r => r.name.toLowerCase().includes(filter.toLowerCase()))
: ranges;
return (
<Box sx={{ position: 'relative' }}>
@ -269,30 +166,76 @@ const MetricRangeEditor = ({ onSave }) => {
<Divider sx={{ mb: 3 }} />
<Box sx={{ height: '60vh', width: '100%' }}>
<AutoSizer>
{({ height, width }) => (
<List
height={height}
width={width}
itemSize={getItemSize}
itemCount={filtered.length}
{filtered.map((metric, i) => (
<Box
key={metric.name}
sx={{
mb: 3,
p: 2,
border: '1px solid',
borderColor: 'divider',
borderRadius: 2,
backgroundColor: 'background.paper'
}}
>
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
{metric.name}
</Typography>
{metric.ranges.map((r, j) => (
<Box
key={j}
sx={{
display: 'flex',
gap: 2,
alignItems: 'center',
mt: 1,
'& > *': { flex: 1 }
}}
>
{({ index, style }) => (
<Box style={style} sx={{ p: 1 }}>
<MetricItem
metric={filtered[index]}
index={index}
updateRange={updateRange}
addRange={addRange}
deleteRange={deleteRange}
/>
</Box>
)}
</List>
)}
</AutoSizer>
</Box>
<TextField
label="Минимум"
type="number"
value={r.min}
onChange={(e) => updateRange(i, j, 'min', e.target.value)}
size="small"
/>
<TextField
label="Максимум"
type="number"
value={r.max}
onChange={(e) => updateRange(i, j, 'max', e.target.value)}
size="small"
/>
<TextField
label="Статус"
type="number"
value={r.status}
onChange={(e) => updateRange(i, j, 'status', e.target.value)}
size="small"
/>
<Tooltip title="Удалить диапазон">
<IconButton
onClick={() => deleteRange(i, j)}
size="small"
sx={{ flex: 'none' }}
>
<DeleteIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
))}
<Button
onClick={() => addRange(i)}
startIcon={<AddIcon />}
size="small"
sx={{ mt: 1 }}
>
Добавить диапазон
</Button>
</Box>
))}
{filtered.length === 0 && (
<Typography color="text.secondary" textAlign="center" py={3}>
@ -301,8 +244,15 @@ const MetricRangeEditor = ({ onSave }) => {
)}
</>
)}
{/* Передаем состояние изменений в родительский компонент */}
{useEffect(() => {
if (onSave) {
onSave({ hasChanges, saveChanges });
}
}, [hasChanges, onSave])}
</Box>
);
};
export default React.memo(MetricRangeEditor);
export default MetricRangeEditor;