; Please enter a commit message to explain why this merge is necessary,
; especially if it merges an updated upstream into a topic branch.
;
; Lines starting with ';' will be ignored, and an empty message aborts
; the commit.
This commit is contained in:
leeheejin 2026-01-05 10:10:23 +09:00
commit 6c75adb61d
3 changed files with 112 additions and 99 deletions

View File

@ -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");
}
};

View File

@ -415,8 +415,10 @@ export const EmbeddedScreen = forwardRef<EmbeddedScreenHandle, EmbeddedScreenPro
maxWidth: `calc(100% - ${compPosition.x || 0}px)`,
};
// 🆕 formDataVersion을 key에서 제거하여 불필요한 remount 방지
// universal-form-modal 같은 컴포넌트가 채번 후 unmount되는 문제 해결
return (
<div key={`${component.id}-${formDataVersion}`} className="absolute" style={componentStyle}>
<div key={component.id} className="absolute" style={componentStyle}>
<DynamicComponentRenderer
component={component}
isInteractive={true}

View File

@ -216,6 +216,12 @@ export function UniversalFormModalComponent({
// 초기화 - 최초 마운트 시 또는 initialData가 변경되었을 때 실행
useEffect(() => {
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,10 +235,21 @@ 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) {
console.log("[UniversalFormModal] ID 변경 감지 - 재초기화:", {
@ -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) => {