import { useState, useEffect } from "react"; import { Drawer, List, styled, IconButton, Tooltip, Box, alpha } from "@mui/material"; import SidebarFooter from "./SidebarMenuComponents/SidebarFooter"; import useSidebarResize from "../hooks/useSidebarResize"; import ChevronLeft from "@mui/icons-material/ChevronLeft"; import ChevronRight from "@mui/icons-material/ChevronRight"; import LogoFull from "../../assets/images/logo.svg?react"; import LogoSmall from "../../assets/images/system_monitor_icon.svg?react"; import { DndContext, closestCenter, PointerSensor, useSensor, useSensors, DragOverlay, MeasuringStrategy } from "@dnd-kit/core"; import { SortableContext, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import SortableMenuItem from "./SidebarMenuComponents/SortableMenuItem"; const SidebarMenu = ({ data, isDarkMode, setIsDarkMode, onSelectItem, forceRefreshMenu, user, }) => { const [collapsed, setCollapsed] = useState(false); const { sidebarWidth, startResizing } = useSidebarResize(320); // Увеличил минимальную ширину const [menuItems, setMenuItems] = useState(data.items || []); const [activeItem, setActiveItem] = useState(null); const [hoveredItem, setHoveredItem] = useState(null); const [dropIndicator, setDropIndicator] = useState({ show: false, position: null, targetId: null }); const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4, }, })); useEffect(() => { const cached = localStorage.getItem("menuTree"); if (cached) { try { setMenuItems(JSON.parse(cached)); } catch { setMenuItems(data.items || []); } } else { setMenuItems(data.items || []); } }, [data]); const handleToggleCollapse = () => { setCollapsed(!collapsed); setHoveredItem(null); }; // Функции для работы с деревом (остаются без изменений) const findItemInTree = (items, id) => { for (const item of items) { if (item.id === id) return item; if (item.items) { const found = findItemInTree(item.items, id); if (found) return found; } } return null; }; const removeItemFromTree = (items, id) => { return items.filter(item => { if (item.id === id) return false; if (item.items) { item.items = removeItemFromTree(item.items, id); } return true; }); }; const addItemToFolder = (items, folderId, newItem) => { return items.map(item => { if (item.id === folderId) { return { ...item, items: [...(item.items || []), newItem] }; } if (item.items) { return { ...item, items: addItemToFolder(item.items, folderId, newItem) }; } return item; }); }; const findParent = (items, childId, parent = null) => { for (const item of items) { if (item.id === childId) return parent; if (item.items) { const found = findParent(item.items, childId, item); if (found) return found; } } return null; }; const addItemAtSameLevel = (items, parentId, newItem, afterId = null) => { return items.map(item => { if (item.id === parentId) { const children = item.items || []; const insertIndex = afterId ? children.findIndex(i => i.id === afterId) + 1 : children.length; const newChildren = [ ...children.slice(0, insertIndex), newItem, ...children.slice(insertIndex) ]; return { ...item, items: newChildren }; } if (item.items) { return { ...item, items: addItemAtSameLevel(item.items, parentId, newItem, afterId) }; } return item; }); }; const handleDragStart = (event) => { const { active } = event; const item = findItemInTree(menuItems, active.id); setActiveItem(item); setDropIndicator({ show: false, position: null, targetId: null }); }; const handleDragEnd = (event) => { const { active, over } = event; setActiveItem(null); setHoveredItem(null); setDropIndicator({ show: false, position: null, targetId: null }); if (!over) return; if (active.id === over.id) return; const draggedItem = findItemInTree(menuItems, active.id); if (!draggedItem) return; const overItem = findItemInTree(menuItems, over.id); // Проверяем, не пытаемся ли переместить элемент в его же потомка if (isDescendant(draggedItem, overItem)) { return; } let newTree; if (dropIndicator.position === 'inside' && overItem && Array.isArray(overItem.items)) { // Вставка внутрь папки newTree = removeItemFromTree([...menuItems], active.id); newTree = addItemToFolder(newTree, over.id, draggedItem); } else { // Вставка на том же уровне const overParent = findParent(menuItems, over.id); if (!overParent) return; newTree = removeItemFromTree([...menuItems], active.id); // Определяем позицию для вставки let insertAfterId = null; if (dropIndicator.position === 'below') { insertAfterId = over.id; } else if (dropIndicator.position === 'above') { const siblings = overParent.items || []; const overIndex = siblings.findIndex(item => item.id === over.id); if (overIndex > 0) { insertAfterId = siblings[overIndex - 1].id; } } newTree = addItemAtSameLevel(newTree, overParent.id, draggedItem, insertAfterId); } setMenuItems(newTree); localStorage.setItem("menuTree", JSON.stringify(newTree)); }; const handleDragOver = (event) => { const { active, over } = event; if (!over) { setDropIndicator({ show: false, position: null, targetId: null }); return; } const overItem = findItemInTree(menuItems, over.id); const activeItem = findItemInTree(menuItems, active.id); if (!overItem || !activeItem || active.id === over.id) { setDropIndicator({ show: false, position: null, targetId: null }); return; } // Проверяем, можно ли перемещать элемент if (isDescendant(activeItem, overItem)) { setDropIndicator({ show: false, position: null, targetId: null }); return; } const overRect = over.rect.current; if (!overRect) return; const relativeY = event.delta.y; const isOverFolder = overItem && Array.isArray(overItem.items); const isTopHalf = relativeY < overRect.height * 0.4; const isBottomHalf = relativeY > overRect.height * 0.6; if (isOverFolder && !isTopHalf && !isBottomHalf) { // Показываем индикатор для вставки в папку setDropIndicator({ show: true, position: 'inside', targetId: over.id }); setHoveredItem(over.id); } else if (isTopHalf) { // Показываем индикатор для вставки выше setDropIndicator({ show: true, position: 'above', targetId: over.id }); setHoveredItem(null); } else if (isBottomHalf) { // Показываем индикатор для вставки ниже setDropIndicator({ show: true, position: 'below', targetId: over.id }); setHoveredItem(null); } else { setDropIndicator({ show: false, position: null, targetId: null }); setHoveredItem(null); } }; const isDescendant = (parent, child) => { if (!parent || !child || !parent.items) return false; const checkChildren = (items, targetId) => { for (const item of items) { if (item.id === targetId) return true; if (item.items && checkChildren(item.items, targetId)) return true; } return false; }; return checkChildren(parent.items, child.id); }; const SidebarResizer = styled("div")(({ theme }) => ({ width: "3px", cursor: "col-resize", backgroundColor: alpha(theme.palette.primary.main, 0.3), "&:hover": { backgroundColor: theme.palette.primary.main, }, height: "100%", position: "absolute", top: 0, right: 0, zIndex: 1000, transition: "background-color 0.2s ease", })); const DropIndicator = ({ position, targetId }) => { if (!targetId) return null; return ( ); }; return ( {/* Заголовок с логотипом */} {collapsed ? ( ) : ( )} {collapsed ? : } {/* Основное содержимое меню */} i.id)} strategy={verticalListSortingStrategy}> {menuItems.map((item) => ( {dropIndicator.show && dropIndicator.targetId === item.id && dropIndicator.position !== 'inside' && ( )} ))} {activeItem ? ( {activeItem.title} ) : null} {!collapsed && ( )} ); }; export default SidebarMenu;