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 ( + + Редактировать элемент меню + + setTitle(e.target.value)} + sx={{ mt: 2 }} + /> + + + + + + + ); +}; + +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} + /> + + setDeleteDialogOpen(false)} + > + Подтверждение удаления + + + Вы уверены, что хотите удалить элемент "{selectedItem?.title}"? + + + + + + + + + + + + + ); +}; + +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 +});