revert fb8e77bb68
Local Build and Deploy / deploy (push) Successful in 1m11s
Local Build and Deploy / deploy (push) Successful in 1m11s
revert style: clean up code with prettier and eslint
This commit is contained in:
1 parent
27de078f54
commit
e85e4d74ea
378 files changed
+20710
-22428
No files matched your search
@@ -1,4 +1,4 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import {
|
||||
closestCenter,
|
||||
@@ -9,93 +9,105 @@ import {
|
||||
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";
|
||||
} 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";
|
||||
} from '@/components/ui/context-menu'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export type { RootTab } from "@/types/catalog";
|
||||
export type { RootTab } from '@/types/catalog'
|
||||
|
||||
import 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;
|
||||
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<number | null>(null);
|
||||
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }));
|
||||
export function RootTabs({
|
||||
tabs,
|
||||
activeTabId,
|
||||
onTabClick,
|
||||
onEditTab,
|
||||
onCreateRoot,
|
||||
loading,
|
||||
}: RootTabsProps) {
|
||||
const [activeDragId, setActiveDragId] = useState<number | null>(null)
|
||||
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }))
|
||||
|
||||
const handleDragStart = useCallback((event: DragStartEvent) => {
|
||||
setActiveDragId(event.active.id as number);
|
||||
}, []);
|
||||
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 { 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 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);
|
||||
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");
|
||||
)
|
||||
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");
|
||||
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"}`);
|
||||
await toggleCatalogPage({ id: tab.id, action: 'toggleEnabled' })
|
||||
toast.success(`${tab.caption} ${tab.enabled === '1' ? 'disabled' : 'enabled'}`)
|
||||
} catch {
|
||||
toast.error("Failed to toggle");
|
||||
toast.error('Failed to toggle')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleDelete = async (tab: RootTab) => {
|
||||
if (!confirm(`Delete root page "${tab.caption}"? Children will be reparented.`)) return;
|
||||
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`);
|
||||
await deleteTreePage({ pageId: tab.id, mode: 'reparent' })
|
||||
toast.success(`"${tab.caption}" deleted`)
|
||||
} catch {
|
||||
toast.error("Failed to delete");
|
||||
toast.error('Failed to delete')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const draggedTab = activeDragId ? tabs.find((t) => t.id === activeDragId) : null;
|
||||
const draggedTab = activeDragId ? tabs.find((t) => t.id === activeDragId) : null
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 border-b bg-muted/30 px-2 py-1.5 overflow-x-auto shrink-0">
|
||||
@@ -120,12 +132,19 @@ export function RootTabs({ tabs, activeTabId, onTabClick, onEditTab, onCreateRoo
|
||||
))}
|
||||
</SortableContext>
|
||||
<DragOverlay>
|
||||
{draggedTab && <TabButton tab={draggedTab} isActive={activeTabId === draggedTab.id} isOverlay />}
|
||||
{draggedTab && (
|
||||
<TabButton tab={draggedTab} isActive={activeTabId === draggedTab.id} isOverlay />
|
||||
)}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-7 w-7 shrink-0 ml-1" onClick={onCreateRoot}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 shrink-0 ml-1"
|
||||
onClick={onCreateRoot}
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
@@ -134,7 +153,7 @@ export function RootTabs({ tabs, activeTabId, onTabClick, onEditTab, onCreateRoo
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// ── Sortable root tab ─────────────────────────────────────────
|
||||
@@ -147,22 +166,22 @@ function SortableRootTab({
|
||||
onToggle,
|
||||
onDelete,
|
||||
}: {
|
||||
tab: RootTab;
|
||||
isActive: boolean;
|
||||
onClick: () => void;
|
||||
onEdit: () => void;
|
||||
onToggle: () => void;
|
||||
onDelete: () => void;
|
||||
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 (
|
||||
<ContextMenu>
|
||||
@@ -178,12 +197,12 @@ function SortableRootTab({
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem onClick={onToggle}>
|
||||
{tab.enabled === "1" ? (
|
||||
{tab.enabled === '1' ? (
|
||||
<PowerOff className="mr-2 h-3.5 w-3.5" />
|
||||
) : (
|
||||
<Power className="mr-2 h-3.5 w-3.5" />
|
||||
)}
|
||||
{tab.enabled === "1" ? "Disable" : "Enable"}
|
||||
{tab.enabled === '1' ? 'Disable' : 'Enable'}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem className="text-destructive focus:text-destructive" onClick={onDelete}>
|
||||
@@ -192,7 +211,7 @@ function SortableRootTab({
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// ── Tab button render ─────────────────────────────────────────
|
||||
@@ -204,11 +223,11 @@ function TabButton({
|
||||
onClick,
|
||||
onDoubleClick,
|
||||
}: {
|
||||
tab: RootTab;
|
||||
isActive: boolean;
|
||||
isOverlay?: boolean;
|
||||
onClick?: () => void;
|
||||
onDoubleClick?: () => void;
|
||||
tab: RootTab
|
||||
isActive: boolean
|
||||
isOverlay?: boolean
|
||||
onClick?: () => void
|
||||
onDoubleClick?: () => void
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
@@ -216,16 +235,16 @@ function TabButton({
|
||||
onClick={onClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-colors whitespace-nowrap shrink-0",
|
||||
'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-colors whitespace-nowrap shrink-0',
|
||||
isActive
|
||||
? "bg-primary text-primary-foreground shadow-sm"
|
||||
: "hover:bg-accent text-muted-foreground hover:text-foreground",
|
||||
tab.enabled !== "1" && "opacity-50",
|
||||
isOverlay && "bg-card shadow-lg border",
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'hover:bg-accent text-muted-foreground hover:text-foreground',
|
||||
tab.enabled !== '1' && 'opacity-50',
|
||||
isOverlay && 'bg-card shadow-lg border',
|
||||
)}
|
||||
>
|
||||
<CatalogIcon iconImage={tab.iconImage} size={16} />
|
||||
<span className="max-w-32 truncate">{tab.caption}</span>
|
||||
</button>
|
||||
);
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user