created modal window for settings
parent
69a5e4ade1
commit
328018edfa
|
|
@ -0,0 +1,153 @@
|
|||
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;
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
// components/SettingsModal.jsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Tabs,
|
||||
Tab,
|
||||
Box,
|
||||
Typography,
|
||||
IconButton,
|
||||
styled,
|
||||
CircularProgress,
|
||||
Slide,
|
||||
Snackbar,
|
||||
Alert
|
||||
} from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
|
||||
const Transition = React.forwardRef(function Transition(props, ref) {
|
||||
return <Slide direction="up" ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
const StyledDialog = styled(Dialog)(({ theme }) => ({
|
||||
'& .MuiDialog-paper': {
|
||||
minWidth: 600,
|
||||
maxHeight: '80vh',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
},
|
||||
}));
|
||||
|
||||
const TabPanel = (props) => {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`settings-tabpanel-${index}`}
|
||||
aria-labelledby={`settings-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Typography component="div">{children}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SettingsModal = ({ open, onClose }) => {
|
||||
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 handleTabChange = (event, newValue) => {
|
||||
if (hasChanges) {
|
||||
setShowConfirmClose(true);
|
||||
} else {
|
||||
setTabValue(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
setIsSaving(true);
|
||||
// Имитация асинхронного сохранения
|
||||
setTimeout(() => {
|
||||
setIsSaving(false);
|
||||
setShowSuccess(true);
|
||||
setHasChanges(false);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
if (hasChanges) {
|
||||
setShowConfirmClose(true);
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmClose = (shouldClose) => {
|
||||
setShowConfirmClose(false);
|
||||
if (shouldClose) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
// Пример обработчика изменений
|
||||
const handleSettingChange = () => {
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledDialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="settings-dialog-title"
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
TransitionComponent={Transition}
|
||||
>
|
||||
<DialogTitle id="settings-dialog-title">
|
||||
Настройки
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: (theme) => theme.palette.grey[500],
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<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-1" aria-controls="settings-tabpanel-1" />
|
||||
{/* Добавляйте новые вкладки здесь */}
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
<DialogContent dividers>
|
||||
<TabPanel value={tabValue} index={0}>
|
||||
<Typography variant="h6">Настройки меню</Typography>
|
||||
{/* Добавьте содержимое для вкладки меню */}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tabValue} index={1}>
|
||||
<Typography variant="h6">Настройки внешнего вида</Typography>
|
||||
{/* Добавьте содержимое для вкладки внешнего вида */}
|
||||
</TabPanel>
|
||||
|
||||
{/* Добавляйте новые TabPanel для новых вкладок */}
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Закрыть</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={isSaving || !hasChanges}
|
||||
startIcon={isSaving ? <CircularProgress size={20} /> : <SaveIcon />}
|
||||
>
|
||||
{isSaving ? 'Сохранение...' : 'Сохранить'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</StyledDialog>
|
||||
|
||||
{/* Уведомление об успешном сохранении */}
|
||||
<Snackbar
|
||||
open={showSuccess}
|
||||
autoHideDuration={3000}
|
||||
onClose={() => setShowSuccess(false)}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
>
|
||||
<Alert onClose={() => setShowSuccess(false)} severity="success" sx={{ width: '100%' }}>
|
||||
Настройки успешно сохранены!
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
|
||||
{/* Диалог подтверждения закрытия */}
|
||||
<Dialog
|
||||
open={showConfirmClose}
|
||||
onClose={() => handleConfirmClose(false)}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Есть несохраненные изменения</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography>Вы уверены, что хотите закрыть без сохранения изменений?</Typography>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => handleConfirmClose(false)}>Отмена</Button>
|
||||
<Button onClick={() => handleConfirmClose(true)} autoFocus color="error">
|
||||
Закрыть без сохранения
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsModal;
|
||||
|
|
@ -3,17 +3,10 @@ import React, { useState, useEffect } from "react";
|
|||
import {
|
||||
Drawer,
|
||||
List,
|
||||
Typography,
|
||||
styled,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Box,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
TextField
|
||||
} from "@mui/material";
|
||||
import MenuItem from "./SidebarMenuComponents/MenuItem";
|
||||
import SidebarFooter from "./SidebarMenuComponents/SidebarFooter";
|
||||
|
|
@ -27,12 +20,7 @@ const SidebarMenu = ({
|
|||
data,
|
||||
isDarkMode,
|
||||
setIsDarkMode,
|
||||
onEditItem,
|
||||
onSelectItem,
|
||||
editModalOpen,
|
||||
editingItem,
|
||||
onCloseEditModal,
|
||||
onSaveChanges
|
||||
onSelectItem
|
||||
}) => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const { sidebarWidth, startResizing } = useSidebarResize(290);
|
||||
|
|
@ -148,7 +136,7 @@ const SidebarMenu = ({
|
|||
item={data}
|
||||
collapsed={collapsed}
|
||||
level={0}
|
||||
onEdit={onEditItem}
|
||||
|
||||
onSelectItem={onSelectItem}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -160,74 +148,12 @@ const SidebarMenu = ({
|
|||
setIsDarkMode={setIsDarkMode}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{!collapsed && (
|
||||
<SidebarResizer onMouseDown={startResizing} />
|
||||
)}
|
||||
</Drawer>
|
||||
|
||||
{/* Модальное окно редактирования */}
|
||||
<EditMenuItemDialog
|
||||
open={editModalOpen}
|
||||
item={editingItem}
|
||||
onClose={onCloseEditModal}
|
||||
onSave={onSaveChanges}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const EditMenuItemDialog = ({ open, item, onClose, onSave }) => {
|
||||
const [formData, setFormData] = useState(item || {});
|
||||
|
||||
useEffect(() => {
|
||||
setFormData(item || {});
|
||||
}, [item]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSave(formData);
|
||||
};
|
||||
|
||||
if (!item) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>Редактирование элемента меню</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Название"
|
||||
name="title"
|
||||
value={formData.title || ''}
|
||||
onChange={handleChange}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="ID"
|
||||
name="id"
|
||||
value={formData.id || ''}
|
||||
onChange={handleChange}
|
||||
disabled
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
{/* Дополнительные поля для редактирования */}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>Отмена</Button>
|
||||
<Button onClick={handleSubmit} variant="contained" color="primary">
|
||||
Сохранить
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarMenu;
|
||||
|
|
@ -7,11 +7,10 @@ import {
|
|||
Collapse,
|
||||
List,
|
||||
styled,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem as MuiMenuItem
|
||||
} from "@mui/material";
|
||||
import { ExpandLess, ExpandMore, Folder, FolderOpen, Edit } from "@mui/icons-material";
|
||||
import { ExpandLess, ExpandMore, Folder, FolderOpen } from "@mui/icons-material";
|
||||
import StatusIndicator from "./StatusIndicator";
|
||||
|
||||
const StyledListItem = styled(ListItem)(({ theme, level }) => ({
|
||||
|
|
@ -44,11 +43,6 @@ const MenuItem = ({ item, onSelectItem, level = 0, collapsed, onEdit }) => {
|
|||
setContextMenu(null);
|
||||
};
|
||||
|
||||
const handleEditClick = () => {
|
||||
onEdit(item);
|
||||
handleCloseContextMenu();
|
||||
};
|
||||
|
||||
const handleToggle = (e) => {
|
||||
e.stopPropagation();
|
||||
setIsOpen(!isOpen);
|
||||
|
|
@ -87,19 +81,6 @@ const MenuItem = ({ item, onSelectItem, level = 0, collapsed, onEdit }) => {
|
|||
}}
|
||||
/>
|
||||
{hasChildren && (isOpen ? <ExpandLess /> : <ExpandMore />)}
|
||||
|
||||
{level > 0 && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEditClick();
|
||||
}}
|
||||
sx={{ ml: 1 }}
|
||||
>
|
||||
<Edit fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StyledListItem>
|
||||
|
|
@ -114,9 +95,7 @@ const MenuItem = ({ item, onSelectItem, level = 0, collapsed, onEdit }) => {
|
|||
: undefined
|
||||
}
|
||||
>
|
||||
<MuiMenuItem onClick={handleEditClick}>
|
||||
<Edit fontSize="small" sx={{ mr: 1 }} /> Редактировать
|
||||
</MuiMenuItem>
|
||||
|
||||
</Menu>
|
||||
|
||||
{hasChildren && !collapsed && (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
// components/SidebarMenuComponents/SidebarFooter.jsx
|
||||
import React, { useState } from "react";
|
||||
import { Brightness4, Brightness7 } from "@mui/icons-material";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
import {
|
||||
|
|
@ -7,8 +8,10 @@ import {
|
|||
ListItemText,
|
||||
styled,
|
||||
Switch,
|
||||
Box
|
||||
Box,
|
||||
Button
|
||||
} from "@mui/material";
|
||||
import SettingsModal from "../SettingsModal";
|
||||
|
||||
const FooterList = styled(List)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.custom.sidebar,
|
||||
|
|
@ -28,7 +31,18 @@ const FooterListItem = styled(ListItem)(({ theme }) => ({
|
|||
}));
|
||||
|
||||
const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode }) => {
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
|
||||
const handleSettingsOpen = () => {
|
||||
setSettingsOpen(true);
|
||||
};
|
||||
|
||||
const handleSettingsClose = () => {
|
||||
setSettingsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FooterList>
|
||||
{!collapsed && (
|
||||
<FooterListItem button>
|
||||
|
|
@ -43,13 +57,24 @@ const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode }) => {
|
|||
)}
|
||||
<FooterListItem>
|
||||
{!collapsed && (
|
||||
<Button
|
||||
onClick={handleSettingsOpen}
|
||||
sx={{
|
||||
color: 'custom.sidebarText',
|
||||
textTransform: 'none',
|
||||
minWidth: 0,
|
||||
padding: 0,
|
||||
marginRight: 'auto'
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary="Настройка"
|
||||
primary="Настройки"
|
||||
primaryTypographyProps={{
|
||||
color: 'custom.sidebarText',
|
||||
variant: 'body2'
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
|
|
@ -72,6 +97,9 @@ const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode }) => {
|
|||
</Box>
|
||||
</FooterListItem>
|
||||
</FooterList>
|
||||
|
||||
<SettingsModal open={settingsOpen} onClose={handleSettingsClose} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue