diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..7267500
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+node_modules
+.git
+.gitignore
+Dockerfile
+.dockerignore
+dist
+npm-debug.log
diff --git a/src/Components/Layout/SettingsComponents/MenuEditor.jsx b/src/Components/Layout/SettingsComponents/MenuEditor.jsx
new file mode 100644
index 0000000..5e45c9e
--- /dev/null
+++ b/src/Components/Layout/SettingsComponents/MenuEditor.jsx
@@ -0,0 +1,303 @@
+import React, { useState, useEffect } from 'react';
+import {
+ Box,
+ Typography,
+ List,
+ ListItem,
+ ListItemText,
+ ListItemSecondaryAction,
+ IconButton,
+ TextField,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ Chip,
+ Collapse,
+ CircularProgress
+} from '@mui/material';
+import {
+ Edit as EditIcon,
+ Delete as DeleteIcon,
+ ExpandMore as ExpandMoreIcon,
+ ExpandLess as ExpandLessIcon
+} from '@mui/icons-material';
+import axios from 'axios';
+
+const MenuItemComponent = ({ item, level = 0, onEdit, onDelete }) => {
+ const [expanded, setExpanded] = useState(false);
+ const hasChildren = item.items && item.items.length > 0;
+
+ const handleToggle = () => {
+ if (hasChildren) {
+ setExpanded(!expanded);
+ }
+ };
+
+ return (
+ <>
+
+
+ {item.title}
+ {item.isDynamic && (
+
+ )}
+
+ }
+ secondary={item.id}
+ />
+
+ {/* */}
+ <>
+ onEdit(item)}
+ sx={{ mr: 1 }}
+ >
+
+
+ onDelete(item)}
+ color="error"
+ >
+
+
+ >
+ {hasChildren && (
+
+ {expanded ? : }
+
+ )}
+
+
+ {hasChildren && (
+
+
+ {item.items.map((child) => (
+
+ ))}
+
+
+ )}
+ >
+ );
+};
+
+const EditDialog = ({ open, item, onClose, onSave }) => {
+ const [title, setTitle] = useState(item?.title || '');
+
+ useEffect(() => {
+ setTitle(item?.title || '');
+ }, [item]);
+
+ const handleSave = () => {
+ onSave(item.id, { title });
+ onClose();
+ };
+
+ return (
+
+ );
+};
+
+const MenuEditor = ({ onSave }) => {
+ const [menuData, setMenuData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [editDialogOpen, setEditDialogOpen] = useState(false);
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+ const [selectedItem, setSelectedItem] = useState(null);
+ const [hasChanges, setHasChanges] = useState(false);
+
+ useEffect(() => {
+ fetchMenuData();
+ }, []);
+
+ const fetchMenuData = async () => {
+ try {
+ setLoading(true);
+ const response = await axios.get('/api/menu/full');
+ setMenuData(response.data);
+ setError(null);
+ } catch (err) {
+ setError('Ошибка загрузки меню');
+ console.error('Error fetching menu:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleEdit = (item) => {
+ setSelectedItem(item);
+ setEditDialogOpen(true);
+ };
+
+ const handleDelete = (item) => {
+ setSelectedItem(item);
+ setDeleteDialogOpen(true);
+ };
+
+ const handleEditSave = async (id, updates) => {
+ try {
+ await axios.put(`/api/menu/${id}`, updates);
+ setHasChanges(true);
+ fetchMenuData();
+ } catch (err) {
+ console.error('Error updating menu item:', err);
+ alert('Ошибка при сохранении изменений');
+ }
+ };
+
+ const handleDeleteConfirm = async () => {
+ try {
+ await axios.delete(`/api/menu/items/${selectedItem.id}`);
+ setHasChanges(true);
+ setDeleteDialogOpen(false);
+ fetchMenuData();
+ } catch (err) {
+ console.error('Error deleting menu item:', err);
+ alert('Ошибка при удалении элемента');
+ }
+ };
+
+ const handleSave = async () => {
+ if (hasChanges) {
+ onSave({
+ hasChanges: true, saveChanges: async () => {
+ // Принудительно обновляем кэш
+ try {
+ await axios.post('/api/menu/invalidate-cache');
+ return true;
+ } catch (err) {
+ console.error('Error invalidating cache:', err);
+ return false;
+ }
+ }
+ });
+ setHasChanges(false);
+ }
+ };
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ {error}
+
+ );
+ }
+
+ return (
+
+
+ Редактирование меню
+
+
+ Вы можете редактировать названия и удалять элементы меню. Динамические элементы (помечены синим) нельзя редактировать.
+
+
+
+ {menuData.items.map((item) => (
+
+ ))}
+
+
+ setEditDialogOpen(false)}
+ onSave={handleEditSave}
+ />
+
+
+
+
+
+
+
+ );
+};
+
+export default MenuEditor;
\ No newline at end of file
diff --git a/src/Components/Layout/SettingsModal.jsx b/src/Components/Layout/SettingsModal.jsx
index 54d7b04..f52cf56 100644
--- a/src/Components/Layout/SettingsModal.jsx
+++ b/src/Components/Layout/SettingsModal.jsx
@@ -21,6 +21,7 @@ import CloseIcon from '@mui/icons-material/Close';
import SaveIcon from '@mui/icons-material/Save';
import MetricRangeEditor from './SettingsComponents/MetricRangeEditor';
import UserManagement from './SettingsComponents/UserManagement';
+import MenuEditor from './SettingsComponents/MenuEditor'
const Transition = React.forwardRef(function Transition(props, ref) {
return ;
@@ -64,6 +65,10 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => {
hasChanges: false,
save: () => { }
});
+ const [menuEditorState, setMenuEditorState] = useState({
+ hasChanges: false,
+ save: () => Promise.resolve(true)
+ });
const handleTabChange = (event, newValue) => {
if (hasChanges) {
@@ -73,12 +78,22 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => {
}
};
+ const handleMenuEditorChange = ({ hasChanges, saveChanges }) => {
+ setMenuEditorState({ hasChanges, save: saveChanges });
+ setHasChanges(hasChanges);
+ };
+
const handleSave = async () => {
setIsSaving(true);
try {
let success = true;
+
+ if (tabValue === 0 && menuEditorState.hasChanges) {
+ success = await menuEditorState.save();
+ }
+
if (tabValue === 1 && metricEditorState.hasChanges) {
- success = await metricEditorState.save();
+ success = success && await metricEditorState.save();
}
if (success) {
@@ -113,7 +128,6 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => {
}
};
- // Пример обработчика изменений
const handleSettingChange = () => {
setHasChanges(true);
};
@@ -149,14 +163,13 @@ const SettingsModal = ({ open, onClose, onMenuUpdate }) => {
- {/* Добавляйте новые вкладки здесь */}
+ {/* Добавить новые вкладки здесь */}
- Настройки меню
- {/* Добавьте содержимое для вкладки меню */}
+
diff --git a/vite.config.js b/vite.config.js
index e8612bd..a57ee5f 100755
--- a/vite.config.js
+++ b/vite.config.js
@@ -14,12 +14,12 @@ export default defineConfig({
rewrite: (path) => path.replace(/^\/ai-api/, ''),
},
'/metrics-ws': {
- target: 'ws://localhost:3001',
+ target: 'ws://192.168.2.39:3001',
ws: true,
changeOrigin: true,
},
'/api': {
- target: 'http://localhost:3000',
+ target: 'http://192.168.2.39:3000',
changeOrigin: true,
bypass(req, res, options) {
console.log('Proxying request:', req.url);
@@ -27,4 +27,4 @@ export default defineConfig({
}
}
}
-});
\ No newline at end of file
+});