"use client"; import { useState, useCallback, useEffect } from "react"; import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; import { ArrowLeft, Save, Smartphone, Tablet } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; import { toast } from "sonner"; import { PopCanvas } from "./PopCanvas"; import { PopPanel } from "./panels/PopPanel"; import { PopLayoutDataV3, PopLayoutModeKey, PopComponentType, GridPosition, PopComponentDefinition, createEmptyPopLayoutV3, ensureV3Layout, addComponentToV3Layout, removeComponentFromV3Layout, updateComponentPositionInModeV3, isV3Layout, } from "./types/pop-layout"; import { screenApi } from "@/lib/api/screen"; import { ScreenDefinition } from "@/types/screen"; // ======================================== // 디바이스 타입 // ======================================== type DeviceType = "mobile" | "tablet"; // ======================================== // Props // ======================================== interface PopDesignerProps { selectedScreen: ScreenDefinition; onBackToList: () => void; onScreenUpdate?: (updatedScreen: Partial) => void; } // ======================================== // 메인 컴포넌트 (v3: 섹션 없이 컴포넌트 직접 배치) // ======================================== export default function PopDesigner({ selectedScreen, onBackToList, onScreenUpdate, }: PopDesignerProps) { // ======================================== // 레이아웃 상태 (v3) // ======================================== const [layout, setLayout] = useState(createEmptyPopLayoutV3()); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [hasChanges, setHasChanges] = useState(false); // ======================================== // 디바이스/모드 상태 // ======================================== const [activeDevice, setActiveDevice] = useState("tablet"); const [activeModeKey, setActiveModeKey] = useState("tablet_landscape"); // ======================================== // 선택 상태 (v3: 섹션 없음, 컴포넌트만) // ======================================== const [selectedComponentId, setSelectedComponentId] = useState(null); // 선택된 컴포넌트 정의 const selectedComponent: PopComponentDefinition | null = selectedComponentId ? layout.components[selectedComponentId] || null : null; // ======================================== // 레이아웃 로드 // ======================================== useEffect(() => { const loadLayout = async () => { if (!selectedScreen?.screenId) return; setIsLoading(true); try { const loadedLayout = await screenApi.getLayoutPop(selectedScreen.screenId); if (loadedLayout) { // v1, v2, v3 → v3로 변환 const v3Layout = ensureV3Layout(loadedLayout); setLayout(v3Layout); const componentCount = Object.keys(v3Layout.components).length; console.log(`POP v3 레이아웃 로드 성공: ${componentCount}개 컴포넌트`); if (!isV3Layout(loadedLayout)) { console.log("v1/v2 → v3 자동 마이그레이션 완료"); } } else { console.log("POP 레이아웃 없음, 빈 v3 레이아웃 생성"); setLayout(createEmptyPopLayoutV3()); } } catch (error) { console.error("레이아웃 로드 실패:", error); toast.error("레이아웃을 불러오는데 실패했습니다"); setLayout(createEmptyPopLayoutV3()); } finally { setIsLoading(false); } }; loadLayout(); }, [selectedScreen?.screenId]); // ======================================== // 저장 // ======================================== const handleSave = useCallback(async () => { if (!selectedScreen?.screenId) return; setIsSaving(true); try { await screenApi.saveLayoutPop(selectedScreen.screenId, layout); toast.success("저장되었습니다"); setHasChanges(false); } catch (error) { console.error("저장 실패:", error); toast.error("저장에 실패했습니다"); } finally { setIsSaving(false); } }, [selectedScreen?.screenId, layout]); // ======================================== // 컴포넌트 추가 (4모드 동기화) // ======================================== const handleDropComponent = useCallback( (type: PopComponentType, gridPosition: GridPosition) => { const newId = `${type}-${Date.now()}`; setLayout((prev) => addComponentToV3Layout(prev, newId, type, gridPosition)); setSelectedComponentId(newId); setHasChanges(true); }, [] ); // ======================================== // 컴포넌트 정의 업데이트 // ======================================== const handleUpdateComponentDefinition = useCallback( (componentId: string, updates: Partial) => { setLayout((prev) => ({ ...prev, components: { ...prev.components, [componentId]: { ...prev.components[componentId], ...updates, }, }, })); setHasChanges(true); }, [] ); // ======================================== // 컴포넌트 위치 업데이트 (현재 모드만) // ======================================== const handleUpdateComponentPosition = useCallback( (componentId: string, position: GridPosition, modeKey?: PopLayoutModeKey) => { const targetMode = modeKey || activeModeKey; setLayout((prev) => updateComponentPositionInModeV3(prev, targetMode, componentId, position)); setHasChanges(true); }, [activeModeKey] ); // ======================================== // 컴포넌트 삭제 (4모드 동기화) // ======================================== const handleDeleteComponent = useCallback((componentId: string) => { setLayout((prev) => removeComponentFromV3Layout(prev, componentId)); setSelectedComponentId(null); setHasChanges(true); }, []); // ======================================== // 디바이스 전환 // ======================================== const handleDeviceChange = useCallback((device: DeviceType) => { setActiveDevice(device); setActiveModeKey(device === "tablet" ? "tablet_landscape" : "mobile_landscape"); }, []); // ======================================== // 모드 키 전환 // ======================================== const handleModeKeyChange = useCallback((modeKey: PopLayoutModeKey) => { setActiveModeKey(modeKey); }, []); // ======================================== // 뒤로가기 // ======================================== const handleBack = useCallback(() => { if (hasChanges) { if (confirm("저장하지 않은 변경사항이 있습니다. 나가시겠습니까?")) { onBackToList(); } } else { onBackToList(); } }, [hasChanges, onBackToList]); // ======================================== // Delete 키 삭제 기능 // ======================================== useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { const target = e.target as HTMLElement; if ( target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable ) { return; } if (e.key === "Delete" || e.key === "Backspace") { e.preventDefault(); if (selectedComponentId) { handleDeleteComponent(selectedComponentId); } } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [selectedComponentId, handleDeleteComponent]); // ======================================== // 로딩 상태 // ======================================== if (isLoading) { return (
로딩 중...
); } // ======================================== // 렌더링 // ======================================== return (
{/* 툴바 */}
{/* 왼쪽: 뒤로가기 + 화면명 */}
{selectedScreen?.screenName || "POP 화면"} {hasChanges && ( *변경됨 )}
{/* 중앙: 디바이스 전환 */}
handleDeviceChange(v as DeviceType)} > 태블릿 모바일
{/* 오른쪽: 저장 */}
{/* 메인 영역 */} {/* 왼쪽: 패널 */} {/* 오른쪽: 캔버스 */}
); }