feat: Ajout des composants manquant dans le FormTemplateBuilder [N3WTS-17]

This commit is contained in:
Luc SORIGNET
2025-09-01 12:09:19 +02:00
parent e89d2fc4c3
commit 5e62ee5100
12 changed files with 525 additions and 38 deletions

View File

@ -6,6 +6,11 @@ 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';
/*
* Récupère une icône Lucide par son nom.
@ -60,6 +65,9 @@ const formConfigTest = {
export default function FormRenderer({
formConfig = formConfigTest,
csrfToken,
onFormSubmit = (data) => {
alert(JSON.stringify(data, null, 2));
}, // Callback de soumission personnalisé (optionnel)
}) {
const {
handleSubmit,
@ -68,19 +76,103 @@ export default function FormRenderer({
reset,
} = useForm();
const onSubmit = (data) => {
// 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);
const formattedData = {
//TODO: idDossierInscriptions: 123,
formId: formConfig.id,
responses: { ...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)
);
});
if (hasFiles) {
// Utiliser FormData pour l'envoi de fichiers
const formData = new FormData();
// Ajouter l'ID du formulaire
formData.append('formId', formConfig.id.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 {
// 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,
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}`);
}
//TODO: ENVOYER LES DONNÉES AU BACKEND
alert('Données reçues : ' + JSON.stringify(formattedData, null, 2));
reset(); // Réinitialiser le formulaire après soumission
logger.debug('=== FIN onSubmit ===');
};
@ -132,7 +224,14 @@ export default function FormRenderer({
<Controller
name={field.id}
control={control}
rules={{ required: field.required }}
rules={{
required: field.required,
pattern: field.validation?.pattern
? new RegExp(field.validation.pattern)
: undefined,
minLength: field.validation?.minLength,
maxLength: field.validation?.maxLength,
}}
render={({ field: { onChange, value, name } }) => (
<InputTextIcon
label={field.label}
@ -153,6 +252,29 @@ export default function FormRenderer({
)}
/>
)}
{field.type === 'phone' && (
<Controller
name={field.id}
control={control}
rules={{ required: field.required }}
render={({ field: { onChange, value, name } }) => (
<InputPhone
label={field.label}
required={field.required}
name={name}
value={value || ''}
onChange={onChange}
errorMsg={
errors[field.id]
? field.required
? `${field.label} est requis`
: 'Champ invalide'
: ''
}
/>
)}
/>
)}
{field.type === 'select' && (
<Controller
name={field.id}
@ -178,6 +300,111 @@ export default function FormRenderer({
)}
/>
)}
{field.type === 'radio' && (
<Controller
name={field.id}
control={control}
rules={{ required: field.required }}
render={({ field: { onChange, value, name } }) => (
<RadioList
items={field.options.map((option, idx) => ({
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' && (
<Controller
name={field.id}
control={control}
defaultValue={field.checked || false}
rules={{ required: field.required }}
render={({ field: { onChange, value, name } }) => (
<div>
<CheckBox
item={{ id: field.id, label: field.label }}
formData={{ [field.id]: value || false }}
handleChange={(e) => onChange(e.target.checked)}
fieldName={field.id}
itemLabelFunc={(item) => item.label}
horizontal={field.horizontal || false}
/>
{errors[field.id] && (
<p className="text-red-500 text-sm mt-1">
{field.required
? `${field.label} est requis`
: 'Champ invalide'}
</p>
)}
</div>
)}
/>
)}
{field.type === 'toggle' && (
<Controller
name={field.id}
control={control}
defaultValue={field.checked || false}
rules={{ required: field.required }}
render={({ field: { onChange, value, name } }) => (
<div>
<ToggleSwitch
name={field.id}
label={field.label + (field.required ? ' *' : '')}
checked={value || false}
onChange={(e) => onChange(e.target.checked)}
/>
{errors[field.id] && (
<p className="text-red-500 text-sm mt-1">
{field.required
? `${field.label} est requis`
: 'Champ invalide'}
</p>
)}
</div>
)}
/>
)}
{field.type === 'file' && (
<Controller
name={field.id}
control={control}
rules={{ required: field.required }}
render={({ field: { onChange, value } }) => (
<FileUpload
selectionMessage={field.label}
required={field.required}
uploadedFileName={value ? value[0]?.name : null}
onFileSelect={(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' && (
<Controller
name={field.id}