271 lines
7.3 KiB
TypeScript
271 lines
7.3 KiB
TypeScript
"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;
|