170 lines
5.3 KiB
TypeScript
170 lines
5.3 KiB
TypeScript
/**
|
|
* 화면 컨텍스트
|
|
* 같은 화면 내의 컴포넌트들이 서로 통신할 수 있도록 합니다.
|
|
*/
|
|
|
|
"use client";
|
|
|
|
import React, { createContext, useContext, useCallback, useRef, useState } from "react";
|
|
import type { DataProvidable, DataReceivable } from "@/types/data-transfer";
|
|
import { logger } from "@/lib/utils/logger";
|
|
import type { SplitPanelPosition } from "@/contexts/SplitPanelContext";
|
|
|
|
interface ScreenContextValue {
|
|
screenId?: number;
|
|
tableName?: string;
|
|
menuObjid?: number; // 메뉴 OBJID (카테고리 값 조회 시 필요)
|
|
splitPanelPosition?: SplitPanelPosition; // 🆕 분할 패널 위치 (left/right)
|
|
|
|
// 🆕 폼 데이터 (RepeaterFieldGroup 등 컴포넌트 데이터 저장)
|
|
formData: Record<string, any>;
|
|
updateFormData: (fieldName: string, value: any) => void;
|
|
|
|
// 컴포넌트 등록
|
|
registerDataProvider: (componentId: string, provider: DataProvidable) => void;
|
|
unregisterDataProvider: (componentId: string) => void;
|
|
registerDataReceiver: (componentId: string, receiver: DataReceivable) => void;
|
|
unregisterDataReceiver: (componentId: string) => void;
|
|
|
|
// 컴포넌트 조회
|
|
getDataProvider: (componentId: string) => DataProvidable | undefined;
|
|
getDataReceiver: (componentId: string) => DataReceivable | undefined;
|
|
|
|
// 모든 컴포넌트 조회
|
|
getAllDataProviders: () => Map<string, DataProvidable>;
|
|
getAllDataReceivers: () => Map<string, DataReceivable>;
|
|
}
|
|
|
|
const ScreenContext = createContext<ScreenContextValue | null>(null);
|
|
|
|
interface ScreenContextProviderProps {
|
|
screenId?: number;
|
|
tableName?: string;
|
|
menuObjid?: number; // 메뉴 OBJID
|
|
splitPanelPosition?: SplitPanelPosition; // 🆕 분할 패널 위치
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
/**
|
|
* 화면 컨텍스트 프로바이더
|
|
*/
|
|
export function ScreenContextProvider({
|
|
screenId,
|
|
tableName,
|
|
menuObjid,
|
|
splitPanelPosition,
|
|
children,
|
|
}: ScreenContextProviderProps) {
|
|
const dataProvidersRef = useRef<Map<string, DataProvidable>>(new Map());
|
|
const dataReceiversRef = useRef<Map<string, DataReceivable>>(new Map());
|
|
|
|
// 🆕 폼 데이터 상태 (RepeaterFieldGroup 등 컴포넌트 데이터 저장)
|
|
const [formData, setFormData] = useState<Record<string, any>>({});
|
|
|
|
// 🆕 폼 데이터 업데이트 함수
|
|
const updateFormData = useCallback((fieldName: string, value: any) => {
|
|
setFormData((prev) => {
|
|
const updated = { ...prev, [fieldName]: value };
|
|
logger.debug("ScreenContext formData 업데이트", {
|
|
fieldName,
|
|
valueType: typeof value,
|
|
isArray: Array.isArray(value),
|
|
});
|
|
return updated;
|
|
});
|
|
}, []);
|
|
|
|
const registerDataProvider = useCallback((componentId: string, provider: DataProvidable) => {
|
|
dataProvidersRef.current.set(componentId, provider);
|
|
logger.debug("데이터 제공자 등록", { componentId, componentType: provider.componentType });
|
|
}, []);
|
|
|
|
const unregisterDataProvider = useCallback((componentId: string) => {
|
|
dataProvidersRef.current.delete(componentId);
|
|
logger.debug("데이터 제공자 해제", { componentId });
|
|
}, []);
|
|
|
|
const registerDataReceiver = useCallback((componentId: string, receiver: DataReceivable) => {
|
|
dataReceiversRef.current.set(componentId, receiver);
|
|
logger.debug("데이터 수신자 등록", { componentId, componentType: receiver.componentType });
|
|
}, []);
|
|
|
|
const unregisterDataReceiver = useCallback((componentId: string) => {
|
|
dataReceiversRef.current.delete(componentId);
|
|
logger.debug("데이터 수신자 해제", { componentId });
|
|
}, []);
|
|
|
|
const getDataProvider = useCallback((componentId: string) => {
|
|
return dataProvidersRef.current.get(componentId);
|
|
}, []);
|
|
|
|
const getDataReceiver = useCallback((componentId: string) => {
|
|
return dataReceiversRef.current.get(componentId);
|
|
}, []);
|
|
|
|
const getAllDataProviders = useCallback(() => {
|
|
return new Map(dataProvidersRef.current);
|
|
}, []);
|
|
|
|
const getAllDataReceivers = useCallback(() => {
|
|
return new Map(dataReceiversRef.current);
|
|
}, []);
|
|
|
|
// 🆕 useMemo로 value 객체 메모이제이션 (무한 루프 방지)
|
|
const value = React.useMemo<ScreenContextValue>(
|
|
() => ({
|
|
screenId,
|
|
tableName,
|
|
menuObjid,
|
|
splitPanelPosition,
|
|
formData,
|
|
updateFormData,
|
|
registerDataProvider,
|
|
unregisterDataProvider,
|
|
registerDataReceiver,
|
|
unregisterDataReceiver,
|
|
getDataProvider,
|
|
getDataReceiver,
|
|
getAllDataProviders,
|
|
getAllDataReceivers,
|
|
}),
|
|
[
|
|
screenId,
|
|
tableName,
|
|
menuObjid,
|
|
splitPanelPosition,
|
|
formData,
|
|
updateFormData,
|
|
registerDataProvider,
|
|
unregisterDataProvider,
|
|
registerDataReceiver,
|
|
unregisterDataReceiver,
|
|
getDataProvider,
|
|
getDataReceiver,
|
|
getAllDataProviders,
|
|
getAllDataReceivers,
|
|
],
|
|
);
|
|
|
|
return <ScreenContext.Provider value={value}>{children}</ScreenContext.Provider>;
|
|
}
|
|
|
|
/**
|
|
* 화면 컨텍스트 훅
|
|
*/
|
|
export function useScreenContext() {
|
|
const context = useContext(ScreenContext);
|
|
if (!context) {
|
|
throw new Error("useScreenContext는 ScreenContextProvider 내부에서만 사용할 수 있습니다.");
|
|
}
|
|
return context;
|
|
}
|
|
|
|
/**
|
|
* 화면 컨텍스트 훅 (선택적)
|
|
* 컨텍스트가 없어도 에러를 발생시키지 않습니다.
|
|
*/
|
|
export function useScreenContextOptional() {
|
|
return useContext(ScreenContext);
|
|
}
|