fix: PopFieldConfig JsonKeySelect - 데이터 없을 때도 Combobox UI 유지
테이블에 데이터가 0건일 때 JsonKeySelect가 plain Input으로 폴백되어 설계 단계에서 Select 박스가 표시되지 않는 문제를 수정한다. [JsonKeySelect 개선] - 항상 Combobox(Popover + Command) UI로 렌더링 - keys 있을 때: 기존과 동일한 자동완성 목록 + 검색 - keys 없을 때: "테이블에 데이터가 없습니다" 안내 + Enter로 직접 입력 확정 - 검색 결과 없을 때도 Enter로 자유 입력 가능 [updateSaveMapping 경합 조건 수정] - onUpdateConfig 두 번 연속 호출 시 React batching으로 첫 번째 호출이 덮어쓰여지는 문제 수정 - syncAndUpdateSaveMappings에 extraPartial 파라미터 추가하여 한 번의 onUpdateConfig 호출로 병합
This commit is contained in:
parent
8ee10e411e
commit
230d35b03a
|
|
@ -356,7 +356,10 @@ function SaveTabContent({
|
|||
};
|
||||
|
||||
const syncAndUpdateSaveMappings = useCallback(
|
||||
(updater?: (prev: PopFieldSaveMapping[]) => PopFieldSaveMapping[]) => {
|
||||
(
|
||||
updater?: (prev: PopFieldSaveMapping[]) => PopFieldSaveMapping[],
|
||||
extraPartial?: Partial<PopFieldConfig>,
|
||||
) => {
|
||||
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<PopFieldSaveMapping>) => {
|
||||
syncAndUpdateSaveMappings((prev) =>
|
||||
prev.map((m) => (m.fieldId === fieldId ? { ...m, ...partial } : m))
|
||||
);
|
||||
let extraPartial: Partial<PopFieldConfig> | 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 (
|
||||
<Input
|
||||
placeholder="키"
|
||||
value={value}
|
||||
onChange={(e) => onValueChange(e.target.value)}
|
||||
onFocus={() => onOpen?.()}
|
||||
className="h-7 w-24 text-xs"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && inputValue.trim()) {
|
||||
e.preventDefault();
|
||||
onValueChange(inputValue.trim());
|
||||
setInputValue("");
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={handleOpenChange}>
|
||||
|
|
@ -2117,33 +2127,51 @@ function JsonKeySelect({
|
|||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-48 p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="키 검색..." className="text-xs" />
|
||||
<Command shouldFilter={keys.length > 0}>
|
||||
<CommandInput
|
||||
placeholder={keys.length > 0 ? "키 검색..." : "키 직접 입력..."}
|
||||
className="text-xs"
|
||||
value={inputValue}
|
||||
onValueChange={setInputValue}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty className="py-2 text-center text-xs">
|
||||
{keys.length === 0 ? "데이터를 불러오는 중..." : "일치하는 키가 없습니다."}
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{keys.map((k) => (
|
||||
<CommandItem
|
||||
key={k}
|
||||
value={k}
|
||||
onSelect={(v) => {
|
||||
onValueChange(v === value ? "" : v);
|
||||
setOpen(false);
|
||||
}}
|
||||
className="text-xs"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
value === k ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{k}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
{keys.length === 0 ? (
|
||||
<div className="px-3 py-2 text-center text-xs text-muted-foreground">
|
||||
{inputValue.trim()
|
||||
? "Enter로 입력 확정"
|
||||
: "테이블에 데이터가 없습니다. 키를 직접 입력하세요."}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<CommandEmpty className="py-2 text-center text-xs">
|
||||
{inputValue.trim()
|
||||
? "Enter로 직접 입력 확정"
|
||||
: "일치하는 키가 없습니다."}
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{keys.map((k) => (
|
||||
<CommandItem
|
||||
key={k}
|
||||
value={k}
|
||||
onSelect={(v) => {
|
||||
onValueChange(v === value ? "" : v);
|
||||
setOpen(false);
|
||||
}}
|
||||
className="text-xs"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-3 w-3",
|
||||
value === k ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{k}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
|
|
|
|||
Loading…
Reference in New Issue