feat: 품목관리 동적 렌더링 시스템 구현
- DynamicItemForm 컴포넌트 구조 생성 - DynamicField: 필드 타입별 렌더링 - DynamicSection: 섹션 단위 렌더링 - DynamicFormRenderer: 페이지 전체 렌더링 - 필드 타입별 컴포넌트 (TextField, NumberField, DropdownField, CheckboxField, DateField, FileField, CustomField) - 커스텀 훅 (useDynamicFormState, useFormStructure, useConditionalFields) - DataTable 공통 컴포넌트 (테이블, 페이지네이션, 검색, 탭필터, 통계카드) - ItemFormWrapper: Feature Flag 기반 폼 선택 - 타입 정의 및 문서화 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
208
src/components/items/DynamicItemForm/index.tsx
Normal file
208
src/components/items/DynamicItemForm/index.tsx
Normal file
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* DynamicItemForm Component
|
||||
*
|
||||
* 품목기준관리 설정에 따라 동적으로 폼을 렌더링하는 메인 컴포넌트
|
||||
*
|
||||
* 특징:
|
||||
* - API에서 폼 구조 로드 (품목 유형별)
|
||||
* - 조건부 섹션/필드 표시
|
||||
* - 동적 유효성 검증
|
||||
* - 기존 특수 컴포넌트 (BOM, 전개도) 통합 가능
|
||||
*
|
||||
* @example
|
||||
* // 신규 등록
|
||||
* <DynamicItemForm
|
||||
* itemType="FG"
|
||||
* onSubmit={handleCreate}
|
||||
* onCancel={handleCancel}
|
||||
* />
|
||||
*
|
||||
* @example
|
||||
* // 수정
|
||||
* <DynamicItemForm
|
||||
* itemType="PT"
|
||||
* partType="BENDING"
|
||||
* initialValues={existingItem}
|
||||
* onSubmit={handleUpdate}
|
||||
* onCancel={handleCancel}
|
||||
* />
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { DynamicFormRenderer } from './DynamicFormRenderer';
|
||||
import { useFormStructure, useDynamicFormState } from './hooks';
|
||||
import type { ItemType, PartType } from '@/types/item';
|
||||
import type { FormData } from './types';
|
||||
|
||||
interface DynamicItemFormProps {
|
||||
/** 품목 유형 */
|
||||
itemType: ItemType;
|
||||
/** 부품 유형 (PT인 경우) */
|
||||
partType?: PartType;
|
||||
/** 초기 값 (수정 모드) */
|
||||
initialValues?: FormData;
|
||||
/** 제출 핸들러 */
|
||||
onSubmit: (data: FormData) => Promise<void>;
|
||||
/** 취소 핸들러 */
|
||||
onCancel?: () => void;
|
||||
/** 폼 비활성화 */
|
||||
disabled?: boolean;
|
||||
/** Mock 데이터 사용 (API 미구현 시) */
|
||||
useMock?: boolean;
|
||||
}
|
||||
|
||||
export function DynamicItemForm({
|
||||
itemType,
|
||||
partType,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
disabled = false,
|
||||
useMock = true, // 기본적으로 Mock 사용 (API 구현 후 false로 변경)
|
||||
}: DynamicItemFormProps) {
|
||||
// 폼 구조 로드
|
||||
const {
|
||||
formStructure,
|
||||
isLoading: isLoadingStructure,
|
||||
error: structureError,
|
||||
refetch: refetchStructure,
|
||||
} = useFormStructure({
|
||||
itemType,
|
||||
partType,
|
||||
useMock,
|
||||
});
|
||||
|
||||
// 폼 상태 관리
|
||||
const {
|
||||
state,
|
||||
setValue,
|
||||
setTouched,
|
||||
validate,
|
||||
reset,
|
||||
handleSubmit,
|
||||
} = useDynamicFormState({
|
||||
sections: formStructure?.sections || [],
|
||||
initialValues,
|
||||
});
|
||||
|
||||
// 폼 구조가 변경되면 값 초기화
|
||||
useEffect(() => {
|
||||
if (formStructure) {
|
||||
reset(initialValues);
|
||||
}
|
||||
}, [formStructure, initialValues, reset]);
|
||||
|
||||
// 로딩 상태
|
||||
if (isLoadingStructure) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
<span className="ml-3 text-muted-foreground">폼 구조 로딩 중...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 에러 상태
|
||||
if (structureError) {
|
||||
return (
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription className="flex items-center justify-between">
|
||||
<span>폼 구조를 불러오는데 실패했습니다: {structureError.message}</span>
|
||||
<Button variant="outline" size="sm" onClick={refetchStructure}>
|
||||
다시 시도
|
||||
</Button>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
// 폼 구조가 없는 경우
|
||||
if (!formStructure) {
|
||||
return (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
해당 품목 유형({itemType})의 폼 구조가 정의되지 않았습니다.
|
||||
품목기준관리에서 페이지를 먼저 설정해주세요.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
// 폼 제출
|
||||
const onFormSubmit = handleSubmit(onSubmit);
|
||||
|
||||
return (
|
||||
<form onSubmit={onFormSubmit} className="space-y-6">
|
||||
{/* 페이지 정보 (디버그용, 필요시 제거) */}
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<div className="text-xs text-muted-foreground bg-gray-50 p-2 rounded">
|
||||
📄 {formStructure.page.page_name} | 품목유형: {formStructure.page.item_type}
|
||||
{formStructure.page.part_type && ` | 부품유형: ${formStructure.page.part_type}`}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 동적 폼 렌더러 */}
|
||||
<DynamicFormRenderer
|
||||
sections={formStructure.sections}
|
||||
conditionalSections={formStructure.conditionalSections}
|
||||
conditionalFields={formStructure.conditionalFields}
|
||||
values={state.values}
|
||||
errors={state.errors}
|
||||
onChange={(fieldKey, value) => {
|
||||
setValue(fieldKey, value);
|
||||
}}
|
||||
onBlur={(fieldKey) => {
|
||||
setTouched(fieldKey);
|
||||
}}
|
||||
disabled={disabled || state.isSubmitting}
|
||||
/>
|
||||
|
||||
{/* 버튼 영역 */}
|
||||
<div className="flex items-center justify-end gap-3 pt-4 border-t">
|
||||
{onCancel && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={onCancel}
|
||||
disabled={state.isSubmitting}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={disabled || state.isSubmitting}
|
||||
>
|
||||
{state.isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
저장 중...
|
||||
</>
|
||||
) : (
|
||||
'저장'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
// 하위 컴포넌트 및 훅 re-export
|
||||
export { DynamicFormRenderer } from './DynamicFormRenderer';
|
||||
export { DynamicSection } from './DynamicSection';
|
||||
export { DynamicField } from './DynamicField';
|
||||
export {
|
||||
useFormStructure,
|
||||
useConditionalFields,
|
||||
useDynamicFormState,
|
||||
clearFormStructureCache,
|
||||
invalidateFormStructureCache,
|
||||
} from './hooks';
|
||||
export * from './types';
|
||||
|
||||
export default DynamicItemForm;
|
||||
Reference in New Issue
Block a user