import React 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' interface SortableMenuTreeProps { items: MenuItem[] onItemsChange: (items: MenuItem[]) => void isDesignMode: boolean } export const SortableMenuTree: React.FC = ({ items, onItemsChange, isDesignMode, }) => { const [activeItem, setActiveItem] = React.useState(null) const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8, }, }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }), ) // Flatten the tree structure to get all items with their paths const flattenItems = ( items: MenuItem[], parentPath: string[] = [], ): Array<{ item: MenuItem; path: number[] }> => { const result: Array<{ item: MenuItem; path: number[] }> = [] items.forEach((item, index) => { const currentPath = [...parentPath, index] result.push({ item, path: currentPath }) if (item.children && item.children.length > 0) { result.push(...flattenItems(item.children, currentPath)) } }) return result } // Find item by ID in the tree 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 } // Remove item from tree by ID const removeItemFromTree = (items: MenuItem[], id: string): MenuItem[] => { return items.reduce((acc: MenuItem[], item) => { if (item.id === id) { return acc // Skip this item (remove it) } const newItem = { ...item } if (newItem.children && newItem.children.length > 0) { newItem.children = removeItemFromTree(newItem.children, id) } acc.push(newItem) return acc }, []) } // Insert item at specific position 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 } // Get the path where an item should be inserted based on over item const getInsertionPath = ( items: MenuItem[], activeId: string, overId: string, ): number[] | null => { const flatItems = flattenItems(items) const activeIndex = flatItems.findIndex(({ item }) => item.id === activeId) const overIndex = flatItems.findIndex(({ item }) => item.id === overId) if (overIndex === -1) return null const overItem = flatItems[overIndex] const insertPath = [...overItem.path] // Aktif item, listedeki over item'den sonra geliyorsa, yukarı taşınıyordur → over item'ın yerine ekle // Aktif item, listedeki over item'den önceyse, aşağı taşınıyordur → bir SONRASINA ekle ki yeniden aynı yere düşmesin 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 // ⛳️ Kullanılması gereken liste: `items` const insertionPath = getInsertionPath(items, activeId, overId) if (!insertionPath) return // Şimdi aktif elemanı çıkar let newItems = removeItemFromTree(items, activeId) // ve hedef konuma ekle newItems = insertItemAtPath(newItems, activeItem, insertionPath) // Sıra numaralarını güncelle const finalItems = updateOrderNumbers(newItems) onItemsChange(finalItems) } const renderMenuItem = (item: MenuItem, depth: number = 0): React.ReactNode => { return ( {item.children && item.children.length > 0 && (
{item.children.map((child) => renderMenuItem(child, depth + 1))}
)}
) } const allItems = flattenItems(items) return ( item.id)} strategy={verticalListSortingStrategy} >
{items.map((item) => renderMenuItem(item))}
{activeItem ? ( ) : null}
) }