ERP-node/frontend/contexts/LayerContext.tsx

338 lines
11 KiB
TypeScript

import React, { createContext, useContext, useState, useCallback, ReactNode, useMemo } from "react";
import { LayerDefinition, LayerType, ComponentData } from "@/types/screen-management";
import { v4 as uuidv4 } from "uuid";
interface LayerContextType {
// 레이어 상태
layers: LayerDefinition[];
activeLayerId: string | null;
activeLayer: LayerDefinition | null;
// 레이어 관리
setLayers: (layers: LayerDefinition[]) => void;
setActiveLayerId: (id: string | null) => void;
addLayer: (type: LayerType, name?: string) => void;
removeLayer: (id: string) => void;
updateLayer: (id: string, updates: Partial<LayerDefinition>) => void;
moveLayer: (dragIndex: number, hoverIndex: number) => void;
toggleLayerVisibility: (id: string) => void;
toggleLayerLock: (id: string) => void;
getLayerById: (id: string) => LayerDefinition | undefined;
// 컴포넌트 관리 (레이어별)
addComponentToLayer: (layerId: string, component: ComponentData) => void;
removeComponentFromLayer: (layerId: string, componentId: string) => void;
updateComponentInLayer: (layerId: string, componentId: string, updates: Partial<ComponentData>) => void;
moveComponentToLayer: (componentId: string, fromLayerId: string, toLayerId: string) => void;
// 컴포넌트 조회
getAllComponents: () => ComponentData[];
getComponentById: (componentId: string) => { component: ComponentData; layerId: string } | null;
getComponentsInActiveLayer: () => ComponentData[];
// 레이어 가시성 (런타임용)
runtimeVisibleLayers: string[];
setRuntimeVisibleLayers: React.Dispatch<React.SetStateAction<string[]>>;
showLayer: (layerId: string) => void;
hideLayer: (layerId: string) => void;
toggleLayerRuntime: (layerId: string) => void;
}
const LayerContext = createContext<LayerContextType | undefined>(undefined);
export const useLayer = () => {
const context = useContext(LayerContext);
if (!context) {
throw new Error("useLayer must be used within a LayerProvider");
}
return context;
};
// LayerProvider가 없을 때 사용할 기본 컨텍스트 (선택적 사용)
export const useLayerOptional = () => {
return useContext(LayerContext);
};
interface LayerProviderProps {
children: ReactNode;
initialLayers?: LayerDefinition[];
onLayersChange?: (layers: LayerDefinition[]) => void;
onActiveLayerChange?: (activeLayerId: string | null) => void; // 🆕 활성 레이어 변경 콜백
}
// 기본 레이어 생성 헬퍼
export const createDefaultLayer = (components?: ComponentData[]): LayerDefinition => ({
id: "default-layer",
name: "기본 레이어",
type: "base",
zIndex: 0,
isVisible: true,
isLocked: false,
components: components || [],
});
export const LayerProvider: React.FC<LayerProviderProps> = ({
children,
initialLayers = [],
onLayersChange,
onActiveLayerChange,
}) => {
// 초기 레이어가 없으면 기본 레이어 생성
const effectiveInitialLayers = initialLayers.length > 0
? initialLayers
: [createDefaultLayer()];
const [layers, setLayersState] = useState<LayerDefinition[]>(effectiveInitialLayers);
const [activeLayerIdState, setActiveLayerIdState] = useState<string | null>(
effectiveInitialLayers.length > 0 ? effectiveInitialLayers[0].id : null,
);
// 🆕 활성 레이어 변경 시 콜백 호출
const setActiveLayerId = useCallback((id: string | null) => {
setActiveLayerIdState(id);
onActiveLayerChange?.(id);
}, [onActiveLayerChange]);
// 활성 레이어 ID (내부 상태 사용)
const activeLayerId = activeLayerIdState;
// 런타임 가시성 상태 (편집기에서의 isVisible과 별개)
const [runtimeVisibleLayers, setRuntimeVisibleLayers] = useState<string[]>(
effectiveInitialLayers.filter(l => l.isVisible).map(l => l.id)
);
// 레이어 변경 시 콜백 호출
const setLayers = useCallback((newLayers: LayerDefinition[]) => {
setLayersState(newLayers);
onLayersChange?.(newLayers);
}, [onLayersChange]);
// 활성 레이어 계산
const activeLayer = useMemo(() => {
return layers.find(l => l.id === activeLayerId) || null;
}, [layers, activeLayerId]);
const addLayer = useCallback(
(type: LayerType, name?: string) => {
const newLayer: LayerDefinition = {
id: uuidv4(),
name: name || `새 레이어 ${layers.length + 1}`,
type,
zIndex: layers.length,
isVisible: true,
isLocked: false,
components: [],
// 모달/드로어 기본 설정
...(type === "modal" || type === "drawer" ? {
overlayConfig: {
backdrop: true,
closeOnBackdropClick: true,
width: type === "drawer" ? "320px" : "600px",
height: type === "drawer" ? "100%" : "auto",
},
} : {}),
};
setLayers([...layers, newLayer]);
setActiveLayerId(newLayer.id);
// 새 레이어는 런타임에서도 기본적으로 표시
setRuntimeVisibleLayers(prev => [...prev, newLayer.id]);
},
[layers, setLayers],
);
const removeLayer = useCallback(
(id: string) => {
// 기본 레이어는 삭제 불가
const layer = layers.find(l => l.id === id);
if (layer?.type === "base") {
console.warn("기본 레이어는 삭제할 수 없습니다.");
return;
}
const filtered = layers.filter((layer) => layer.id !== id);
setLayers(filtered);
if (activeLayerId === id) {
setActiveLayerId(filtered.length > 0 ? filtered[0].id : null);
}
setRuntimeVisibleLayers(prev => prev.filter(lid => lid !== id));
},
[layers, activeLayerId, setLayers],
);
const updateLayer = useCallback((id: string, updates: Partial<LayerDefinition>) => {
setLayers(layers.map((layer) => (layer.id === id ? { ...layer, ...updates } : layer)));
}, [layers, setLayers]);
const moveLayer = useCallback((dragIndex: number, hoverIndex: number) => {
const newLayers = [...layers];
const [removed] = newLayers.splice(dragIndex, 1);
newLayers.splice(hoverIndex, 0, removed);
// Update zIndex based on new order
setLayers(newLayers.map((layer, index) => ({ ...layer, zIndex: index })));
}, [layers, setLayers]);
const toggleLayerVisibility = useCallback((id: string) => {
setLayers(layers.map((layer) => (layer.id === id ? { ...layer, isVisible: !layer.isVisible } : layer)));
}, [layers, setLayers]);
const toggleLayerLock = useCallback((id: string) => {
setLayers(layers.map((layer) => (layer.id === id ? { ...layer, isLocked: !layer.isLocked } : layer)));
}, [layers, setLayers]);
const getLayerById = useCallback(
(id: string) => {
return layers.find((layer) => layer.id === id);
},
[layers],
);
// ===== 컴포넌트 관리 함수 =====
const addComponentToLayer = useCallback((layerId: string, component: ComponentData) => {
setLayers(layers.map(layer => {
if (layer.id === layerId) {
return {
...layer,
components: [...layer.components, component],
};
}
return layer;
}));
}, [layers, setLayers]);
const removeComponentFromLayer = useCallback((layerId: string, componentId: string) => {
setLayers(layers.map(layer => {
if (layer.id === layerId) {
return {
...layer,
components: layer.components.filter(c => c.id !== componentId),
};
}
return layer;
}));
}, [layers, setLayers]);
const updateComponentInLayer = useCallback((layerId: string, componentId: string, updates: Partial<ComponentData>) => {
setLayers(layers.map(layer => {
if (layer.id === layerId) {
return {
...layer,
components: layer.components.map(c =>
c.id === componentId ? { ...c, ...updates } as ComponentData : c
),
};
}
return layer;
}));
}, [layers, setLayers]);
const moveComponentToLayer = useCallback((componentId: string, fromLayerId: string, toLayerId: string) => {
if (fromLayerId === toLayerId) return;
const fromLayer = layers.find(l => l.id === fromLayerId);
const component = fromLayer?.components.find(c => c.id === componentId);
if (!component) return;
setLayers(layers.map(layer => {
if (layer.id === fromLayerId) {
return {
...layer,
components: layer.components.filter(c => c.id !== componentId),
};
}
if (layer.id === toLayerId) {
return {
...layer,
components: [...layer.components, component],
};
}
return layer;
}));
}, [layers, setLayers]);
// ===== 컴포넌트 조회 함수 =====
const getAllComponents = useCallback((): ComponentData[] => {
return layers.flatMap(layer => layer.components);
}, [layers]);
const getComponentById = useCallback((componentId: string): { component: ComponentData; layerId: string } | null => {
for (const layer of layers) {
const component = layer.components.find(c => c.id === componentId);
if (component) {
return { component, layerId: layer.id };
}
}
return null;
}, [layers]);
const getComponentsInActiveLayer = useCallback((): ComponentData[] => {
const layer = layers.find(l => l.id === activeLayerId);
return layer?.components || [];
}, [layers, activeLayerId]);
// ===== 런타임 레이어 가시성 관리 =====
const showLayer = useCallback((layerId: string) => {
setRuntimeVisibleLayers(prev => [...new Set([...prev, layerId])]);
}, []);
const hideLayer = useCallback((layerId: string) => {
setRuntimeVisibleLayers(prev => prev.filter(id => id !== layerId));
}, []);
const toggleLayerRuntime = useCallback((layerId: string) => {
setRuntimeVisibleLayers(prev =>
prev.includes(layerId)
? prev.filter(id => id !== layerId)
: [...prev, layerId]
);
}, []);
return (
<LayerContext.Provider
value={{
// 레이어 상태
layers,
activeLayerId,
activeLayer,
// 레이어 관리
setLayers,
setActiveLayerId,
addLayer,
removeLayer,
updateLayer,
moveLayer,
toggleLayerVisibility,
toggleLayerLock,
getLayerById,
// 컴포넌트 관리
addComponentToLayer,
removeComponentFromLayer,
updateComponentInLayer,
moveComponentToLayer,
// 컴포넌트 조회
getAllComponents,
getComponentById,
getComponentsInActiveLayer,
// 런타임 가시성
runtimeVisibleLayers,
setRuntimeVisibleLayers,
showLayer,
hideLayer,
toggleLayerRuntime,
}}
>
{children}
</LayerContext.Provider>
);
};