"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 { deleteBcTreePage, reorderBcTreePage, toggleBcPage, } from "@/actions/catalog-bc"; import { CatalogIcon } from "@/components/admin/catalog-tree"; import { useConfirmDialog } from "@/components/admin/confirm-dialog"; import { Button } from "@/components/ui/button"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, } from "@/components/ui/context-menu"; import { cn } from "@/lib/utils"; import { useCatalogManager } from "./use-catalog-store"; export type { RootTab } from "@/types/catalog"; import type { RootTab } from "@/types/catalog"; interface RootTabsProps { tabs: RootTab[]; activeTabId: number | null; onTabClick: (id: number) => void | Promise; onEditTab: (id: number) => void | Promise; onCreateRoot: () => void; loading?: boolean; } export function RootTabs({ tabs, activeTabId, onTabClick, onEditTab, onCreateRoot, loading, }: RootTabsProps) { const { catalogType, canEdit } = useCatalogManager(); const { confirm, dialog: confirmDialog } = useConfirmDialog(); 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; if (!canEdit) return; const newOrder = arrayMove(tabs, oldIndex, newIndex); // Batch reorder try { const reorder = catalogType === "bc" ? reorderBcTreePage : reorderTreePage; const results = await Promise.allSettled( newOrder.map((tab, idx) => reorder({ 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, canEdit, catalogType], ); const handleToggle = async (tab: RootTab) => { if (!canEdit) return; try { if (catalogType === "bc") { await toggleBcPage({ id: tab.id, field: "enabled" }); } else { 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 (!canEdit) return; const ok = await confirm({ title: "Delete root page", description: `Delete root page "${tab.caption}"? Child pages (if any) will be moved to root; items on this page will be deleted.`, confirmLabel: "Delete", }); if (!ok) return; try { if (catalogType === "bc") { await deleteBcTreePage({ pageId: tab.id, mode: "reparent" }); } else { 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 (
{confirmDialog} {loading && ( )} t.id)} strategy={horizontalListSortingStrategy} > {tabs.map((tab) => ( onTabClick(tab.id)} onEdit={() => onEditTab(tab.id)} onToggle={() => handleToggle(tab)} onDelete={() => handleDelete(tab)} /> ))} {draggedTab && ( )} {canEdit && ( )}
); } // ── 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 ( ); }