ERP-node/frontend/hooks/useFormCompatibility.ts

271 lines
7.3 KiB
TypeScript
Raw Normal View History

2026-01-15 09:50:33 +09:00
"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;