ERP-node/frontend/contexts/ScreenContext.tsx

227 lines
7.1 KiB
TypeScript
Raw Normal View History

2025-11-27 12:08:32 +09:00
/**
*
* .
*/
"use client";
import React, { createContext, useContext, useCallback, useRef, useState } from "react";
import type { DataProvidable, DataReceivable, DataReceiverConfig } from "@/types/data-transfer";
2025-11-27 12:08:32 +09:00
import { logger } from "@/lib/utils/logger";
2025-11-28 14:56:11 +09:00
import type { SplitPanelPosition } from "@/contexts/SplitPanelContext";
2025-11-27 12:08:32 +09:00
/**
*
* ( )
*/
export interface PendingTransfer {
targetComponentId: string;
data: any[];
config: DataReceiverConfig;
timestamp: number;
targetLayerId?: string;
}
2025-11-27 12:08:32 +09:00
interface ScreenContextValue {
screenId?: number;
tableName?: string;
menuObjid?: number;
splitPanelPosition?: SplitPanelPosition;
formData: Record<string, any>;
updateFormData: (fieldName: string, value: any) => void;
2025-11-27 12:08:32 +09:00
// 컴포넌트 등록
registerDataProvider: (componentId: string, provider: DataProvidable) => void;
unregisterDataProvider: (componentId: string) => void;
registerDataReceiver: (componentId: string, receiver: DataReceivable) => void;
unregisterDataReceiver: (componentId: string) => void;
2025-11-27 12:08:32 +09:00
// 컴포넌트 조회
getDataProvider: (componentId: string) => DataProvidable | undefined;
getDataReceiver: (componentId: string) => DataReceivable | undefined;
2025-11-27 12:08:32 +09:00
// 모든 컴포넌트 조회
getAllDataProviders: () => Map<string, DataProvidable>;
getAllDataReceivers: () => Map<string, DataReceivable>;
// 대기 중인 데이터 전달 (레이어 내부 컴포넌트 미마운트 대응)
addPendingTransfer: (transfer: PendingTransfer) => void;
getPendingTransfer: (componentId: string) => PendingTransfer | undefined;
clearPendingTransfer: (componentId: string) => void;
2025-11-27 12:08:32 +09:00
}
const ScreenContext = createContext<ScreenContextValue | null>(null);
interface ScreenContextProviderProps {
screenId?: number;
tableName?: string;
menuObjid?: number; // 메뉴 OBJID
2025-11-28 14:56:11 +09:00
splitPanelPosition?: SplitPanelPosition; // 🆕 분할 패널 위치
2025-11-27 12:08:32 +09:00
children: React.ReactNode;
}
/**
*
*/
export function ScreenContextProvider({
screenId,
tableName,
menuObjid,
splitPanelPosition,
children,
}: ScreenContextProviderProps) {
2025-11-27 12:08:32 +09:00
const dataProvidersRef = useRef<Map<string, DataProvidable>>(new Map());
const dataReceiversRef = useRef<Map<string, DataReceivable>>(new Map());
const pendingTransfersRef = useRef<Map<string, PendingTransfer>>(new Map());
2025-11-27 12:08:32 +09:00
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;
});
}, []);
2025-11-27 12:08:32 +09:00
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 pending = pendingTransfersRef.current.get(componentId);
if (pending) {
logger.info("대기 중인 데이터 전달 자동 수신", {
componentId,
dataCount: pending.data.length,
waitedMs: Date.now() - pending.timestamp,
});
receiver
.receiveData(pending.data, pending.config)
.then(() => {
pendingTransfersRef.current.delete(componentId);
logger.info("대기 데이터 전달 완료", { componentId });
})
.catch((err) => {
logger.error("대기 데이터 전달 실패", { componentId, error: err });
});
}
2025-11-27 12:08:32 +09:00
}, []);
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);
}, []);
const addPendingTransfer = useCallback((transfer: PendingTransfer) => {
pendingTransfersRef.current.set(transfer.targetComponentId, transfer);
logger.info("데이터 전달 대기열 추가", {
targetComponentId: transfer.targetComponentId,
dataCount: transfer.data.length,
targetLayerId: transfer.targetLayerId,
});
}, []);
const getPendingTransfer = useCallback((componentId: string) => {
return pendingTransfersRef.current.get(componentId);
}, []);
const clearPendingTransfer = useCallback((componentId: string) => {
pendingTransfersRef.current.delete(componentId);
logger.debug("대기 데이터 전달 클리어", { componentId });
}, []);
const value = React.useMemo<ScreenContextValue>(
() => ({
screenId,
tableName,
menuObjid,
splitPanelPosition,
formData,
updateFormData,
registerDataProvider,
unregisterDataProvider,
registerDataReceiver,
unregisterDataReceiver,
getDataProvider,
getDataReceiver,
getAllDataProviders,
getAllDataReceivers,
addPendingTransfer,
getPendingTransfer,
clearPendingTransfer,
}),
[
screenId,
tableName,
menuObjid,
splitPanelPosition,
formData,
updateFormData,
registerDataProvider,
unregisterDataProvider,
registerDataReceiver,
unregisterDataReceiver,
getDataProvider,
getDataReceiver,
getAllDataProviders,
getAllDataReceivers,
addPendingTransfer,
getPendingTransfer,
clearPendingTransfer,
],
);
2025-11-27 12:08:32 +09:00
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);
}