mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 15:33:22 +00:00
feat: Ajout des composants manquant dans le FormTemplateBuilder [N3WTS-17]
This commit is contained in:
@ -4,7 +4,7 @@ import Tab from '@/components/Tab';
|
||||
import TabContent from '@/components/TabContent';
|
||||
import Button from '@/components/Form/Button';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import CheckBox from '@/components/CheckBox'; // Import du composant CheckBox
|
||||
import CheckBox from '@/components/Form/CheckBox'; // Import du composant CheckBox
|
||||
import logger from '@/utils/logger';
|
||||
import {
|
||||
fetchSmtpSettings,
|
||||
|
||||
@ -10,7 +10,7 @@ import logger from '@/utils/logger';
|
||||
import { useClasses } from '@/context/ClassesContext';
|
||||
import Button from '@/components/Form/Button';
|
||||
import SelectChoice from '@/components/Form/SelectChoice';
|
||||
import CheckBox from '@/components/CheckBox';
|
||||
import CheckBox from '@/components/Form/CheckBox';
|
||||
import {
|
||||
fetchAbsences,
|
||||
createAbsences,
|
||||
|
||||
@ -10,7 +10,7 @@ import FeesSection from '@/components/Structure/Tarification/FeesSection';
|
||||
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
|
||||
import SectionTitle from '@/components/SectionTitle';
|
||||
import InputPhone from '@/components/Form/InputPhone';
|
||||
import CheckBox from '@/components/CheckBox';
|
||||
import CheckBox from '@/components/Form/CheckBox';
|
||||
import RadioList from '@/components/Form/RadioList';
|
||||
import SelectChoice from '@/components/Form/SelectChoice';
|
||||
import Loader from '@/components/Loader';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import InputTextIcon from './InputTextIcon';
|
||||
import SelectChoice from './SelectChoice';
|
||||
@ -16,23 +16,69 @@ export default function AddFieldModal({
|
||||
}) {
|
||||
const isEditing = editingIndex >= 0;
|
||||
|
||||
const [currentField, setCurrentField] = useState(
|
||||
editingField || {
|
||||
id: '',
|
||||
label: '',
|
||||
type: 'text',
|
||||
required: false,
|
||||
icon: '',
|
||||
options: [],
|
||||
text: '',
|
||||
placeholder: '',
|
||||
}
|
||||
);
|
||||
const [currentField, setCurrentField] = useState({
|
||||
id: '',
|
||||
label: '',
|
||||
type: 'text',
|
||||
required: false,
|
||||
icon: '',
|
||||
options: [],
|
||||
text: '',
|
||||
placeholder: '',
|
||||
acceptTypes: '',
|
||||
maxSize: 5, // 5MB par défaut
|
||||
checked: false,
|
||||
validation: {
|
||||
pattern: '',
|
||||
minLength: '',
|
||||
maxLength: '',
|
||||
},
|
||||
});
|
||||
|
||||
const [showIconPicker, setShowIconPicker] = useState(false);
|
||||
const [newOption, setNewOption] = useState('');
|
||||
|
||||
const { control, handleSubmit, reset } = useForm();
|
||||
const { control, handleSubmit, reset, setValue } = useForm();
|
||||
|
||||
// Mettre à jour l'état et les valeurs du formulaire lorsque editingField change
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
const defaultValues = editingField || {
|
||||
id: '',
|
||||
label: '',
|
||||
type: 'text',
|
||||
required: false,
|
||||
icon: '',
|
||||
options: [],
|
||||
text: '',
|
||||
placeholder: '',
|
||||
acceptTypes: '',
|
||||
maxSize: 5,
|
||||
checked: false,
|
||||
validation: {
|
||||
pattern: '',
|
||||
minLength: '',
|
||||
maxLength: '',
|
||||
},
|
||||
};
|
||||
|
||||
setCurrentField(defaultValues);
|
||||
|
||||
// Réinitialiser le formulaire avec les valeurs de l'élément à éditer
|
||||
reset({
|
||||
type: defaultValues.type,
|
||||
label: defaultValues.label,
|
||||
placeholder: defaultValues.placeholder,
|
||||
required: defaultValues.required,
|
||||
icon: defaultValues.icon,
|
||||
text: defaultValues.text,
|
||||
acceptTypes: defaultValues.acceptTypes,
|
||||
maxSize: defaultValues.maxSize,
|
||||
checked: defaultValues.checked,
|
||||
validation: defaultValues.validation,
|
||||
});
|
||||
}
|
||||
}, [isOpen, editingField, reset]);
|
||||
|
||||
// Ajouter une option au select
|
||||
const addOption = () => {
|
||||
@ -326,6 +372,195 @@ export default function AddFieldModal({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentField.type === 'radio' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Options des boutons radio
|
||||
</label>
|
||||
<div className="flex gap-2 mb-2">
|
||||
<input
|
||||
type="text"
|
||||
value={newOption}
|
||||
onChange={(e) => setNewOption(e.target.value)}
|
||||
placeholder="Nouvelle option"
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
onKeyPress={(e) =>
|
||||
e.key === 'Enter' && (e.preventDefault(), addOption())
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
text="Ajouter"
|
||||
onClick={addOption}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{currentField.options.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between bg-gray-50 p-2 rounded"
|
||||
>
|
||||
<span>{option}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeOption(index)}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentField.type === 'phone' && (
|
||||
<Controller
|
||||
name="validation.pattern"
|
||||
control={control}
|
||||
defaultValue={currentField.validation?.pattern || ''}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<InputTextIcon
|
||||
label="Format de téléphone (optionnel, exemple: ^\\+?[0-9]{10,15}$)"
|
||||
name="phonePattern"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
setCurrentField({
|
||||
...currentField,
|
||||
validation: {
|
||||
...currentField.validation,
|
||||
pattern: e.target.value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentField.type === 'file' && (
|
||||
<>
|
||||
<Controller
|
||||
name="acceptTypes"
|
||||
control={control}
|
||||
defaultValue={currentField.acceptTypes || ''}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<InputTextIcon
|
||||
label="Types de fichiers acceptés (ex: .pdf,.jpg,.png)"
|
||||
name="acceptTypes"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
setCurrentField({
|
||||
...currentField,
|
||||
acceptTypes: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="maxSize"
|
||||
control={control}
|
||||
defaultValue={currentField.maxSize || 5}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<InputTextIcon
|
||||
label="Taille maximale (MB)"
|
||||
name="maxSize"
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
setCurrentField({
|
||||
...currentField,
|
||||
maxSize: parseInt(e.target.value) || 5,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{currentField.type === 'checkbox' && (
|
||||
<>
|
||||
<div className="flex items-center mt-2">
|
||||
<Controller
|
||||
name="checked"
|
||||
control={control}
|
||||
defaultValue={currentField.checked || false}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<input
|
||||
type="checkbox"
|
||||
id="defaultChecked"
|
||||
checked={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.checked);
|
||||
setCurrentField({
|
||||
...currentField,
|
||||
checked: e.target.checked,
|
||||
});
|
||||
}}
|
||||
className="mr-2"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label htmlFor="defaultChecked">Coché par défaut</label>
|
||||
</div>
|
||||
<div className="flex items-center mt-2">
|
||||
<Controller
|
||||
name="horizontal"
|
||||
control={control}
|
||||
defaultValue={currentField.horizontal || false}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<input
|
||||
type="checkbox"
|
||||
id="horizontal"
|
||||
checked={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.checked);
|
||||
setCurrentField({
|
||||
...currentField,
|
||||
horizontal: e.target.checked,
|
||||
});
|
||||
}}
|
||||
className="mr-2"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label htmlFor="horizontal">Label au-dessus (horizontal)</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{currentField.type === 'toggle' && (
|
||||
<div className="flex items-center mt-2">
|
||||
<Controller
|
||||
name="checked"
|
||||
control={control}
|
||||
defaultValue={currentField.checked || false}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<input
|
||||
type="checkbox"
|
||||
id="defaultToggled"
|
||||
checked={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.checked);
|
||||
setCurrentField({
|
||||
...currentField,
|
||||
checked: e.target.checked,
|
||||
});
|
||||
}}
|
||||
className="mr-2"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label htmlFor="defaultToggled">Activé par défaut</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2 mt-6">
|
||||
<Button
|
||||
type="submit"
|
||||
|
||||
@ -7,7 +7,7 @@ const CheckBox = ({
|
||||
handleChange,
|
||||
fieldName,
|
||||
itemLabelFunc = () => null,
|
||||
horizontal,
|
||||
horizontal = false,
|
||||
}) => {
|
||||
// Vérifier si formData[fieldName] est un tableau ou une valeur booléenne
|
||||
const isChecked = Array.isArray(formData[fieldName])
|
||||
@ -22,7 +22,7 @@ const CheckBox = ({
|
||||
{horizontal && (
|
||||
<label
|
||||
htmlFor={`${fieldName}-${item.id}`}
|
||||
className="block text-sm text-center mb-1 font-medium text-gray-700"
|
||||
className="block text-sm text-center mb-1 font-medium text-gray-700 cursor-pointer"
|
||||
>
|
||||
{itemLabelFunc(item)}
|
||||
</label>
|
||||
@ -40,7 +40,7 @@ const CheckBox = ({
|
||||
{!horizontal && (
|
||||
<label
|
||||
htmlFor={`${fieldName}-${item.id}`}
|
||||
className="block text-sm text-center mb-1 font-medium text-gray-700"
|
||||
className="block text-sm font-medium text-gray-700 cursor-pointer"
|
||||
>
|
||||
{itemLabelFunc(item)}
|
||||
</label>
|
||||
@ -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}
|
||||
|
||||
@ -29,13 +29,23 @@ import {
|
||||
Code,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Phone,
|
||||
Radio,
|
||||
ToggleLeft,
|
||||
CheckSquare,
|
||||
FileUp,
|
||||
} from 'lucide-react';
|
||||
|
||||
const FIELD_TYPES_ICON = {
|
||||
text: { icon: TextCursorInput },
|
||||
email: { icon: AtSign },
|
||||
phone: { icon: Phone },
|
||||
date: { icon: Calendar },
|
||||
select: { icon: ChevronDown },
|
||||
radio: { icon: Radio },
|
||||
checkbox: { icon: CheckSquare },
|
||||
toggle: { icon: ToggleLeft },
|
||||
file: { icon: FileUp },
|
||||
textarea: { icon: Type },
|
||||
paragraph: { icon: AlignLeft },
|
||||
heading1: { icon: Heading1 },
|
||||
@ -224,10 +234,22 @@ export default function FormTemplateBuilder() {
|
||||
id: isContentTypeOnly
|
||||
? undefined
|
||||
: generateFieldId(data.label || 'field'),
|
||||
options: data.type === 'select' ? currentField.options : undefined,
|
||||
options: ['select', 'radio'].includes(data.type)
|
||||
? currentField.options
|
||||
: undefined,
|
||||
icon: data.icon || currentField.icon || undefined,
|
||||
placeholder: data.placeholder || undefined,
|
||||
text: isContentTypeOnly ? data.text : undefined,
|
||||
checked: ['checkbox', 'toggle'].includes(data.type)
|
||||
? currentField.checked
|
||||
: undefined,
|
||||
horizontal:
|
||||
data.type === 'checkbox' ? currentField.horizontal : undefined,
|
||||
acceptTypes: data.type === 'file' ? currentField.acceptTypes : undefined,
|
||||
maxSize: data.type === 'file' ? currentField.maxSize : undefined,
|
||||
validation: ['phone', 'email', 'text'].includes(data.type)
|
||||
? currentField.validation
|
||||
: undefined,
|
||||
};
|
||||
|
||||
// Nettoyer les propriétés undefined
|
||||
@ -247,9 +269,7 @@ export default function FormTemplateBuilder() {
|
||||
|
||||
setFormConfig({ ...formConfig, fields: newFields });
|
||||
setEditingIndex(-1);
|
||||
};
|
||||
|
||||
// Modifier un champ existant
|
||||
}; // Modifier un champ existant
|
||||
const editField = (index) => {
|
||||
setEditingIndex(index);
|
||||
setShowAddFieldModal(true);
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
export const FIELD_TYPES = [
|
||||
{ value: 'text', label: 'Texte' },
|
||||
{ value: 'email', label: 'Email' },
|
||||
{ value: 'phone', label: 'Téléphone' },
|
||||
{ value: 'date', label: 'Date' },
|
||||
{ value: 'select', label: 'Liste déroulante' },
|
||||
{ value: 'radio', label: 'Boutons radio' },
|
||||
{ value: 'checkbox', label: 'Case à cocher' },
|
||||
{ value: 'toggle', label: 'Interrupteur' },
|
||||
{ value: 'file', label: 'Upload de fichier' },
|
||||
{ value: 'textarea', label: 'Zone de texte riche' },
|
||||
{ value: 'paragraph', label: 'Paragraphe' },
|
||||
{ value: 'heading1', label: 'Titre 1' },
|
||||
|
||||
@ -4,7 +4,7 @@ import Table from '@/components/Table';
|
||||
import Popup from '@/components/Popup';
|
||||
import logger from '@/utils/logger';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import CheckBox from '@/components/CheckBox';
|
||||
import CheckBox from '@/components/Form/CheckBox';
|
||||
|
||||
const paymentPlansOptions = [
|
||||
{ id: 1, name: '1 fois', frequency: 1 },
|
||||
|
||||
@ -3,7 +3,7 @@ import TreeView from '@/components/Structure/Competencies/TreeView';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
import { Award, CheckCircle } from 'lucide-react';
|
||||
import SelectChoice from '@/components/Form/SelectChoice';
|
||||
import CheckBox from '@/components/CheckBox';
|
||||
import CheckBox from '@/components/Form/CheckBox';
|
||||
import Button from '@/components/Form/Button';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
import {
|
||||
|
||||
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||
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 CheckBox from '@/components/Form/CheckBox';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import logger from '@/utils/logger';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
|
||||
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||
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 CheckBox from '@/components/Form/CheckBox';
|
||||
import InputText from '@/components/Form/InputText';
|
||||
import logger from '@/utils/logger';
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
|
||||
Reference in New Issue
Block a user