'use client' import { closestCenter, DndContext, type DragEndEvent, DragOverlay, type DragStartEvent, PointerSensor, useSensor, useSensors, } from '@dnd-kit/core' import { arrayMove, horizontalListSortingStrategy, SortableContext, useSortable, } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import { Loader2, Pencil, Plus, Power, PowerOff, Trash2 } from 'lucide-react' import { useCallback, useState } from 'react' import { toast } from 'sonner' import { deleteTreePage, reorderTreePage, toggleCatalogPage } from '@/actions/catalog' import { CatalogIcon } from '@/components/admin/catalog-tree' import { Button } from '@/components/ui/button' import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, } from '@/components/ui/context-menu' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { cn } from '@/lib/utils' export type { RootTab } from '@/types/catalog' import type { RootTab } from '@/types/catalog' interface RootTabsProps { tabs: RootTab[] activeTabId: number | null onTabClick: (id: number) => void onEditTab: (id: number) => void onCreateRoot: () => void loading?: boolean } export function RootTabs({ tabs, activeTabId, onTabClick, onEditTab, onCreateRoot, loading, }: RootTabsProps) { const [activeDragId, setActiveDragId] = useState(null) const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } })) const handleDragStart = useCallback((event: DragStartEvent) => { setActiveDragId(event.active.id as number) }, []) const handleDragEnd = useCallback( async (event: DragEndEvent) => { const { active, over } = event setActiveDragId(null) if (!over || active.id === over.id) return const oldIndex = tabs.findIndex((t) => t.id === active.id) const newIndex = tabs.findIndex((t) => t.id === over.id) if (oldIndex === -1 || newIndex === -1) return const newOrder = arrayMove(tabs, oldIndex, newIndex) // Batch reorder try { const results = await Promise.allSettled( newOrder.map((tab, idx) => reorderTreePage({ pageId: tab.id, newOrderNum: idx + 1 })), ) const failed = results.filter((r) => r.status === 'rejected').length if (failed) toast.error(`${failed} reorder(s) failed`) else toast.success('Tabs reordered') } catch { toast.error('Failed to reorder tabs') } }, [tabs], ) const handleToggle = async (tab: RootTab) => { try { await toggleCatalogPage({ id: tab.id, action: 'toggleEnabled' }) toast.success(`${tab.caption} ${tab.enabled === '1' ? 'disabled' : 'enabled'}`) } catch { toast.error('Failed to toggle') } } const handleDelete = async (tab: RootTab) => { if (!confirm(`Delete root page "${tab.caption}"? Children will be reparented.`)) return try { await deleteTreePage({ pageId: tab.id, mode: 'reparent' }) toast.success(`"${tab.caption}" deleted`) } catch { toast.error('Failed to delete') } } const draggedTab = activeDragId ? tabs.find((t) => t.id === activeDragId) : null return (
{loading && } t.id)} strategy={horizontalListSortingStrategy}> {tabs.map((tab) => ( onTabClick(tab.id)} onEdit={() => onEditTab(tab.id)} onToggle={() => handleToggle(tab)} onDelete={() => handleDelete(tab)} /> ))} {draggedTab && ( )}

New root page

) } // ── Sortable root tab ───────────────────────────────────────── function SortableRootTab({ tab, isActive, onClick, onEdit, onToggle, onDelete, }: { tab: RootTab isActive: boolean onClick: () => void onEdit: () => void onToggle: () => void onDelete: () => void }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: tab.id, }) const style: React.CSSProperties = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.3 : 1, } return (
Edit page {tab.enabled === '1' ? ( ) : ( )} {tab.enabled === '1' ? 'Disable' : 'Enable'} Delete
) } // ── Tab button render ───────────────────────────────────────── function TabButton({ tab, isActive, isOverlay, onClick, onDoubleClick, }: { tab: RootTab isActive: boolean isOverlay?: boolean onClick?: () => void onDoubleClick?: () => void }) { return ( ) }