From 5a94afc1d5cc35ef96b0981523ad44d819a475e0 Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Thu, 1 Jan 2026 02:29:53 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20=ED=9A=8C=EC=82=AC?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/layout/AppLayout.tsx | 25 +++--------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/frontend/components/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx index b28e4d01..236071ac 100644 --- a/frontend/components/layout/AppLayout.tsx +++ b/frontend/components/layout/AppLayout.tsx @@ -370,32 +370,13 @@ function AppLayoutInner({ children }: AppLayoutProps) { }; // 모드 전환 핸들러 - const handleModeSwitch = async () => { + const handleModeSwitch = () => { if (isAdminMode) { // 관리자 → 사용자 모드: 선택한 회사 유지 router.push("/main"); } else { - // 사용자 → 관리자 모드: WACE로 복귀 필요 (SUPER_ADMIN만) - if ((user as ExtendedUserInfo)?.userType === "SUPER_ADMIN") { - const currentCompanyCode = (user as ExtendedUserInfo)?.companyCode; - - // 이미 WACE("*")가 아니면 WACE로 전환 후 관리자 페이지로 이동 - if (currentCompanyCode !== "*") { - const result = await switchCompany("*"); - if (result.success) { - // 페이지 새로고침 (관리자 페이지로 이동) - window.location.href = "/admin"; - } else { - toast.error("WACE로 전환 실패"); - } - } else { - // 이미 WACE면 바로 관리자 페이지로 이동 - router.push("/admin"); - } - } else { - // 일반 관리자는 바로 관리자 페이지로 이동 - router.push("/admin"); - } + // 사용자 → 관리자 모드: 선택한 회사 유지 (회사 전환 없음) + router.push("/admin"); } }; From 4ad58ba942e6e868aeb08f76efa1a652eb20e8c1 Mon Sep 17 00:00:00 2001 From: SeongHyun Kim Date: Sun, 4 Jan 2026 17:41:07 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=ED=8F=BC=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=B1=84=EB=B2=88=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=9E=84=EB=B2=A0=EB=94=94?= =?UTF-8?q?=EB=93=9C=20=EC=8A=A4=ED=81=AC=EB=A6=B0=20=EB=A0=8C=EB=8D=94?= =?UTF-8?q?=EB=A7=81=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UniversalFormModalComponent에 폼 초기화 시 채번 규칙을 가져와 적용하는 generateNumberingValues 구현 채번 생성 중복 호출 방지를 위한 useRef 로직 추가 데이터 업데이트 시 불필요한 리마운트 및 포커스 분실을 방지하기 위해 EmbeddedScreen 컴포넌트 key에서 formDataVersion 제거 --- .../screen-embedding/EmbeddedScreen.tsx | 4 +- .../UniversalFormModalComponent.tsx | 182 ++++++++++-------- 2 files changed, 109 insertions(+), 77 deletions(-) diff --git a/frontend/components/screen-embedding/EmbeddedScreen.tsx b/frontend/components/screen-embedding/EmbeddedScreen.tsx index 12496310..3bfb7a77 100644 --- a/frontend/components/screen-embedding/EmbeddedScreen.tsx +++ b/frontend/components/screen-embedding/EmbeddedScreen.tsx @@ -415,8 +415,10 @@ export const EmbeddedScreen = forwardRef +
{ + console.log("[UniversalFormModal] useEffect 시작", { + initialData, + hasInitialized: hasInitialized.current, + lastInitializedId: lastInitializedId.current, + }); + // initialData에서 ID 값 추출 (id, ID, objid 등) const currentId = initialData?.id || initialData?.ID || initialData?.objid; const currentIdString = currentId !== undefined ? String(currentId) : undefined; @@ -229,9 +235,20 @@ export function UniversalFormModalComponent({ if (hasInitialized.current && lastInitializedId.current === currentIdString) { // 생성 모드에서 데이터가 새로 전달된 경우는 재초기화 필요 if (!createModeDataHash || capturedInitialData.current) { + console.log("[UniversalFormModal] 초기화 스킵 - 이미 초기화됨"); + // 🆕 채번 플래그가 true인데 formData에 값이 없으면 재생성 필요 + // (컴포넌트 remount로 인해 state가 초기화된 경우) return; } } + + // 🆕 컴포넌트 remount 감지: hasInitialized가 true인데 formData가 비어있으면 재초기화 + // (React의 Strict Mode나 EmbeddedScreen 리렌더링으로 인한 remount) + if (hasInitialized.current && !currentIdString) { + console.log("[UniversalFormModal] 컴포넌트 remount 감지 - 채번 플래그 초기화"); + numberingGeneratedRef.current = false; + isGeneratingRef.current = false; + } // 🆕 수정 모드: initialData에 데이터가 있으면서 ID가 변경된 경우 재초기화 if (hasInitialized.current && currentIdString && lastInitializedId.current !== currentIdString) { @@ -252,6 +269,7 @@ export function UniversalFormModalComponent({ console.log("[UniversalFormModal] 초기 데이터 캡처:", capturedInitialData.current); } + console.log("[UniversalFormModal] initializeForm 호출 예정"); hasInitialized.current = true; initializeForm(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -389,6 +407,94 @@ export function UniversalFormModalComponent({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [config.sections]); + // 채번규칙 자동 생성 (중복 호출 방지) + // 중요: initializeForm에서 호출되므로 반드시 initializeForm보다 먼저 선언해야 함 + const numberingGeneratedRef = useRef(false); + const isGeneratingRef = useRef(false); // 진행 중 플래그 추가 + + const generateNumberingValues = useCallback( + async (currentFormData: FormDataState) => { + // 이미 생성되었거나 진행 중이면 스킵 + if (numberingGeneratedRef.current) { + console.log("[채번] 이미 생성됨 - 스킵"); + return; + } + + if (isGeneratingRef.current) { + console.log("[채번] 생성 진행 중 - 스킵"); + return; + } + + isGeneratingRef.current = true; // 진행 중 표시 + console.log("[채번] 생성 시작", { sectionsCount: config.sections.length }); + + const updatedData = { ...currentFormData }; + let hasChanges = false; + + for (const section of config.sections) { + console.log("[채번] 섹션 검사:", section.title, { type: section.type, repeatable: section.repeatable, fieldsCount: section.fields?.length }); + if (section.repeatable || section.type === "table") continue; + + for (const field of (section.fields || [])) { + // generateOnOpen은 기본값 true (undefined일 경우 true로 처리) + const shouldGenerateOnOpen = field.numberingRule?.generateOnOpen !== false; + console.log("[채번] 필드 검사:", field.columnName, { + hasNumberingRule: !!field.numberingRule, + enabled: field.numberingRule?.enabled, + generateOnOpen: field.numberingRule?.generateOnOpen, + shouldGenerateOnOpen, + ruleId: field.numberingRule?.ruleId, + currentValue: updatedData[field.columnName], + }); + if ( + field.numberingRule?.enabled && + shouldGenerateOnOpen && + field.numberingRule?.ruleId && + !updatedData[field.columnName] + ) { + try { + console.log(`[채번 미리보기 API 호출] ${field.columnName}, ruleId: ${field.numberingRule.ruleId}`); + // generateOnOpen: 미리보기만 표시 (DB 시퀀스 증가 안 함) + const response = await previewNumberingCode(field.numberingRule.ruleId); + if (response.success && response.data?.generatedCode) { + updatedData[field.columnName] = response.data.generatedCode; + + // 저장 시 실제 할당을 위해 ruleId 저장 (TextInput과 동일한 키 형식) + const ruleIdKey = `${field.columnName}_numberingRuleId`; + updatedData[ruleIdKey] = field.numberingRule.ruleId; + + hasChanges = true; + numberingGeneratedRef.current = true; // 생성 완료 표시 + console.log( + `[채번 미리보기 완료] ${field.columnName} = ${response.data.generatedCode} (저장 시 실제 할당)`, + ); + console.log(`[채번 규칙 ID 저장] ${ruleIdKey} = ${field.numberingRule.ruleId}`); + + // 부모 컴포넌트에도 ruleId 전달 (ModalRepeaterTable → ScreenModal) + if (onChange) { + onChange({ + ...updatedData, + [ruleIdKey]: field.numberingRule.ruleId, + }); + console.log(`[채번] 부모에게 ruleId 전달: ${ruleIdKey}`); + } + } + } catch (error) { + console.error(`채번규칙 미리보기 실패 (${field.columnName}):`, error); + } + } + } + } + + isGeneratingRef.current = false; // 진행 완료 + + if (hasChanges) { + setFormData(updatedData); + } + }, + [config, onChange], + ); + // 폼 초기화 const initializeForm = useCallback(async () => { console.log("[initializeForm] 시작"); @@ -585,82 +691,6 @@ export function UniversalFormModalComponent({ return item; }; - // 채번규칙 자동 생성 (중복 호출 방지) - const numberingGeneratedRef = useRef(false); - const isGeneratingRef = useRef(false); // 진행 중 플래그 추가 - - const generateNumberingValues = useCallback( - async (currentFormData: FormDataState) => { - // 이미 생성되었거나 진행 중이면 스킵 - if (numberingGeneratedRef.current) { - console.log("[채번] 이미 생성됨 - 스킵"); - return; - } - - if (isGeneratingRef.current) { - console.log("[채번] 생성 진행 중 - 스킵"); - return; - } - - isGeneratingRef.current = true; // 진행 중 표시 - console.log("[채번] 생성 시작"); - - const updatedData = { ...currentFormData }; - let hasChanges = false; - - for (const section of config.sections) { - if (section.repeatable || section.type === "table") continue; - - for (const field of (section.fields || [])) { - if ( - field.numberingRule?.enabled && - field.numberingRule?.generateOnOpen && - field.numberingRule?.ruleId && - !updatedData[field.columnName] - ) { - try { - console.log(`[채번 미리보기 API 호출] ${field.columnName}, ruleId: ${field.numberingRule.ruleId}`); - // generateOnOpen: 미리보기만 표시 (DB 시퀀스 증가 안 함) - const response = await previewNumberingCode(field.numberingRule.ruleId); - if (response.success && response.data?.generatedCode) { - updatedData[field.columnName] = response.data.generatedCode; - - // 저장 시 실제 할당을 위해 ruleId 저장 (TextInput과 동일한 키 형식) - const ruleIdKey = `${field.columnName}_numberingRuleId`; - updatedData[ruleIdKey] = field.numberingRule.ruleId; - - hasChanges = true; - numberingGeneratedRef.current = true; // 생성 완료 표시 - console.log( - `[채번 미리보기 완료] ${field.columnName} = ${response.data.generatedCode} (저장 시 실제 할당)`, - ); - console.log(`[채번 규칙 ID 저장] ${ruleIdKey} = ${field.numberingRule.ruleId}`); - - // 부모 컴포넌트에도 ruleId 전달 (ModalRepeaterTable → ScreenModal) - if (onChange) { - onChange({ - ...updatedData, - [ruleIdKey]: field.numberingRule.ruleId, - }); - console.log(`[채번] 부모에게 ruleId 전달: ${ruleIdKey}`); - } - } - } catch (error) { - console.error(`채번규칙 미리보기 실패 (${field.columnName}):`, error); - } - } - } - } - - isGeneratingRef.current = false; // 진행 완료 - - if (hasChanges) { - setFormData(updatedData); - } - }, - [config, onChange], - ); - // 필드 값 변경 핸들러 const handleFieldChange = useCallback( (columnName: string, value: any) => {