From 1503dd87bbc3091e0cad52495067469c6bd7ec22 Mon Sep 17 00:00:00 2001 From: kjs Date: Mon, 1 Dec 2025 10:09:19 +0900 Subject: [PATCH] =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=B6=84=ED=95=A0?= =?UTF-8?q?=ED=8C=A8=EB=84=90=20=EC=88=98=EC=A0=95=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/common/ScreenModal.tsx | 3 - .../RepeaterFieldGroupRenderer.tsx | 77 ++++++++++++- frontend/lib/utils/buttonActions.ts | 104 ++++++++++++++++++ 3 files changed, 177 insertions(+), 7 deletions(-) diff --git a/frontend/components/common/ScreenModal.tsx b/frontend/components/common/ScreenModal.tsx index 3e0f1a61..70567c99 100644 --- a/frontend/components/common/ScreenModal.tsx +++ b/frontend/components/common/ScreenModal.tsx @@ -177,7 +177,6 @@ export const ScreenModal: React.FC = ({ className }) => { }); setScreenData(null); setFormData({}); - setSelectedData([]); // πŸ†• μ„ νƒλœ 데이터 μ΄ˆκΈ°ν™” setContinuousMode(false); localStorage.setItem("screenModal_continuousMode", "false"); // localStorage에 μ €μž₯ console.log("πŸ”„ 연속 λͺ¨λ“œ μ΄ˆκΈ°ν™”: false"); @@ -637,8 +636,6 @@ export const ScreenModal: React.FC = ({ className }) => { userId={userId} userName={userName} companyCode={user?.companyCode} - // πŸ†• μ„ νƒλœ 데이터 전달 (RepeatScreenModal λ“±μ—μ„œ μ‚¬μš©) - groupedData={selectedData.length > 0 ? selectedData : undefined} /> ); })} diff --git a/frontend/lib/registry/components/repeater-field-group/RepeaterFieldGroupRenderer.tsx b/frontend/lib/registry/components/repeater-field-group/RepeaterFieldGroupRenderer.tsx index a4dbd157..3b7fc339 100644 --- a/frontend/lib/registry/components/repeater-field-group/RepeaterFieldGroupRenderer.tsx +++ b/frontend/lib/registry/components/repeater-field-group/RepeaterFieldGroupRenderer.tsx @@ -244,11 +244,46 @@ const RepeaterFieldGroupComponent: React.FC = (props) => const currentValue = parsedValueRef.current; // modeκ°€ "replace"인 경우 κΈ°μ‘΄ 데이터 λŒ€μ²΄, κ·Έ μ™Έμ—λŠ” μΆ”κ°€ - // πŸ†• ν•„ν„°λ§λœ 데이터 μ‚¬μš© const mode = typeof mappingRulesOrMode === "string" ? mappingRulesOrMode : "append"; - const newItems = mode === "replace" ? filteredData : [...currentValue, ...filteredData]; + + let newItems: any[]; + let addedCount = 0; + let duplicateCount = 0; + + if (mode === "replace") { + newItems = filteredData; + addedCount = filteredData.length; + } else { + // πŸ†• 쀑볡 체크: id λ˜λŠ” 고유 μ‹λ³„μžλ₯Ό κΈ°μ€€μœΌλ‘œ 이미 μ‘΄μž¬ν•˜λŠ” ν•­λͺ© μ œμ™Έ + const existingIds = new Set( + currentValue + .map((item: any) => item.id || item.po_item_id || item.item_id) + .filter(Boolean) + ); + + const uniqueNewItems = filteredData.filter((item: any) => { + const itemId = item.id || item.po_item_id || item.item_id; + if (itemId && existingIds.has(itemId)) { + duplicateCount++; + return false; // 쀑볡 ν•­λͺ© μ œμ™Έ + } + return true; + }); + + newItems = [...currentValue, ...uniqueNewItems]; + addedCount = uniqueNewItems.length; + } - console.log("πŸ“₯ [RepeaterFieldGroup] μ΅œμ’… 데이터:", { currentValue, newItems, mode }); + console.log("πŸ“₯ [RepeaterFieldGroup] μ΅œμ’… 데이터:", { + currentValue, + newItems, + mode, + addedCount, + duplicateCount, + }); + + // πŸ†• groupedData μƒνƒœλ„ 직접 μ—…λ°μ΄νŠΈ (UI μ¦‰μ‹œ 반영) + setGroupedData(newItems); // JSON λ¬Έμžμ—΄λ‘œ λ³€ν™˜ν•˜μ—¬ μ €μž₯ const jsonValue = JSON.stringify(newItems); @@ -268,7 +303,16 @@ const RepeaterFieldGroupComponent: React.FC = (props) => onChangeRef.current(jsonValue); } - toast.success(`${filteredData.length}개 ν•­λͺ©μ΄ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€`); + // κ²°κ³Ό λ©”μ‹œμ§€ ν‘œμ‹œ + if (addedCount > 0) { + if (duplicateCount > 0) { + toast.success(`${addedCount}개 ν•­λͺ©μ΄ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€ (${duplicateCount}개 쀑볡 μ œμ™Έ)`); + } else { + toast.success(`${addedCount}개 ν•­λͺ©μ΄ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€`); + } + } else if (duplicateCount > 0) { + toast.warning(`${duplicateCount}개 ν•­λͺ©μ΄ 이미 μΆ”κ°€λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€`); + } }, []); // DataReceivable μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„ @@ -311,6 +355,31 @@ const RepeaterFieldGroupComponent: React.FC = (props) => } }, [splitPanelContext, screenContext?.splitPanelPosition, component.id, dataReceiver]); + // πŸ†• μ „μ—­ 이벀트 λ¦¬μŠ€λ„ˆ (splitPanelDataTransfer) + useEffect(() => { + const handleSplitPanelDataTransfer = (event: CustomEvent) => { + const { data, mode, mappingRules } = event.detail; + + console.log("πŸ“₯ [RepeaterFieldGroup] splitPanelDataTransfer 이벀트 μˆ˜μ‹ :", { + dataCount: data?.length, + mode, + componentId: component.id, + }); + + // 우츑 νŒ¨λ„μ˜ 리피터 ν•„λ“œ 그룹만 데이터λ₯Ό μˆ˜μ‹  + const splitPanelPosition = screenContext?.splitPanelPosition; + if (splitPanelPosition === "right" && data && data.length > 0) { + handleReceiveData(data, mappingRules || mode || "append"); + } + }; + + window.addEventListener("splitPanelDataTransfer", handleSplitPanelDataTransfer as EventListener); + + return () => { + window.removeEventListener("splitPanelDataTransfer", handleSplitPanelDataTransfer as EventListener); + }; + }, [screenContext?.splitPanelPosition, handleReceiveData, component.id]); + return ( { + try { + console.log("πŸ“€ [handleTransferData] 데이터 전달 μ‹œμž‘:", { config, context }); + + // μ„ νƒλœ ν–‰ 데이터 확인 + const selectedRows = context.selectedRowsData || context.flowSelectedData || []; + + if (!selectedRows || selectedRows.length === 0) { + toast.error("전달할 데이터λ₯Ό μ„ νƒν•΄μ£Όμ„Έμš”."); + return false; + } + + console.log("πŸ“€ [handleTransferData] μ„ νƒλœ 데이터:", selectedRows); + + // dataTransfer μ„€μ • 확인 + const dataTransfer = config.dataTransfer; + + if (!dataTransfer) { + // dataTransfer 섀정이 μ—†μœΌλ©΄ κΈ°λ³Έ λ™μž‘: μ „μ—­ 이벀트둜 데이터 전달 + console.log("πŸ“€ [handleTransferData] dataTransfer μ„€μ • μ—†μŒ - μ „μ—­ 이벀트 λ°œμƒ"); + + const transferEvent = new CustomEvent("splitPanelDataTransfer", { + detail: { + data: selectedRows, + mode: "append", + sourcePosition: "left", + }, + }); + window.dispatchEvent(transferEvent); + + toast.success(`${selectedRows.length}개 ν•­λͺ©μ΄ μ „λ‹¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€.`); + return true; + } + + // dataTransfer 섀정이 μžˆλŠ” 경우 + const { targetType, targetComponentId, targetScreenId, mappingRules, receiveMode } = dataTransfer; + + if (targetType === "component" && targetComponentId) { + // 같은 ν™”λ©΄ λ‚΄ μ»΄ν¬λ„ŒνŠΈλ‘œ 전달 + console.log("πŸ“€ [handleTransferData] μ»΄ν¬λ„ŒνŠΈλ‘œ 전달:", targetComponentId); + + const transferEvent = new CustomEvent("componentDataTransfer", { + detail: { + targetComponentId, + data: selectedRows, + mappingRules, + mode: receiveMode || "append", + }, + }); + window.dispatchEvent(transferEvent); + + toast.success(`${selectedRows.length}개 ν•­λͺ©μ΄ μ „λ‹¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€.`); + return true; + } else if (targetType === "screen" && targetScreenId) { + // λ‹€λ₯Έ ν™”λ©΄μœΌλ‘œ 전달 (λΆ„ν•  νŒ¨λ„ λ“±) + console.log("πŸ“€ [handleTransferData] ν™”λ©΄μœΌλ‘œ 전달:", targetScreenId); + + const transferEvent = new CustomEvent("screenDataTransfer", { + detail: { + targetScreenId, + data: selectedRows, + mappingRules, + mode: receiveMode || "append", + }, + }); + window.dispatchEvent(transferEvent); + + toast.success(`${selectedRows.length}개 ν•­λͺ©μ΄ μ „λ‹¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€.`); + return true; + } else { + // κΈ°λ³Έ: λΆ„ν•  νŒ¨λ„ 데이터 전달 이벀트 + console.log("πŸ“€ [handleTransferData] κΈ°λ³Έ λΆ„ν•  νŒ¨λ„ 전달"); + + const transferEvent = new CustomEvent("splitPanelDataTransfer", { + detail: { + data: selectedRows, + mappingRules, + mode: receiveMode || "append", + sourcePosition: "left", + }, + }); + window.dispatchEvent(transferEvent); + + toast.success(`${selectedRows.length}개 ν•­λͺ©μ΄ μ „λ‹¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€.`); + return true; + } + } catch (error: any) { + console.error("❌ 데이터 전달 μ‹€νŒ¨:", error); + toast.error(error.message || "데이터 전달 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."); + return false; + } + } + /** * 폼 데이터 μœ νš¨μ„± 검사 */ @@ -3293,4 +3392,9 @@ export const DEFAULT_BUTTON_ACTIONS: Record