import React, { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Badge } from "@/components/ui/badge"; import { Plus, Trash2, GripVertical, Layers, SplitSquareVertical, ChevronDown, ChevronRight, Zap, Loader2, } from "lucide-react"; import { cn } from "@/lib/utils"; import { screenApi } from "@/lib/api/screen"; import { convertV2ToLegacy } from "@/lib/utils/layoutV2Converter"; import { toast } from "sonner"; import { LayerConditionPanel } from "./LayerConditionPanel"; import { ComponentData, LayerCondition, DisplayRegion } from "@/types/screen-management"; // DB 레이어 타입 interface DBLayer { layer_id: number; layer_name: string; condition_config: any; component_count: number; updated_at: string; } interface LayerManagerPanelProps { screenId: number | null; activeLayerId: number; // 현재 활성 레이어 ID (DB layer_id) onLayerChange: (layerId: number) => void; // 레이어 전환 components?: ComponentData[]; // 현재 활성 레이어의 컴포넌트 (폴백용) } export const LayerManagerPanel: React.FC = ({ screenId, activeLayerId, onLayerChange, components = [], }) => { const [layers, setLayers] = useState([]); const [isLoading, setIsLoading] = useState(false); const [conditionOpenLayerId, setConditionOpenLayerId] = useState(null); // 기본 레이어(layer_id=1)의 컴포넌트 (조건 설정 시 트리거 대상) const [baseLayerComponents, setBaseLayerComponents] = useState([]); // 레이어 목록 로드 const loadLayers = useCallback(async () => { if (!screenId) return; setIsLoading(true); try { const data = await screenApi.getScreenLayers(screenId); setLayers(data); } catch (error) { console.error("레이어 목록 로드 실패:", error); } finally { setIsLoading(false); } }, [screenId]); // 기본 레이어 컴포넌트 로드 (조건 설정 패널에서 트리거 컴포넌트 선택용) const loadBaseLayerComponents = useCallback(async () => { if (!screenId) return; try { const data = await screenApi.getLayerLayout(screenId, 1); if (data && data.components) { const legacy = convertV2ToLegacy(data); if (legacy) { setBaseLayerComponents(legacy.components as ComponentData[]); return; } } setBaseLayerComponents([]); } catch { // 기본 레이어가 없거나 로드 실패 시 현재 컴포넌트 사용 setBaseLayerComponents(components); } }, [screenId, components]); useEffect(() => { loadLayers(); }, [loadLayers]); // 조건 설정 패널이 열릴 때 기본 레이어 컴포넌트 로드 useEffect(() => { if (conditionOpenLayerId !== null) { loadBaseLayerComponents(); } }, [conditionOpenLayerId, loadBaseLayerComponents]); // 새 레이어 추가 const handleAddLayer = useCallback(async () => { if (!screenId) return; // 다음 layer_id 계산 const maxLayerId = layers.length > 0 ? Math.max(...layers.map((l) => l.layer_id)) : 0; const newLayerId = maxLayerId + 1; try { // 빈 레이아웃으로 새 레이어 저장 await screenApi.saveLayoutV2(screenId, { version: "2.0", components: [], layerId: newLayerId, layerName: `조건부 레이어 ${newLayerId}`, }); toast.success(`조건부 레이어 ${newLayerId}가 생성되었습니다.`); await loadLayers(); // 새 레이어로 전환 onLayerChange(newLayerId); } catch (error) { console.error("레이어 추가 실패:", error); toast.error("레이어 추가에 실패했습니다."); } }, [screenId, layers, loadLayers, onLayerChange]); // 레이어 삭제 const handleDeleteLayer = useCallback(async (layerId: number) => { if (!screenId || layerId === 1) return; try { await screenApi.deleteLayer(screenId, layerId); toast.success("레이어가 삭제되었습니다."); await loadLayers(); // 기본 레이어로 전환 if (activeLayerId === layerId) { onLayerChange(1); } } catch (error) { console.error("레이어 삭제 실패:", error); toast.error("레이어 삭제에 실패했습니다."); } }, [screenId, activeLayerId, loadLayers, onLayerChange]); // 조건 업데이트 const handleUpdateCondition = useCallback(async (layerId: number, condition: LayerCondition | undefined) => { if (!screenId) return; try { await screenApi.updateLayerCondition(screenId, layerId, condition || null); toast.success("조건이 저장되었습니다."); await loadLayers(); } catch (error) { console.error("조건 업데이트 실패:", error); toast.error("조건 저장에 실패했습니다."); } }, [screenId, loadLayers]); return (
{/* 헤더 */}

레이어

{layers.length}
{/* 레이어 목록 */}
{isLoading ? (
로딩 중...
) : layers.length === 0 ? (

레이어를 로드하는 중...

먼저 화면을 저장하면 기본 레이어가 생성됩니다.

) : ( layers .slice() .reverse() .map((layer) => { const isActive = activeLayerId === layer.layer_id; const isBase = layer.layer_id === 1; const hasCondition = !!layer.condition_config; const isConditionOpen = conditionOpenLayerId === layer.layer_id; return (
onLayerChange(layer.layer_id)} // 조건부 레이어를 캔버스로 드래그 (영역 배치용) draggable={!isBase} onDragStart={(e) => { if (isBase) return; e.dataTransfer.setData("application/json", JSON.stringify({ type: "layer-region", layerId: layer.layer_id, layerName: layer.layer_name, })); e.dataTransfer.effectAllowed = "copy"; }} >
{isBase ? : } {layer.layer_name}
{isBase ? "기본" : "조건부"} {layer.component_count}개 컴포넌트 {hasCondition && ( 조건 )}
{/* 액션 버튼 */}
{!isBase && ( )} {!isBase && ( )}
{/* 조건 설정 패널 */} {!isBase && isConditionOpen && (
handleUpdateCondition(layer.layer_id, condition)} onUpdateDisplayRegion={() => {}} onClose={() => setConditionOpenLayerId(null)} />
)}
); }) )}
{/* 도움말 */}

레이어를 클릭하여 편집 | 조건부 레이어를 캔버스에 드래그하여 영역 설정

); };