"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; } if (screenContext?.formData) { return screenContext.formData as Record; } return {}; }, [unifiedContext, screenContext?.formData]); // ===== 폼 액션 ===== /** * 저장 */ const submit = useCallback( async (config?: Partial): Promise => { // 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 => { 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; 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) => void, deps: unknown[] = []) { const stableHandler = useCallback(handler, deps); // 이벤트 리스너 등록 if (typeof window !== "undefined") { // useEffect 내에서 처리해야 하지만, 훅의 단순성을 위해 여기서 처리 // 실제 사용 시에는 컴포넌트에서 useEffect로 감싸서 사용 } return stableHandler; } export default useFormCompatibility;