From 34202be8436f48867fbc9e886257386e6e88ddcb Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 5 Feb 2026 16:31:39 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20NumberingRuleDesigner=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=ED=8C=8C=ED=8A=B8=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 파트 업데이트 및 삭제 시 id 대신 order를 사용하여 로직을 개선하였습니다. - savedRules 상태 업데이트 시 깊은 복사를 통해 객체 참조를 분리하여 클로저 문제를 방지하였습니다. - 규칙 선택 시 깊은 복사를 통해 좌측 목록과 편집 영역의 객체가 공유되지 않도록 하여 데이터 무결성을 강화하였습니다. - 관련 로깅을 추가하여 상태 변경 시 디버깅을 용이하게 하였습니다. --- .../numbering-rule/NumberingRuleDesigner.tsx | 63 ++++++++++++++----- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/frontend/components/numbering-rule/NumberingRuleDesigner.tsx b/frontend/components/numbering-rule/NumberingRuleDesigner.tsx index 2ab42e75..9320f00e 100644 --- a/frontend/components/numbering-rule/NumberingRuleDesigner.tsx +++ b/frontend/components/numbering-rule/NumberingRuleDesigner.tsx @@ -260,22 +260,24 @@ export const NumberingRuleDesigner: React.FC = ({ toast.success(`규칙 ${newPart.order}가 추가되었습니다`); }, [currentRule, maxRules]); - const handleUpdatePart = useCallback((partId: string, updates: Partial) => { + // partOrder 기반으로 파트 업데이트 (id가 null일 수 있으므로 order 사용) + const handleUpdatePart = useCallback((partOrder: number, updates: Partial) => { setCurrentRule((prev) => { if (!prev) return null; return { ...prev, - parts: prev.parts.map((part) => (part.id === partId ? { ...part, ...updates } : part)), + parts: prev.parts.map((part) => (part.order === partOrder ? { ...part, ...updates } : part)), }; }); }, []); - const handleDeletePart = useCallback((partId: string) => { + // partOrder 기반으로 파트 삭제 (id가 null일 수 있으므로 order 사용) + const handleDeletePart = useCallback((partOrder: number) => { setCurrentRule((prev) => { if (!prev) return null; return { ...prev, - parts: prev.parts.filter((part) => part.id !== partId).map((part, index) => ({ ...part, order: index + 1 })), + parts: prev.parts.filter((part) => part.order !== partOrder).map((part, index) => ({ ...part, order: index + 1 })), }; }); @@ -295,8 +297,6 @@ export const NumberingRuleDesigner: React.FC = ({ setLoading(true); try { - const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId); - // 파트별 기본 autoConfig 정의 const defaultAutoConfigs: Record = { sequence: { sequenceLength: 3, startFrom: 1 }, @@ -345,15 +345,30 @@ export const NumberingRuleDesigner: React.FC = ({ const response = await saveNumberingRuleToTest(ruleToSave); if (response.success && response.data) { + // 깊은 복사하여 savedRules와 currentRule이 다른 객체를 참조하도록 함 + const currentData = JSON.parse(JSON.stringify(response.data)) as NumberingRuleConfig; + + // setSavedRules 내부에서 prev를 사용해서 existing 확인 (클로저 문제 방지) setSavedRules((prev) => { - if (existing) { - return prev.map((r) => (r.ruleId === ruleToSave.ruleId ? response.data! : r)); + const savedData = JSON.parse(JSON.stringify(response.data)) as NumberingRuleConfig; + const existsInPrev = prev.some((r) => r.ruleId === ruleToSave.ruleId); + + console.log("🔍 [handleSave] setSavedRules:", { + ruleId: ruleToSave.ruleId, + existsInPrev, + prevCount: prev.length, + }); + + if (existsInPrev) { + // 기존 규칙 업데이트 + return prev.map((r) => (r.ruleId === ruleToSave.ruleId ? savedData : r)); } else { - return [...prev, response.data!]; + // 새 규칙 추가 + return [...prev, savedData]; } }); - setCurrentRule(response.data); + setCurrentRule(currentData); setSelectedRuleId(response.data.ruleId); await onSave?.(response.data); @@ -366,11 +381,27 @@ export const NumberingRuleDesigner: React.FC = ({ } finally { setLoading(false); } - }, [currentRule, savedRules, onSave, currentTableName]); + }, [currentRule, onSave, currentTableName, menuObjid]); const handleSelectRule = useCallback((rule: NumberingRuleConfig) => { + console.log("🔍 [handleSelectRule] 규칙 선택:", { + ruleId: rule.ruleId, + ruleName: rule.ruleName, + partsCount: rule.parts?.length || 0, + parts: rule.parts?.map(p => ({ id: p.id, order: p.order, partType: p.partType })), + }); + setSelectedRuleId(rule.ruleId); - setCurrentRule(rule); + // 깊은 복사하여 객체 참조 분리 (좌측 목록과 편집 영역의 객체가 공유되지 않도록) + const ruleCopy = JSON.parse(JSON.stringify(rule)) as NumberingRuleConfig; + + console.log("🔍 [handleSelectRule] 깊은 복사 후:", { + ruleId: ruleCopy.ruleId, + partsCount: ruleCopy.parts?.length || 0, + parts: ruleCopy.parts?.map(p => ({ id: p.id, order: p.order, partType: p.partType })), + }); + + setCurrentRule(ruleCopy); toast.info(`"${rule.ruleName}" 규칙을 불러왔습니다`); }, []); @@ -595,12 +626,12 @@ export const NumberingRuleDesigner: React.FC = ({ ) : (
- {currentRule.parts.map((part) => ( + {currentRule.parts.map((part, index) => ( handleUpdatePart(part.id, updates)} - onDelete={() => handleDeletePart(part.id)} + onUpdate={(updates) => handleUpdatePart(part.order, updates)} + onDelete={() => handleDeletePart(part.order)} isPreview={isPreview} /> ))}