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:
@@ -724,17 +724,28 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
|
||||
try {
|
||||
// 1. 소스 컴포넌트에서 데이터 가져오기
|
||||
let sourceProvider = screenContext.getDataProvider(dataTransferConfig.sourceComponentId);
|
||||
let sourceProvider: import("@/types/data-transfer").DataProvidable | undefined;
|
||||
|
||||
// 🆕 소스 컴포넌트를 찾을 수 없으면, 현재 화면에서 테이블 리스트 자동 탐색
|
||||
// (조건부 컨테이너의 다른 섹션으로 전환했을 때 이전 컴포넌트 ID가 남아있는 경우 대응)
|
||||
const isAutoSource =
|
||||
!dataTransferConfig.sourceComponentId || dataTransferConfig.sourceComponentId === "__auto__";
|
||||
|
||||
if (!isAutoSource) {
|
||||
sourceProvider = screenContext.getDataProvider(dataTransferConfig.sourceComponentId);
|
||||
}
|
||||
|
||||
// 자동 탐색 모드이거나, 지정된 소스를 찾지 못한 경우
|
||||
// 현재 마운트된 DataProvider 중에서 table-list를 자동 탐색
|
||||
if (!sourceProvider) {
|
||||
console.log(`⚠️ [ButtonPrimary] 지정된 소스 컴포넌트를 찾을 수 없음: ${dataTransferConfig.sourceComponentId}`);
|
||||
console.log("🔍 [ButtonPrimary] 현재 화면에서 DataProvider 자동 탐색...");
|
||||
if (!isAutoSource) {
|
||||
console.log(
|
||||
`⚠️ [ButtonPrimary] 지정된 소스 컴포넌트를 찾을 수 없음: ${dataTransferConfig.sourceComponentId}`,
|
||||
);
|
||||
}
|
||||
console.log("🔍 [ButtonPrimary] 현재 활성 DataProvider 자동 탐색...");
|
||||
|
||||
const allProviders = screenContext.getAllDataProviders();
|
||||
|
||||
// 테이블 리스트 우선 탐색
|
||||
// table-list 우선 탐색
|
||||
for (const [id, provider] of allProviders) {
|
||||
if (provider.componentType === "table-list") {
|
||||
sourceProvider = provider;
|
||||
@@ -743,7 +754,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
// 테이블 리스트가 없으면 첫 번째 DataProvider 사용
|
||||
// table-list가 없으면 첫 번째 DataProvider 사용
|
||||
if (!sourceProvider && allProviders.size > 0) {
|
||||
const firstEntry = allProviders.entries().next().value;
|
||||
if (firstEntry) {
|
||||
@@ -784,15 +795,12 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
const additionalValues = additionalProvider.getSelectedData();
|
||||
|
||||
if (additionalValues && additionalValues.length > 0) {
|
||||
// 첫 번째 값 사용 (조건부 컨테이너는 항상 1개)
|
||||
const firstValue = additionalValues[0];
|
||||
|
||||
// fieldName이 지정되어 있으면 그 필드만 추출
|
||||
if (additionalSource.fieldName) {
|
||||
additionalData[additionalSource.fieldName] =
|
||||
firstValue[additionalSource.fieldName] || firstValue.condition || firstValue;
|
||||
} else {
|
||||
// fieldName이 없으면 전체 객체 병합
|
||||
additionalData = { ...additionalData, ...firstValue };
|
||||
}
|
||||
|
||||
@@ -802,6 +810,25 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
value: additionalData[additionalSource.fieldName || "all"],
|
||||
});
|
||||
}
|
||||
} else if (formData) {
|
||||
// DataProvider로 등록되지 않은 컴포넌트(v2-select 등)는 formData에서 값을 가져옴
|
||||
const comp = allComponents?.find((c: any) => c.id === additionalSource.componentId);
|
||||
const columnName =
|
||||
comp?.columnName ||
|
||||
comp?.componentConfig?.columnName ||
|
||||
comp?.overrides?.columnName;
|
||||
|
||||
if (columnName && formData[columnName] !== undefined && formData[columnName] !== "") {
|
||||
const targetField = additionalSource.fieldName || columnName;
|
||||
additionalData[targetField] = formData[columnName];
|
||||
|
||||
console.log("📦 추가 데이터 수집 (formData 폴백):", {
|
||||
sourceId: additionalSource.componentId,
|
||||
columnName,
|
||||
targetField,
|
||||
value: formData[columnName],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -881,33 +908,96 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||
};
|
||||
});
|
||||
|
||||
// 5. targetType / targetComponentId 기본값 및 자동 탐색
|
||||
const effectiveTargetType = dataTransferConfig.targetType || "component";
|
||||
let effectiveTargetComponentId = dataTransferConfig.targetComponentId;
|
||||
|
||||
// targetComponentId가 없으면 현재 화면에서 DataReceiver 자동 탐색
|
||||
if (effectiveTargetType === "component" && !effectiveTargetComponentId) {
|
||||
console.log("🔍 [ButtonPrimary] 타겟 컴포넌트 자동 탐색...");
|
||||
const allReceivers = screenContext.getAllDataReceivers();
|
||||
|
||||
// repeater 계열 우선 탐색
|
||||
for (const [id, receiver] of allReceivers) {
|
||||
if (
|
||||
receiver.componentType === "repeater-field-group" ||
|
||||
receiver.componentType === "v2-repeater" ||
|
||||
receiver.componentType === "repeater"
|
||||
) {
|
||||
effectiveTargetComponentId = id;
|
||||
console.log(`✅ [ButtonPrimary] 리피터 자동 발견: ${id} (${receiver.componentType})`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// repeater가 없으면 소스가 아닌 첫 번째 DataReceiver 사용
|
||||
if (!effectiveTargetComponentId) {
|
||||
for (const [id, receiver] of allReceivers) {
|
||||
if (receiver.componentType === "table-list" || receiver.componentType === "data-table") {
|
||||
effectiveTargetComponentId = id;
|
||||
console.log(`✅ [ButtonPrimary] DataReceiver 자동 발견: ${id} (${receiver.componentType})`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!effectiveTargetComponentId) {
|
||||
toast.error("데이터를 받을 수 있는 타겟 컴포넌트를 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("📦 데이터 전달:", {
|
||||
sourceData,
|
||||
mappedData,
|
||||
targetType: dataTransferConfig.targetType,
|
||||
targetComponentId: dataTransferConfig.targetComponentId,
|
||||
targetType: effectiveTargetType,
|
||||
targetComponentId: effectiveTargetComponentId,
|
||||
targetScreenId: dataTransferConfig.targetScreenId,
|
||||
});
|
||||
|
||||
// 5. 타겟으로 데이터 전달
|
||||
if (dataTransferConfig.targetType === "component") {
|
||||
// 같은 화면의 컴포넌트로 전달
|
||||
const targetReceiver = screenContext.getDataReceiver(dataTransferConfig.targetComponentId);
|
||||
// 6. 타겟으로 데이터 전달
|
||||
if (effectiveTargetType === "component") {
|
||||
const targetReceiver = screenContext.getDataReceiver(effectiveTargetComponentId);
|
||||
|
||||
const receiverConfig = {
|
||||
targetComponentId: effectiveTargetComponentId,
|
||||
targetComponentType: targetReceiver?.componentType || ("table" as const),
|
||||
mode: dataTransferConfig.mode || ("append" as const),
|
||||
mappingRules: dataTransferConfig.mappingRules || [],
|
||||
};
|
||||
|
||||
if (!targetReceiver) {
|
||||
toast.error(`타겟 컴포넌트를 찾을 수 없습니다: ${dataTransferConfig.targetComponentId}`);
|
||||
// 타겟이 아직 마운트되지 않은 경우 (조건부 레이어 등)
|
||||
// 버퍼에 저장하고 레이어 활성화 요청
|
||||
console.log(
|
||||
`⏳ [ButtonPrimary] 타겟 컴포넌트 미마운트, 대기열에 추가: ${effectiveTargetComponentId}`,
|
||||
);
|
||||
|
||||
screenContext.addPendingTransfer({
|
||||
targetComponentId: effectiveTargetComponentId,
|
||||
data: mappedData,
|
||||
config: receiverConfig,
|
||||
timestamp: Date.now(),
|
||||
targetLayerId: dataTransferConfig.targetLayerId,
|
||||
});
|
||||
|
||||
// 레이어 활성화 이벤트 발행 (page.tsx에서 수신)
|
||||
const activateEvent = new CustomEvent("activateLayerForComponent", {
|
||||
detail: {
|
||||
componentId: effectiveTargetComponentId,
|
||||
targetLayerId: dataTransferConfig.targetLayerId,
|
||||
},
|
||||
});
|
||||
window.dispatchEvent(activateEvent);
|
||||
|
||||
toast.info(`타겟 레이어를 활성화하고 데이터 전달을 준비합니다...`);
|
||||
return;
|
||||
}
|
||||
|
||||
await targetReceiver.receiveData(mappedData, {
|
||||
targetComponentId: dataTransferConfig.targetComponentId,
|
||||
targetComponentType: targetReceiver.componentType,
|
||||
mode: dataTransferConfig.mode || "append",
|
||||
mappingRules: dataTransferConfig.mappingRules || [],
|
||||
});
|
||||
await targetReceiver.receiveData(mappedData, receiverConfig);
|
||||
|
||||
toast.success(`${sourceData.length}개 항목이 전달되었습니다.`);
|
||||
} else if (dataTransferConfig.targetType === "splitPanel") {
|
||||
} else if (effectiveTargetType === "splitPanel") {
|
||||
// 🆕 분할 패널의 반대편 화면으로 전달
|
||||
if (!splitPanelContext) {
|
||||
toast.error("분할 패널 컨텍스트를 찾을 수 없습니다. 이 버튼이 분할 패널 내부에 있는지 확인하세요.");
|
||||
|
||||
@@ -97,6 +97,7 @@ const V2RepeaterRenderer: React.FC<V2RepeaterRendererProps> = ({
|
||||
return (
|
||||
<V2Repeater
|
||||
config={config}
|
||||
componentId={component?.id}
|
||||
parentId={resolvedParentId}
|
||||
data={Array.isArray(data) ? data : undefined}
|
||||
onDataChange={onDataChange}
|
||||
|
||||
Reference in New Issue
Block a user