"use client"; import React, { createContext, useContext, useCallback, useRef, useState } from "react"; import { logger } from "@/lib/utils/logger"; /** * 분할 패널 내 화면 위치 */ export type SplitPanelPosition = "left" | "right"; /** * 데이터 수신자 인터페이스 */ export interface SplitPanelDataReceiver { componentId: string; componentType: string; receiveData: (data: any[], mode: "append" | "replace" | "merge") => Promise; } /** * 분할 패널 컨텍스트 값 */ interface SplitPanelContextValue { // 분할 패널 ID splitPanelId: string; // 좌측/우측 화면 ID leftScreenId: number | null; rightScreenId: number | null; // 데이터 수신자 등록/해제 registerReceiver: (position: SplitPanelPosition, componentId: string, receiver: SplitPanelDataReceiver) => void; unregisterReceiver: (position: SplitPanelPosition, componentId: string) => void; // 반대편 화면으로 데이터 전달 transferToOtherSide: ( fromPosition: SplitPanelPosition, data: any[], targetComponentId?: string, // 특정 컴포넌트 지정 (없으면 첫 번째 수신자) mode?: "append" | "replace" | "merge" ) => Promise<{ success: boolean; message: string }>; // 반대편 화면의 수신자 목록 가져오기 getOtherSideReceivers: (fromPosition: SplitPanelPosition) => SplitPanelDataReceiver[]; // 현재 위치 확인 isInSplitPanel: boolean; // screenId로 위치 찾기 getPositionByScreenId: (screenId: number) => SplitPanelPosition | null; // 🆕 우측에 추가된 항목 ID 관리 (좌측 테이블에서 필터링용) addedItemIds: Set; addItemIds: (ids: string[]) => void; removeItemIds: (ids: string[]) => void; clearItemIds: () => void; } const SplitPanelContext = createContext(null); interface SplitPanelProviderProps { splitPanelId: string; leftScreenId: number | null; rightScreenId: number | null; children: React.ReactNode; } /** * 분할 패널 컨텍스트 프로바이더 */ export function SplitPanelProvider({ splitPanelId, leftScreenId, rightScreenId, children, }: SplitPanelProviderProps) { // 좌측/우측 화면의 데이터 수신자 맵 const leftReceiversRef = useRef>(new Map()); const rightReceiversRef = useRef>(new Map()); // 강제 리렌더링용 상태 const [, forceUpdate] = useState(0); // 🆕 우측에 추가된 항목 ID 상태 const [addedItemIds, setAddedItemIds] = useState>(new Set()); /** * 데이터 수신자 등록 */ const registerReceiver = useCallback( (position: SplitPanelPosition, componentId: string, receiver: SplitPanelDataReceiver) => { const receiversRef = position === "left" ? leftReceiversRef : rightReceiversRef; receiversRef.current.set(componentId, receiver); logger.debug(`[SplitPanelContext] 수신자 등록: ${position} - ${componentId}`, { componentType: receiver.componentType, }); forceUpdate((n) => n + 1); }, [] ); /** * 데이터 수신자 해제 */ const unregisterReceiver = useCallback( (position: SplitPanelPosition, componentId: string) => { const receiversRef = position === "left" ? leftReceiversRef : rightReceiversRef; receiversRef.current.delete(componentId); logger.debug(`[SplitPanelContext] 수신자 해제: ${position} - ${componentId}`); forceUpdate((n) => n + 1); }, [] ); /** * 반대편 화면의 수신자 목록 가져오기 */ const getOtherSideReceivers = useCallback( (fromPosition: SplitPanelPosition): SplitPanelDataReceiver[] => { const receiversRef = fromPosition === "left" ? rightReceiversRef : leftReceiversRef; return Array.from(receiversRef.current.values()); }, [] ); /** * 반대편 화면으로 데이터 전달 */ const transferToOtherSide = useCallback( async ( fromPosition: SplitPanelPosition, data: any[], targetComponentId?: string, mode: "append" | "replace" | "merge" = "append" ): Promise<{ success: boolean; message: string }> => { const toPosition = fromPosition === "left" ? "right" : "left"; const receiversRef = fromPosition === "left" ? rightReceiversRef : leftReceiversRef; logger.info(`[SplitPanelContext] 데이터 전달 시작: ${fromPosition} → ${toPosition}`, { dataCount: data.length, targetComponentId, mode, availableReceivers: Array.from(receiversRef.current.keys()), }); if (receiversRef.current.size === 0) { const message = `${toPosition === "left" ? "좌측" : "우측"} 화면에 데이터를 받을 수 있는 컴포넌트가 없습니다.`; logger.warn(`[SplitPanelContext] ${message}`); return { success: false, message }; } try { let targetReceiver: SplitPanelDataReceiver | undefined; if (targetComponentId) { // 특정 컴포넌트 지정 targetReceiver = receiversRef.current.get(targetComponentId); if (!targetReceiver) { const message = `타겟 컴포넌트 '${targetComponentId}'를 찾을 수 없습니다.`; logger.warn(`[SplitPanelContext] ${message}`); return { success: false, message }; } } else { // 첫 번째 수신자 사용 targetReceiver = receiversRef.current.values().next().value; } if (!targetReceiver) { return { success: false, message: "데이터 수신자를 찾을 수 없습니다." }; } await targetReceiver.receiveData(data, mode); const message = `${data.length}개 항목이 ${toPosition === "left" ? "좌측" : "우측"} 화면으로 전달되었습니다.`; logger.info(`[SplitPanelContext] ${message}`); return { success: true, message }; } catch (error: any) { const message = error.message || "데이터 전달 중 오류가 발생했습니다."; logger.error(`[SplitPanelContext] 데이터 전달 실패`, error); return { success: false, message }; } }, [] ); /** * screenId로 위치 찾기 */ const getPositionByScreenId = useCallback( (screenId: number): SplitPanelPosition | null => { if (leftScreenId === screenId) return "left"; if (rightScreenId === screenId) return "right"; return null; }, [leftScreenId, rightScreenId] ); /** * 🆕 추가된 항목 ID 등록 */ const addItemIds = useCallback((ids: string[]) => { setAddedItemIds((prev) => { const newSet = new Set(prev); ids.forEach((id) => newSet.add(id)); logger.debug(`[SplitPanelContext] 항목 ID 추가: ${ids.length}개`, { ids }); return newSet; }); }, []); /** * 🆕 추가된 항목 ID 제거 */ const removeItemIds = useCallback((ids: string[]) => { setAddedItemIds((prev) => { const newSet = new Set(prev); ids.forEach((id) => newSet.delete(id)); logger.debug(`[SplitPanelContext] 항목 ID 제거: ${ids.length}개`, { ids }); return newSet; }); }, []); /** * 🆕 모든 항목 ID 초기화 */ const clearItemIds = useCallback(() => { setAddedItemIds(new Set()); logger.debug(`[SplitPanelContext] 항목 ID 초기화`); }, []); // 🆕 useMemo로 value 객체 메모이제이션 (무한 루프 방지) const value = React.useMemo(() => ({ splitPanelId, leftScreenId, rightScreenId, registerReceiver, unregisterReceiver, transferToOtherSide, getOtherSideReceivers, isInSplitPanel: true, getPositionByScreenId, addedItemIds, addItemIds, removeItemIds, clearItemIds, }), [ splitPanelId, leftScreenId, rightScreenId, registerReceiver, unregisterReceiver, transferToOtherSide, getOtherSideReceivers, getPositionByScreenId, addedItemIds, addItemIds, removeItemIds, clearItemIds, ]); return ( {children} ); } /** * 분할 패널 컨텍스트 훅 */ export function useSplitPanelContext() { return useContext(SplitPanelContext); } /** * 분할 패널 내부인지 확인하는 훅 */ export function useIsInSplitPanel(): boolean { const context = useContext(SplitPanelContext); return context?.isInSplitPanel ?? false; }