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;