From 230d35b03aaeb83c7198b5dcbb164c65e17c6e2c Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Mon, 16 Mar 2026 16:24:27 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20PopFieldConfig=20JsonKeySelect=20-=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=86=EC=9D=84=20=EB=95=8C?= =?UTF-8?q?=EB=8F=84=20Combobox=20UI=20=EC=9C=A0=EC=A7=80=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=EC=97=90=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EA=B0=80=200=EA=B1=B4=EC=9D=BC=20=EB=95=8C=20JsonKeySelect?= =?UTF-8?q?=EA=B0=80=20plain=20Input=EC=9C=BC=EB=A1=9C=20=ED=8F=B4?= =?UTF-8?q?=EB=B0=B1=EB=90=98=EC=96=B4=20=EC=84=A4=EA=B3=84=20=EB=8B=A8?= =?UTF-8?q?=EA=B3=84=EC=97=90=EC=84=9C=20Select=20=EB=B0=95=EC=8A=A4?= =?UTF-8?q?=EA=B0=80=20=ED=91=9C=EC=8B=9C=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=EB=A5=BC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=9C=EB=8B=A4.=20[JsonKeySelect=20=EA=B0=9C=EC=84=A0]=20-?= =?UTF-8?q?=20=ED=95=AD=EC=83=81=20Combobox(Popover=20+=20Command)=20UI?= =?UTF-8?q?=EB=A1=9C=20=EB=A0=8C=EB=8D=94=EB=A7=81=20-=20keys=20=EC=9E=88?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C:=20=EA=B8=B0=EC=A1=B4=EA=B3=BC=20?= =?UTF-8?q?=EB=8F=99=EC=9D=BC=ED=95=9C=20=EC=9E=90=EB=8F=99=EC=99=84?= =?UTF-8?q?=EC=84=B1=20=EB=AA=A9=EB=A1=9D=20+=20=EA=B2=80=EC=83=89=20-=20k?= =?UTF-8?q?eys=20=EC=97=86=EC=9D=84=20=EB=95=8C:=20"=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=EC=97=90=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EA=B0=80=20?= =?UTF-8?q?=EC=97=86=EC=8A=B5=EB=8B=88=EB=8B=A4"=20=EC=95=88=EB=82=B4=20+?= =?UTF-8?q?=20Enter=EB=A1=9C=20=EC=A7=81=EC=A0=91=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=ED=99=95=EC=A0=95=20-=20=EA=B2=80=EC=83=89=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=97=86=EC=9D=84=20=EB=95=8C=EB=8F=84=20Enter?= =?UTF-8?q?=EB=A1=9C=20=EC=9E=90=EC=9C=A0=20=EC=9E=85=EB=A0=A5=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=20[updateSaveMapping=20=EA=B2=BD=ED=95=A9=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EC=88=98=EC=A0=95]=20-=20onUpdateConfig=20?= =?UTF-8?q?=EB=91=90=20=EB=B2=88=20=EC=97=B0=EC=86=8D=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=20=EC=8B=9C=20React=20batching=EC=9C=BC=EB=A1=9C=20=EC=B2=AB?= =?UTF-8?q?=20=EB=B2=88=EC=A7=B8=20=ED=98=B8=EC=B6=9C=EC=9D=B4=20=20=20?= =?UTF-8?q?=EB=8D=AE=EC=96=B4=EC=93=B0=EC=97=AC=EC=A7=80=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95=20-=20syncAndUpdateSave?= =?UTF-8?q?Mappings=EC=97=90=20extraPartial=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80=ED=95=98=EC=97=AC=20=20=20?= =?UTF-8?q?=ED=95=9C=20=EB=B2=88=EC=9D=98=20onUpdateConfig=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=EB=A1=9C=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pop-field/PopFieldConfig.tsx | 128 +++++++++++------- 1 file changed, 78 insertions(+), 50 deletions(-) diff --git a/frontend/lib/registry/pop-components/pop-field/PopFieldConfig.tsx b/frontend/lib/registry/pop-components/pop-field/PopFieldConfig.tsx index 8e0f8ad1..965bccee 100644 --- a/frontend/lib/registry/pop-components/pop-field/PopFieldConfig.tsx +++ b/frontend/lib/registry/pop-components/pop-field/PopFieldConfig.tsx @@ -356,7 +356,10 @@ function SaveTabContent({ }; const syncAndUpdateSaveMappings = useCallback( - (updater?: (prev: PopFieldSaveMapping[]) => PopFieldSaveMapping[]) => { + ( + updater?: (prev: PopFieldSaveMapping[]) => PopFieldSaveMapping[], + extraPartial?: Partial, + ) => { const fieldIds = new Set(allFields.map(({ field }) => field.id)); const prev = saveMappings.filter((m) => fieldIds.has(m.fieldId)); const next = updater ? updater(prev) : prev; @@ -381,6 +384,7 @@ function SaveTabContent({ tableName: saveTableName, fieldMappings: merged, }, + ...extraPartial, }); } }, @@ -395,22 +399,27 @@ function SaveTabContent({ const updateSaveMapping = useCallback( (fieldId: string, partial: Partial) => { - syncAndUpdateSaveMappings((prev) => - prev.map((m) => (m.fieldId === fieldId ? { ...m, ...partial } : m)) - ); + let extraPartial: Partial | undefined; if (partial.targetColumn !== undefined) { const newFieldName = partial.targetColumn || ""; - const sections = cfg.sections.map((s) => ({ - ...s, - fields: (s.fields ?? []).map((f) => - f.id === fieldId ? { ...f, fieldName: newFieldName } : f - ), - })); - onUpdateConfig({ sections }); + extraPartial = { + sections: cfg.sections.map((s) => ({ + ...s, + fields: (s.fields ?? []).map((f) => + f.id === fieldId ? { ...f, fieldName: newFieldName } : f + ), + })), + }; } + + syncAndUpdateSaveMappings( + (prev) => + prev.map((m) => (m.fieldId === fieldId ? { ...m, ...partial } : m)), + extraPartial, + ); }, - [syncAndUpdateSaveMappings, cfg, onUpdateConfig] + [syncAndUpdateSaveMappings, cfg.sections] ); // --- 숨은 필드 매핑 로직 --- @@ -2086,23 +2095,24 @@ function JsonKeySelect({ onOpen?: () => void; }) { const [open, setOpen] = useState(false); + const [inputValue, setInputValue] = useState(""); const handleOpenChange = (nextOpen: boolean) => { setOpen(nextOpen); - if (nextOpen) onOpen?.(); + if (nextOpen) { + onOpen?.(); + setInputValue(""); + } }; - if (keys.length === 0 && !value) { - return ( - onValueChange(e.target.value)} - onFocus={() => onOpen?.()} - className="h-7 w-24 text-xs" - /> - ); - } + const handleInputKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && inputValue.trim()) { + e.preventDefault(); + onValueChange(inputValue.trim()); + setInputValue(""); + setOpen(false); + } + }; return ( @@ -2117,33 +2127,51 @@ function JsonKeySelect({ - - + 0}> + 0 ? "키 검색..." : "키 직접 입력..."} + className="text-xs" + value={inputValue} + onValueChange={setInputValue} + onKeyDown={handleInputKeyDown} + /> - - {keys.length === 0 ? "데이터를 불러오는 중..." : "일치하는 키가 없습니다."} - - - {keys.map((k) => ( - { - onValueChange(v === value ? "" : v); - setOpen(false); - }} - className="text-xs" - > - - {k} - - ))} - + {keys.length === 0 ? ( +
+ {inputValue.trim() + ? "Enter로 입력 확정" + : "테이블에 데이터가 없습니다. 키를 직접 입력하세요."} +
+ ) : ( + <> + + {inputValue.trim() + ? "Enter로 직접 입력 확정" + : "일치하는 키가 없습니다."} + + + {keys.map((k) => ( + { + onValueChange(v === value ? "" : v); + setOpen(false); + }} + className="text-xs" + > + + {k} + + ))} + + + )}