feat: Implement layer activation and data transfer enhancements

- Added support for force-activated layer IDs in ScreenViewPage, allowing layers to be activated based on data events.
- Introduced ScreenContextProvider in ScreenModal and EditModal to manage screen-specific data and context.
- Enhanced V2Repeater to register as a DataReceiver, enabling automatic data handling and integration with ScreenContext.
- Improved ButtonPrimaryComponent to support automatic target component discovery and layer activation for data transfers.
- Updated various components to streamline data handling and improve user experience during data transfers and layer management.
This commit is contained in:
kjs
2026-02-25 17:40:17 +09:00
parent 55cbd8778a
commit 863ec614f4
12 changed files with 487 additions and 81 deletions
+64 -7
View File
@@ -6,17 +6,28 @@
"use client";
import React, { createContext, useContext, useCallback, useRef, useState } from "react";
import type { DataProvidable, DataReceivable } from "@/types/data-transfer";
import type { DataProvidable, DataReceivable, DataReceiverConfig } from "@/types/data-transfer";
import { logger } from "@/lib/utils/logger";
import type { SplitPanelPosition } from "@/contexts/SplitPanelContext";
/**
* 대기 중인 데이터 전달 항목
* 타겟 컴포넌트가 아직 마운트되지 않은 경우 (조건부 레이어 등) 버퍼에 저장
*/
export interface PendingTransfer {
targetComponentId: string;
data: any[];
config: DataReceiverConfig;
timestamp: number;
targetLayerId?: string;
}
interface ScreenContextValue {
screenId?: number;
tableName?: string;
menuObjid?: number; // 메뉴 OBJID (카테고리 값 조회 시 필요)
splitPanelPosition?: SplitPanelPosition; // 🆕 분할 패널 위치 (left/right)
menuObjid?: number;
splitPanelPosition?: SplitPanelPosition;
// 🆕 폼 데이터 (RepeaterFieldGroup 등 컴포넌트 데이터 저장)
formData: Record<string, any>;
updateFormData: (fieldName: string, value: any) => void;
@@ -33,6 +44,11 @@ interface ScreenContextValue {
// 모든 컴포넌트 조회
getAllDataProviders: () => Map<string, DataProvidable>;
getAllDataReceivers: () => Map<string, DataReceivable>;
// 대기 중인 데이터 전달 (레이어 내부 컴포넌트 미마운트 대응)
addPendingTransfer: (transfer: PendingTransfer) => void;
getPendingTransfer: (componentId: string) => PendingTransfer | undefined;
clearPendingTransfer: (componentId: string) => void;
}
const ScreenContext = createContext<ScreenContextValue | null>(null);
@@ -57,11 +73,10 @@ export function ScreenContextProvider({
}: ScreenContextProviderProps) {
const dataProvidersRef = useRef<Map<string, DataProvidable>>(new Map());
const dataReceiversRef = useRef<Map<string, DataReceivable>>(new Map());
const pendingTransfersRef = useRef<Map<string, PendingTransfer>>(new Map());
// 🆕 폼 데이터 상태 (RepeaterFieldGroup 등 컴포넌트 데이터 저장)
const [formData, setFormData] = useState<Record<string, any>>({});
// 🆕 폼 데이터 업데이트 함수
const updateFormData = useCallback((fieldName: string, value: any) => {
setFormData((prev) => {
const updated = { ...prev, [fieldName]: value };
@@ -87,6 +102,25 @@ export function ScreenContextProvider({
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 });
});
}
}, []);
const unregisterDataReceiver = useCallback((componentId: string) => {
@@ -110,7 +144,24 @@ export function ScreenContextProvider({
return new Map(dataReceiversRef.current);
}, []);
// 🆕 useMemo로 value 객체 메모이제이션 (무한 루프 방지)
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,
@@ -127,6 +178,9 @@ export function ScreenContextProvider({
getDataReceiver,
getAllDataProviders,
getAllDataReceivers,
addPendingTransfer,
getPendingTransfer,
clearPendingTransfer,
}),
[
screenId,
@@ -143,6 +197,9 @@ export function ScreenContextProvider({
getDataReceiver,
getAllDataProviders,
getAllDataReceivers,
addPendingTransfer,
getPendingTransfer,
clearPendingTransfer,
],
);