"use client"; import { useState } from "react"; import { useDrag } from "react-dnd"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { Plus, Settings, LayoutGrid, Type, MousePointer, List, Activity, ScanLine, Calculator, Trash2, ChevronDown, GripVertical, } from "lucide-react"; import { cn } from "@/lib/utils"; import { PopLayoutData, PopSectionData, PopComponentType, } from "../types/pop-layout"; // 드래그 아이템 타입 export const DND_ITEM_TYPES = { SECTION: "section", COMPONENT: "component", } as const; // 드래그 아이템 데이터 export interface DragItemSection { type: typeof DND_ITEM_TYPES.SECTION; } export interface DragItemComponent { type: typeof DND_ITEM_TYPES.COMPONENT; componentType: PopComponentType; } interface PopPanelProps { layout: PopLayoutData; selectedSectionId: string | null; selectedSection: PopSectionData | null; onUpdateSection: (id: string, updates: Partial) => void; onDeleteSection: (id: string) => void; activeDevice: "mobile" | "tablet"; } // 컴포넌트 팔레트 정의 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: "숫자 입력 전용", }, ]; export function PopPanel({ layout, selectedSectionId, selectedSection, onUpdateSection, onDeleteSection, activeDevice, }: PopPanelProps) { const [activeTab, setActiveTab] = useState("components"); return (
컴포넌트 편집 {/* 컴포넌트 탭 */}
{/* 섹션 드래그 아이템 */}

레이아웃

캔버스에 드래그하여 섹션 추가

{/* 컴포넌트 팔레트 */}

컴포넌트

{COMPONENT_PALETTE.map((item) => ( ))}

섹션 안으로 드래그하여 배치

{/* 편집 탭 */} {selectedSection ? ( onUpdateSection(selectedSection.id, updates)} onDelete={() => onDeleteSection(selectedSection.id)} activeDevice={activeDevice} /> ) : (
섹션을 선택하세요
)}
); } // 드래그 가능한 섹션 아이템 function DraggableSectionItem() { const [{ isDragging }, drag] = useDrag(() => ({ type: DND_ITEM_TYPES.SECTION, item: { type: DND_ITEM_TYPES.SECTION } as DragItemSection, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), })); return (

섹션

컴포넌트를 그룹화하는 컨테이너

); } // 드래그 가능한 컴포넌트 아이템 interface DraggableComponentItemProps { type: PopComponentType; label: string; icon: React.ElementType; description: string; } function DraggableComponentItem({ type, label, icon: Icon, description, }: DraggableComponentItemProps) { const [{ isDragging }, drag] = useDrag(() => ({ type: DND_ITEM_TYPES.COMPONENT, item: { type: DND_ITEM_TYPES.COMPONENT, componentType: type } as DragItemComponent, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), })); return (

{label}

{description}

); } // 섹션 편집기 interface SectionEditorProps { section: PopSectionData; onUpdate: (updates: Partial) => void; onDelete: () => void; activeDevice: "mobile" | "tablet"; } function SectionEditor({ section, onUpdate, onDelete, activeDevice, }: SectionEditorProps) { const [isGridOpen, setIsGridOpen] = useState(true); const [isMobileOpen, setIsMobileOpen] = useState(false); return (
{/* 섹션 기본 정보 */}
섹션
{/* 라벨 */}
onUpdate({ label: e.target.value })} placeholder="섹션 이름" className="h-8 text-xs" />
{/* 그리드 위치/크기 */} 그리드 위치
onUpdate({ grid: { ...section.grid, col: parseInt(e.target.value) || 1 }, }) } className="h-8 text-xs" />
onUpdate({ grid: { ...section.grid, row: parseInt(e.target.value) || 1 }, }) } className="h-8 text-xs" />
onUpdate({ grid: { ...section.grid, colSpan: parseInt(e.target.value) || 1, }, }) } className="h-8 text-xs" />
onUpdate({ grid: { ...section.grid, rowSpan: parseInt(e.target.value) || 1, }, }) } className="h-8 text-xs" />

캔버스는 24열 그리드입니다

{/* 내부 그리드 설정 */}

섹션 내부에서 컴포넌트를 배치할 그리드 (점으로 표시)

{/* 모바일 전용 설정 */} 모바일 전용 설정
onUpdate({ mobileGrid: { col: section.mobileGrid?.col || 1, row: section.mobileGrid?.row || section.grid.row, colSpan: parseInt(e.target.value) || 4, rowSpan: section.mobileGrid?.rowSpan || section.grid.rowSpan, }, }) } className="h-8 text-xs" />
onUpdate({ mobileGrid: { col: section.mobileGrid?.col || 1, row: section.mobileGrid?.row || section.grid.row, colSpan: section.mobileGrid?.colSpan || 4, rowSpan: parseInt(e.target.value) || 1, }, }) } className="h-8 text-xs" />

모바일에서는 4열 그리드로 자동 변환됩니다

); }