From b3ee2b50e8ad0da804de0c1f3f2e9e28e7c10388 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Mon, 5 Jan 2026 18:41:49 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC?= =?UTF-8?q?=20Select=20=ED=95=84=EB=93=9C=20=EC=A0=80=EC=9E=A5=20=EC=8B=9C?= =?UTF-8?q?=20=EB=9D=BC=EB=B2=A8=EA=B0=92=20=EB=8C=80=EC=8B=A0=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EA=B0=92=20=EC=A0=80=EC=9E=A5=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UniversalFormModalComponent.tsx: 카테고리 옵션 value를 valueLabel에서 valueCode로 변경 - 제어 로직 조건 비교 정상화 및 500 에러 해결 --- .../universal-form-modal/UniversalFormModalComponent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx index 5f087b71..1484d4fd 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx @@ -866,9 +866,9 @@ export function UniversalFormModalComponent({ `/table-categories/${categoryTable}/${categoryColumn}/values` ); if (response.data?.success && response.data?.data) { - // 라벨값을 DB에 저장 (화면에 표시되는 값 그대로 저장) + // 코드값을 DB에 저장하고 라벨값을 화면에 표시 options = response.data.data.map((item: any) => ({ - value: item.valueLabel || item.value_label, + value: item.valueCode || item.value_code, label: item.valueLabel || item.value_label, })); } From 40fd5f905564cddb75935ddab965d381aefba0e5 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Tue, 6 Jan 2026 13:06:28 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=EC=B1=84=EB=B2=88=EA=B7=9C?= =?UTF-8?q?=EC=B9=99=20editable=20=EC=98=B5=EC=85=98=20=EC=88=98=EB=8F=99?= =?UTF-8?q?=20=EB=AA=A8=EB=93=9C=20=EA=B0=90=EC=A7=80=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20=EB=AA=A8=EB=8B=AC=20=EC=98=A4=ED=94=88?= =?UTF-8?q?=20=EC=8B=9C=20=EC=B1=84=EB=B2=88=20=EB=AF=B8=EB=A6=AC=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EC=9B=90=EB=B3=B8=EA=B0=92=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?(numberingOriginalValues)=20handleFieldChange=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EC=9B=90=EB=B3=B8=EA=B0=92=20=EB=B9=84=EA=B5=90=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=88=98=EB=8F=99/=EC=9E=90=EB=8F=99=20=EB=AA=A8?= =?UTF-8?q?=EB=93=9C=20=EC=A0=84=ED=99=98=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=8B=9C=20ruleId=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EC=A0=80=EC=9E=A5=20=EC=8B=9C=20=EC=B1=84?= =?UTF-8?q?=EB=B2=88=20=EC=8A=A4=ED=82=B5=20=EC=9B=90=EB=B3=B8=EA=B0=92=20?= =?UTF-8?q?=EB=B3=B5=EA=B5=AC=20=EC=8B=9C=20ruleId=20=EB=B3=B5=EA=B5=AC?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EC=9E=90=EB=8F=99=20=EB=AA=A8=EB=93=9C=20?= =?UTF-8?q?=EB=B3=B5=EC=9B=90=20handleSave=EC=97=90=EC=84=9C=20=EC=B1=84?= =?UTF-8?q?=EB=B2=88=20=ED=95=A0=EB=8B=B9=20=EC=A1=B0=EA=B1=B4=20=EB=B6=84?= =?UTF-8?q?=EA=B8=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UniversalFormModalComponent.tsx | 95 +++++++++++++++++-- 1 file changed, 85 insertions(+), 10 deletions(-) diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx index 1484d4fd..312c3de6 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx @@ -197,6 +197,10 @@ export function UniversalFormModalComponent({ // 로딩 상태 const [saving, setSaving] = useState(false); + // 채번규칙 원본 값 추적 (수동 모드 감지용) + // key: columnName, value: 자동 생성된 원본 값 + const [numberingOriginalValues, setNumberingOriginalValues] = useState>({}); + // 🆕 수정 모드: 원본 그룹 데이터 (INSERT/UPDATE/DELETE 추적용) const [originalGroupedData, setOriginalGroupedData] = useState([]); const groupedDataInitializedRef = useRef(false); @@ -457,16 +461,23 @@ export function UniversalFormModalComponent({ // generateOnOpen: 미리보기만 표시 (DB 시퀀스 증가 안 함) const response = await previewNumberingCode(field.numberingRule.ruleId); if (response.success && response.data?.generatedCode) { - updatedData[field.columnName] = response.data.generatedCode; + const generatedCode = response.data.generatedCode; + updatedData[field.columnName] = generatedCode; // 저장 시 실제 할당을 위해 ruleId 저장 (TextInput과 동일한 키 형식) const ruleIdKey = `${field.columnName}_numberingRuleId`; updatedData[ruleIdKey] = field.numberingRule.ruleId; + // 원본 채번 값 저장 (수동 모드 감지용) + setNumberingOriginalValues((prev) => ({ + ...prev, + [field.columnName]: generatedCode, + })); + hasChanges = true; numberingGeneratedRef.current = true; // 생성 완료 표시 console.log( - `[채번 미리보기 완료] ${field.columnName} = ${response.data.generatedCode} (저장 시 실제 할당)`, + `[채번 미리보기 완료] ${field.columnName} = ${generatedCode} (저장 시 실제 할당)`, ); console.log(`[채번 규칙 ID 저장] ${ruleIdKey} = ${field.numberingRule.ruleId}`); @@ -694,8 +705,46 @@ export function UniversalFormModalComponent({ // 필드 값 변경 핸들러 const handleFieldChange = useCallback( (columnName: string, value: any) => { + // 채번규칙 필드의 수동 모드 감지 + const originalNumberingValue = numberingOriginalValues[columnName]; + const ruleIdKey = `${columnName}_numberingRuleId`; + + // 해당 필드의 채번규칙 설정 찾기 + let fieldConfig: FormFieldConfig | undefined; + for (const section of config.sections) { + if (section.type === "table" || section.repeatable) continue; + fieldConfig = section.fields?.find((f) => f.columnName === columnName); + if (fieldConfig) break; + // 옵셔널 필드 그룹에서도 찾기 + for (const group of section.optionalFieldGroups || []) { + fieldConfig = group.fields?.find((f) => f.columnName === columnName); + if (fieldConfig) break; + } + if (fieldConfig) break; + } + setFormData((prev) => { const newData = { ...prev, [columnName]: value }; + + // 채번규칙이 활성화된 필드이고, "사용자 수정 가능"이 ON인 경우 + if ( + fieldConfig?.numberingRule?.enabled && + fieldConfig?.numberingRule?.editable && + originalNumberingValue + ) { + // 사용자가 값을 수정했으면 (원본과 다르면) ruleId 제거 → 수동 모드 + if (value !== originalNumberingValue) { + delete newData[ruleIdKey]; + console.log(`[채번 수동 모드] ${columnName}: 사용자가 값 수정 → ruleId 제거`); + } else { + // 원본 값으로 복구하면 ruleId 복구 → 자동 모드 + if (fieldConfig.numberingRule.ruleId) { + newData[ruleIdKey] = fieldConfig.numberingRule.ruleId; + console.log(`[채번 자동 모드] ${columnName}: 원본 값 복구 → ruleId 복구`); + } + } + } + // onChange는 렌더링 외부에서 호출해야 함 (setTimeout 사용) if (onChange) { setTimeout(() => onChange(newData), 0); @@ -703,7 +752,7 @@ export function UniversalFormModalComponent({ return newData; }); }, - [onChange], + [onChange, numberingOriginalValues, config.sections], ); // 반복 섹션 필드 값 변경 핸들러 @@ -975,19 +1024,45 @@ export function UniversalFormModalComponent({ } }); - // 저장 시점 채번규칙 처리 (generateOnSave만 처리) + // 저장 시점 채번규칙 처리 for (const section of config.sections) { // 테이블 타입 섹션은 건너뛰기 if (section.type === "table") continue; for (const field of (section.fields || [])) { - if (field.numberingRule?.enabled && field.numberingRule?.generateOnSave && field.numberingRule?.ruleId) { - const response = await allocateNumberingCode(field.numberingRule.ruleId); - if (response.success && response.data?.generatedCode) { - dataToSave[field.columnName] = response.data.generatedCode; - console.log(`[채번 할당] ${field.columnName} = ${response.data.generatedCode}`); + if (field.numberingRule?.enabled && field.numberingRule?.ruleId) { + const ruleIdKey = `${field.columnName}_numberingRuleId`; + const hasRuleId = dataToSave[ruleIdKey]; // 사용자가 수정하지 않았으면 ruleId 유지됨 + + // 채번 규칙 할당 조건 + const shouldAllocate = + // 1. generateOnSave가 ON인 경우: 항상 저장 시점에 할당 + field.numberingRule.generateOnSave || + // 2. editable이 OFF인 경우: 사용자 입력 무시하고 채번 규칙으로 덮어씌움 + !field.numberingRule.editable || + // 3. editable이 ON이고 사용자가 수정하지 않은 경우 (ruleId 유지됨): 실제 번호 할당 + (field.numberingRule.editable && hasRuleId); + + if (shouldAllocate) { + const response = await allocateNumberingCode(field.numberingRule.ruleId); + if (response.success && response.data?.generatedCode) { + dataToSave[field.columnName] = response.data.generatedCode; + let reason = "(알 수 없음)"; + if (field.numberingRule.generateOnSave) { + reason = "(generateOnSave)"; + } else if (!field.numberingRule.editable) { + reason = "(editable=OFF, 강제 덮어씌움)"; + } else if (hasRuleId) { + reason = "(editable=ON, 사용자 미수정)"; + } + console.log(`[채번 할당] ${field.columnName} = ${response.data.generatedCode} ${reason}`); + } else { + console.error(`[채번 실패] ${field.columnName}:`, response.error); + } } else { - console.error(`[채번 실패] ${field.columnName}:`, response.error); + console.log( + `[채번 스킵] ${field.columnName}: 사용자가 직접 입력한 값 유지 = ${dataToSave[field.columnName]}`, + ); } } }