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"
- />
-
-
- Статус
-
-
-
- }
- onClick={handleAddRange}
- >
- Добавить
-
-
-
-
-
-
-
-
- );
-};
-
-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 для новых вкладок */}
-
+
- : }
diff --git a/src/Components/Layout/SidebarMenu.jsx b/src/Components/Layout/SidebarMenu.jsx
index 2617bda..d592dc9 100644
--- a/src/Components/Layout/SidebarMenu.jsx
+++ b/src/Components/Layout/SidebarMenu.jsx
@@ -20,7 +20,8 @@ const SidebarMenu = ({
data,
isDarkMode,
setIsDarkMode,
- onSelectItem
+ onSelectItem,
+ forceRefreshMenu
}) => {
const [collapsed, setCollapsed] = useState(false);
const { sidebarWidth, startResizing } = useSidebarResize(290);
@@ -146,6 +147,7 @@ const SidebarMenu = ({
collapsed={collapsed}
isDarkMode={isDarkMode}
setIsDarkMode={setIsDarkMode}
+ forceRefreshMenu={forceRefreshMenu}
/>
{!collapsed && (
diff --git a/src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx b/src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx
index 66a37e7..d9546ff 100644
--- a/src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx
+++ b/src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx
@@ -30,7 +30,7 @@ const FooterListItem = styled(ListItem)(({ theme }) => ({
alignItems: 'center'
}));
-const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode }) => {
+const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode, forceRefreshMenu }) => {
const [settingsOpen, setSettingsOpen] = useState(false);
const handleSettingsOpen = () => {
@@ -98,7 +98,9 @@ const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode }) => {
-
+
>
);
};
diff --git a/src/Components/Layout/SidebarMenuWrapper.jsx b/src/Components/Layout/SidebarMenuWrapper.jsx
index 2980c64..ddce8fa 100644
--- a/src/Components/Layout/SidebarMenuWrapper.jsx
+++ b/src/Components/Layout/SidebarMenuWrapper.jsx
@@ -11,6 +11,13 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect }) => {
const [editingItem, setEditingItem] = useState(null);
const [editModalOpen, setEditModalOpen] = useState(false);
const [backgroundLoading, setBackgroundLoading] = useState(false);
+ const [refreshTrigger, setRefreshTrigger] = useState(0);
+
+
+ const forceRefreshMenu = () => {
+ setRefreshTrigger(prev => prev + 1);
+ localStorage.removeItem('menuCache'); // Очищаем кэш
+ };
// Загружаем меню из localStorage при инициализации
useEffect(() => {
@@ -62,7 +69,7 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect }) => {
};
fetchMenuData();
- }, []);
+ }, [refreshTrigger]);
// Фоновая проверка обновлений
useEffect(() => {
@@ -169,6 +176,7 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect }) => {
editingItem={editingItem}
onCloseEditModal={() => setEditModalOpen(false)}
onSaveChanges={handleSaveChanges}
+ forceRefreshMenu={forceRefreshMenu}
/>
);
};