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>
2025-11-28 20:14:43 +09:00
|
|
|
/**
|
2025-12-04 12:48:41 +09:00
|
|
|
* 드롭다운(Select) 필드 컴포넌트
|
|
|
|
|
* 기존 ItemForm과 100% 동일한 디자인
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
'use client';
|
|
|
|
|
|
2025-12-04 12:48:41 +09:00
|
|
|
import { Label } from '@/components/ui/label';
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from '@/components/ui/select';
|
2025-12-04 12:48:41 +09:00
|
|
|
import type { DynamicFieldRendererProps } from '../types';
|
|
|
|
|
|
|
|
|
|
// 옵션을 {label, value} 형태로 정규화
|
|
|
|
|
function normalizeOptions(rawOptions: unknown): Array<{ label: string; value: string }> {
|
|
|
|
|
if (!rawOptions) return [];
|
|
|
|
|
|
|
|
|
|
// 문자열인 경우: 콤마로 분리
|
|
|
|
|
if (typeof rawOptions === 'string') {
|
|
|
|
|
return rawOptions.split(',').map(o => {
|
|
|
|
|
const trimmed = o.trim();
|
|
|
|
|
return { label: trimmed, value: trimmed };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 배열인 경우
|
|
|
|
|
if (Array.isArray(rawOptions)) {
|
|
|
|
|
return rawOptions.map(item => {
|
|
|
|
|
// 이미 {label, value} 형태
|
|
|
|
|
if (typeof item === 'object' && item !== null && 'value' in item) {
|
|
|
|
|
return {
|
|
|
|
|
label: String(item.label || item.value),
|
|
|
|
|
value: String(item.value),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
// 문자열 배열
|
|
|
|
|
const str = String(item);
|
|
|
|
|
return { label: str, value: str };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
}
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
|
|
|
|
|
export function DropdownField({
|
|
|
|
|
field,
|
|
|
|
|
value,
|
|
|
|
|
onChange,
|
2025-12-04 12:48:41 +09:00
|
|
|
error,
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
disabled,
|
2025-12-04 12:48:41 +09:00
|
|
|
unitOptions,
|
|
|
|
|
}: DynamicFieldRendererProps) {
|
|
|
|
|
const fieldKey = field.field_key || `field_${field.id}`;
|
2025-12-20 14:33:11 +09:00
|
|
|
|
|
|
|
|
// is_active 필드인지 확인
|
|
|
|
|
const isActiveField = fieldKey === 'is_active' || fieldKey.endsWith('_is_active');
|
|
|
|
|
|
|
|
|
|
// 옵션을 먼저 정규화 (is_active 값 변환에 필요)
|
|
|
|
|
const rawOptions = normalizeOptions(field.options);
|
|
|
|
|
|
|
|
|
|
// is_active 필드일 때 boolean 값을 옵션에 맞게 변환
|
|
|
|
|
let stringValue = '';
|
|
|
|
|
if (value !== null && value !== undefined) {
|
|
|
|
|
if (isActiveField && rawOptions.length >= 2) {
|
|
|
|
|
// boolean/숫자 값을 첫번째(활성) 또는 두번째(비활성) 옵션 값으로 매핑
|
|
|
|
|
const isActive = value === true || value === 'true' || value === 1 || value === '1' || value === '활성';
|
|
|
|
|
stringValue = isActive ? rawOptions[0].value : rawOptions[1].value;
|
|
|
|
|
} else {
|
|
|
|
|
stringValue = String(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-04 12:48:41 +09:00
|
|
|
|
|
|
|
|
// field_key 또는 field_name이 '단위'/'unit' 관련이면 unitOptions 사용
|
|
|
|
|
const isUnitField =
|
|
|
|
|
fieldKey.toLowerCase().includes('unit') ||
|
|
|
|
|
fieldKey.includes('단위') ||
|
|
|
|
|
field.field_name.includes('단위') ||
|
|
|
|
|
field.field_name.toLowerCase().includes('unit');
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
|
2025-12-04 12:48:41 +09:00
|
|
|
// 옵션 목록 결정
|
|
|
|
|
let options: Array<{ label: string; value: string }> = [];
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
|
2025-12-04 12:48:41 +09:00
|
|
|
if (isUnitField && unitOptions && unitOptions.length > 0) {
|
|
|
|
|
options = unitOptions.map((u) => ({
|
|
|
|
|
label: `${u.label} (${u.value})`,
|
|
|
|
|
value: u.value,
|
|
|
|
|
}));
|
|
|
|
|
} else {
|
2025-12-20 14:33:11 +09:00
|
|
|
// rawOptions는 이미 위에서 정규화됨
|
|
|
|
|
options = rawOptions;
|
2025-12-04 12:48:41 +09:00
|
|
|
}
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
|
2025-12-04 12:48:41 +09:00
|
|
|
// 옵션이 없으면 드롭다운을 disabled로 표시
|
|
|
|
|
const hasOptions = options.length > 0;
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
|
|
|
|
|
return (
|
2025-12-04 12:48:41 +09:00
|
|
|
<div>
|
|
|
|
|
<Label htmlFor={fieldKey}>
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
{field.field_name}
|
2025-12-04 12:48:41 +09:00
|
|
|
{field.is_required && <span className="text-red-500"> *</span>}
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
</Label>
|
|
|
|
|
<Select
|
2025-12-06 11:36:38 +09:00
|
|
|
key={`${fieldKey}-${stringValue}`}
|
2025-12-04 12:48:41 +09:00
|
|
|
value={stringValue}
|
|
|
|
|
onValueChange={onChange}
|
|
|
|
|
disabled={disabled || !hasOptions}
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
>
|
|
|
|
|
<SelectTrigger
|
2025-12-04 12:48:41 +09:00
|
|
|
id={fieldKey}
|
|
|
|
|
className={error ? 'border-red-500' : ''}
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
>
|
2025-12-04 12:48:41 +09:00
|
|
|
<SelectValue placeholder={
|
|
|
|
|
hasOptions
|
|
|
|
|
? (field.placeholder || `${field.field_name}을(를) 선택하세요`)
|
|
|
|
|
: '옵션이 없습니다'
|
|
|
|
|
} />
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
2025-12-04 12:48:41 +09:00
|
|
|
{options.map((option, index) => (
|
|
|
|
|
<SelectItem key={`${option.value}-${index}`} value={option.value}>
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
{option.label}
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
2025-12-04 12:48:41 +09:00
|
|
|
{error && (
|
|
|
|
|
<p className="text-xs text-red-500 mt-1">{error}</p>
|
|
|
|
|
)}
|
|
|
|
|
{!error && field.description && (
|
|
|
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
|
|
|
* {field.description}
|
|
|
|
|
</p>
|
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>
2025-11-28 20:14:43 +09:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|