From 405bda3df9eaf9905cea6cf0595f091cdac3edeb Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 11 Jun 2025 07:30:34 -0400 Subject: [PATCH] added ranages editor --- .../SettingsComponents/MetricRangeEditor.jsx | 258 ++++++++++++++++++ .../Layout/SettingsComponents/RangeEditor.jsx | 153 ----------- src/Components/Layout/SettingsModal.jsx | 55 ++-- src/Components/Layout/SidebarMenu.jsx | 4 +- .../SidebarMenuComponents/SidebarFooter.jsx | 6 +- src/Components/Layout/SidebarMenuWrapper.jsx | 10 +- 6 files changed, 311 insertions(+), 175 deletions(-) create mode 100644 src/Components/Layout/SettingsComponents/MetricRangeEditor.jsx delete mode 100644 src/Components/Layout/SettingsComponents/RangeEditor.jsx diff --git a/src/Components/Layout/SettingsComponents/MetricRangeEditor.jsx b/src/Components/Layout/SettingsComponents/MetricRangeEditor.jsx new file mode 100644 index 0000000..62b4471 --- /dev/null +++ b/src/Components/Layout/SettingsComponents/MetricRangeEditor.jsx @@ -0,0 +1,258 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { + TextField, Box, Typography, IconButton, Divider, + CircularProgress, Alert, Collapse, Tooltip, Button +} from '@mui/material'; +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'; + +const MetricRangeEditor = ({ onSave }) => { + const [ranges, setRanges] = useState([]); + const [filter, setFilter] = useState(''); + const [newMetricName, setNewMetricName] = useState(''); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [hasChanges, setHasChanges] = useState(false); + const [success, setSuccess] = useState(false); + + // Загрузка данных + const loadRanges = useCallback(async () => { + try { + setLoading(true); + const res = await axios.get(`${import.meta.env.VITE_BACK_URL}/api/ranges/list`); + setRanges( + Object.entries(res.data).map(([name, r]) => ({ + name, + ranges: Array.isArray(r) ? r : [] + })) + ); + setError(null); + } catch (err) { + console.error('Ошибка при получении ranges:', err); + setError('Не удалось загрузить данные'); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + loadRanges(); + }, [loadRanges]); + + // Обновление диапазона + const updateRange = (metricIndex, rangeIndex, field, value) => { + const newRanges = [...ranges]; + newRanges[metricIndex].ranges[rangeIndex][field] = Number(value); + setRanges(newRanges); + setHasChanges(true); + }; + + // Добавление диапазона + const addRange = (metricIndex) => { + const newRanges = [...ranges]; + newRanges[metricIndex].ranges.push({ min: 0, max: 100, status: 1 }); + setRanges(newRanges); + setHasChanges(true); + }; + + // Удаление диапазона + const deleteRange = (metricIndex, rangeIndex) => { + const newRanges = [...ranges]; + newRanges[metricIndex].ranges.splice(rangeIndex, 1); + setRanges(newRanges); + setHasChanges(true); + }; + + const saveChanges = async () => { + try { + setLoading(true); + await axios.post(`${import.meta.env.VITE_BACK_URL}/api/ranges/update`, ranges); + setHasChanges(false); + setSuccess(true); + setTimeout(() => setSuccess(false), 3000); + + if (onSave) { + onSave({ + hasChanges: false, + saveChanges: saveChanges + }); + } + return true; + } catch (err) { + console.error('Ошибка при сохранении:', err); + setError('Ошибка при сохранении'); + return false; + } finally { + setLoading(false); + } + }; + + // Добавление новой метрики + const addNewMetric = () => { + if (!newMetricName.trim()) { + setError('Введите название метрики'); + return; + } + if (ranges.some(r => r.name === newMetricName)) { + setError('Метрика с таким именем уже существует'); + return; + } + setRanges([...ranges, { + name: newMetricName, + ranges: [{ min: 0, max: 100, status: 1 }] + }]); + setNewMetricName(''); + setHasChanges(true); + setError(null); + }; + + const filtered = filter + ? ranges.filter(r => r.name.toLowerCase().includes(filter.toLowerCase())) + : ranges; + + return ( + + {loading && ( + + + + )} + + + setError(null)}> + {error} + + + + + setSuccess(false)}> + Изменения успешно сохранены! + + + + {!loading && ( + <> + + + setFilter(e.target.value)} + variant="standard" + /> + + + + setNewMetricName(e.target.value)} + fullWidth + variant="standard" + /> + + + + + + + + + + {filtered.map((metric, i) => ( + + + {metric.name} + + + {metric.ranges.map((r, j) => ( + *': { flex: 1 } + }} + > + 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' }} + > + + + + + ))} + + + + ))} + + {filtered.length === 0 && ( + + {filter ? 'Ничего не найдено' : 'Нет метрик для отображения'} + + )} + + )} + + {/* Передаем состояние изменений в родительский компонент */} + {useEffect(() => { + if (onSave) { + onSave({ hasChanges, saveChanges }); + } + }, [hasChanges, onSave])} + + ); +}; + +export default MetricRangeEditor; \ No newline at end of file diff --git a/src/Components/Layout/SettingsComponents/RangeEditor.jsx b/src/Components/Layout/SettingsComponents/RangeEditor.jsx deleted file mode 100644 index d0c3d65..0000000 --- a/src/Components/Layout/SettingsComponents/RangeEditor.jsx +++ /dev/null @@ -1,153 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - Box, - Button, - TextField, - IconButton, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Paper, - Typography, - Divider, - Select, - MenuItem, - FormControl, - InputLabel -} from '@mui/material'; -import AddIcon from '@mui/icons-material/Add'; -import DeleteIcon from '@mui/icons-material/Delete'; - -const RangeEditor = ({ metric, onSave, onCancel }) => { - const [ranges, setRanges] = useState([...metric.ranges]); - const [newRange, setNewRange] = useState({ min: 0, max: 0, status: 1 }); - - const handleAddRange = () => { - if (newRange.min >= newRange.max) { - alert('Минимальное значение должно быть меньше максимального'); - return; - } - - // Проверка на пересечение с существующими диапазонами - const overlaps = ranges.some(range => - (newRange.min >= range.min && newRange.min <= range.max) || - (newRange.max >= range.min && newRange.max <= range.max) - ); - - if (overlaps) { - alert('Диапазон пересекается с существующим'); - return; - } - - setRanges([...ranges, newRange].sort((a, b) => a.min - b.min)); - setNewRange({ min: 0, max: 0, status: 1 }); - }; - - const handleDeleteRange = (index) => { - const newRanges = [...ranges]; - newRanges.splice(index, 1); - setRanges(newRanges); - }; - - const handleSave = () => { - onSave({ - ...metric, - ranges - }); - }; - - return ( - - - Редактирование диапазонов для: {metric.name} - - - - - - - Минимум - Максимум - Статус - Действия - - - - {ranges.map((range, index) => ( - - {range.min} - {range.max} - {range.status} - - handleDeleteRange(index)}> - - - - - ))} - -
-
- - - - - Добавить новый диапазон - - - - setNewRange({ ...newRange, min: parseInt(e.target.value) || 0 })} - size="small" - /> - - setNewRange({ ...newRange, max: parseInt(e.target.value) || 0 })} - size="small" - /> - - - Статус - - - - - - - - - - -
- ); -}; - -export default RangeEditor; \ No newline at end of file diff --git a/src/Components/Layout/SettingsModal.jsx b/src/Components/Layout/SettingsModal.jsx index f9f7b6f..c61340c 100644 --- a/src/Components/Layout/SettingsModal.jsx +++ b/src/Components/Layout/SettingsModal.jsx @@ -19,6 +19,7 @@ import { } from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; import SaveIcon from '@mui/icons-material/Save'; +import MetricRangeEditor from './SettingsComponents/MetricRangeEditor'; const Transition = React.forwardRef(function Transition(props, ref) { return ; @@ -52,12 +53,16 @@ const TabPanel = (props) => { ); }; -const SettingsModal = ({ open, onClose }) => { +const SettingsModal = ({ open, onClose, onMenuUpdate }) => { const [tabValue, setTabValue] = useState(0); const [isSaving, setIsSaving] = useState(false); const [showSuccess, setShowSuccess] = useState(false); const [hasChanges, setHasChanges] = useState(false); const [showConfirmClose, setShowConfirmClose] = useState(false); + const [metricEditorState, setMetricEditorState] = useState({ + hasChanges: false, + save: () => { } + }); const handleTabChange = (event, newValue) => { if (hasChanges) { @@ -67,14 +72,29 @@ const SettingsModal = ({ open, onClose }) => { } }; - const handleSave = () => { + const handleSave = async () => { setIsSaving(true); - // Имитация асинхронного сохранения - setTimeout(() => { + try { + let success = true; + if (tabValue === 1 && metricEditorState.hasChanges) { + success = await metricEditorState.save(); + } + + if (success) { + setShowSuccess(true); + setHasChanges(false); + if (onMenuUpdate) { + onMenuUpdate(); + } + } + } finally { setIsSaving(false); - setShowSuccess(true); - setHasChanges(false); - }, 1500); + } + }; + + const handleMetricEditorChange = ({ hasChanges, saveChanges }) => { + setMetricEditorState({ hasChanges, save: saveChanges }); + setHasChanges(hasChanges); }; const handleClose = () => { @@ -122,34 +142,33 @@ const SettingsModal = ({ open, onClose }) => { - + - + {/* Добавляйте новые вкладки здесь */} - + Настройки меню {/* Добавьте содержимое для вкладки меню */} - + - Настройки внешнего вида - {/* Добавьте содержимое для вкладки внешнего вида */} + - + {/* Добавляйте новые TabPanel для новых вкладок */} - + -