mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
feat: creation d'un FormRenderer.js pour creer un formulaire dynamique [NEWTS-17]
This commit is contained in:
@ -9,10 +9,10 @@ import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import AlertMessage from '@/components/AlertMessage';
|
||||
import RecipientInput from '@/components/RecipientInput';
|
||||
import { useRouter } from 'next/navigation'; // Ajoute cette ligne
|
||||
import WisiwigTextArea from '@/components/WisiwigTextArea';
|
||||
import WisiwigTextArea from '@/components/Form/WisiwigTextArea';
|
||||
import logger from '@/utils/logger';
|
||||
import InputText from '@/components/InputText';
|
||||
import Button from '@/components/Button';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import Button from '@/components/Form/Button';
|
||||
|
||||
export default function EmailSender({ csrfToken }) {
|
||||
const [recipients, setRecipients] = useState([]);
|
||||
|
||||
194
Front-End/src/components/Form/FormRenderer.js
Normal file
194
Front-End/src/components/Form/FormRenderer.js
Normal file
@ -0,0 +1,194 @@
|
||||
import logger from '@/utils/logger';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
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';
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
const formConfigTest = {
|
||||
id: 0,
|
||||
title: 'Mon formulaire dynamique',
|
||||
submitLabel: 'Envoyer',
|
||||
fields: [
|
||||
{ id: 'name', label: 'Nom', type: 'text', required: true },
|
||||
{ id: 'email', label: 'Email', type: 'email' },
|
||||
{
|
||||
id: 'email2',
|
||||
label: 'Email',
|
||||
type: 'text',
|
||||
icon: 'Mail',
|
||||
},
|
||||
{
|
||||
id: 'role',
|
||||
label: 'Rôle',
|
||||
type: 'select',
|
||||
options: ['Admin', 'Utilisateur', 'Invité'],
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: "Bonjour, Bienvenue dans ce formulaire d'inscription haha",
|
||||
},
|
||||
{
|
||||
id: 'birthdate',
|
||||
label: 'Date de naissance',
|
||||
type: 'date',
|
||||
icon: 'Calendar',
|
||||
},
|
||||
{
|
||||
id: 'textarea',
|
||||
label: 'toto',
|
||||
type: 'textarea',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default function FormRenderer({
|
||||
formConfig = formConfigTest,
|
||||
csrfToken,
|
||||
}) {
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors },
|
||||
reset,
|
||||
} = useForm();
|
||||
|
||||
const onSubmit = (data) => {
|
||||
logger.debug('=== DÉBUT onSubmit ===');
|
||||
logger.debug('Réponses :', data);
|
||||
|
||||
const formattedData = {
|
||||
//TODO: idDossierInscriptions: 123,
|
||||
formId: formConfig.id,
|
||||
responses: { ...data },
|
||||
};
|
||||
|
||||
//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 ===');
|
||||
};
|
||||
|
||||
const onError = (errors) => {
|
||||
logger.error('=== ERREURS DE VALIDATION ===');
|
||||
logger.error('Erreurs :', errors);
|
||||
alert('Erreurs de validation : ' + JSON.stringify(errors, null, 2));
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit, onError)}
|
||||
className="max-w-md mx-auto"
|
||||
>
|
||||
{csrfToken ? <DjangoCSRFToken csrfToken={csrfToken} /> : null}
|
||||
<h2 className="text-2xl font-bold text-center mb-4">
|
||||
{formConfig.title}
|
||||
</h2>
|
||||
|
||||
{formConfig.fields.map((field) => (
|
||||
<div key={field.id} className="flex flex-col mt-4">
|
||||
{field.type === 'paragraph' && <p>{field.text}</p>}
|
||||
|
||||
{(field.type === 'text' ||
|
||||
field.type === 'email' ||
|
||||
field.type === 'date') && (
|
||||
<Controller
|
||||
name={field.id}
|
||||
control={control}
|
||||
rules={{ required: field.required }}
|
||||
render={({ field: { onChange, value, name } }) => (
|
||||
<InputTextIcon
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
IconItem={field.icon ? getIcon(field.icon) : null}
|
||||
type={field.type}
|
||||
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}
|
||||
control={control}
|
||||
rules={{ required: field.required }}
|
||||
render={({ field: { onChange, value, name } }) => (
|
||||
<SelectChoice
|
||||
label={field.label}
|
||||
required={field.required}
|
||||
name={name}
|
||||
selected={value || ''}
|
||||
callback={onChange}
|
||||
choices={field.options.map((e) => ({ label: e, value: e }))}
|
||||
placeHolder={`Sélectionner ${field.label.toLowerCase()}`}
|
||||
errorMsg={
|
||||
errors[field.id]
|
||||
? field.required
|
||||
? `${field.label} est requis`
|
||||
: 'Champ invalide'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{field.type === 'textarea' && (
|
||||
<Controller
|
||||
name={field.id}
|
||||
control={control}
|
||||
rules={{ required: field.required }}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<WisiwigTextArea
|
||||
label={field.label}
|
||||
placeholder={field.placeholder}
|
||||
value={value || ''}
|
||||
onChange={onChange}
|
||||
required={field.required}
|
||||
errorMsg={
|
||||
errors[field.id]
|
||||
? field.required
|
||||
? `${field.label} est requis`
|
||||
: 'Champ invalide'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="form-group-submit mt-4">
|
||||
<Button
|
||||
type="submit"
|
||||
primary
|
||||
text={formConfig.submitLabel ? formConfig.submitLabel : 'Envoyer'}
|
||||
className="mb-1 px-4 py-2 rounded-md shadow bg-emerald-500 text-white hover:bg-emerald-600 w-full"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function InputTextIcon({
|
||||
name,
|
||||
type,
|
||||
@ -31,9 +33,11 @@ export default function InputTextIcon({
|
||||
!enable ? 'bg-gray-100 cursor-not-allowed' : ''
|
||||
}`}
|
||||
>
|
||||
<span className="inline-flex min-h-9 items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
|
||||
{IconItem && <IconItem />}
|
||||
</span>
|
||||
{IconItem ? (
|
||||
<span className="inline-flex min-h-9 items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
|
||||
<IconItem />
|
||||
</span>
|
||||
) : null}
|
||||
<input
|
||||
type={type}
|
||||
id={name}
|
||||
@ -4,10 +4,10 @@ import 'react-quill/dist/quill.snow.css';
|
||||
const ReactQuill = dynamic(() => import('react-quill'), { ssr: false });
|
||||
|
||||
export default function WisiwigTextArea({
|
||||
label = 'Mail',
|
||||
label = 'Zone de Texte',
|
||||
value,
|
||||
onChange,
|
||||
placeholder = 'Ecrivez votre mail ici...',
|
||||
placeholder = 'Ecrivez votre texte ici...',
|
||||
className = 'h-64',
|
||||
required = false,
|
||||
errorMsg,
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||
import Button from '@/components/Button';
|
||||
import ToggleSwitch from '@/components/Form/ToggleSwitch';
|
||||
import Button from '@/components/Form/Button';
|
||||
import Popup from '@/components/Popup';
|
||||
import { useNotification } from '@/context/NotificationContext';
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useState, useMemo, useEffect } from 'react';
|
||||
import { BookOpen, CheckCircle, AlertCircle, Clock } from 'lucide-react';
|
||||
import RadioList from '@/components/RadioList';
|
||||
import RadioList from '@/components/Form/RadioList';
|
||||
|
||||
const LEVELS = [
|
||||
{ value: 0, label: 'Non évalué' },
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import Table from '@/components/Table';
|
||||
import FileUpload from '@/components/FileUpload';
|
||||
import FileUpload from '@/components/Form/FileUpload';
|
||||
import { Upload, Eye, Trash2, FileText } from 'lucide-react';
|
||||
import { BASE_URL } from '@/utils/Url';
|
||||
import Popup from '@/components/Popup';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Import des dépendances nécessaires
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Button from '@/components/Button';
|
||||
import Button from '@/components/Form/Button';
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||
import {
|
||||
fetchSchoolFileTemplatesFromRegistrationFiles,
|
||||
@ -220,9 +220,7 @@ export default function InscriptionFormShared({
|
||||
.then((data) => {
|
||||
setProfiles(data);
|
||||
})
|
||||
.catch((error) =>
|
||||
logger.error('Error fetching profiles : ', error)
|
||||
);
|
||||
.catch((error) => logger.error('Error fetching profiles : ', error));
|
||||
|
||||
if (selectedEstablishmentId) {
|
||||
// Fetch data for registration payment modes
|
||||
@ -385,7 +383,7 @@ export default function InscriptionFormShared({
|
||||
// Soumission du formulaire
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
// Vérifier si le mode de paiement sélectionné est un prélèvement SEPA
|
||||
const isSepaPayment = formData.isSepa === 1;
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
import RadioList from '@/components/RadioList';
|
||||
import SelectChoice from '@/components/Form/SelectChoice';
|
||||
import RadioList from '@/components/Form/RadioList';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
export default function PaymentMethodSelector({
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import InputText from '@/components/InputText';
|
||||
import InputPhone from '@/components/InputPhone';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import InputPhone from '@/components/Form/InputPhone';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Trash2, Plus, Users } from 'lucide-react';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import InputText from '@/components/InputText';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Trash2, Plus, Users } from 'lucide-react';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import InputText from '@/components/InputText';
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import SelectChoice from '@/components/Form/SelectChoice';
|
||||
import Loader from '@/components/Loader';
|
||||
import { fetchRegisterForm } from '@/app/actions/subscriptionAction';
|
||||
import logger from '@/utils/logger';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
import { User } from 'lucide-react';
|
||||
import FileUpload from '@/components/FileUpload';
|
||||
import FileUpload from '@/components/Form/FileUpload';
|
||||
import { BASE_URL } from '@/utils/Url';
|
||||
import { levels, genders } from '@/utils/constants';
|
||||
|
||||
@ -112,13 +112,10 @@ export default function StudentInfoForm({
|
||||
(field === 'birth_place' &&
|
||||
(!formData.birth_place || formData.birth_place.trim() === '')) ||
|
||||
(field === 'birth_postal_code' &&
|
||||
(
|
||||
!formData.birth_postal_code ||
|
||||
(!formData.birth_postal_code ||
|
||||
String(formData.birth_postal_code).trim() === '' ||
|
||||
isNaN(Number(formData.birth_postal_code)) ||
|
||||
!Number.isInteger(Number(formData.birth_postal_code))
|
||||
)
|
||||
) ||
|
||||
!Number.isInteger(Number(formData.birth_postal_code)))) ||
|
||||
(field === 'address' &&
|
||||
(!formData.address || formData.address.trim() === '')) ||
|
||||
(field === 'attending_physician' &&
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
import ToggleSwitch from '@/components/Form/ToggleSwitch';
|
||||
import SelectChoice from '@/components/Form/SelectChoice';
|
||||
import { BASE_URL } from '@/utils/Url';
|
||||
import {
|
||||
fetchSchoolFileTemplatesFromRegistrationFiles,
|
||||
@ -10,7 +10,7 @@ import {
|
||||
import logger from '@/utils/logger';
|
||||
import { School, CheckCircle, Hourglass, FileText } from 'lucide-react';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
import Button from '@/components/Button';
|
||||
import Button from '@/components/Form/Button';
|
||||
|
||||
export default function ValidateSubscription({
|
||||
studentId,
|
||||
|
||||
@ -2,9 +2,9 @@ import React, { useState, useRef, useCallback } from 'react';
|
||||
import TreeView from '@/components/Structure/Competencies/TreeView';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
import { Award, CheckCircle } from 'lucide-react';
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
import SelectChoice from '@/components/Form/SelectChoice';
|
||||
import CheckBox from '@/components/CheckBox';
|
||||
import Button from '@/components/Button';
|
||||
import Button from '@/components/Form/Button';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import {
|
||||
fetchEstablishmentCompetencies,
|
||||
|
||||
@ -2,10 +2,10 @@ import { Trash2, Edit3, ZoomIn, Users, Check, X, Hand } from 'lucide-react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Table from '@/components/Table';
|
||||
import Popup from '@/components/Popup';
|
||||
import InputText from '@/components/InputText';
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import SelectChoice from '@/components/Form/SelectChoice';
|
||||
import TeacherItem from '@/components/Structure/Configuration/TeacherItem';
|
||||
import MultiSelect from '@/components/MultiSelect';
|
||||
import MultiSelect from '@/components/Form/MultiSelect';
|
||||
import LevelLabel from '@/components/CustomLabels/LevelLabel';
|
||||
import { DndProvider, useDrop } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
|
||||
@ -2,7 +2,7 @@ import { Trash2, Edit3, Check, X, BookOpen } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import Table from '@/components/Table';
|
||||
import Popup from '@/components/Popup';
|
||||
import InputTextWithColorIcon from '@/components/InputTextWithColorIcon';
|
||||
import InputTextWithColorIcon from '@/components/Form/InputTextWithColorIcon';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
||||
|
||||
@ -2,11 +2,11 @@ import React, { useState, useEffect } from 'react';
|
||||
import { Edit3, Trash2, GraduationCap, Check, X, Hand } from 'lucide-react';
|
||||
import Table from '@/components/Table';
|
||||
import Popup from '@/components/Popup';
|
||||
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||
import ToggleSwitch from '@/components/Form/ToggleSwitch';
|
||||
import { useCsrfToken } from '@/context/CsrfContext';
|
||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import InputText from '@/components/InputText';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
|
||||
import TeacherItem from './TeacherItem';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
} from '@/app/actions/registerFileGroupAction';
|
||||
import { DocusealBuilder } from '@docuseal/react';
|
||||
import logger from '@/utils/logger';
|
||||
import MultiSelect from '@/components/MultiSelect'; // Import du composant MultiSelect
|
||||
import MultiSelect from '@/components/Form/MultiSelect'; // Import du composant MultiSelect
|
||||
import { useCsrfToken } from '@/context/CsrfContext';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import Popup from '@/components/Popup';
|
||||
@ -121,7 +121,13 @@ export default function FileUploadDocuSeal({
|
||||
|
||||
guardianDetails.forEach((guardian, index) => {
|
||||
logger.debug('creation du clone avec required : ', is_required);
|
||||
cloneTemplate(templateMaster?.id, guardian.email, is_required, selectedEstablishmentId, apiDocuseal)
|
||||
cloneTemplate(
|
||||
templateMaster?.id,
|
||||
guardian.email,
|
||||
is_required,
|
||||
selectedEstablishmentId,
|
||||
apiDocuseal
|
||||
)
|
||||
.then((clonedDocument) => {
|
||||
// Sauvegarde des schoolFileTemplates clonés dans la base de données
|
||||
const data = {
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Plus, Edit3, Trash2, Check, X, FileText } from 'lucide-react';
|
||||
import Table from '@/components/Table';
|
||||
import InputText from '@/components/InputText';
|
||||
import MultiSelect from '@/components/MultiSelect';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import MultiSelect from '@/components/Form/MultiSelect';
|
||||
import Popup from '@/components/Popup';
|
||||
import logger from '@/utils/logger';
|
||||
import { createRegistrationParentFileTemplate } from '@/app/actions/registerFileGroupAction';
|
||||
import { useCsrfToken } from '@/context/CsrfContext';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
import ToggleSwitch from '@/components/ToggleSwitch';
|
||||
import ToggleSwitch from '@/components/Form/ToggleSwitch';
|
||||
import { useNotification } from '@/context/NotificationContext';
|
||||
import AlertMessage from '@/components/AlertMessage';
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import { Trash2, Edit3, Check, X, Percent, EuroIcon, Tag } from 'lucide-react';
|
||||
import Table from '@/components/Table';
|
||||
import Popup from '@/components/Popup';
|
||||
import CheckBox from '@/components/CheckBox';
|
||||
import InputText from '@/components/InputText';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import logger from '@/utils/logger';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
@ -3,7 +3,7 @@ import { Trash2, Edit3, Check, X, EyeOff, Eye, CreditCard } from 'lucide-react';
|
||||
import Table from '@/components/Table';
|
||||
import Popup from '@/components/Popup';
|
||||
import CheckBox from '@/components/CheckBox';
|
||||
import InputText from '@/components/InputText';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import logger from '@/utils/logger';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
Reference in New Issue
Block a user