ERP-node/frontend/components/admin/multilang/CategoryTree.tsx

200 lines
5.4 KiB
TypeScript
Raw Normal View History

"use client";
import { useState, useEffect } from "react";
import { ChevronRight, ChevronDown, Folder, FolderOpen, Tag } from "lucide-react";
import { cn } from "@/lib/utils";
import { LangCategory, getCategories } from "@/lib/api/multilang";
interface CategoryTreeProps {
selectedCategoryId: number | null;
onSelectCategory: (category: LangCategory | null) => void;
onDoubleClickCategory?: (category: LangCategory) => void;
}
interface CategoryNodeProps {
category: LangCategory;
level: number;
selectedCategoryId: number | null;
onSelectCategory: (category: LangCategory) => void;
onDoubleClickCategory?: (category: LangCategory) => void;
}
function CategoryNode({
category,
level,
selectedCategoryId,
onSelectCategory,
onDoubleClickCategory,
}: CategoryNodeProps) {
const [isExpanded, setIsExpanded] = useState(true);
const hasChildren = category.children && category.children.length > 0;
const isSelected = selectedCategoryId === category.categoryId;
return (
<div>
<div
className={cn(
"flex cursor-pointer items-center gap-1 rounded-md px-2 py-1.5 text-sm transition-colors",
isSelected
? "bg-primary text-primary-foreground"
: "hover:bg-muted"
)}
style={{ paddingLeft: `${level * 16 + 8}px` }}
onClick={() => onSelectCategory(category)}
onDoubleClick={() => onDoubleClickCategory?.(category)}
>
{/* 확장/축소 아이콘 */}
{hasChildren ? (
<button
className="shrink-0"
onClick={(e) => {
e.stopPropagation();
setIsExpanded(!isExpanded);
}}
>
{isExpanded ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
</button>
) : (
<span className="w-4" />
)}
{/* 폴더/태그 아이콘 */}
{hasChildren || level === 0 ? (
isExpanded ? (
<FolderOpen className="h-4 w-4 shrink-0 text-amber-500" />
) : (
<Folder className="h-4 w-4 shrink-0 text-amber-500" />
)
) : (
<Tag className="h-4 w-4 shrink-0 text-blue-500" />
)}
{/* 카테고리 이름 */}
<span className="truncate">{category.categoryName}</span>
{/* prefix 표시 */}
<span
className={cn(
"ml-auto text-xs",
isSelected ? "text-primary-foreground/70" : "text-muted-foreground"
)}
>
{category.keyPrefix}
</span>
</div>
{/* 자식 카테고리 */}
{hasChildren && isExpanded && (
<div>
{category.children!.map((child) => (
<CategoryNode
key={child.categoryId}
category={child}
level={level + 1}
selectedCategoryId={selectedCategoryId}
onSelectCategory={onSelectCategory}
onDoubleClickCategory={onDoubleClickCategory}
/>
))}
</div>
)}
</div>
);
}
export function CategoryTree({
selectedCategoryId,
onSelectCategory,
onDoubleClickCategory,
}: CategoryTreeProps) {
const [categories, setCategories] = useState<LangCategory[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadCategories();
}, []);
const loadCategories = async () => {
try {
setLoading(true);
const response = await getCategories();
if (response.success && response.data) {
setCategories(response.data);
} else {
setError(response.error?.details || "카테고리 로드 실패");
}
} catch (err) {
setError("카테고리 로드 중 오류 발생");
} finally {
setLoading(false);
}
};
if (loading) {
return (
<div className="flex h-32 items-center justify-center">
<div className="animate-pulse text-sm text-muted-foreground">
...
</div>
</div>
);
}
if (error) {
return (
<div className="flex h-32 items-center justify-center">
<div className="text-sm text-destructive">{error}</div>
</div>
);
}
if (categories.length === 0) {
return (
<div className="flex h-32 items-center justify-center">
<div className="text-sm text-muted-foreground">
</div>
</div>
);
}
return (
<div className="space-y-0.5">
{/* 전체 선택 옵션 */}
<div
className={cn(
"flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
selectedCategoryId === null
? "bg-primary text-primary-foreground"
: "hover:bg-muted"
)}
onClick={() => onSelectCategory(null)}
>
<Folder className="h-4 w-4 shrink-0" />
<span></span>
</div>
{/* 카테고리 트리 */}
{categories.map((category) => (
<CategoryNode
key={category.categoryId}
category={category}
level={0}
selectedCategoryId={selectedCategoryId}
onSelectCategory={onSelectCategory}
onDoubleClickCategory={onDoubleClickCategory}
/>
))}
</div>
);
}
export default CategoryTree;