created modal window for settings

pull/46/head
DmitriyA 2025-06-11 04:49:48 -04:00
parent 69a5e4ade1
commit 328018edfa
5 changed files with 425 additions and 143 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 && (

View File

@ -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,51 +31,76 @@ const FooterListItem = styled(ListItem)(({ theme }) => ({
}));
const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode }) => {
return (
<FooterList>
{!collapsed && (
<FooterListItem button>
<ListItemText
primary="Помощь"
primaryTypographyProps={{
color: 'custom.sidebarText',
variant: 'body2'
}}
/>
</FooterListItem>
)}
<FooterListItem>
{!collapsed && (
<ListItemText
primary="Настройка"
primaryTypographyProps={{
color: 'custom.sidebarText',
variant: 'body2'
}}
/>
)}
const [settingsOpen, setSettingsOpen] = useState(false);
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Tooltip title="Переключить тему">
<IconButton
size="small"
onClick={() => setIsDarkMode(!isDarkMode)}
sx={{ color: 'custom.sidebarText' }}
>
{isDarkMode ? <Brightness4 /> : <Brightness7 />}
</IconButton>
</Tooltip>
{!collapsed && (
<Switch
checked={isDarkMode}
onChange={() => setIsDarkMode(!isDarkMode)}
size="small"
const handleSettingsOpen = () => {
setSettingsOpen(true);
};
const handleSettingsClose = () => {
setSettingsOpen(false);
};
return (
<>
<FooterList>
{!collapsed && (
<FooterListItem button>
<ListItemText
primary="Помощь"
primaryTypographyProps={{
color: 'custom.sidebarText',
variant: 'body2'
}}
/>
</FooterListItem>
)}
<FooterListItem>
{!collapsed && (
<Button
onClick={handleSettingsOpen}
sx={{
color: 'custom.sidebarText',
textTransform: 'none',
minWidth: 0,
padding: 0,
marginRight: 'auto'
}}
>
<ListItemText
primary="Настройки"
primaryTypographyProps={{
color: 'custom.sidebarText',
variant: 'body2'
}}
/>
</Button>
)}
</Box>
</FooterListItem>
</FooterList>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Tooltip title="Переключить тему">
<IconButton
size="small"
onClick={() => setIsDarkMode(!isDarkMode)}
sx={{ color: 'custom.sidebarText' }}
>
{isDarkMode ? <Brightness4 /> : <Brightness7 />}
</IconButton>
</Tooltip>
{!collapsed && (
<Switch
checked={isDarkMode}
onChange={() => setIsDarkMode(!isDarkMode)}
size="small"
/>
)}
</Box>
</FooterListItem>
</FooterList>
<SettingsModal open={settingsOpen} onClose={handleSettingsClose} />
</>
);
};
export default SidebarFooter;
export default SidebarFooter;