From 4e997ae36b37a8ce3a35d6473de257a934e9c480 Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Fri, 27 Feb 2026 08:48:21 +0900 Subject: [PATCH] feat: Enhance V2Select component with automatic value normalization and update handling - Implemented automatic normalization of legacy plain text values to category codes, improving data consistency. - Added logic to handle comma-separated values, allowing for better processing of complex input formats. - Integrated automatic updates to the onChange handler when the normalized value differs from the original, ensuring accurate data saving. - Updated various select components to utilize the resolved value for consistent behavior across different selection types. --- frontend/components/v2/V2Select.tsx | 52 ++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/frontend/components/v2/V2Select.tsx b/frontend/components/v2/V2Select.tsx index bd81c3ca..a7769227 100644 --- a/frontend/components/v2/V2Select.tsx +++ b/frontend/components/v2/V2Select.tsx @@ -820,6 +820,42 @@ export const V2Select = forwardRef( loadOptions(); }, [source, entityTable, entityValueColumn, entityLabelColumn, codeGroup, table, valueColumn, labelColumn, apiEndpoint, staticOptions, optionsLoaded, hierarchical, parentValue]); + // 레거시 평문값 → 카테고리 코드 자동 정규화 (한글 텍스트로 저장된 데이터 대응) + const resolvedValue = useMemo(() => { + if (!value || options.length === 0) return value; + + const resolveOne = (v: string): string => { + if (options.some(o => o.value === v)) return v; + const trimmed = v.trim(); + const match = options.find(o => { + const cleanLabel = o.label.replace(/^[\s└]+/, '').trim(); + return cleanLabel === trimmed; + }); + return match ? match.value : v; + }; + + if (Array.isArray(value)) { + const resolved = value.map(resolveOne); + return resolved.every((v, i) => v === value[i]) ? value : resolved; + } + + // 콤마 구분 복합값 처리 (e.g., "구매품,판매품,CAT_xxx") + if (typeof value === "string" && value.includes(",")) { + const parts = value.split(","); + const resolved = parts.map(p => resolveOne(p.trim())); + const result = resolved.join(","); + return result === value ? value : result; + } + + return resolveOne(value); + }, [value, options]); + + // 정규화 결과가 원본과 다르면 onChange로 자동 업데이트 (저장 시 코드 변환) + useEffect(() => { + if (!onChange || options.length === 0 || !value || value === resolvedValue) return; + onChange(resolvedValue as string | string[]); + }, [resolvedValue]); // eslint-disable-line react-hooks/exhaustive-deps + // 같은 폼에서 참조 테이블(entityTable) 컬럼을 사용하는 다른 컴포넌트 자동 감지 const autoFillTargets = useMemo(() => { if (source !== "entity" || !entityTable || !allComponents) return []; @@ -945,7 +981,7 @@ export const V2Select = forwardRef( return ( ( return ( handleChangeWithAutoFill(v)} disabled={isDisabled} /> @@ -972,7 +1008,7 @@ export const V2Select = forwardRef( return ( ( return ( ( return ( ( return ( handleChangeWithAutoFill(v)} disabled={isDisabled} /> @@ -1017,7 +1053,7 @@ export const V2Select = forwardRef( return ( ( return (