폼 통합
This commit is contained in:
270
frontend/hooks/useFormCompatibility.ts
Normal file
270
frontend/hooks/useFormCompatibility.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* 폼 호환성 브릿지 훅
|
||||
*
|
||||
* 레거시 컴포넌트와 새로운 Unified 폼 시스템 간의 호환성을 제공합니다.
|
||||
*
|
||||
* 사용 시나리오:
|
||||
* 1. UnifiedFormProvider 내부에서 사용 → Unified 시스템 사용
|
||||
* 2. UnifiedFormProvider 없이 사용 → 레거시 방식 사용
|
||||
* 3. 두 시스템이 공존할 때 → 양쪽에 모두 전파
|
||||
*/
|
||||
|
||||
import { useCallback, useContext, useMemo } from "react";
|
||||
import UnifiedFormContext, { useUnifiedFormOptional } from "@/components/unified/UnifiedFormContext";
|
||||
import { useScreenContext } from "@/contexts/ScreenContext";
|
||||
import type {
|
||||
FormCompatibilityBridge,
|
||||
FormCompatibilityOptions,
|
||||
SubmitConfig,
|
||||
SubmitResult,
|
||||
ValidationResult,
|
||||
FieldError,
|
||||
FormEventDetail,
|
||||
} from "@/types/unified-form";
|
||||
|
||||
/**
|
||||
* 폼 호환성 브릿지 훅
|
||||
*
|
||||
* @param options - 레거시 시스템 연동 옵션
|
||||
* @returns 통합된 폼 API
|
||||
*
|
||||
* @example
|
||||
* // 레거시 컴포넌트에서 사용
|
||||
* const { getValue, setValue, formData } = useFormCompatibility({
|
||||
* legacyOnFormDataChange: props.onFormDataChange,
|
||||
* });
|
||||
*
|
||||
* // 값 변경 시
|
||||
* setValue("fieldName", newValue);
|
||||
*
|
||||
* // 저장 시
|
||||
* const result = await submit({ tableName: "my_table", mode: "insert" });
|
||||
*/
|
||||
export function useFormCompatibility(options: FormCompatibilityOptions = {}): FormCompatibilityBridge {
|
||||
const { legacyOnFormDataChange, screenContext: externalScreenContext, emitLegacyEvents = true } = options;
|
||||
|
||||
// Unified 시스템 (있으면 사용)
|
||||
const unifiedContext = useUnifiedFormOptional();
|
||||
|
||||
// ScreenContext (레거시 시스템)
|
||||
const internalScreenContext = useScreenContext();
|
||||
const screenContext = externalScreenContext || internalScreenContext;
|
||||
|
||||
// 모드 판별
|
||||
const isUnifiedMode = !!unifiedContext;
|
||||
const isLegacyMode = !unifiedContext;
|
||||
|
||||
// ===== 값 관리 =====
|
||||
|
||||
/**
|
||||
* 필드 값 가져오기
|
||||
*/
|
||||
const getValue = useCallback(
|
||||
(field: string): unknown => {
|
||||
// Unified 시스템 우선
|
||||
if (unifiedContext) {
|
||||
return unifiedContext.getValue(field);
|
||||
}
|
||||
|
||||
// ScreenContext 폴백
|
||||
if (screenContext?.formData) {
|
||||
return screenContext.formData[field];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
[unifiedContext, screenContext?.formData],
|
||||
);
|
||||
|
||||
/**
|
||||
* 필드 값 설정 (모든 시스템에 전파)
|
||||
*/
|
||||
const setValue = useCallback(
|
||||
(field: string, value: unknown) => {
|
||||
// 1. Unified 시스템
|
||||
if (unifiedContext) {
|
||||
unifiedContext.setValue(field, value);
|
||||
}
|
||||
|
||||
// 2. ScreenContext (레거시)
|
||||
if (screenContext?.updateFormData) {
|
||||
screenContext.updateFormData(field, value);
|
||||
}
|
||||
|
||||
// 3. 레거시 콜백
|
||||
if (legacyOnFormDataChange) {
|
||||
legacyOnFormDataChange(field, value);
|
||||
}
|
||||
},
|
||||
[unifiedContext, screenContext, legacyOnFormDataChange],
|
||||
);
|
||||
|
||||
// ===== 폼 데이터 =====
|
||||
|
||||
const formData = useMemo(() => {
|
||||
if (unifiedContext) {
|
||||
return unifiedContext.formData as Record<string, unknown>;
|
||||
}
|
||||
if (screenContext?.formData) {
|
||||
return screenContext.formData as Record<string, unknown>;
|
||||
}
|
||||
return {};
|
||||
}, [unifiedContext, screenContext?.formData]);
|
||||
|
||||
// ===== 폼 액션 =====
|
||||
|
||||
/**
|
||||
* 저장
|
||||
*/
|
||||
const submit = useCallback(
|
||||
async (config?: Partial<SubmitConfig>): Promise<SubmitResult> => {
|
||||
// Unified 시스템이 있으면 그쪽 사용 (레거시 이벤트도 내부적으로 발생시킴)
|
||||
if (unifiedContext) {
|
||||
return unifiedContext.submit(config);
|
||||
}
|
||||
|
||||
// 레거시 모드: beforeFormSave 이벤트 발생
|
||||
if (emitLegacyEvents && typeof window !== "undefined") {
|
||||
const eventDetail: FormEventDetail = { formData: { ...formData } };
|
||||
const legacyEvent = new CustomEvent("beforeFormSave", { detail: eventDetail });
|
||||
window.dispatchEvent(legacyEvent);
|
||||
|
||||
// 이벤트에서 수집된 데이터 반환 (실제 저장은 외부에서 처리)
|
||||
return {
|
||||
success: true,
|
||||
data: { ...formData, ...eventDetail.formData },
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true, data: formData };
|
||||
},
|
||||
[unifiedContext, formData, emitLegacyEvents],
|
||||
);
|
||||
|
||||
/**
|
||||
* 초기화
|
||||
*/
|
||||
const reset = useCallback(() => {
|
||||
if (unifiedContext) {
|
||||
unifiedContext.reset();
|
||||
}
|
||||
// 레거시 모드에서는 특별한 처리 없음 (외부에서 처리)
|
||||
}, [unifiedContext]);
|
||||
|
||||
/**
|
||||
* 검증
|
||||
*/
|
||||
const validate = useCallback(async (): Promise<ValidationResult> => {
|
||||
if (unifiedContext) {
|
||||
return unifiedContext.validate();
|
||||
}
|
||||
|
||||
// 레거시 모드에서는 항상 valid
|
||||
return { valid: true, errors: [] };
|
||||
}, [unifiedContext]);
|
||||
|
||||
// ===== 상태 =====
|
||||
|
||||
const isSubmitting = unifiedContext?.status.isSubmitting ?? false;
|
||||
const isDirty = unifiedContext?.status.isDirty ?? false;
|
||||
const errors = unifiedContext?.errors ?? [];
|
||||
|
||||
return {
|
||||
// 값 관리
|
||||
getValue,
|
||||
setValue,
|
||||
formData,
|
||||
|
||||
// 폼 액션
|
||||
submit,
|
||||
reset,
|
||||
validate,
|
||||
|
||||
// 상태
|
||||
isSubmitting,
|
||||
isDirty,
|
||||
errors,
|
||||
|
||||
// 모드
|
||||
isUnifiedMode,
|
||||
isLegacyMode,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* ScreenContext를 사용하는 레거시 컴포넌트를 위한 간편 훅
|
||||
* ScreenContext가 없어도 동작 (null 반환하지 않고 빈 객체)
|
||||
*/
|
||||
function useScreenContext() {
|
||||
// ScreenContext import는 동적으로 처리 (순환 의존성 방지)
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { useScreenContext: useCtx } = require("@/contexts/ScreenContext");
|
||||
return useCtx?.() || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DynamicComponentRenderer에서 사용하는 통합 onChange 핸들러 생성
|
||||
*
|
||||
* @example
|
||||
* const handleChange = createUnifiedChangeHandler({
|
||||
* fieldName: "customer_name",
|
||||
* unifiedContext,
|
||||
* screenContext,
|
||||
* legacyOnFormDataChange: props.onFormDataChange,
|
||||
* });
|
||||
*/
|
||||
export function createUnifiedChangeHandler(options: {
|
||||
fieldName: string;
|
||||
unifiedContext?: ReturnType<typeof useUnifiedFormOptional>;
|
||||
screenContext?: { updateFormData?: (field: string, value: unknown) => void };
|
||||
legacyOnFormDataChange?: (field: string, value: unknown) => void;
|
||||
}): (value: unknown) => void {
|
||||
const { fieldName, unifiedContext, screenContext, legacyOnFormDataChange } = options;
|
||||
|
||||
return (value: unknown) => {
|
||||
// 1. Unified 시스템
|
||||
if (unifiedContext) {
|
||||
unifiedContext.setValue(fieldName, value);
|
||||
}
|
||||
|
||||
// 2. ScreenContext
|
||||
if (screenContext?.updateFormData) {
|
||||
screenContext.updateFormData(fieldName, value);
|
||||
}
|
||||
|
||||
// 3. 레거시 콜백
|
||||
if (legacyOnFormDataChange) {
|
||||
legacyOnFormDataChange(fieldName, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 레거시 beforeFormSave 이벤트 리스너 등록 훅
|
||||
*
|
||||
* 리피터 컴포넌트 등에서 저장 시 데이터를 수집하기 위해 사용
|
||||
*
|
||||
* @example
|
||||
* useBeforeFormSave((event) => {
|
||||
* event.detail.formData["repeater_field"] = myRepeaterData;
|
||||
* });
|
||||
*/
|
||||
export function useBeforeFormSave(handler: (event: CustomEvent<FormEventDetail>) => void, deps: unknown[] = []) {
|
||||
const stableHandler = useCallback(handler, deps);
|
||||
|
||||
// 이벤트 리스너 등록
|
||||
if (typeof window !== "undefined") {
|
||||
// useEffect 내에서 처리해야 하지만, 훅의 단순성을 위해 여기서 처리
|
||||
// 실제 사용 시에는 컴포넌트에서 useEffect로 감싸서 사용
|
||||
}
|
||||
|
||||
return stableHandler;
|
||||
}
|
||||
|
||||
export default useFormCompatibility;
|
||||
Reference in New Issue
Block a user