revert fb8e77bb68
Local Build and Deploy / deploy (push) Successful in 1m11s

revert style: clean up code with prettier and eslint
This commit is contained in:
remco committed 2026-07-12 21:02:03 +02:00
1 parent 27de078f54
commit e85e4d74ea
378 files changed
+20710 -22428

No files matched your search

+244 -225
View File
@@ -1,4 +1,4 @@
"use client";
'use client'
import {
ChevronRight,
@@ -20,13 +20,13 @@ import {
ToggleRight,
Trash2,
X,
} from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
} from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
import { toast } from 'sonner'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
ContextMenu,
ContextMenuContent,
@@ -34,63 +34,63 @@ import {
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuTrigger,
} from "@/components/ui/context-menu";
import { Input } from "@/components/ui/input";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { translateCaption } from "@/lib/catalog-translations";
import { cn } from "@/lib/utils";
} from '@/components/ui/context-menu'
import { Input } from '@/components/ui/input'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { translateCaption } from '@/lib/catalog-translations'
import { cn } from '@/lib/utils'
/* ─── Types ──────────────────────────────────────────────── */
interface TreeNode {
id: number;
caption: string;
parentId: number;
enabled: string;
visible: string;
iconImage: number;
iconColor: number;
pageLayout: string;
childCount: number;
itemCount: number;
id: number
caption: string
parentId: number
enabled: string
visible: string
iconImage: number
iconColor: number
pageLayout: string
childCount: number
itemCount: number
}
interface CatalogTreeProps {
rootPages: TreeNode[];
catalogType?: "normal" | "bc";
rootPages: TreeNode[]
catalogType?: 'normal' | 'bc'
}
/* ─── Context for expand/collapse all ────────────────────── */
interface TreeContext {
expandSignal: number;
collapseSignal: number;
filterMode: FilterMode;
onToggleEnabled: (node: TreeNode) => void;
onToggleVisible: (node: TreeNode) => void;
onDeletePage: (node: TreeNode) => void;
searchQuery: string;
catalogType: "normal" | "bc";
expandSignal: number
collapseSignal: number
filterMode: FilterMode
onToggleEnabled: (node: TreeNode) => void
onToggleVisible: (node: TreeNode) => void
onDeletePage: (node: TreeNode) => void
searchQuery: string
catalogType: 'normal' | 'bc'
}
const TreeCtx = createContext<TreeContext>({
expandSignal: 0,
collapseSignal: 0,
filterMode: "all",
filterMode: 'all',
onToggleEnabled: () => {},
onToggleVisible: () => {},
onDeletePage: () => {},
searchQuery: "",
catalogType: "normal",
});
searchQuery: '',
catalogType: 'normal',
})
/* ─── Catalog Icon ───────────────────────────────────────── */
export function CatalogIcon({ iconImage, size = 20 }: { iconImage: number; size?: number }) {
const [error, setError] = useState(false);
const [error, setError] = useState(false)
if (error || iconImage <= 0) {
return <Package className="h-4 w-4 text-muted-foreground shrink-0" />;
return <Package className="h-4 w-4 text-muted-foreground shrink-0" />
}
return (
@@ -100,172 +100,175 @@ export function CatalogIcon({ iconImage, size = 20 }: { iconImage: number; size?
width={size}
height={size}
className="shrink-0 object-contain"
style={{ imageRendering: "pixelated", minWidth: size, minHeight: size }}
style={{ imageRendering: 'pixelated', minWidth: size, minHeight: size }}
onError={() => setError(true)}
/>
);
)
}
/* ─── Layout label helper ────────────────────────────────── */
export const LAYOUT_COLORS: Record<string, string> = {
default_3x3: "bg-[var(--admin-info-subtle)] text-[var(--admin-info)] dark:text-[var(--admin-info)]",
frontpage: "bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)] dark:text-[var(--admin-warning)]",
spaces_new: "bg-[var(--admin-success-subtle)] text-[var(--admin-success)] dark:text-[var(--admin-success)]",
pets: "bg-[var(--admin-error)]/15 text-[var(--admin-error)]",
pets2: "bg-[var(--admin-error)]/15 text-[var(--admin-error)]",
pets3: "bg-[var(--admin-error)]/15 text-[var(--admin-error)]",
marketplace: "bg-[var(--admin-accent)]/15 text-[var(--admin-accent-text)]",
recycler: "bg-[var(--admin-success-subtle)] text-[var(--admin-success)] dark:text-[var(--admin-success)]",
club_buy: "bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)] dark:text-[var(--admin-warning)]",
single_bundle: "bg-[var(--admin-info-subtle)] text-[var(--admin-info)] dark:text-[var(--admin-info)]",
room_bundle: "bg-[var(--admin-info-subtle)] text-[var(--admin-info)] dark:text-[var(--admin-info)]",
};
default_3x3: 'bg-[var(--admin-info-subtle)] text-[var(--admin-info)] dark:text-[var(--admin-info)]',
frontpage: 'bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)] dark:text-[var(--admin-warning)]',
spaces_new: 'bg-[var(--admin-success-subtle)] text-[var(--admin-success)] dark:text-[var(--admin-success)]',
pets: 'bg-[var(--admin-error)]/15 text-[var(--admin-error)]',
pets2: 'bg-[var(--admin-error)]/15 text-[var(--admin-error)]',
pets3: 'bg-[var(--admin-error)]/15 text-[var(--admin-error)]',
marketplace: 'bg-[var(--admin-accent)]/15 text-[var(--admin-accent-text)]',
recycler: 'bg-[var(--admin-success-subtle)] text-[var(--admin-success)] dark:text-[var(--admin-success)]',
club_buy: 'bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)] dark:text-[var(--admin-warning)]',
single_bundle: 'bg-[var(--admin-info-subtle)] text-[var(--admin-info)] dark:text-[var(--admin-info)]',
room_bundle: 'bg-[var(--admin-info-subtle)] text-[var(--admin-info)] dark:text-[var(--admin-info)]',
}
/* ─── Highlight matched text ─────────────────────────────── */
function HighlightText({ text, query }: { text: string; query: string }) {
if (!query.trim()) return <>{text}</>;
const idx = text.toLowerCase().indexOf(query.toLowerCase());
if (idx === -1) return <>{text}</>;
if (!query.trim()) return <>{text}</>
const idx = text.toLowerCase().indexOf(query.toLowerCase())
if (idx === -1) return <>{text}</>
return (
<>
{text.slice(0, idx)}
<mark className="bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)] rounded-sm px-0.5">
<mark className="bg-[var(--admin-warning-subtle)] text-[var(--admin-warning)] rounded-sm px-0.5">
{text.slice(idx, idx + query.length)}
</mark>
{text.slice(idx + query.length)}
</>
);
)
}
/* ─── Main Tree Component ────────────────────────────────── */
type FilterMode = "all" | "enabled" | "disabled";
type FilterMode = 'all' | 'enabled' | 'disabled'
export function CatalogTree({ rootPages, catalogType = "normal" }: CatalogTreeProps) {
const router = useRouter();
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState<TreeNode[] | null>(null);
const [searching, setSearching] = useState(false);
const searchTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
const searchInputRef = useRef<HTMLInputElement>(null);
const [expandSignal, setExpandSignal] = useState(0);
const [collapseSignal, setCollapseSignal] = useState(0);
const [filterMode, setFilterMode] = useState<FilterMode>("all");
export function CatalogTree({ rootPages, catalogType = 'normal' }: CatalogTreeProps) {
const router = useRouter()
const [searchQuery, setSearchQuery] = useState('')
const [searchResults, setSearchResults] = useState<TreeNode[] | null>(null)
const [searching, setSearching] = useState(false)
const searchTimeout = useRef<ReturnType<typeof setTimeout> | null>(null)
const searchInputRef = useRef<HTMLInputElement>(null)
const [expandSignal, setExpandSignal] = useState(0)
const [collapseSignal, setCollapseSignal] = useState(0)
const [filterMode, setFilterMode] = useState<FilterMode>('all')
// "/" keyboard shortcut to focus search
useEffect(() => {
function onKeyDown(e: KeyboardEvent) {
if (e.key === "/" && !e.metaKey && !e.ctrlKey && !e.altKey) {
const tag = (e.target as HTMLElement)?.tagName;
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
e.preventDefault();
searchInputRef.current?.focus();
if (e.key === '/' && !e.metaKey && !e.ctrlKey && !e.altKey) {
const tag = (e.target as HTMLElement)?.tagName
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return
e.preventDefault()
searchInputRef.current?.focus()
}
}
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, []);
document.addEventListener('keydown', onKeyDown)
return () => document.removeEventListener('keydown', onKeyDown)
}, [])
// Debounced search
// biome-ignore lint/correctness/useExhaustiveDependencies: explicitly chosen here, see surrounding code
useEffect(() => {
if (!searchQuery.trim()) {
setSearchResults(null);
return;
setSearchResults(null)
return
}
if (searchTimeout.current) clearTimeout(searchTimeout.current);
if (searchTimeout.current) clearTimeout(searchTimeout.current)
searchTimeout.current = setTimeout(async () => {
setSearching(true);
setSearching(true)
try {
const catParam = catalogType === "bc" ? "&catalog=bc" : "";
const catParam = catalogType === 'bc' ? '&catalog=bc' : ''
const res = await fetch(
`/api/admin/catalog/tree?search=${encodeURIComponent(searchQuery.trim())}${catParam}`,
);
)
if (res.ok) {
const data = await res.json();
setSearchResults(data.pages || []);
const data = await res.json()
setSearchResults(data.pages || [])
}
} catch (error) {
console.error("[CatalogTree] Search failed:", error);
console.error('[CatalogTree] Search failed:', error)
} finally {
setSearching(false);
setSearching(false)
}
}, 300);
}, 300)
return () => {
if (searchTimeout.current) clearTimeout(searchTimeout.current);
};
}, [searchQuery]);
if (searchTimeout.current) clearTimeout(searchTimeout.current)
}
}, [searchQuery])
// Quick actions from context menu
const catParam = catalogType === "bc" ? "&catalog=bc" : "";
const catBody = catalogType === "bc" ? ', "catalog": "bc"' : "";
const catParam = catalogType === 'bc' ? '&catalog=bc' : ''
const catBody = catalogType === 'bc' ? ', "catalog": "bc"' : ''
const handleToggleEnabled = useCallback(
async (node: TreeNode) => {
try {
const res = await fetch("/api/admin/catalog/tree", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
const res = await fetch('/api/admin/catalog/tree', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: `{"pageId":${node.id},"toggleField":"toggleEnabled"${catBody}}`,
});
if (!res.ok) throw new Error();
toast.success(`${node.caption} ${node.enabled === "1" ? "disabled" : "enabled"}`);
router.refresh();
})
if (!res.ok) throw new Error()
toast.success(`${node.caption} ${node.enabled === '1' ? 'disabled' : 'enabled'}`)
router.refresh()
} catch {
toast.error("Failed to toggle. Open the page to edit.");
toast.error('Failed to toggle. Open the page to edit.')
}
},
[router, catBody],
);
)
const handleToggleVisible = useCallback(
async (node: TreeNode) => {
try {
const res = await fetch("/api/admin/catalog/tree", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
const res = await fetch('/api/admin/catalog/tree', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: `{"pageId":${node.id},"toggleField":"toggleVisible"${catBody}}`,
});
if (!res.ok) throw new Error();
toast.success(`${node.caption} ${node.visible === "1" ? "hidden" : "visible"}`);
router.refresh();
})
if (!res.ok) throw new Error()
toast.success(`${node.caption} ${node.visible === '1' ? 'hidden' : 'visible'}`)
router.refresh()
} catch {
toast.error("Failed to toggle. Open the page to edit.");
toast.error('Failed to toggle. Open the page to edit.')
}
},
[router, catBody],
);
)
const handleDeletePage = useCallback(
async (node: TreeNode) => {
const childInfo =
node.childCount > 0 ? ` Its ${node.childCount} child page(s) will be moved to parent.` : "";
const itemInfo = node.itemCount > 0 ? ` ${node.itemCount} item(s) will be deleted.` : "";
if (!confirm(`Delete "${node.caption}"?${childInfo}${itemInfo}`)) return;
node.childCount > 0 ? ` Its ${node.childCount} child page(s) will be moved to parent.` : ''
const itemInfo = node.itemCount > 0 ? ` ${node.itemCount} item(s) will be deleted.` : ''
if (!confirm(`Delete "${node.caption}"?${childInfo}${itemInfo}`)) return
try {
const res = await fetch(`/api/admin/catalog/tree?pageId=${node.id}&mode=reparent${catParam}`, {
method: "DELETE",
});
if (!res.ok) throw new Error();
toast.success(`"${node.caption}" deleted`);
router.refresh();
const res = await fetch(
`/api/admin/catalog/tree?pageId=${node.id}&mode=reparent${catParam}`,
{ method: 'DELETE' },
)
if (!res.ok) throw new Error()
toast.success(`"${node.caption}" deleted`)
router.refresh()
} catch {
toast.error("Failed to delete page.");
toast.error('Failed to delete page.')
}
},
[router, catParam],
);
)
const unfilteredPages = searchResults ?? rootPages;
const unfilteredPages = searchResults ?? rootPages
const displayPages =
filterMode === "all"
filterMode === 'all'
? unfilteredPages
: unfilteredPages.filter((p) => (filterMode === "enabled" ? p.enabled === "1" : p.enabled !== "1"));
: unfilteredPages.filter((p) =>
filterMode === 'enabled' ? p.enabled === '1' : p.enabled !== '1',
)
return (
<TreeCtx.Provider
@@ -273,7 +276,7 @@ export function CatalogTree({ rootPages, catalogType = "normal" }: CatalogTreePr
expandSignal,
collapseSignal,
filterMode,
searchQuery: searchResults !== null ? searchQuery : "",
searchQuery: searchResults !== null ? searchQuery : '',
onToggleEnabled: handleToggleEnabled,
onToggleVisible: handleToggleVisible,
onDeletePage: handleDeletePage,
@@ -289,7 +292,7 @@ export function CatalogTree({ rootPages, catalogType = "normal" }: CatalogTreePr
<h3 className="text-sm font-semibold">Catalog Structure</h3>
<Badge variant="secondary" className="text-[10px] h-5">
{displayPages.length}
{filterMode !== "all" ? ` / ${unfilteredPages.length}` : ""} pages
{filterMode !== 'all' ? ` / ${unfilteredPages.length}` : ''} pages
</Badge>
</div>
<div className="flex items-center gap-1.5">
@@ -299,12 +302,12 @@ export function CatalogTree({ rootPages, catalogType = "normal" }: CatalogTreePr
<TooltipTrigger asChild>
<button
type="button"
onClick={() => setFilterMode("all")}
onClick={() => setFilterMode('all')}
className={cn(
"px-2 py-1 rounded text-xs font-medium transition-colors",
filterMode === "all"
? "bg-background shadow-sm text-foreground"
: "text-muted-foreground hover:text-foreground",
'px-2 py-1 rounded text-xs font-medium transition-colors',
filterMode === 'all'
? 'bg-background shadow-sm text-foreground'
: 'text-muted-foreground hover:text-foreground',
)}
>
All
@@ -318,12 +321,12 @@ export function CatalogTree({ rootPages, catalogType = "normal" }: CatalogTreePr
<TooltipTrigger asChild>
<button
type="button"
onClick={() => setFilterMode("enabled")}
onClick={() => setFilterMode('enabled')}
className={cn(
"px-2 py-1 rounded text-xs font-medium transition-colors flex items-center gap-1",
filterMode === "enabled"
? "bg-background shadow-sm text-[var(--admin-success)] dark:text-[var(--admin-success)]"
: "text-muted-foreground hover:text-foreground",
'px-2 py-1 rounded text-xs font-medium transition-colors flex items-center gap-1',
filterMode === 'enabled'
? 'bg-background shadow-sm text-[var(--admin-success)] dark:text-[var(--admin-success)]'
: 'text-muted-foreground hover:text-foreground',
)}
>
<ToggleRight className="h-3 w-3" />
@@ -338,12 +341,12 @@ export function CatalogTree({ rootPages, catalogType = "normal" }: CatalogTreePr
<TooltipTrigger asChild>
<button
type="button"
onClick={() => setFilterMode("disabled")}
onClick={() => setFilterMode('disabled')}
className={cn(
"px-2 py-1 rounded text-xs font-medium transition-colors flex items-center gap-1",
filterMode === "disabled"
? "bg-background shadow-sm text-[var(--admin-error)] dark:text-[var(--admin-error)]"
: "text-muted-foreground hover:text-foreground",
'px-2 py-1 rounded text-xs font-medium transition-colors flex items-center gap-1',
filterMode === 'disabled'
? 'bg-background shadow-sm text-[var(--admin-error)] dark:text-[var(--admin-error)]'
: 'text-muted-foreground hover:text-foreground',
)}
>
<ToggleLeft className="h-3 w-3" />
@@ -402,9 +405,9 @@ export function CatalogTree({ rootPages, catalogType = "normal" }: CatalogTreePr
onChange={(e) => setSearchQuery(e.target.value)}
className="h-8 pl-8 pr-8 text-sm"
onKeyDown={(e) => {
if (e.key === "Escape") {
setSearchQuery("");
searchInputRef.current?.blur();
if (e.key === 'Escape') {
setSearchQuery('')
searchInputRef.current?.blur()
}
}}
/>
@@ -414,7 +417,7 @@ export function CatalogTree({ rootPages, catalogType = "normal" }: CatalogTreePr
searchQuery && (
<button
type="button"
onClick={() => setSearchQuery("")}
onClick={() => setSearchQuery('')}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
<X className="h-3.5 w-3.5" />
@@ -430,7 +433,7 @@ export function CatalogTree({ rootPages, catalogType = "normal" }: CatalogTreePr
{searchResults !== null && (
<div className="px-2 py-1.5 mb-1 flex items-center justify-between">
<p className="text-xs text-muted-foreground">
{searchResults.length} result{searchResults.length !== 1 ? "s" : ""} for &quot;
{searchResults.length} result{searchResults.length !== 1 ? 's' : ''} for &quot;
{searchQuery}&quot;
</p>
</div>
@@ -446,14 +449,14 @@ export function CatalogTree({ rootPages, catalogType = "normal" }: CatalogTreePr
))}
{displayPages.length === 0 && (
<p className="text-sm text-muted-foreground text-center py-8">
{searchResults !== null ? "No pages match your search" : "No catalog pages found"}
{searchResults !== null ? 'No pages match your search' : 'No catalog pages found'}
</p>
)}
</div>
</div>
</TooltipProvider>
</TreeCtx.Provider>
);
)
}
/* ─── Tree Item ──────────────────────────────────────────── */
@@ -465,10 +468,10 @@ function TreeItem({
isSearchResult,
defaultExpanded,
}: {
node: TreeNode;
level: number;
isSearchResult?: boolean;
defaultExpanded?: boolean;
node: TreeNode
level: number
isSearchResult?: boolean
defaultExpanded?: boolean
}) {
const {
expandSignal,
@@ -479,73 +482,73 @@ function TreeItem({
onToggleVisible,
onDeletePage,
catalogType,
} = useContext(TreeCtx);
const router = useRouter();
const [expanded, setExpanded] = useState(false);
const [children, setChildren] = useState<TreeNode[]>([]);
const [loading, setLoading] = useState(false);
const [loaded, setLoaded] = useState(false);
const hasChildren = node.childCount > 0;
} = useContext(TreeCtx)
const router = useRouter()
const [expanded, setExpanded] = useState(false)
const [children, setChildren] = useState<TreeNode[]>([])
const [loading, setLoading] = useState(false)
const [loaded, setLoaded] = useState(false)
const hasChildren = node.childCount > 0
// Auto-expand root level on mount
// biome-ignore lint/correctness/useExhaustiveDependencies: explicitly chosen here, see surrounding code
useEffect(() => {
if (defaultExpanded && level === 0 && hasChildren && !loaded) {
loadChildren().then(() => setExpanded(true));
loadChildren().then(() => setExpanded(true))
}
}, []);
}, [])
// Expand all signal
// biome-ignore lint/correctness/useExhaustiveDependencies: explicitly chosen here, see surrounding code
useEffect(() => {
if (expandSignal > 0 && hasChildren) {
if (!loaded) {
loadChildren().then(() => setExpanded(true));
loadChildren().then(() => setExpanded(true))
} else {
setExpanded(true);
setExpanded(true)
}
}
}, [expandSignal]);
}, [expandSignal])
// Collapse all signal
useEffect(() => {
if (collapseSignal > 0) {
setExpanded(false);
setExpanded(false)
}
}, [collapseSignal]);
}, [collapseSignal])
const loadChildren = useCallback(async () => {
if (loaded || !hasChildren) return;
setLoading(true);
if (loaded || !hasChildren) return
setLoading(true)
try {
const catParam = catalogType === "bc" ? "&catalog=bc" : "";
const res = await fetch(`/api/admin/catalog/tree?parentId=${node.id}${catParam}`);
const catParam = catalogType === 'bc' ? '&catalog=bc' : ''
const res = await fetch(`/api/admin/catalog/tree?parentId=${node.id}${catParam}`)
if (res.ok) {
const data = await res.json();
setChildren(data.pages || []);
setLoaded(true);
const data = await res.json()
setChildren(data.pages || [])
setLoaded(true)
}
} catch (error) {
console.error("[CatalogTree] Failed to load children:", error);
console.error('[CatalogTree] Failed to load children:', error)
} finally {
setLoading(false);
setLoading(false)
}
}, [node.id, loaded, hasChildren, catalogType]);
}, [node.id, loaded, hasChildren, catalogType])
const handleToggle = () => {
if (!expanded && !loaded) {
loadChildren().then(() => setExpanded(true));
return;
loadChildren().then(() => setExpanded(true))
return
}
setExpanded(!expanded);
};
setExpanded(!expanded)
}
const isDisabled = node.enabled !== "1";
const isHidden = node.visible !== "1";
const isEmpty = node.itemCount === 0 && node.childCount === 0;
const layoutColor = LAYOUT_COLORS[node.pageLayout] || "bg-muted text-muted-foreground";
const italianName = translateCaption(node.caption);
const displayName = italianName ?? node.caption;
const isDisabled = node.enabled !== '1'
const isHidden = node.visible !== '1'
const isEmpty = node.itemCount === 0 && node.childCount === 0
const layoutColor = LAYOUT_COLORS[node.pageLayout] || 'bg-muted text-muted-foreground'
const italianName = translateCaption(node.caption)
const displayName = italianName ?? node.caption
return (
<div>
@@ -558,26 +561,26 @@ function TreeItem({
role="button"
tabIndex={0}
className={cn(
"flex items-center gap-1.5 rounded-md px-2 py-1 hover:bg-accent/60 group transition-colors cursor-default",
isDisabled && "opacity-40",
isEmpty && !isDisabled && "opacity-60",
'flex items-center gap-1.5 rounded-md px-2 py-1 hover:bg-accent/60 group transition-colors cursor-default',
isDisabled && 'opacity-40',
isEmpty && !isDisabled && 'opacity-60',
)}
style={{ paddingLeft: `${level * 20 + 8}px` }}
onDoubleClick={() =>
router.push(
catalogType === "bc"
catalogType === 'bc'
? `/admin/catalog/builder-club/${node.id}`
: `/admin/catalog/${node.id}`,
)
}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
if (e.key === 'Enter') {
e.preventDefault()
router.push(
catalogType === "bc"
catalogType === 'bc'
? `/admin/catalog/builder-club/${node.id}`
: `/admin/catalog/${node.id}`,
);
)
}
}}
>
@@ -593,8 +596,8 @@ function TreeItem({
) : (
<ChevronRight
className={cn(
"h-3.5 w-3.5 text-muted-foreground transition-transform duration-150",
expanded && "rotate-90",
'h-3.5 w-3.5 text-muted-foreground transition-transform duration-150',
expanded && 'rotate-90',
)}
/>
)}
@@ -609,16 +612,16 @@ function TreeItem({
{/* Caption link */}
<Link
href={
catalogType === "bc"
catalogType === 'bc'
? `/admin/catalog/builder-club/${node.id}`
: `/admin/catalog/${node.id}`
}
className={cn(
"flex-1 min-w-0 hover:text-primary transition-colors",
isEmpty && !isDisabled ? "text-muted-foreground italic" : "",
'flex-1 min-w-0 hover:text-primary transition-colors',
isEmpty && !isDisabled ? 'text-muted-foreground italic' : '',
)}
>
<span className={cn("text-sm truncate block", !isEmpty && "font-medium")}>
<span className={cn('text-sm truncate block', !isEmpty && 'font-medium')}>
<HighlightText text={displayName} query={searchQuery} />
</span>
{italianName && (
@@ -634,21 +637,25 @@ function TreeItem({
<button
type="button"
onClick={(e) => {
e.stopPropagation();
onToggleEnabled(node);
e.stopPropagation()
onToggleEnabled(node)
}}
className={cn(
"flex items-center justify-center h-5 w-5 shrink-0 rounded opacity-0 group-hover:opacity-100 transition-opacity",
'flex items-center justify-center h-5 w-5 shrink-0 rounded opacity-0 group-hover:opacity-100 transition-opacity',
isDisabled
? "hover:bg-[var(--admin-success-subtle)] text-muted-foreground hover:text-[var(--admin-success)]"
: "hover:bg-[var(--admin-error-subtle)] text-muted-foreground hover:text-[var(--admin-error)]",
? 'hover:bg-[var(--admin-success-subtle)] text-muted-foreground hover:text-[var(--admin-success)]'
: 'hover:bg-[var(--admin-error-subtle)] text-muted-foreground hover:text-[var(--admin-error)]',
)}
>
{isDisabled ? <Power className="h-3 w-3" /> : <PowerOff className="h-3 w-3" />}
{isDisabled ? (
<Power className="h-3 w-3" />
) : (
<PowerOff className="h-3 w-3" />
)}
</button>
</TooltipTrigger>
<TooltipContent side="top">
<p>{isDisabled ? "Enable" : "Disable"}</p>
<p>{isDisabled ? 'Enable' : 'Disable'}</p>
</TooltipContent>
</Tooltip>
@@ -658,12 +665,12 @@ function TreeItem({
<button
type="button"
onClick={(e) => {
e.stopPropagation();
e.stopPropagation()
router.push(
catalogType === "bc"
catalogType === 'bc'
? `/admin/catalog/builder-club/${node.id}`
: `/admin/catalog?newPageParent=${node.id}`,
);
)
}}
className="flex items-center justify-center h-5 w-5 shrink-0 rounded hover:bg-primary/20 opacity-0 group-hover:opacity-100 transition-opacity text-muted-foreground hover:text-primary"
>
@@ -691,11 +698,11 @@ function TreeItem({
{/* Layout badge on hover */}
<Badge
className={cn(
"h-5 px-1.5 text-[9px] font-mono opacity-0 group-hover:opacity-100 transition-opacity border-0",
'h-5 px-1.5 text-[9px] font-mono opacity-0 group-hover:opacity-100 transition-opacity border-0',
layoutColor,
)}
>
{node.pageLayout.replace("default_3x3", "3x3")}
{node.pageLayout.replace('default_3x3', '3x3')}
</Badge>
</div>
</div>
@@ -724,11 +731,11 @@ function TreeItem({
</tr>
<tr>
<td className="text-muted-foreground pr-3 py-px">Visible</td>
<td className="py-px">{node.visible === "1" ? "Yes" : "No"}</td>
<td className="py-px">{node.visible === '1' ? 'Yes' : 'No'}</td>
</tr>
<tr>
<td className="text-muted-foreground pr-3 py-px">Enabled</td>
<td className="py-px">{node.enabled === "1" ? "Yes" : "No"}</td>
<td className="py-px">{node.enabled === '1' ? 'Yes' : 'No'}</td>
</tr>
</tbody>
</table>
@@ -747,7 +754,9 @@ function TreeItem({
<ContextMenuItem
onClick={() =>
router.push(
catalogType === "bc" ? `/admin/catalog/builder-club/${node.id}` : `/admin/catalog/${node.id}`,
catalogType === 'bc'
? `/admin/catalog/builder-club/${node.id}`
: `/admin/catalog/${node.id}`,
)
}
>
@@ -760,18 +769,26 @@ function TreeItem({
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem onClick={() => onToggleEnabled(node)}>
{node.enabled === "1" ? <PowerOff className="h-4 w-4" /> : <Power className="h-4 w-4" />}
{node.enabled === "1" ? "Disable" : "Enable"}
{node.enabled === '1' ? (
<PowerOff className="h-4 w-4" />
) : (
<Power className="h-4 w-4" />
)}
{node.enabled === '1' ? 'Disable' : 'Enable'}
</ContextMenuItem>
<ContextMenuItem onClick={() => onToggleVisible(node)}>
{node.visible === "1" ? <EyeOffIcon className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
{node.visible === "1" ? "Hide" : "Show"}
{node.visible === '1' ? (
<EyeOffIcon className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
{node.visible === '1' ? 'Hide' : 'Show'}
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
onClick={() =>
router.push(
catalogType === "bc"
catalogType === 'bc'
? `/admin/catalog/builder-club/${node.id}`
: `/admin/catalog?newPageParent=${node.id}`,
)
@@ -793,9 +810,11 @@ function TreeItem({
children.length > 0 &&
(() => {
const filteredChildren =
filterMode === "all"
filterMode === 'all'
? children
: children.filter((c) => (filterMode === "enabled" ? c.enabled === "1" : c.enabled !== "1"));
: children.filter((c) =>
filterMode === 'enabled' ? c.enabled === '1' : c.enabled !== '1',
)
return filteredChildren.length > 0 ? (
<div className="relative">
{/* Tree guide line */}
@@ -807,8 +826,8 @@ function TreeItem({
<TreeItem key={child.id} node={child} level={level + 1} />
))}
</div>
) : null;
) : null
})()}
</div>
);
)
}