"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, Columns2, RotateCcw } 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 { PopLayoutData, PopSectionData, PopComponentData, PopComponentType, createEmptyPopLayout, createPopSection, createPopComponent, GridPosition, } from "./types/pop-layout"; import { screenApi } from "@/lib/api/screen"; import { ScreenDefinition } from "@/types/screen"; // 디바이스 타입 type DeviceType = "mobile" | "tablet"; interface PopDesignerProps { selectedScreen: ScreenDefinition; onBackToList: () => void; onScreenUpdate?: (updatedScreen: Partial) => void; } export default function PopDesigner({ selectedScreen, onBackToList, onScreenUpdate, }: PopDesignerProps) { // 레이아웃 상태 const [layout, setLayout] = useState(createEmptyPopLayout()); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [hasChanges, setHasChanges] = useState(false); // 디바이스 프리뷰 상태 const [activeDevice, setActiveDevice] = useState("tablet"); const [showBothDevices, setShowBothDevices] = useState(false); const [isLandscape, setIsLandscape] = useState(true); // 선택된 섹션/컴포넌트 const [selectedSectionId, setSelectedSectionId] = useState(null); const [selectedComponentId, setSelectedComponentId] = useState(null); // 선택된 섹션 객체 const selectedSection = selectedSectionId ? layout.sections.find((s) => s.id === selectedSectionId) || null : null; // 레이아웃 로드 // API는 이미 언래핑된 layout_data를 반환하므로 response 자체가 레이아웃 데이터 useEffect(() => { const loadLayout = async () => { if (!selectedScreen?.screenId) return; setIsLoading(true); try { // API가 layout_data 내용을 직접 반환함 (언래핑된 상태) const loadedLayout = await screenApi.getLayoutPop(selectedScreen.screenId); if (loadedLayout && loadedLayout.version === "pop-1.0") { // 유효한 POP 레이아웃 setLayout(loadedLayout as PopLayoutData); console.log("POP 레이아웃 로드 성공:", loadedLayout.sections?.length || 0, "개 섹션"); } else if (loadedLayout && loadedLayout.sections) { // 버전 태그 없지만 sections 구조가 있으면 사용 console.warn("버전 태그 없음, sections 구조 감지하여 사용"); setLayout({ ...createEmptyPopLayout(), ...loadedLayout, version: "pop-1.0", } as PopLayoutData); } else { // 레이아웃 없음 - 빈 레이아웃 생성 console.log("POP 레이아웃 없음, 빈 레이아웃 생성"); setLayout(createEmptyPopLayout()); } } catch (error) { console.error("레이아웃 로드 실패:", error); toast.error("레이아웃을 불러오는데 실패했습니다"); setLayout(createEmptyPopLayout()); } 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]); // 섹션 드롭 (팔레트 → 캔버스) const handleDropSection = useCallback((gridPosition: GridPosition) => { const newId = `section-${Date.now()}`; const newSection = createPopSection(newId, gridPosition); setLayout((prev) => ({ ...prev, sections: [...prev.sections, newSection], })); setSelectedSectionId(newId); setHasChanges(true); }, []); // 컴포넌트 드롭 (팔레트 → 섹션) const handleDropComponent = useCallback( (sectionId: string, type: PopComponentType, gridPosition: GridPosition) => { const newId = `${type}-${Date.now()}`; const newComponent = createPopComponent(newId, type, gridPosition); setLayout((prev) => ({ ...prev, sections: prev.sections.map((s) => s.id === sectionId ? { ...s, components: [...s.components, newComponent] } : s ), })); setSelectedComponentId(newId); setHasChanges(true); }, [] ); // 섹션 업데이트 const handleUpdateSection = useCallback( (id: string, updates: Partial) => { setLayout((prev) => ({ ...prev, sections: prev.sections.map((s) => s.id === id ? { ...s, ...updates } : s ), })); setHasChanges(true); }, [] ); // 섹션 삭제 const handleDeleteSection = useCallback((id: string) => { setLayout((prev) => ({ ...prev, sections: prev.sections.filter((s) => s.id !== id), })); setSelectedSectionId(null); setHasChanges(true); }, []); // 레이아웃 변경 (드래그/리사이즈) const handleLayoutChange = useCallback((sections: PopSectionData[]) => { setLayout((prev) => ({ ...prev, sections, })); setHasChanges(true); }, []); // 컴포넌트 업데이트 const handleUpdateComponent = useCallback( (sectionId: string, componentId: string, updates: Partial) => { setLayout((prev) => ({ ...prev, sections: prev.sections.map((s) => s.id === sectionId ? { ...s, components: s.components.map((c) => c.id === componentId ? { ...c, ...updates } : c ), } : s ), })); setHasChanges(true); }, [] ); // 컴포넌트 삭제 const handleDeleteComponent = useCallback( (sectionId: string, componentId: string) => { setLayout((prev) => ({ ...prev, sections: prev.sections.map((s) => s.id === sectionId ? { ...s, components: s.components.filter((c) => c.id !== componentId) } : s ), })); setSelectedComponentId(null); setHasChanges(true); }, [] ); // 뒤로가기 const handleBack = useCallback(() => { if (hasChanges) { if (confirm("저장하지 않은 변경사항이 있습니다. 나가시겠습니까?")) { onBackToList(); } } else { onBackToList(); } }, [hasChanges, onBackToList]); if (isLoading) { return (
로딩 중...
); } return (
{/* 툴바 */}
{/* 왼쪽: 뒤로가기 + 화면명 */}
{selectedScreen?.screenName || "POP 화면"} {hasChanges && ( *변경됨 )}
{/* 중앙: 디바이스 전환 */}
setActiveDevice(v as DeviceType)} > 태블릿 모바일
{/* 오른쪽: 저장 */}
{/* 메인 영역: 리사이즈 가능한 패널 */} {/* 왼쪽: 패널 (컴포넌트/편집 탭) */} {/* 오른쪽: 캔버스 */}
); }