Compare commits
5 Commits
179baad012
...
558cf8eaba
| Author | SHA1 | Date |
|---|---|---|
|
|
558cf8eaba | |
|
|
585692c838 | |
|
|
04738fae91 | |
|
|
555c28d942 | |
|
|
97295a6748 |
|
|
@ -0,0 +1,482 @@
|
||||||
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
TextField, Box, Typography, IconButton, Divider,
|
||||||
|
CircularProgress, Alert, Collapse, Tooltip, Button,
|
||||||
|
Card, CardContent, Chip, Dialog, DialogTitle,
|
||||||
|
DialogContent, DialogActions, Snackbar
|
||||||
|
} from '@mui/material';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
import SaveIcon from '@mui/icons-material/Save';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const FormulaItem = React.memo(({ data, onEdit }) => {
|
||||||
|
const formatValue = (value) => {
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
return JSON.stringify(value, null, 2);
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getValueColor = (value) => {
|
||||||
|
if (typeof value === 'boolean') return 'primary';
|
||||||
|
if (typeof value === 'number') return 'secondary';
|
||||||
|
if (value === null) return 'default';
|
||||||
|
return 'info';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card sx={{ mb: 2, border: '1px solid', borderColor: 'divider' }}>
|
||||||
|
<CardContent>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
|
||||||
|
<Typography variant="h6" color="primary">
|
||||||
|
ID: {data.id || 'Без ID'}
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
startIcon={<EditIcon />}
|
||||||
|
onClick={() => onEdit(data)}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Редактировать
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 'bold' }}>
|
||||||
|
{data.data.name}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||||
|
{data.data.desription}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 2 }}>
|
||||||
|
<Typography variant="subtitle2" gutterBottom>
|
||||||
|
Параметры:
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ mb: 2 }}>
|
||||||
|
<Chip label="statusarr" color="primary" sx={{ mb: 1 }} />
|
||||||
|
<Typography variant="body2" component="pre" sx={{
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
backgroundColor: 'grey.100',
|
||||||
|
p: 1,
|
||||||
|
borderRadius: 1
|
||||||
|
}}>
|
||||||
|
{JSON.stringify(data.data.values?.statusarr, null, 2)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ mb: 2 }}>
|
||||||
|
<Chip label="warr" color="secondary" sx={{ mb: 1 }} />
|
||||||
|
<Typography variant="body2" component="pre" sx={{
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
backgroundColor: 'grey.100',
|
||||||
|
p: 1,
|
||||||
|
borderRadius: 1
|
||||||
|
}}>
|
||||||
|
{JSON.stringify(data.data.values?.warr, null, 2)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ mb: 2 }}>
|
||||||
|
<Chip label="formula" color="success" sx={{ mb: 1 }} />
|
||||||
|
<Typography variant="body2" component="pre" sx={{
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
backgroundColor: 'success.light',
|
||||||
|
color: 'success.contrastText',
|
||||||
|
p: 1,
|
||||||
|
borderRadius: 1
|
||||||
|
}}>
|
||||||
|
{data.data.formula}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', gap: 1, mt: 2, flexWrap: 'wrap' }}>
|
||||||
|
<Chip
|
||||||
|
label={`Поля: ${Object.keys(data.data || {}).length}`}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
<Chip
|
||||||
|
label={`Тип: ${typeof data.data}`}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const EditFormulaDialog = ({ open, formula, onClose, onSave }) => {
|
||||||
|
const [editedFormula, setEditedFormula] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (formula) {
|
||||||
|
setEditedFormula(formula.data.formula || '');
|
||||||
|
}
|
||||||
|
}, [formula]);
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
if (formula && editedFormula.trim()) {
|
||||||
|
onSave(formula.id, editedFormula.trim());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
||||||
|
<DialogTitle>
|
||||||
|
Редактирование формулы: {formula?.data.name}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||||
|
{formula?.data.desription}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 2, mb: 2 }}>
|
||||||
|
<Typography variant="subtitle2" gutterBottom>
|
||||||
|
Доступные переменные:
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||||
|
<Chip label="statusarr[]" size="small" />
|
||||||
|
<Chip label="warr[]" size="small" />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Формула"
|
||||||
|
value={editedFormula}
|
||||||
|
onChange={(e) => setEditedFormula(e.target.value)}
|
||||||
|
multiline
|
||||||
|
rows={6}
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
placeholder="Введите формулу..."
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onClose}>Отмена</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSave}
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<SaveIcon />}
|
||||||
|
disabled={!editedFormula.trim()}
|
||||||
|
>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormulaEditor = () => {
|
||||||
|
const [formulas, setFormulas] = useState([]);
|
||||||
|
const [filter, setFilter] = useState('');
|
||||||
|
const [formulaId, setFormulaId] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
const [editingFormula, setEditingFormula] = useState(null);
|
||||||
|
const [saveLoading, setSaveLoading] = useState(false);
|
||||||
|
const [snackbar, setSnackbar] = useState({ open: false, message: '', severity: 'success' });
|
||||||
|
|
||||||
|
const showSnackbar = (message, severity = 'success') => {
|
||||||
|
setSnackbar({ open: true, message, severity });
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadFormulas = useCallback(async (id = null) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const targetId = id || formulaId;
|
||||||
|
const res = await axios.get(`http://192.168.2.39:3000/api/formula/7777/options`);
|
||||||
|
|
||||||
|
console.log('Полученные данные:', res.data);
|
||||||
|
|
||||||
|
let formattedData;
|
||||||
|
|
||||||
|
if (Array.isArray(res.data)) {
|
||||||
|
formattedData = res.data.map((item, index) => ({
|
||||||
|
id: item.id || `formula_${index + 1}`,
|
||||||
|
data: item
|
||||||
|
}));
|
||||||
|
} else if (typeof res.data === 'object' && res.data !== null) {
|
||||||
|
formattedData = [{
|
||||||
|
id: targetId,
|
||||||
|
data: res.data
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
formattedData = [{
|
||||||
|
id: targetId,
|
||||||
|
data: { value: res.data }
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Форматированные данные:', formattedData);
|
||||||
|
setFormulas(formattedData);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Ошибка при загрузке формул:', err);
|
||||||
|
setError(`Ошибка загрузки: ${err.message}`);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
setRefreshing(false);
|
||||||
|
}
|
||||||
|
}, [formulaId]);
|
||||||
|
|
||||||
|
const handleEditFormula = (formula) => {
|
||||||
|
setEditingFormula(formula);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveFormula = async (formulaId, newFormula) => {
|
||||||
|
try {
|
||||||
|
setSaveLoading(true);
|
||||||
|
|
||||||
|
// Обновляем формулу в локальном состоянии
|
||||||
|
const updatedFormulas = formulas.map(formula =>
|
||||||
|
formula.id === formulaId
|
||||||
|
? { ...formula, data: { ...formula.data, formula: newFormula } }
|
||||||
|
: formula
|
||||||
|
);
|
||||||
|
|
||||||
|
setFormulas(updatedFormulas);
|
||||||
|
setEditingFormula(null);
|
||||||
|
|
||||||
|
showSnackbar('Формула успешно обновлена!');
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Ошибка при сохранении формулы:', err);
|
||||||
|
showSnackbar('Ошибка при сохранении формулы', 'error');
|
||||||
|
} finally {
|
||||||
|
setSaveLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendAllFormulas = async () => {
|
||||||
|
try {
|
||||||
|
setSaveLoading(true);
|
||||||
|
|
||||||
|
// Преобразуем данные обратно в исходный формат для отправки
|
||||||
|
const dataToSend = formulas.map(formula => ({
|
||||||
|
id: formula.data.id,
|
||||||
|
name: formula.data.name,
|
||||||
|
desription: formula.data.desription,
|
||||||
|
values: formula.data.values,
|
||||||
|
formula: formula.data.formula
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('Отправляемые данные:', dataToSend);
|
||||||
|
|
||||||
|
const response = await axios.post(
|
||||||
|
'http://192.168.2.39:9999/api/integration/3333',
|
||||||
|
dataToSend,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Ответ сервера:', response.data);
|
||||||
|
showSnackbar('Данные успешно отправлены на сервер!');
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Ошибка при отправке данных:', err);
|
||||||
|
showSnackbar(`Ошибка отправки: ${err.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
setSaveLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshData = useCallback(() => {
|
||||||
|
setRefreshing(true);
|
||||||
|
loadFormulas();
|
||||||
|
}, [loadFormulas]);
|
||||||
|
|
||||||
|
const handleFormulaIdChange = (e) => {
|
||||||
|
setFormulaId(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoadClick = () => {
|
||||||
|
if (formulaId.trim()) {
|
||||||
|
loadFormulas(formulaId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredFormulas = formulas.filter(formula =>
|
||||||
|
formula.id.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
|
JSON.stringify(formula.data).toLowerCase().includes(filter.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadFormulas();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ position: 'relative', p: 2 }}>
|
||||||
|
{/* Загрузка */}
|
||||||
|
{(loading || refreshing) && (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Ошибки */}
|
||||||
|
<Collapse in={!!error}>
|
||||||
|
<Alert
|
||||||
|
severity="error"
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
action={
|
||||||
|
<Button color="inherit" size="small" onClick={refreshData}>
|
||||||
|
Повторить
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
{/* Панель управления */}
|
||||||
|
<Box sx={{ mb: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||||
|
<Typography variant="h3" color="primary">
|
||||||
|
Редактор формул
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleSendAllFormulas}
|
||||||
|
variant="contained"
|
||||||
|
color="success"
|
||||||
|
startIcon={<SaveIcon />}
|
||||||
|
disabled={formulas.length === 0 || saveLoading}
|
||||||
|
>
|
||||||
|
{saveLoading ? <CircularProgress size={24} /> : 'Отправить все формулы'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, alignItems: 'flex-end', mb: 2 }}>
|
||||||
|
<TextField
|
||||||
|
label="ID формулы"
|
||||||
|
value={formulaId}
|
||||||
|
onChange={handleFormulaIdChange}
|
||||||
|
variant="outlined"
|
||||||
|
placeholder="Например: 7777"
|
||||||
|
sx={{ minWidth: 200 }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={handleLoadClick}
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<RefreshIcon />}
|
||||||
|
disabled={!formulaId.trim() || loading}
|
||||||
|
>
|
||||||
|
Загрузить
|
||||||
|
</Button>
|
||||||
|
<Chip
|
||||||
|
label={`Найдено: ${formulas.length}`}
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'flex-end', gap: 1 }}>
|
||||||
|
<TextField
|
||||||
|
label="Поиск по формулам"
|
||||||
|
fullWidth
|
||||||
|
value={filter}
|
||||||
|
onChange={(e) => setFilter(e.target.value)}
|
||||||
|
variant="standard"
|
||||||
|
placeholder="Введите текст для поиска..."
|
||||||
|
/>
|
||||||
|
<SearchIcon sx={{ color: 'action.active', mb: 1 }} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider sx={{ mb: 3 }} />
|
||||||
|
|
||||||
|
{/* Список формул */}
|
||||||
|
<Box sx={{ maxHeight: '70vh', overflowY: 'auto', pr: 1 }}>
|
||||||
|
{filteredFormulas.map((formula, index) => (
|
||||||
|
<FormulaItem
|
||||||
|
key={formula.id || index}
|
||||||
|
data={formula}
|
||||||
|
onEdit={handleEditFormula}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{filteredFormulas.length === 0 && !loading && (
|
||||||
|
<Typography
|
||||||
|
color="text.secondary"
|
||||||
|
textAlign="center"
|
||||||
|
py={3}
|
||||||
|
variant="h6"
|
||||||
|
>
|
||||||
|
{filter ? 'Формулы не найдены' : 'Загрузите формулы по ID'}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Статус бар */}
|
||||||
|
<Box sx={{
|
||||||
|
position: 'sticky',
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: 'background.paper',
|
||||||
|
p: 1,
|
||||||
|
borderTop: 1,
|
||||||
|
borderColor: 'divider',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Всего формул: {formulas.length} • Отфильтровано: {filteredFormulas.length}
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
onClick={refreshData}
|
||||||
|
startIcon={<RefreshIcon />}
|
||||||
|
disabled={refreshing}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Обновить
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Диалог редактирования */}
|
||||||
|
<EditFormulaDialog
|
||||||
|
open={!!editingFormula}
|
||||||
|
formula={editingFormula}
|
||||||
|
onClose={() => setEditingFormula(null)}
|
||||||
|
onSave={handleSaveFormula}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Уведомления */}
|
||||||
|
<Snackbar
|
||||||
|
open={snackbar.open}
|
||||||
|
autoHideDuration={6000}
|
||||||
|
onClose={() => setSnackbar({ ...snackbar, open: false })}
|
||||||
|
>
|
||||||
|
<Alert
|
||||||
|
onClose={() => setSnackbar({ ...snackbar, open: false })}
|
||||||
|
severity={snackbar.severity}
|
||||||
|
>
|
||||||
|
{snackbar.message}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(FormulaEditor);
|
||||||
|
|
@ -21,7 +21,8 @@ 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';
|
import MetricRangeEditor from './SettingsComponents/MetricRangeEditor';
|
||||||
import UserManagement from './SettingsComponents/UserManagement';
|
import UserManagement from './SettingsComponents/UserManagement';
|
||||||
import MenuEditor from './SettingsComponents/MenuEditor'
|
import MenuEditor from './SettingsComponents/MenuEditor';
|
||||||
|
import FormulaEditor from './SettingsComponents/FormulaEditor';
|
||||||
|
|
||||||
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} />;
|
||||||
|
|
@ -69,6 +70,10 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => {
|
||||||
hasChanges: false,
|
hasChanges: false,
|
||||||
save: () => Promise.resolve(true)
|
save: () => Promise.resolve(true)
|
||||||
});
|
});
|
||||||
|
const [formulaEditorState, setFormulaEditorState] = useState({
|
||||||
|
hasChanges: false,
|
||||||
|
save: () => Promise.resolve(true)
|
||||||
|
});
|
||||||
|
|
||||||
const handleTabChange = (event, newValue) => {
|
const handleTabChange = (event, newValue) => {
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
|
|
@ -96,6 +101,10 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => {
|
||||||
success = success && await metricEditorState.save();
|
success = success && await metricEditorState.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tabValue === 3 && formulaEditorState.hasChanges) {
|
||||||
|
success = success && await formulaEditorState.save();
|
||||||
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
setShowSuccess(true);
|
setShowSuccess(true);
|
||||||
setHasChanges(false);
|
setHasChanges(false);
|
||||||
|
|
@ -113,6 +122,11 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => {
|
||||||
setHasChanges(hasChanges);
|
setHasChanges(hasChanges);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleFormulaEditorChange = ({ hasChanges, saveChanges }) => {
|
||||||
|
setFormulaEditorState({ hasChanges, save: saveChanges });
|
||||||
|
setHasChanges(hasChanges);
|
||||||
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
setShowConfirmClose(true);
|
setShowConfirmClose(true);
|
||||||
|
|
@ -163,6 +177,7 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => {
|
||||||
<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" />
|
||||||
<Tab label="Управление пользователями" id="settings-tab-2" aria-controls="settings-tabpanel-2" />
|
<Tab label="Управление пользователями" id="settings-tab-2" aria-controls="settings-tabpanel-2" />
|
||||||
|
<Tab label="Настройка формул" id="settings-tab-3" aria-controls="settings-tabpanel-3" />
|
||||||
{/* Добавить новые вкладки здесь */}
|
{/* Добавить новые вкладки здесь */}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -180,6 +195,10 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => {
|
||||||
<UserManagement />
|
<UserManagement />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel value={tabValue} index={3}>
|
||||||
|
<FormulaEditor onSave={handleFormulaEditorChange} />
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
{/* Добавляйте новые TabPanel для новых вкладок */}
|
{/* Добавляйте новые TabPanel для новых вкладок */}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue