import React, { useEffect, useState } from 'react' import { DndContext, DragOverlay, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragStartEvent, DragEndEvent, } from '@dnd-kit/core' import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, } from '@dnd-kit/sortable' import { MenuItemComponent } from './MenuItemComponent' import { MenuItem } from '@/@types/menu' import { getPermissionsList } from '@/services/identity.service' import { PermissionDefinitionRecord } from '@/proxy/admin/models' import { SelectBoxOption } from '@/shared/types' interface SortableMenuTreeProps { items: MenuItem[] onItemsChange: (items: MenuItem[]) => void isDesignMode: boolean refetch: () => void } export const SortableMenuTree: React.FC = ({ items, onItemsChange, isDesignMode, refetch, }) => { const [permissions, setPermissions] = useState([]) const [activeItem, setActiveItem] = React.useState(null) const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8, }, }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }), ) const flattenItems = ( items: MenuItem[], parentPath: number[] = [], ): Array<{ item: MenuItem; path: number[] }> => { return items.flatMap((item, index) => { const path = [...parentPath, index] const self = { item, path } if (item.children?.length) { return [self, ...flattenItems(item.children, path)] } return [self] }) } const findItemById = (items: MenuItem[], id: string): MenuItem | null => { for (const item of items) { if (item.id === id) return item if (item.children) { const found = findItemById(item.children, id) if (found) return found } } return null } const removeItemFromTree = (items: MenuItem[], id: string): MenuItem[] => { return items.reduce((acc: MenuItem[], item) => { if (item.id === id) return acc const newItem = { ...item } if (newItem.children && newItem.children.length > 0) { newItem.children = removeItemFromTree(newItem.children, id) } acc.push(newItem) return acc }, []) } const insertItemAtPath = ( items: MenuItem[], item: MenuItem, targetPath: number[], ): MenuItem[] => { if (targetPath.length === 1) { const newItems = [...items] newItems.splice(targetPath[0], 0, item) return newItems } const [firstIndex, ...restPath] = targetPath const newItems = [...items] if (newItems[firstIndex]) { newItems[firstIndex] = { ...newItems[firstIndex], children: insertItemAtPath(newItems[firstIndex].children || [], item, restPath), } } return newItems } const getInsertionPath = ( items: MenuItem[], activeId: string, overId: string, ): number[] | null => { const flat = flattenItems(items) const activeFlat = flat.find((f) => f.item.id === activeId) const overFlat = flat.find((f) => f.item.id === overId) if (!activeFlat || !overFlat) return null const isSameParent = activeFlat.path.slice(0, -1).join() === overFlat.path.slice(0, -1).join() const insertPath = [...overFlat.path] if (isSameParent) { const activeIndex = activeFlat.path[activeFlat.path.length - 2] const overIndex = overFlat.path[overFlat.path.length - 1] if (activeIndex < overIndex) { insertPath[insertPath.length - 1] += 1 } } return insertPath } const updateOrderNumbers = (items: MenuItem[]): MenuItem[] => { return items.map((item, index) => ({ ...item, order: index + 1, children: item.children ? updateOrderNumbers(item.children) : [], })) } const handleDragStart = (event: DragStartEvent) => { const { active } = event const activeItem = findItemById(items, active.id as string) setActiveItem(activeItem) } const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event setActiveItem(null) if (!over || active.id === over.id || !isDesignMode) return const activeId = active.id as string const overId = over.id as string const activeItem = findItemById(items, activeId) if (!activeItem) return const insertionPath = getInsertionPath(items, activeId, overId) if (!insertionPath) return let newItems = removeItemFromTree(items, activeId) newItems = insertItemAtPath(newItems, activeItem, insertionPath) const finalItems = updateOrderNumbers(newItems) onItemsChange(finalItems) } useEffect(() => { const fetchPermissions = async () => { const response = await getPermissionsList() if (response.data) { setPermissions( response.data.map((p: PermissionDefinitionRecord) => ({ value: p.name, label: p.name, })), ) } } fetchPermissions() }, []) const renderMenuItem = (item: MenuItem, depth: number = 0): React.ReactNode => { return (
{Array.isArray(item.children) && item.children.length > 0 && ( !!child.id) .map((child) => child.id)} strategy={verticalListSortingStrategy} >
{item.children.map((child) => renderMenuItem(child, depth + 1))}
)}
) } return (
{items.map((item) => renderMenuItem(item))}
{activeItem ? ( ) : null}
) }