import logger from '@/utils/logger'; import { useForm, Controller } from 'react-hook-form'; import { useEffect } from 'react'; import SelectChoice from './SelectChoice'; import InputTextIcon from './InputTextIcon'; import * as LucideIcons from 'lucide-react'; import Button from './Button'; import DjangoCSRFToken from '../DjangoCSRFToken'; import WisiwigTextArea from './WisiwigTextArea'; import RadioList from './RadioList'; import CheckBox from './CheckBox'; import ToggleSwitch from './ToggleSwitch'; import InputPhone from './InputPhone'; import FileUpload from './FileUpload'; import SignatureField from './SignatureField'; /* * Récupère une icône Lucide par son nom. */ export function getIcon(name) { if (Object.keys(LucideIcons).includes(name)) { const Icon = LucideIcons[name]; return Icon ?? null; } else { return null; } } export default function FormRenderer({ formConfig, csrfToken, initialValues = {}, onFormSubmit = (data) => { alert(JSON.stringify(data, null, 2)); }, // Callback de soumission personnalisé (optionnel) }) { const { handleSubmit, control, formState: { errors }, reset, } = useForm({ defaultValues: initialValues }); // Réinitialiser le formulaire quand les valeurs initiales changent useEffect(() => { if (initialValues && Object.keys(initialValues).length > 0) { reset(initialValues); } }, [initialValues, reset]); // Fonction utilitaire pour envoyer les données au backend const sendFormDataToBackend = async (formData) => { try { // Cette fonction peut être remplacée par votre propre implémentation // Exemple avec fetch: const response = await fetch('/api/submit-form', { method: 'POST', body: formData, // Les en-têtes sont automatiquement définis pour FormData }); if (!response.ok) { throw new Error(`Erreur HTTP ${response.status}`); } const result = await response.json(); logger.debug('Envoi réussi:', result); return result; } catch (error) { logger.error("Erreur lors de l'envoi:", error); throw error; } }; const onSubmit = async (data) => { logger.debug('=== DÉBUT onSubmit ==='); logger.debug('Réponses :', data); try { // Vérifier si nous avons des fichiers dans les données const hasFiles = Object.keys(data).some((key) => { return ( data[key] instanceof FileList || (data[key] && data[key][0] instanceof File) || (typeof data[key] === 'string' && data[key].startsWith('data:image')) ); }); if (hasFiles) { // Utiliser FormData pour l'envoi de fichiers const formData = new FormData(); // Ajouter l'ID du formulaire formData.append('formId', (formConfig?.id || 'unknown').toString()); // Traiter chaque champ et ses valeurs Object.keys(data).forEach((key) => { const value = data[key]; if ( value instanceof FileList || (value && value[0] instanceof File) ) { // Gérer les champs de type fichier if (value.length > 0) { for (let i = 0; i < value.length; i++) { formData.append(`files.${key}`, value[i]); } } } else if ( typeof value === 'string' && value.startsWith('data:image') ) { // Gérer les signatures (SVG ou images base64) if (value.includes('svg+xml')) { // Gérer les signatures SVG const svgData = value.split(',')[1]; const svgBlob = new Blob([atob(svgData)], { type: 'image/svg+xml', }); formData.append(`files.${key}`, svgBlob, `signature_${key}.svg`); } else { // Gérer les images base64 classiques const byteString = atob(value.split(',')[1]); const ab = new ArrayBuffer(byteString.length); const ia = new Uint8Array(ab); for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } const blob = new Blob([ab], { type: 'image/png' }); formData.append(`files.${key}`, blob, `signature_${key}.png`); } } else { // Gérer les autres types de champs formData.append( `data.${key}`, value !== undefined ? value.toString() : '' ); } }); if (onFormSubmit) { // Utiliser le callback personnalisé si fourni await onFormSubmit(formData, true); } else { // Sinon, utiliser la fonction par défaut await sendFormDataToBackend(formData); alert('Formulaire avec fichier(s) envoyé avec succès'); } } else { // Pas de fichier, on peut utiliser JSON const formattedData = { formId: formConfig?.id || 'unknown', responses: { ...data }, }; if (onFormSubmit) { // Utiliser le callback personnalisé si fourni await onFormSubmit(formattedData, false); } else { // Afficher un message pour démonstration alert('Données reçues : ' + JSON.stringify(formattedData, null, 2)); } } reset(); // Réinitialiser le formulaire après soumission } catch (error) { logger.error('Erreur lors de la soumission du formulaire:', error); alert(`Erreur lors de l'envoi du formulaire: ${error.message}`); } logger.debug('=== FIN onSubmit ==='); }; const onError = (errors) => { logger.error('=== ERREURS DE VALIDATION ==='); logger.error('Erreurs :', errors); alert('Erreurs de validation : ' + JSON.stringify(errors, null, 2)); }; return (
{csrfToken ? : null}

{formConfig?.title || 'Formulaire'}

{(formConfig?.fields || []).map((field) => (
{field.type === 'heading1' && (

{field.text}

)} {field.type === 'heading2' && (

{field.text}

)} {field.type === 'heading3' && (

{field.text}

)} {field.type === 'heading4' && (

{field.text}

)} {field.type === 'heading5' && (
{field.text}
)} {field.type === 'heading6' && (
{field.text}
)} {field.type === 'paragraph' &&

{field.text}

} {(field.type === 'text' || field.type === 'email' || field.type === 'date') && ( ( )} /> )} {field.type === 'phone' && ( ( )} /> )} {field.type === 'select' && ( ( ({ label: e, value: e }))} placeHolder={`Sélectionner ${field.label.toLowerCase()}`} errorMsg={ errors[field.id] ? field.required ? `${field.label} est requis` : 'Champ invalide' : '' } /> )} /> )} {field.type === 'radio' && ( ( ({ id: idx, label: option, }))} formData={{ [field.id]: value ? field.options.findIndex((o) => o === value) : '', }} handleChange={(e) => onChange(field.options[parseInt(e.target.value)]) } fieldName={field.id} sectionLabel={field.label} required={field.required} /> )} /> )} {field.type === 'checkbox' && ( (
onChange(e.target.checked)} fieldName={field.id} itemLabelFunc={(item) => item.label} horizontal={field.horizontal || false} /> {errors[field.id] && (

{field.required ? `${field.label} est requis` : 'Champ invalide'}

)}
)} /> )} {field.type === 'toggle' && ( (
onChange(e.target.checked)} /> {errors[field.id] && (

{field.required ? `${field.label} est requis` : 'Champ invalide'}

)}
)} /> )} {field.type === 'file' && ( ( { // Créer un objet de type FileList similaire pour la compatibilité const dataTransfer = new DataTransfer(); dataTransfer.items.add(file); onChange(dataTransfer.files); }} errorMsg={ errors[field.id] ? field.required ? `${field.label} est requis` : 'Champ invalide' : '' } /> )} /> )} {field.type === 'textarea' && ( ( )} /> )} {field.type === 'signature' && ( (
{errors[field.id] && (

{field.required ? `${field.label} est requis` : 'Champ invalide'}

)}
)} /> )}
))}
); }