"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 ( ); }