ERP-node/frontend/components/report/designer/ComponentPalette.tsx

138 lines
3.8 KiB
TypeScript

"use client";
import { useState } from "react";
import { useDrag } from "react-dnd";
import {
Type,
Table,
Image,
Minus,
PenLine,
Stamp as StampIcon,
Hash,
CreditCard,
Calculator,
Barcode,
CheckSquare,
ChevronRight,
LayoutGrid,
FileText,
Database,
} from "lucide-react";
interface ComponentItem {
type: string;
label: string;
icon: React.ReactNode;
}
interface ComponentCategory {
id: string;
label: string;
icon: React.ReactNode;
items: ComponentItem[];
}
const CATEGORIES: ComponentCategory[] = [
{
id: "basic",
label: "기본 요소",
icon: <LayoutGrid className="h-5 w-5" />,
items: [
{ type: "text", label: "텍스트", icon: <Type className="h-5 w-5" /> },
{ type: "image", label: "이미지", icon: <Image className="h-5 w-5" /> },
{ type: "divider", label: "구분선", icon: <Minus className="h-5 w-5" /> },
],
},
{
id: "data",
label: "데이터 표시",
icon: <Database className="h-5 w-5" />,
items: [
{ type: "table", label: "테이블", icon: <Table className="h-5 w-5" /> },
{ type: "card", label: "정보카드", icon: <CreditCard className="h-5 w-5" /> },
{ type: "calculation", label: "계산", icon: <Calculator className="h-5 w-5" /> },
],
},
{
id: "form",
label: "입력/인증",
icon: <FileText className="h-5 w-5" />,
items: [
{ type: "checkbox", label: "체크박스", icon: <CheckSquare className="h-5 w-5" /> },
{ type: "signature", label: "서명란", icon: <PenLine className="h-5 w-5" /> },
{ type: "stamp", label: "도장란", icon: <StampIcon className="h-5 w-5" /> },
{ type: "barcode", label: "바코드/QR", icon: <Barcode className="h-5 w-5" /> },
],
},
];
function DraggableComponentItem({ type, label, icon }: ComponentItem) {
const [{ isDragging }, drag] = useDrag(() => ({
type: "component",
item: { componentType: type },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
}));
return (
<div
ref={drag}
className={`flex h-20 cursor-move flex-col items-center justify-center gap-1.5 rounded-lg border border-gray-200 bg-gray-50 transition-colors hover:border-indigo-400 hover:bg-indigo-50 ${
isDragging ? "opacity-50" : ""
}`}
>
<div className="text-gray-600">{icon}</div>
<span className="text-xs font-medium text-gray-700">{label}</span>
</div>
);
}
export function ComponentPalette() {
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(
new Set(CATEGORIES.map((c) => c.id)),
);
const toggleCategory = (id: string) => {
setExpandedCategories((prev) => {
const next = new Set(prev);
if (next.has(id)) {
next.delete(id);
} else {
next.add(id);
}
return next;
});
};
return (
<div className="space-y-1">
{CATEGORIES.map((category) => {
const isExpanded = expandedCategories.has(category.id);
return (
<div key={category.id}>
<button
onClick={() => toggleCategory(category.id)}
className="flex w-full items-center gap-2.5 rounded-md px-2 py-2 text-sm font-semibold text-gray-700 transition-colors hover:bg-gray-100"
>
<ChevronRight
className={`h-4 w-4 text-gray-400 transition-transform ${isExpanded ? "rotate-90" : ""}`}
/>
<span className="text-gray-500">{category.icon}</span>
{category.label}
</button>
{isExpanded && (
<div className="grid grid-cols-2 gap-3 px-1 pt-2 pb-3">
{category.items.map((item) => (
<DraggableComponentItem key={item.type} {...item} />
))}
</div>
)}
</div>
);
})}
</div>
);
}