import { useState, useCallback } from 'react'; import { Input } from '@/components/ui/input'; import { Select, SelectOption } from '@/components/ui/select'; import { DatePicker } from '@/components/ui/date-picker'; import { FileUpload } from '@/components/ui/file-upload'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { cn } from '@/lib/utils'; export interface FormField { name: string; type: 'text' | 'email' | 'password' | 'number' | 'textarea' | 'select' | 'date' | 'file'; label: string; placeholder?: string; required?: boolean; disabled?: boolean; defaultValue?: any; validation?: (value: any) => string | null; // Options pour le type select options?: SelectOption[]; // Options pour le type file accept?: string; multiple?: boolean; maxSize?: number; // Options pour le type date minDate?: Date; maxDate?: Date; mode?: 'single' | 'range'; } export interface FormBuilderProps { fields: FormField[]; onSubmit: (data: Record) => void; submitLabel?: string; className?: string; disabled?: boolean; } /** * Composant FormBuilder pour créer des formulaires dynamiques à partir de configuration. */ export function FormBuilder({ fields, onSubmit, submitLabel = 'Submit', className, disabled = false, }: FormBuilderProps) { const [formData, setFormData] = useState>(() => { const initial: Record = {}; fields.forEach(field => { if (field.defaultValue !== undefined) { initial[field.name] = field.defaultValue; } else if (field.type === 'select') { initial[field.name] = field.multiple ? [] : ''; } else if (field.type === 'file') { initial[field.name] = field.multiple ? [] : null; } else if (field.type === 'date') { initial[field.name] = field.mode === 'range' ? { start: null, end: null } : null; } else { initial[field.name] = ''; } }); return initial; }); const [errors, setErrors] = useState>({}); const [touched, setTouched] = useState>({}); const validateField = useCallback((field: FormField, value: any): string | null => { // Validation required if (field.required) { if (value === null || value === undefined || value === '' || (Array.isArray(value) && value.length === 0) || (typeof value === 'object' && field.type === 'date' && field.mode === 'range' && (!value.start || !value.end))) { return `${field.label} is required`; } } // Validation email if (field.type === 'email' && value) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(value)) { return 'Please enter a valid email address'; } } // Validation personnalisée if (field.validation) { const customError = field.validation(value); if (customError) { return customError; } } return null; }, []); const handleFieldChange = useCallback((fieldName: string, value: any) => { setFormData(prev => ({ ...prev, [fieldName]: value, })); // Valider le champ si déjà touché if (touched[fieldName]) { const field = fields.find(f => f.name === fieldName); if (field) { const error = validateField(field, value); setErrors(prev => { if (error) { return { ...prev, [fieldName]: error }; } else { const newErrors = { ...prev }; delete newErrors[fieldName]; return newErrors; } }); } } }, [fields, touched, validateField]); const handleFieldBlur = useCallback((fieldName: string) => { setTouched(prev => ({ ...prev, [fieldName]: true })); const field = fields.find(f => f.name === fieldName); if (field) { const value = formData[fieldName]; const error = validateField(field, value); setErrors(prev => { if (error) { return { ...prev, [fieldName]: error }; } else { const newErrors = { ...prev }; delete newErrors[fieldName]; return newErrors; } }); } }, [fields, formData, validateField]); const handleSubmit = useCallback((e: React.FormEvent) => { e.preventDefault(); // Marquer tous les champs comme touchés const allTouched: Record = {}; const newErrors: Record = {}; fields.forEach(field => { allTouched[field.name] = true; const value = formData[field.name]; const error = validateField(field, value); if (error) { newErrors[field.name] = error; } }); setTouched(allTouched); setErrors(newErrors); // Si pas d'erreurs, soumettre if (Object.keys(newErrors).length === 0) { onSubmit(formData); } }, [fields, formData, validateField, onSubmit]); const renderField = (field: FormField, hasError: boolean) => { switch (field.type) { case 'text': case 'email': case 'password': case 'number': return ( handleFieldChange(field.name, e.target.value)} onBlur={() => handleFieldBlur(field.name)} placeholder={field.placeholder} disabled={disabled || field.disabled} className={hasError ? 'border-destructive' : ''} /> ); case 'textarea': return (