2026-02-04 14:14:48 +09:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useDrag } from "react-dnd";
|
|
|
|
|
import {
|
|
|
|
|
Type,
|
|
|
|
|
MousePointer,
|
|
|
|
|
List,
|
|
|
|
|
Activity,
|
|
|
|
|
ScanLine,
|
|
|
|
|
Calculator,
|
|
|
|
|
GripVertical,
|
|
|
|
|
Space,
|
2026-02-04 18:23:59 +09:00
|
|
|
WrapText,
|
2026-02-04 14:14:48 +09:00
|
|
|
} from "lucide-react";
|
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
import { PopComponentType } from "../types/pop-layout";
|
|
|
|
|
import { DND_ITEM_TYPES, DragItemComponent } from "./PopPanel";
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
// 컴포넌트 팔레트 정의
|
|
|
|
|
// ========================================
|
|
|
|
|
const COMPONENT_PALETTE: {
|
|
|
|
|
type: PopComponentType;
|
|
|
|
|
label: string;
|
|
|
|
|
icon: React.ElementType;
|
|
|
|
|
description: string;
|
|
|
|
|
}[] = [
|
|
|
|
|
{
|
|
|
|
|
type: "pop-field",
|
|
|
|
|
label: "필드",
|
|
|
|
|
icon: Type,
|
|
|
|
|
description: "텍스트, 숫자 등 데이터 입력",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: "pop-button",
|
|
|
|
|
label: "버튼",
|
|
|
|
|
icon: MousePointer,
|
|
|
|
|
description: "저장, 삭제 등 액션 실행",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: "pop-list",
|
|
|
|
|
label: "리스트",
|
|
|
|
|
icon: List,
|
|
|
|
|
description: "데이터 목록 (카드 템플릿 지원)",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: "pop-indicator",
|
|
|
|
|
label: "인디케이터",
|
|
|
|
|
icon: Activity,
|
|
|
|
|
description: "KPI, 상태 표시",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: "pop-scanner",
|
|
|
|
|
label: "스캐너",
|
|
|
|
|
icon: ScanLine,
|
|
|
|
|
description: "바코드/QR 스캔",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: "pop-numpad",
|
|
|
|
|
label: "숫자패드",
|
|
|
|
|
icon: Calculator,
|
|
|
|
|
description: "숫자 입력 전용",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: "pop-spacer",
|
|
|
|
|
label: "스페이서",
|
|
|
|
|
icon: Space,
|
|
|
|
|
description: "빈 공간 (정렬용)",
|
|
|
|
|
},
|
2026-02-04 18:23:59 +09:00
|
|
|
{
|
|
|
|
|
type: "pop-break",
|
|
|
|
|
label: "줄바꿈",
|
|
|
|
|
icon: WrapText,
|
|
|
|
|
description: "강제 줄바꿈 (flex-basis: 100%)",
|
|
|
|
|
},
|
2026-02-04 14:14:48 +09:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
// v4 컴포넌트 팔레트
|
|
|
|
|
// ========================================
|
|
|
|
|
export function ComponentPaletteV4() {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex h-full flex-col">
|
|
|
|
|
{/* 헤더 */}
|
|
|
|
|
<div className="border-b p-3">
|
|
|
|
|
<div className="text-sm font-medium text-muted-foreground">
|
|
|
|
|
편집 중: v4 (자동 반응형)
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs text-muted-foreground mt-1">
|
|
|
|
|
규칙 기반 레이아웃
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 컴포넌트 목록 */}
|
|
|
|
|
<div className="flex-1 overflow-auto p-3">
|
|
|
|
|
<div className="text-xs font-medium text-muted-foreground mb-2">
|
|
|
|
|
컴포넌트
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
{COMPONENT_PALETTE.map((item) => (
|
|
|
|
|
<DraggableComponentV4
|
|
|
|
|
key={item.type}
|
|
|
|
|
type={item.type}
|
|
|
|
|
label={item.label}
|
|
|
|
|
icon={item.icon}
|
|
|
|
|
description={item.description}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mt-4 text-xs text-muted-foreground">
|
|
|
|
|
캔버스로 드래그하여 배치
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
// 드래그 가능한 컴포넌트 아이템
|
|
|
|
|
// ========================================
|
|
|
|
|
interface DraggableComponentV4Props {
|
|
|
|
|
type: PopComponentType;
|
|
|
|
|
label: string;
|
|
|
|
|
icon: React.ElementType;
|
|
|
|
|
description: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function DraggableComponentV4({ type, label, icon: Icon, description }: DraggableComponentV4Props) {
|
|
|
|
|
const [{ isDragging }, drag] = useDrag(
|
|
|
|
|
() => ({
|
|
|
|
|
type: DND_ITEM_TYPES.COMPONENT,
|
|
|
|
|
item: { type: DND_ITEM_TYPES.COMPONENT, componentType: type } as DragItemComponent,
|
|
|
|
|
collect: (monitor) => ({
|
|
|
|
|
isDragging: monitor.isDragging(),
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
[type]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
ref={drag}
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex items-center gap-2 rounded-lg border p-2 cursor-grab transition-all",
|
|
|
|
|
"hover:bg-accent hover:border-primary/30",
|
|
|
|
|
isDragging && "opacity-50 cursor-grabbing"
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<GripVertical className="h-4 w-4 text-muted-foreground" />
|
|
|
|
|
<Icon className="h-4 w-4 shrink-0" />
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<div className="text-sm font-medium truncate">{label}</div>
|
|
|
|
|
<div className="text-xs text-muted-foreground truncate">{description}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default ComponentPaletteV4;
|