added ranages editor
test-org/trust-module-frontend/pipeline/pr-rc This commit looks good
Details
test-org/trust-module-frontend/pipeline/pr-rc This commit looks good
Details
parent
328018edfa
commit
405bda3df9
|
|
@ -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 (
|
||||||
|
<Box sx={{ position: 'relative' }}>
|
||||||
|
{loading && (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Collapse in={!!error}>
|
||||||
|
<Alert severity="error" sx={{ mb: 2 }} onClose={() => setError(null)}>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
<Collapse in={success}>
|
||||||
|
<Alert severity="success" sx={{ mb: 2 }} onClose={() => setSuccess(false)}>
|
||||||
|
Изменения успешно сохранены!
|
||||||
|
</Alert>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
{!loading && (
|
||||||
|
<>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'flex-end', gap: 1, mb: 2 }}>
|
||||||
|
<SearchIcon sx={{ color: 'action.active', mr: 1 }} />
|
||||||
|
<TextField
|
||||||
|
label="Поиск по метрике"
|
||||||
|
fullWidth
|
||||||
|
value={filter}
|
||||||
|
onChange={(e) => setFilter(e.target.value)}
|
||||||
|
variant="standard"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', mb: 3 }}>
|
||||||
|
<TextField
|
||||||
|
label="Новая метрика"
|
||||||
|
value={newMetricName}
|
||||||
|
onChange={(e) => setNewMetricName(e.target.value)}
|
||||||
|
fullWidth
|
||||||
|
variant="standard"
|
||||||
|
/>
|
||||||
|
<Tooltip title="Добавить метрику">
|
||||||
|
<IconButton
|
||||||
|
onClick={addNewMetric}
|
||||||
|
color="primary"
|
||||||
|
disabled={!newMetricName.trim()}
|
||||||
|
>
|
||||||
|
<AddIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider sx={{ mb: 3 }} />
|
||||||
|
|
||||||
|
{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 }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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}>
|
||||||
|
{filter ? 'Ничего не найдено' : 'Нет метрик для отображения'}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Передаем состояние изменений в родительский компонент */}
|
||||||
|
{useEffect(() => {
|
||||||
|
if (onSave) {
|
||||||
|
onSave({ hasChanges, saveChanges });
|
||||||
|
}
|
||||||
|
}, [hasChanges, onSave])}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MetricRangeEditor;
|
||||||
|
|
@ -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 (
|
|
||||||
<Box sx={{ p: 2 }}>
|
|
||||||
<Typography variant="h6" gutterBottom>
|
|
||||||
Редактирование диапазонов для: {metric.name}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<TableContainer component={Paper} sx={{ mb: 2 }}>
|
|
||||||
<Table size="small">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>Минимум</TableCell>
|
|
||||||
<TableCell>Максимум</TableCell>
|
|
||||||
<TableCell>Статус</TableCell>
|
|
||||||
<TableCell>Действия</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{ranges.map((range, index) => (
|
|
||||||
<TableRow key={index}>
|
|
||||||
<TableCell>{range.min}</TableCell>
|
|
||||||
<TableCell>{range.max}</TableCell>
|
|
||||||
<TableCell>{range.status}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<IconButton onClick={() => handleDeleteRange(index)}>
|
|
||||||
<DeleteIcon color="error" />
|
|
||||||
</IconButton>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
|
|
||||||
<Divider sx={{ my: 2 }} />
|
|
||||||
|
|
||||||
<Typography variant="subtitle1" gutterBottom>
|
|
||||||
Добавить новый диапазон
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', mb: 2 }}>
|
|
||||||
<TextField
|
|
||||||
label="Минимум"
|
|
||||||
type="number"
|
|
||||||
value={newRange.min}
|
|
||||||
onChange={(e) => setNewRange({ ...newRange, min: parseInt(e.target.value) || 0 })}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
label="Максимум"
|
|
||||||
type="number"
|
|
||||||
value={newRange.max}
|
|
||||||
onChange={(e) => setNewRange({ ...newRange, max: parseInt(e.target.value) || 0 })}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
|
||||||
<InputLabel>Статус</InputLabel>
|
|
||||||
<Select
|
|
||||||
value={newRange.status}
|
|
||||||
label="Статус"
|
|
||||||
onChange={(e) => setNewRange({ ...newRange, status: parseInt(e.target.value) })}
|
|
||||||
>
|
|
||||||
<MenuItem value={1}>Норма (1)</MenuItem>
|
|
||||||
<MenuItem value={2}>Предупреждение (2)</MenuItem>
|
|
||||||
<MenuItem value={3}>Опасность (3)</MenuItem>
|
|
||||||
<MenuItem value={4}>Критично (4)</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
startIcon={<AddIcon />}
|
|
||||||
onClick={handleAddRange}
|
|
||||||
>
|
|
||||||
Добавить
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 2 }}>
|
|
||||||
<Button variant="outlined" onClick={onCancel}>
|
|
||||||
Отмена
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" onClick={handleSave}>
|
|
||||||
Сохранить
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RangeEditor;
|
|
||||||
|
|
@ -19,6 +19,7 @@ import {
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import SaveIcon from '@mui/icons-material/Save';
|
import SaveIcon from '@mui/icons-material/Save';
|
||||||
|
import MetricRangeEditor from './SettingsComponents/MetricRangeEditor';
|
||||||
|
|
||||||
const Transition = React.forwardRef(function Transition(props, ref) {
|
const Transition = React.forwardRef(function Transition(props, ref) {
|
||||||
return <Slide direction="up" ref={ref} {...props} />;
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
|
@ -52,12 +53,16 @@ const TabPanel = (props) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsModal = ({ open, onClose }) => {
|
const SettingsModal = ({ open, onClose, onMenuUpdate }) => {
|
||||||
const [tabValue, setTabValue] = useState(0);
|
const [tabValue, setTabValue] = useState(0);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [showSuccess, setShowSuccess] = useState(false);
|
const [showSuccess, setShowSuccess] = useState(false);
|
||||||
const [hasChanges, setHasChanges] = useState(false);
|
const [hasChanges, setHasChanges] = useState(false);
|
||||||
const [showConfirmClose, setShowConfirmClose] = useState(false);
|
const [showConfirmClose, setShowConfirmClose] = useState(false);
|
||||||
|
const [metricEditorState, setMetricEditorState] = useState({
|
||||||
|
hasChanges: false,
|
||||||
|
save: () => { }
|
||||||
|
});
|
||||||
|
|
||||||
const handleTabChange = (event, newValue) => {
|
const handleTabChange = (event, newValue) => {
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
|
|
@ -67,14 +72,29 @@ const SettingsModal = ({ open, onClose }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = async () => {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
// Имитация асинхронного сохранения
|
try {
|
||||||
setTimeout(() => {
|
let success = true;
|
||||||
setIsSaving(false);
|
if (tabValue === 1 && metricEditorState.hasChanges) {
|
||||||
|
success = await metricEditorState.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
setShowSuccess(true);
|
setShowSuccess(true);
|
||||||
setHasChanges(false);
|
setHasChanges(false);
|
||||||
}, 1500);
|
if (onMenuUpdate) {
|
||||||
|
onMenuUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setIsSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMetricEditorChange = ({ hasChanges, saveChanges }) => {
|
||||||
|
setMetricEditorState({ hasChanges, save: saveChanges });
|
||||||
|
setHasChanges(hasChanges);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
|
|
@ -126,7 +146,7 @@ const SettingsModal = ({ open, onClose }) => {
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||||
<Tabs value={tabValue} onChange={handleTabChange} aria-label="settings tabs">
|
<Tabs value={tabValue} onChange={handleTabChange} aria-label="settings tabs">
|
||||||
<Tab label="Меню" id="settings-tab-0" aria-controls="settings-tabpanel-0" />
|
<Tab label="Меню" id="settings-tab-0" aria-controls="settings-tabpanel-0" />
|
||||||
<Tab label="Внешний вид" id="settings-tab-1" aria-controls="settings-tabpanel-1" />
|
<Tab label="Границы метрик" id="settings-tab-1" aria-controls="settings-tabpanel-1" />
|
||||||
{/* Добавляйте новые вкладки здесь */}
|
{/* Добавляйте новые вкладки здесь */}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -138,8 +158,7 @@ const SettingsModal = ({ open, onClose }) => {
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel value={tabValue} index={1}>
|
<TabPanel value={tabValue} index={1}>
|
||||||
<Typography variant="h6">Настройки внешнего вида</Typography>
|
<MetricRangeEditor onSave={handleMetricEditorChange} />
|
||||||
{/* Добавьте содержимое для вкладки внешнего вида */}
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
{/* Добавляйте новые TabPanel для новых вкладок */}
|
{/* Добавляйте новые TabPanel для новых вкладок */}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ const SidebarMenu = ({
|
||||||
data,
|
data,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
setIsDarkMode,
|
setIsDarkMode,
|
||||||
onSelectItem
|
onSelectItem,
|
||||||
|
forceRefreshMenu
|
||||||
}) => {
|
}) => {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const { sidebarWidth, startResizing } = useSidebarResize(290);
|
const { sidebarWidth, startResizing } = useSidebarResize(290);
|
||||||
|
|
@ -146,6 +147,7 @@ const SidebarMenu = ({
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
isDarkMode={isDarkMode}
|
isDarkMode={isDarkMode}
|
||||||
setIsDarkMode={setIsDarkMode}
|
setIsDarkMode={setIsDarkMode}
|
||||||
|
forceRefreshMenu={forceRefreshMenu}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ const FooterListItem = styled(ListItem)(({ theme }) => ({
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode }) => {
|
const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode, forceRefreshMenu }) => {
|
||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
|
|
||||||
const handleSettingsOpen = () => {
|
const handleSettingsOpen = () => {
|
||||||
|
|
@ -98,7 +98,9 @@ const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode }) => {
|
||||||
</FooterListItem>
|
</FooterListItem>
|
||||||
</FooterList>
|
</FooterList>
|
||||||
|
|
||||||
<SettingsModal open={settingsOpen} onClose={handleSettingsClose} />
|
<SettingsModal open={settingsOpen}
|
||||||
|
onClose={handleSettingsClose}
|
||||||
|
onMenuUpdate={forceRefreshMenu} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,13 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect }) => {
|
||||||
const [editingItem, setEditingItem] = useState(null);
|
const [editingItem, setEditingItem] = useState(null);
|
||||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||||
const [backgroundLoading, setBackgroundLoading] = useState(false);
|
const [backgroundLoading, setBackgroundLoading] = useState(false);
|
||||||
|
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
||||||
|
|
||||||
|
|
||||||
|
const forceRefreshMenu = () => {
|
||||||
|
setRefreshTrigger(prev => prev + 1);
|
||||||
|
localStorage.removeItem('menuCache'); // Очищаем кэш
|
||||||
|
};
|
||||||
|
|
||||||
// Загружаем меню из localStorage при инициализации
|
// Загружаем меню из localStorage при инициализации
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -62,7 +69,7 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchMenuData();
|
fetchMenuData();
|
||||||
}, []);
|
}, [refreshTrigger]);
|
||||||
|
|
||||||
// Фоновая проверка обновлений
|
// Фоновая проверка обновлений
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -169,6 +176,7 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect }) => {
|
||||||
editingItem={editingItem}
|
editingItem={editingItem}
|
||||||
onCloseEditModal={() => setEditModalOpen(false)}
|
onCloseEditModal={() => setEditModalOpen(false)}
|
||||||
onSaveChanges={handleSaveChanges}
|
onSaveChanges={handleSaveChanges}
|
||||||
|
forceRefreshMenu={forceRefreshMenu}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue