mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
1247 lines
42 KiB
JavaScript
1247 lines
42 KiB
JavaScript
'use client';
|
|
|
|
import React, { useState, useRef, useEffect } from 'react';
|
|
import { User, Mail } from 'lucide-react';
|
|
import InputTextIcon from '@/components/InputTextIcon';
|
|
import ToggleSwitch from '@/components/ToggleSwitch';
|
|
import Button from '@/components/Button';
|
|
import Table from '@/components/Table';
|
|
import FeesSection from '@/components/Structure/Tarification/FeesSection';
|
|
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
|
|
import SectionTitle from '@/components/SectionTitle';
|
|
import InputPhone from '@/components/InputPhone';
|
|
import CheckBox from '@/components/CheckBox';
|
|
import RadioList from '@/components/RadioList';
|
|
import SelectChoice from '@/components/SelectChoice';
|
|
import Loader from '@/components/Loader';
|
|
import { getCurrentSchoolYear, getNextSchoolYear } from '@/utils/Date';
|
|
import logger from '@/utils/logger';
|
|
import { levels, genders } from '@/utils/constants';
|
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
|
import { useSearchParams, useRouter } from 'next/navigation';
|
|
import {
|
|
fetchRegisterForm,
|
|
fetchStudents,
|
|
createRegisterForm,
|
|
editRegisterForm,
|
|
} from '@/app/actions/subscriptionAction';
|
|
import {
|
|
fetchRegistrationDiscounts,
|
|
fetchTuitionDiscounts,
|
|
fetchRegistrationFees,
|
|
fetchTuitionFees,
|
|
} from '@/app/actions/schoolAction';
|
|
import {
|
|
fetchRegistrationFileGroups,
|
|
fetchRegistrationSchoolFileMasters,
|
|
fetchRegistrationParentFileMasters,
|
|
cloneTemplate,
|
|
createRegistrationSchoolFileTemplate,
|
|
createRegistrationParentFileTemplate,
|
|
} from '@/app/actions/registerFileGroupAction';
|
|
import { fetchProfiles } from '@/app/actions/authAction';
|
|
import { useClasses } from '@/context/ClassesContext';
|
|
import { useCsrfToken } from '@/context/CsrfContext';
|
|
import { FE_ADMIN_SUBSCRIPTIONS_URL, BASE_URL } from '@/utils/Url';
|
|
import { useNotification } from '@/context/NotificationContext';
|
|
|
|
export default function CreateSubscriptionPage() {
|
|
const [formData, setFormData] = useState({
|
|
studentLastName: '',
|
|
studentFirstName: '',
|
|
studentLevel: '',
|
|
studentGender: '',
|
|
guardianLastName: '',
|
|
guardianFirstName: '',
|
|
guardianEmail: '',
|
|
guardianPhone: '',
|
|
guardianProfileRole: '',
|
|
selectedGuardians: [],
|
|
associatedGuardians: [],
|
|
autoMail: false,
|
|
selectedRegistrationDiscounts: [],
|
|
selectedRegistrationFees: [],
|
|
selectedTuitionDiscounts: [],
|
|
selectedTuitionFees: [],
|
|
selectedFileGroup: null,
|
|
schoolYear: getCurrentSchoolYear(),
|
|
});
|
|
|
|
const searchParams = useSearchParams();
|
|
const { showNotification } = useNotification();
|
|
// Si l'ID est valorisé, alors on est en mode édition
|
|
const registerFormID = searchParams.get('id');
|
|
const registerFormMoment = searchParams.get('school_year');
|
|
|
|
const [students, setStudents] = useState([]);
|
|
const [registrationDiscounts, setRegistrationDiscounts] = useState([]);
|
|
const [tuitionDiscounts, setTuitionDiscounts] = useState([]);
|
|
const [registrationFees, setRegistrationFees] = useState([]);
|
|
const [tuitionFees, setTuitionFees] = useState([]);
|
|
const [groups, setGroups] = useState([]);
|
|
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
|
|
const [parentFileMasters, setParentFileMasters] = useState([]);
|
|
const [profiles, setProfiles] = useState([]);
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const [existingGuardians, setExistingGuardians] = useState([]);
|
|
const [totalRegistrationAmount, setTotalRegistrationAmount] = useState(0);
|
|
const [totalTuitionAmount, setTotalTuitionAmount] = useState(0);
|
|
const [selectedStudent, setSelectedEleve] = useState(null);
|
|
const [isNewResponsable, setIsNewResponsable] = useState(true);
|
|
|
|
const [initialGuardianEmail, setInitialGuardianEmail] = useState('');
|
|
|
|
const { getNiveauLabel } = useClasses();
|
|
|
|
const formDataRef = useRef(formData);
|
|
const { selectedEstablishmentId, apiDocuseal } = useEstablishment();
|
|
|
|
const csrfToken = useCsrfToken();
|
|
const router = useRouter();
|
|
|
|
const isSubmitDisabled = () => {
|
|
// Vérifie si les champs requis sont remplis
|
|
const requiredFields = [
|
|
'schoolYear',
|
|
'studentLastName',
|
|
'studentFirstName',
|
|
];
|
|
const hasErrors = requiredFields.some(
|
|
(field) => getLocalError(field) !== ''
|
|
);
|
|
|
|
// Vérifie si un groupe de fichiers est sélectionné
|
|
const isFileGroupSelected = formData.selectedFileGroup !== null;
|
|
|
|
// Vérifie si au moins un frais de scolarité est sélectionné
|
|
const hasSelectedTuitionFees = formData.selectedTuitionFees.length > 0;
|
|
|
|
// Vérifie les conditions spécifiques pour le responsable
|
|
const isGuardianValid = isNewResponsable
|
|
? getLocalError('guardianEmail') === ''
|
|
: formData.selectedGuardians.length > 0;
|
|
|
|
// Retourne true si une des conditions n'est pas remplie
|
|
return (
|
|
hasErrors ||
|
|
!isFileGroupSelected ||
|
|
!hasSelectedTuitionFees ||
|
|
!isGuardianValid
|
|
);
|
|
};
|
|
|
|
const getLocalError = (field) => {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
|
|
if (field === 'guardianEmail') {
|
|
if (!formData.guardianEmail || formData.guardianEmail.trim() === '') {
|
|
return 'Champs requis';
|
|
}
|
|
|
|
if (!emailRegex.test(formData.guardianEmail)) {
|
|
return 'Email invalide';
|
|
}
|
|
|
|
// Vérifiez si l'email existe déjà, mais ne mettez pas à jour `formData` ici
|
|
const existingGuardian = students
|
|
.flatMap((student) => student.guardians)
|
|
.find(
|
|
(guardian) =>
|
|
guardian.associated_profile_email === formData.guardianEmail
|
|
);
|
|
|
|
if (existingGuardian) {
|
|
return ''; // Pas d'erreur, mais l'email existe déjà
|
|
}
|
|
}
|
|
|
|
if (
|
|
// Student Form
|
|
(field === 'studentLastName' &&
|
|
(!formData.studentLastName ||
|
|
formData.studentLastName.trim() === '')) ||
|
|
(field === 'studentFirstName' &&
|
|
(!formData.studentFirstName ||
|
|
formData.studentFirstName.trim() === '')) ||
|
|
(field === 'schoolYear' &&
|
|
(!formData.schoolYear || formData.schoolYear.trim() === ''))
|
|
) {
|
|
return 'Champs requis';
|
|
}
|
|
return '';
|
|
};
|
|
|
|
const requestErrorHandler = (err) => {
|
|
logger.error('Error fetching data:', err);
|
|
//setErrors(err);
|
|
};
|
|
|
|
useEffect(() => {
|
|
formDataRef.current = formData;
|
|
}, [formData]);
|
|
|
|
useEffect(() => {
|
|
if (!formData.guardianEmail) {
|
|
// Si l'email est vide, réinitialiser existingProfileId et existingProfileInSchool
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
isExistingParentProfile: false,
|
|
existingProfileId: null,
|
|
existingProfileInSchool: false,
|
|
associatedGuardians: [],
|
|
}));
|
|
return;
|
|
}
|
|
|
|
// Vérifiez si le profil existe dans la liste des profils
|
|
const existingProfile = profiles.find(
|
|
(profile) => profile.email === formData.guardianEmail
|
|
);
|
|
|
|
if (existingProfile) {
|
|
// Vérifiez si le profil parent est associé à l'établissement sélectionné
|
|
const isInSchool = existingProfile.roles.some(
|
|
(role) =>
|
|
role.role_type === 2 && role.establishment === selectedEstablishmentId
|
|
);
|
|
|
|
// Récupérer l'ID de l'id_associated_person si applicable
|
|
const associatedPersonId = existingProfile.roles.find(
|
|
(role) =>
|
|
role.role_type === 2 && role.establishment === selectedEstablishmentId
|
|
)?.id_associated_person;
|
|
|
|
// Mettre à jour les variables en fonction des résultats
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
isExistingParentProfile: true,
|
|
existingProfileId: existingProfile.id, // Récupérer l'ID du profil associé
|
|
existingProfileInSchool: isInSchool, // Vérifie si le profil est dans l'établissement
|
|
guardianEmail: existingProfile.email || '',
|
|
associatedGuardians: associatedPersonId
|
|
? [associatedPersonId] // Ajouter l'ID de l'id_associated_person si trouvé
|
|
: [],
|
|
}));
|
|
} else {
|
|
// Si aucun profil avec cet email n'existe, réinitialiser les champs
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
isExistingParentProfile: false,
|
|
existingProfileId: null,
|
|
existingProfileInSchool: false,
|
|
associatedGuardians: [],
|
|
}));
|
|
}
|
|
}, [formData.guardianEmail, profiles, selectedEstablishmentId]);
|
|
|
|
useEffect(() => {
|
|
fetchProfiles()
|
|
.then((data) => {
|
|
setProfiles(data);
|
|
})
|
|
.catch(requestErrorHandler);
|
|
if (selectedEstablishmentId) {
|
|
if (registerFormID) {
|
|
fetchRegisterForm(registerFormID)
|
|
.then((data) => {
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
studentLastName: data?.student?.last_name || '',
|
|
studentFirstName: data?.student?.first_name || '',
|
|
studentLevel: data?.student?.level || '',
|
|
studentGender: data?.student?.gender || '',
|
|
guardianLastName: data?.student?.guardians[0]?.last_name || '',
|
|
guardianFirstName: data?.student?.guardians[0]?.first_name || '',
|
|
guardianEmail:
|
|
data?.student?.guardians[0]?.associated_profile_email || '',
|
|
guardianPhone: data?.student?.guardians[0]?.phone || '',
|
|
selectedFileGroup: data?.fileGroup || '',
|
|
schoolYear: data?.school_year || '',
|
|
guardianProfileRole:
|
|
data?.student?.guardians[0]?.profile_role || '',
|
|
}));
|
|
setIsNewResponsable(
|
|
!Array.isArray(data?.student?.guardians) ||
|
|
data.student.guardians.length <= 1
|
|
);
|
|
// Définir l'email initial
|
|
setInitialGuardianEmail(
|
|
data?.student?.guardians[0]?.associated_profile_email || ''
|
|
);
|
|
})
|
|
.catch(requestErrorHandler);
|
|
}
|
|
|
|
fetchStudents(selectedEstablishmentId)
|
|
.then((studentsData) => {
|
|
setStudents(studentsData);
|
|
})
|
|
.catch(requestErrorHandler);
|
|
|
|
fetchRegistrationDiscounts(selectedEstablishmentId)
|
|
.then((data) => {
|
|
setRegistrationDiscounts(data);
|
|
})
|
|
.catch(requestErrorHandler);
|
|
fetchTuitionDiscounts(selectedEstablishmentId)
|
|
.then((data) => {
|
|
setTuitionDiscounts(data);
|
|
})
|
|
.catch(requestErrorHandler);
|
|
fetchRegistrationFees(selectedEstablishmentId)
|
|
.then((data) => {
|
|
setRegistrationFees(data);
|
|
// Sélectionner par défaut les frais d'inscription
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
selectedRegistrationFees: data.map((fee) => fee.id),
|
|
}));
|
|
})
|
|
.catch(requestErrorHandler);
|
|
|
|
fetchTuitionFees(selectedEstablishmentId)
|
|
.then((data) => {
|
|
setTuitionFees(data);
|
|
})
|
|
.catch(requestErrorHandler);
|
|
|
|
fetchRegistrationFileGroups(selectedEstablishmentId)
|
|
.then((data) => {
|
|
setGroups(data);
|
|
})
|
|
.catch(requestErrorHandler);
|
|
|
|
fetchRegistrationSchoolFileMasters()
|
|
.then((data) => {
|
|
setSchoolFileMasters(data);
|
|
})
|
|
.catch(requestErrorHandler);
|
|
|
|
fetchRegistrationParentFileMasters()
|
|
.then((data) => {
|
|
setParentFileMasters(data);
|
|
})
|
|
.catch(requestErrorHandler);
|
|
}
|
|
}, [selectedEstablishmentId]);
|
|
|
|
useEffect(() => {
|
|
if (registrationFees.length > 0) {
|
|
const defaultSelectedFees = registrationFees.map((fee) => fee.id);
|
|
const totalAmount = calculateFinalRegistrationAmount(
|
|
defaultSelectedFees,
|
|
formData.selectedRegistrationDiscounts
|
|
);
|
|
setTotalRegistrationAmount(totalAmount);
|
|
}
|
|
}, [registrationFees]);
|
|
|
|
const handleChange = (e) => {
|
|
const { name, value } = e.target;
|
|
setFormData((prevState) => ({
|
|
...prevState,
|
|
[name]: value,
|
|
}));
|
|
};
|
|
|
|
const resetGuardianFields = () => {
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
guardianLastName: '',
|
|
guardianFirstName: '',
|
|
guardianEmail: '',
|
|
guardianPhone: '',
|
|
selectedGuardians: [],
|
|
}));
|
|
};
|
|
|
|
// Gestion du changement du toggle
|
|
const handleToggleChange = (e) => {
|
|
const isChecked = e.target.checked;
|
|
setIsNewResponsable(isChecked);
|
|
|
|
if (isChecked) {
|
|
// Réinitialiser les champs si on passe à "Nouveau responsable"
|
|
resetGuardianFields();
|
|
}
|
|
};
|
|
|
|
const submit = () => {
|
|
logger.debug('formData:', formDataRef.current);
|
|
|
|
const selectedRegistrationFeesIds =
|
|
formDataRef.current.selectedRegistrationFees.map((feeId) => feeId);
|
|
const selectedRegistrationDiscountsIds =
|
|
formDataRef.current.selectedRegistrationDiscounts.map(
|
|
(discountId) => discountId
|
|
);
|
|
const selectedTuitionFeesIds = formDataRef.current.selectedTuitionFees.map(
|
|
(feeId) => feeId
|
|
);
|
|
const selectedTuitionDiscountsIds =
|
|
formDataRef.current.selectedTuitionDiscounts.map(
|
|
(discountId) => discountId
|
|
);
|
|
const selectedFileGroup = formDataRef.current.selectedFileGroup;
|
|
|
|
const allFeesIds = [
|
|
...selectedRegistrationFeesIds,
|
|
...selectedTuitionFeesIds,
|
|
];
|
|
const allDiscountsIds = [
|
|
...selectedRegistrationDiscountsIds,
|
|
...selectedTuitionDiscountsIds,
|
|
];
|
|
|
|
// Vérifiez si le profil existe dans la liste des profils
|
|
const existingProfile = profiles.find(
|
|
(profile) => profile.id === formDataRef.current.existingProfileId
|
|
);
|
|
|
|
const guardians = (() => {
|
|
if (formDataRef.current.selectedGuardians.length > 0) {
|
|
// Cas 3 : Des guardians sont sélectionnés
|
|
logger.debug('Cas 3 : Des guardians sont sélectionnés');
|
|
return formDataRef.current.selectedGuardians.map((guardianId) => ({
|
|
id: guardianId,
|
|
}));
|
|
} else if (formDataRef.current.isExistingParentProfile) {
|
|
if (initialGuardianEmail !== existingProfile?.email) {
|
|
// Cas 2 : Profil existant différent de l'ancien
|
|
logger.debug(
|
|
"Cas 2 : Profil existant différent de l'ancien, mise à jour du profil",
|
|
{
|
|
existingProfile,
|
|
guardianEmail: formDataRef.current.guardianEmail,
|
|
}
|
|
);
|
|
return [
|
|
{
|
|
profile_role_data: {
|
|
establishment: selectedEstablishmentId,
|
|
role_type: 2,
|
|
is_active: true,
|
|
profile: formDataRef.current.existingProfileId,
|
|
},
|
|
last_name: formDataRef.current.guardianLastName,
|
|
first_name: formDataRef.current.guardianFirstName,
|
|
phone: formDataRef.current.guardianPhone,
|
|
},
|
|
];
|
|
} else {
|
|
// Cas 4 : Profil existant avec le même email
|
|
logger.debug('Cas 4 : Profil existant avec le même email', {
|
|
existingProfile,
|
|
});
|
|
return [
|
|
{
|
|
profile_role_data: {
|
|
establishment: selectedEstablishmentId,
|
|
role_type: 2,
|
|
is_active: true,
|
|
profile: formDataRef.current.existingProfileId,
|
|
},
|
|
last_name: formDataRef.current.guardianLastName,
|
|
first_name: formDataRef.current.guardianFirstName,
|
|
phone: formDataRef.current.guardianPhone,
|
|
},
|
|
];
|
|
}
|
|
} else {
|
|
// Cas 1 : Profil inexistant
|
|
logger.debug("Cas 1 : Profil inexistant, création d'un nouveau profil");
|
|
return [
|
|
{
|
|
profile_role_data: {
|
|
establishment: selectedEstablishmentId,
|
|
role_type: 2,
|
|
is_active: false,
|
|
profile_data: {
|
|
email: formDataRef.current.guardianEmail,
|
|
password: 'Provisoire01!',
|
|
username: formDataRef.current.guardianEmail,
|
|
},
|
|
},
|
|
last_name: formDataRef.current.guardianLastName,
|
|
first_name: formDataRef.current.guardianFirstName,
|
|
birth_date: formDataRef.current.guardianBirthDate,
|
|
phone: formDataRef.current.guardianPhone,
|
|
},
|
|
];
|
|
}
|
|
})();
|
|
|
|
const data = {
|
|
student: {
|
|
last_name: formDataRef.current.studentLastName,
|
|
first_name: formDataRef.current.studentFirstName,
|
|
...(formDataRef.current.studentLevel && {
|
|
level: formDataRef.current.studentLevel,
|
|
}),
|
|
...(formDataRef.current.studentGender && {
|
|
gender: formDataRef.current.studentGender,
|
|
}),
|
|
guardians,
|
|
sibling: [],
|
|
},
|
|
fees: allFeesIds,
|
|
discounts: allDiscountsIds,
|
|
fileGroup: selectedFileGroup,
|
|
establishment: selectedEstablishmentId,
|
|
school_year: formDataRef.current.schoolYear,
|
|
};
|
|
|
|
setIsLoading(true);
|
|
if (registerFormID) {
|
|
const formData = new FormData();
|
|
|
|
// Ajouter les données JSON sous forme de chaîne
|
|
formData.append('data', JSON.stringify(data));
|
|
// Mode édition
|
|
editRegisterForm(registerFormID, formData, csrfToken)
|
|
.then((response) => {
|
|
logger.debug('Dossier mis à jour avec succès:', response);
|
|
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
|
})
|
|
.catch((error) => {
|
|
setIsLoading(false);
|
|
logger.error('Erreur lors de la mise à jour du dossier:', error);
|
|
showNotification(
|
|
"Erreur lors de la mise à jour du dossier d'inscription",
|
|
'error',
|
|
'Erreur',
|
|
'ERR_ADM_SUB_06'
|
|
);
|
|
});
|
|
} else {
|
|
// Création du dossier d'inscription
|
|
createRegisterForm(data, csrfToken)
|
|
.then((data) => {
|
|
// Clonage des schoolFileTemplates
|
|
const masters = schoolFileMasters.filter((file) =>
|
|
file.groups.includes(selectedFileGroup)
|
|
);
|
|
const parentMasters = parentFileMasters.filter((file) =>
|
|
file.groups.includes(selectedFileGroup)
|
|
);
|
|
|
|
const clonePromises = masters.map((templateMaster) =>
|
|
cloneTemplate(
|
|
templateMaster.id,
|
|
formDataRef.current.guardianEmail,
|
|
templateMaster.is_required,
|
|
selectedEstablishmentId,
|
|
apiDocuseal
|
|
)
|
|
.then((clonedDocument) => {
|
|
const cloneData = {
|
|
name: `${templateMaster.name}_${formDataRef.current.studentFirstName}_${formDataRef.current.studentLastName}`,
|
|
slug: clonedDocument.slug,
|
|
id: clonedDocument.id,
|
|
master: templateMaster.id,
|
|
registration_form: data.student.id,
|
|
};
|
|
|
|
return createRegistrationSchoolFileTemplate(
|
|
cloneData,
|
|
csrfToken
|
|
)
|
|
.then((response) =>
|
|
logger.debug('Template enregistré avec succès:', response)
|
|
)
|
|
.catch((error) => {
|
|
setIsLoading(false);
|
|
logger.error(
|
|
"Erreur lors de l'enregistrement du template:",
|
|
error
|
|
);
|
|
showNotification(
|
|
"Erreur lors de la création du dossier d'inscription",
|
|
'error',
|
|
'Erreur',
|
|
'ERR_ADM_SUB_03'
|
|
);
|
|
});
|
|
})
|
|
.catch((error) => {
|
|
setIsLoading(false);
|
|
logger.error('Error during cloning or sending:', error);
|
|
showNotification(
|
|
"Erreur lors de la création du dossier d'inscription",
|
|
'error',
|
|
'Erreur',
|
|
'ERR_ADM_SUB_05'
|
|
);
|
|
})
|
|
);
|
|
|
|
// Clonage des parentFileTemplates
|
|
const parentClonePromises = parentMasters.map((parentMaster) => {
|
|
const parentTemplateData = {
|
|
master: parentMaster.id,
|
|
registration_form: data.student.id,
|
|
};
|
|
|
|
return createRegistrationParentFileTemplate(
|
|
parentTemplateData,
|
|
csrfToken
|
|
)
|
|
.then((response) =>
|
|
logger.debug(
|
|
'Parent template enregistré avec succès:',
|
|
response
|
|
)
|
|
)
|
|
.catch((error) => {
|
|
setIsLoading(false);
|
|
logger.error(
|
|
"Erreur lors de l'enregistrement du parent template:",
|
|
error
|
|
);
|
|
showNotification(
|
|
"Erreur lors de la création du dossier d'inscription",
|
|
'error',
|
|
'Erreur',
|
|
'ERR_ADM_SUB_02'
|
|
);
|
|
});
|
|
});
|
|
|
|
// Attendre que tous les clones soient créés
|
|
Promise.all([...clonePromises, ...parentClonePromises])
|
|
.then(() => {
|
|
// Redirection après succès
|
|
router.push(FE_ADMIN_SUBSCRIPTIONS_URL);
|
|
})
|
|
.catch((error) => {
|
|
setIsLoading(false);
|
|
showNotification(
|
|
"Erreur lors de la création du dossier d'inscription",
|
|
'error',
|
|
'Erreur',
|
|
'ERR_ADM_SUB_04'
|
|
);
|
|
logger.error('Error during cloning or sending:', error);
|
|
});
|
|
})
|
|
.catch((error) => {
|
|
setIsLoading(false);
|
|
showNotification(
|
|
"Erreur lors de la création du dossier d'inscription",
|
|
'error',
|
|
'Erreur',
|
|
'ERR_ADM_SUB_01'
|
|
);
|
|
logger.error('Error during register form creation:', error);
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleEleveSelection = (student) => {
|
|
setSelectedEleve(student);
|
|
setExistingGuardians(student.guardians);
|
|
};
|
|
|
|
// Gestion des changements dans la checkbox
|
|
const handleGuardianCheckboxChange = (guardian) => {
|
|
setFormData((prevData) => {
|
|
const isSelected = prevData.selectedGuardians.includes(guardian.id);
|
|
const updatedSelectedGuardians = isSelected
|
|
? prevData.selectedGuardians.filter((id) => id !== guardian.id)
|
|
: [...prevData.selectedGuardians, guardian.id];
|
|
|
|
return {
|
|
...prevData,
|
|
selectedGuardians: updatedSelectedGuardians,
|
|
guardianEmail: guardian.associated_profile_email,
|
|
};
|
|
});
|
|
};
|
|
|
|
// Gestion de la désélection d'un élève
|
|
const handleEleveDeselection = () => {
|
|
setSelectedEleve(null);
|
|
setExistingGuardians([]);
|
|
resetGuardianFields();
|
|
};
|
|
|
|
const handleRegistrationFeeSelection = (feeId) => {
|
|
setFormData((prevData) => {
|
|
const selectedRegistrationFees =
|
|
prevData.selectedRegistrationFees.includes(feeId)
|
|
? prevData.selectedRegistrationFees.filter((id) => id !== feeId)
|
|
: [...prevData.selectedRegistrationFees, feeId];
|
|
const finalAmount = calculateFinalRegistrationAmount(
|
|
selectedRegistrationFees,
|
|
prevData.selectedRegistrationDiscounts
|
|
);
|
|
setTotalRegistrationAmount(finalAmount);
|
|
return { ...prevData, selectedRegistrationFees };
|
|
});
|
|
};
|
|
|
|
const handleTuitionFeeSelection = (feeId) => {
|
|
setFormData((prevData) => {
|
|
const selectedTuitionFees = prevData.selectedTuitionFees.includes(feeId)
|
|
? prevData.selectedTuitionFees.filter((id) => id !== feeId)
|
|
: [...prevData.selectedTuitionFees, feeId];
|
|
const finalAmount = calculateFinalTuitionAmount(
|
|
selectedTuitionFees,
|
|
prevData.selectedTuitionDiscounts
|
|
);
|
|
setTotalTuitionAmount(finalAmount);
|
|
return { ...prevData, selectedTuitionFees };
|
|
});
|
|
};
|
|
|
|
const handleRegistrationDiscountSelection = (discountId) => {
|
|
setFormData((prevData) => {
|
|
const selectedRegistrationDiscounts =
|
|
prevData.selectedRegistrationDiscounts.includes(discountId)
|
|
? prevData.selectedRegistrationDiscounts.filter(
|
|
(id) => id !== discountId
|
|
)
|
|
: [...prevData.selectedRegistrationDiscounts, discountId];
|
|
const finalAmount = calculateFinalRegistrationAmount(
|
|
prevData.selectedRegistrationFees,
|
|
selectedRegistrationDiscounts
|
|
);
|
|
setTotalRegistrationAmount(finalAmount);
|
|
return { ...prevData, selectedRegistrationDiscounts };
|
|
});
|
|
};
|
|
|
|
const handleTuitionDiscountSelection = (discountId) => {
|
|
setFormData((prevData) => {
|
|
const selectedTuitionDiscounts =
|
|
prevData.selectedTuitionDiscounts.includes(discountId)
|
|
? prevData.selectedTuitionDiscounts.filter((id) => id !== discountId)
|
|
: [...prevData.selectedTuitionDiscounts, discountId];
|
|
const finalAmount = calculateFinalTuitionAmount(
|
|
prevData.selectedTuitionFees,
|
|
selectedTuitionDiscounts
|
|
);
|
|
setTotalTuitionAmount(finalAmount);
|
|
return { ...prevData, selectedTuitionDiscounts };
|
|
});
|
|
};
|
|
|
|
const calculateFinalRegistrationAmount = (
|
|
selectedRegistrationFees,
|
|
selectedRegistrationDiscounts
|
|
) => {
|
|
const totalFees = selectedRegistrationFees.reduce((sum, feeId) => {
|
|
const fee = registrationFees.find((f) => f.id === feeId);
|
|
if (fee && !isNaN(parseFloat(fee.base_amount))) {
|
|
return sum + parseFloat(fee.base_amount);
|
|
}
|
|
return sum;
|
|
}, 0);
|
|
|
|
const totalDiscounts = selectedRegistrationDiscounts.reduce(
|
|
(sum, discountId) => {
|
|
const discount = registrationDiscounts.find((d) => d.id === discountId);
|
|
if (discount) {
|
|
if (
|
|
discount.discount_type === 0 &&
|
|
!isNaN(parseFloat(discount.amount))
|
|
) {
|
|
// Currency
|
|
return sum + parseFloat(discount.amount);
|
|
} else if (
|
|
discount.discount_type === 1 &&
|
|
!isNaN(parseFloat(discount.amount))
|
|
) {
|
|
// Percent
|
|
return sum + (totalFees * parseFloat(discount.amount)) / 100;
|
|
}
|
|
}
|
|
return sum;
|
|
},
|
|
0
|
|
);
|
|
|
|
const finalAmount = totalFees - totalDiscounts;
|
|
|
|
return finalAmount.toFixed(2);
|
|
};
|
|
|
|
const calculateFinalTuitionAmount = (
|
|
selectedTuitionFees,
|
|
selectedTuitionDiscounts
|
|
) => {
|
|
const totalFees = selectedTuitionFees.reduce((sum, feeId) => {
|
|
const fee = tuitionFees.find((f) => f.id === feeId);
|
|
if (fee && !isNaN(parseFloat(fee.base_amount))) {
|
|
return sum + parseFloat(fee.base_amount);
|
|
}
|
|
return sum;
|
|
}, 0);
|
|
|
|
const totalDiscounts = selectedTuitionDiscounts.reduce(
|
|
(sum, discountId) => {
|
|
const discount = tuitionDiscounts.find((d) => d.id === discountId);
|
|
if (discount) {
|
|
if (
|
|
discount.discount_type === 0 &&
|
|
!isNaN(parseFloat(discount.amount))
|
|
) {
|
|
// Currency
|
|
return sum + parseFloat(discount.amount);
|
|
} else if (
|
|
discount.discount_type === 1 &&
|
|
!isNaN(parseFloat(discount.amount))
|
|
) {
|
|
// Percent
|
|
return sum + (totalFees * parseFloat(discount.amount)) / 100;
|
|
}
|
|
}
|
|
return sum;
|
|
},
|
|
0
|
|
);
|
|
|
|
const finalAmount = totalFees - totalDiscounts;
|
|
|
|
return finalAmount.toFixed(2);
|
|
};
|
|
|
|
if (isLoading === true) {
|
|
return <Loader />; // Affichez le composant Loader
|
|
}
|
|
|
|
return (
|
|
<div className="mx-auto p-12 space-y-12">
|
|
{registerFormID ? (
|
|
<h1 className="text-2xl font-bold">
|
|
Modifier un dossier d'inscription
|
|
</h1>
|
|
) : (
|
|
<h1 className="text-2xl font-bold">
|
|
Créer un dossier d'inscription
|
|
</h1>
|
|
)}
|
|
|
|
{/* Sélection de l'année scolaire */}
|
|
<div className="grid grid-cols-1 md:grid-cols-5 gap-8">
|
|
<div>
|
|
<SelectChoice
|
|
name="schoolYear"
|
|
label="Année scolaire"
|
|
placeHolder="Sélectionnez une année scolaire"
|
|
choices={[
|
|
{ value: getCurrentSchoolYear(), label: getCurrentSchoolYear() },
|
|
{ value: getNextSchoolYear(), label: getNextSchoolYear() },
|
|
]}
|
|
selected={formData.schoolYear}
|
|
callback={(e) =>
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
schoolYear: e.target.value,
|
|
}))
|
|
}
|
|
errorLocalMsg={getLocalError('schoolYear')}
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Informations sur l'élève */}
|
|
<SectionTitle title="Elève">
|
|
{/* Nom et Prénom côte à côte */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
<InputTextIcon
|
|
name="studentLastName"
|
|
type="text"
|
|
IconItem={User}
|
|
label="Nom de l'élève"
|
|
placeholder="Nom de l'élève"
|
|
value={formData.studentLastName}
|
|
onChange={handleChange}
|
|
errorLocalMsg={getLocalError('studentLastName')}
|
|
required
|
|
/>
|
|
<InputTextIcon
|
|
name="studentFirstName"
|
|
type="text"
|
|
IconItem={User}
|
|
label="Prénom de l'élève"
|
|
placeholder="Prénom de l'élève"
|
|
value={formData.studentFirstName}
|
|
onChange={handleChange}
|
|
errorLocalMsg={getLocalError('studentFirstName')}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
{/* Genre et Niveau côte à côte */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mt-4">
|
|
<SelectChoice
|
|
name="studentGender"
|
|
label="Genre"
|
|
placeHolder="Sélectionnez un genre"
|
|
choices={genders}
|
|
selected={formData.studentGender}
|
|
callback={(e) =>
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
studentGender: e.target.value,
|
|
}))
|
|
}
|
|
/>
|
|
<SelectChoice
|
|
name="studentLevel"
|
|
label="Niveau"
|
|
placeHolder="Sélectionnez un niveau"
|
|
choices={levels}
|
|
selected={formData.studentLevel}
|
|
callback={(e) =>
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
studentLevel: e.target.value,
|
|
}))
|
|
}
|
|
/>
|
|
</div>
|
|
</SectionTitle>
|
|
|
|
{/* Informations sur le responsable */}
|
|
<SectionTitle title="Responsable principal">
|
|
<ToggleSwitch
|
|
name="isNewResponsable"
|
|
label="Nouveau responsable ?"
|
|
checked={isNewResponsable}
|
|
onChange={handleToggleChange}
|
|
/>
|
|
|
|
{/* Nom et Prénom côte à côte */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mt-8">
|
|
<InputTextIcon
|
|
name="guardianLastName"
|
|
type="text"
|
|
IconItem={User}
|
|
label="Nom du responsable"
|
|
placeholder="Nom du responsable"
|
|
value={formData.guardianLastName}
|
|
onChange={handleChange}
|
|
enable={isNewResponsable}
|
|
/>
|
|
<InputTextIcon
|
|
name="guardianFirstName"
|
|
type="text"
|
|
IconItem={User}
|
|
label="Prénom du responsable"
|
|
placeholder="Prénom du responsable"
|
|
value={formData.guardianFirstName}
|
|
onChange={handleChange}
|
|
enable={isNewResponsable}
|
|
/>
|
|
</div>
|
|
|
|
{/* Email et Numéro de téléphone côte à côte */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mt-4">
|
|
<InputTextIcon
|
|
name="guardianEmail"
|
|
type="email"
|
|
IconItem={Mail}
|
|
label="Email du responsable"
|
|
placeholder="Email du responsable"
|
|
value={formData.guardianEmail}
|
|
onChange={handleChange}
|
|
errorMsg={getLocalError('guardianEmail') && isNewResponsable}
|
|
required
|
|
enable={isNewResponsable}
|
|
/>
|
|
<InputPhone
|
|
name="guardianPhone"
|
|
label="Numéro de téléphone"
|
|
value={formData.guardianPhone}
|
|
onChange={handleChange}
|
|
enable={isNewResponsable}
|
|
/>
|
|
</div>
|
|
</SectionTitle>
|
|
|
|
{/* Tableau des élèves pour responsable existant */}
|
|
{!isNewResponsable && (
|
|
<div className="mt-4">
|
|
<Table
|
|
data={students}
|
|
columns={[
|
|
{
|
|
name: 'photo',
|
|
transform: (row) => (
|
|
<div className="flex justify-center items-center">
|
|
{row.photo ? (
|
|
<a
|
|
href={`${BASE_URL}${row.photo}`} // Lien vers la photo
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<img
|
|
src={`${BASE_URL}${row.photo}`}
|
|
alt={`${row.first_name} ${row.last_name}`}
|
|
className="w-10 h-10 object-cover transition-transform duration-200 hover:scale-125 cursor-pointer rounded-full"
|
|
/>
|
|
</a>
|
|
) : (
|
|
<div className="w-10 h-10 flex items-center justify-center bg-gray-200 rounded-full">
|
|
<span className="text-gray-500 text-sm font-semibold">
|
|
{row.first_name[0]}
|
|
{row.last_name[0]}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
name: 'Nom',
|
|
transform: (row) => row.last_name,
|
|
},
|
|
{
|
|
name: 'Prénom',
|
|
transform: (row) => row.first_name,
|
|
},
|
|
{
|
|
name: 'Niveau',
|
|
transform: (row) => (
|
|
<div className="text-center">{getNiveauLabel(row.level)}</div>
|
|
),
|
|
},
|
|
]}
|
|
isSelectable={true}
|
|
onRowClick={(event) => {
|
|
if (event.deselected) {
|
|
handleEleveDeselection();
|
|
} else {
|
|
handleEleveSelection(event);
|
|
}
|
|
}}
|
|
rowClassName={(row) =>
|
|
selectedStudent && selectedStudent.id === row.id
|
|
? 'bg-emerald-600 text-white'
|
|
: ''
|
|
}
|
|
selectedRows={selectedStudent ? [selectedStudent.id] : []} // Assurez-vous que selectedRows est un tableau
|
|
/>
|
|
|
|
{selectedStudent && (
|
|
<div className="mt-4">
|
|
<h3 className="font-bold">
|
|
Responsables associés à {selectedStudent.last_name}{' '}
|
|
{selectedStudent.first_name} :
|
|
</h3>
|
|
{existingGuardians.map((guardian) => (
|
|
<div key={guardian.id} className="mt-2">
|
|
<CheckBox
|
|
item={{ id: guardian.id }}
|
|
formData={{
|
|
selectedGuardians: formData.selectedGuardians,
|
|
}}
|
|
handleChange={() => handleGuardianCheckboxChange(guardian)}
|
|
fieldName="selectedGuardians"
|
|
itemLabelFunc={() =>
|
|
guardian.last_name && guardian.first_name
|
|
? `${guardian.last_name} ${guardian.first_name} - ${guardian.associated_profile_email}`
|
|
: `${guardian.associated_profile_email}`
|
|
}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Section des frais d'inscription */}
|
|
<SectionTitle title="Frais d'inscription">
|
|
{registrationFees.length > 0 || registrationDiscounts.length > 0 ? (
|
|
<>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
{/* Tableau des frais */}
|
|
<div>
|
|
<FeesSection
|
|
fees={registrationFees}
|
|
type={0}
|
|
subscriptionMode={true}
|
|
selectedFees={formData.selectedRegistrationFees}
|
|
handleFeeSelection={handleRegistrationFeeSelection}
|
|
/>
|
|
</div>
|
|
|
|
{/* Tableau des réductions */}
|
|
<div>
|
|
{registrationDiscounts.length > 0 ? (
|
|
<DiscountsSection
|
|
discounts={registrationDiscounts}
|
|
type={0}
|
|
subscriptionMode={true}
|
|
selectedDiscounts={formData.selectedRegistrationDiscounts}
|
|
handleDiscountSelection={
|
|
handleRegistrationDiscountSelection
|
|
}
|
|
/>
|
|
) : (
|
|
<p
|
|
className="bg-orange-100 border border-orange-400 text-orange-700 px-4 py-3 rounded relative"
|
|
role="alert"
|
|
>
|
|
<strong className="font-bold">Information</strong>
|
|
<span className="block sm:inline">
|
|
Aucune réduction n'a été créée sur les frais
|
|
d'inscription.
|
|
</span>
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Montant total */}
|
|
<div className="flex items-center justify-between bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-300 mt-4">
|
|
<span className="text-sm font-medium text-gray-600">
|
|
Montant total des frais d'inscription :
|
|
</span>
|
|
<span className="text-lg font-semibold text-gray-800">
|
|
{totalRegistrationAmount} €
|
|
</span>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<p
|
|
className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
|
|
role="alert"
|
|
>
|
|
<strong className="font-bold">Attention!</strong>
|
|
<span className="block sm:inline">
|
|
Aucun frais d'inscription n'a été créé.
|
|
</span>
|
|
</p>
|
|
)}
|
|
</SectionTitle>
|
|
|
|
{/* Section des frais de scolarité */}
|
|
<SectionTitle title="Frais de scolarité">
|
|
{tuitionFees.length > 0 || tuitionDiscounts.length > 0 ? (
|
|
<>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
{/* Tableau des frais */}
|
|
<div>
|
|
<FeesSection
|
|
fees={tuitionFees}
|
|
type={1}
|
|
subscriptionMode={true}
|
|
selectedFees={formData.selectedTuitionFees}
|
|
handleFeeSelection={handleTuitionFeeSelection}
|
|
/>
|
|
</div>
|
|
|
|
{/* Tableau des réductions */}
|
|
<div>
|
|
{tuitionDiscounts.length > 0 ? (
|
|
<DiscountsSection
|
|
discounts={tuitionDiscounts}
|
|
type={1}
|
|
subscriptionMode={true}
|
|
selectedDiscounts={formData.selectedTuitionDiscounts}
|
|
handleDiscountSelection={handleTuitionDiscountSelection}
|
|
/>
|
|
) : (
|
|
<p
|
|
className="bg-orange-100 border border-orange-400 text-orange-700 px-4 py-3 rounded relative"
|
|
role="alert"
|
|
>
|
|
<strong className="font-bold">Information</strong>
|
|
<span className="block sm:inline">
|
|
Aucune réduction n'a été créée sur les frais de
|
|
scolarité.
|
|
</span>
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Montant total */}
|
|
<div className="flex items-center justify-between bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-300 mt-4">
|
|
<span className="text-sm font-medium text-gray-600">
|
|
Montant total des frais de scolarité :
|
|
</span>
|
|
<span className="text-lg font-semibold text-gray-800">
|
|
{totalTuitionAmount} €
|
|
</span>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<p
|
|
className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
|
|
role="alert"
|
|
>
|
|
<strong className="font-bold">Attention!</strong>
|
|
<span className="block sm:inline">
|
|
Aucun frais de scolarité n'a été créé.
|
|
</span>
|
|
</p>
|
|
)}
|
|
</SectionTitle>
|
|
|
|
{/* Section des groupes de documents */}
|
|
<SectionTitle title="Formulaires d'inscription">
|
|
{groups.length > 0 ? (
|
|
<div className="space-y-4">
|
|
<RadioList
|
|
sectionLabel="Sélectionner un formulaire"
|
|
items={groups.map((group) => ({
|
|
id: group.id,
|
|
label: `${group.name}${
|
|
group.description ? ` (${group.description})` : ''
|
|
}`,
|
|
}))}
|
|
formData={{
|
|
...formData,
|
|
selectedFileGroup: parseInt(formData.selectedFileGroup, 10),
|
|
}}
|
|
handleChange={(e) => {
|
|
const value = parseInt(e.target.value, 10);
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
selectedFileGroup: value,
|
|
}));
|
|
}}
|
|
fieldName="selectedFileGroup"
|
|
required
|
|
className="mt-4"
|
|
/>
|
|
</div>
|
|
) : (
|
|
<p
|
|
className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
|
|
role="alert"
|
|
>
|
|
<strong className="font-bold">Attention!</strong>
|
|
<span className="block sm:inline">
|
|
Aucun groupe de documents n'a été créé.
|
|
</span>
|
|
</p>
|
|
)}
|
|
</SectionTitle>
|
|
|
|
{/* Bouton de soumission */}
|
|
<div className="flex justify-end">
|
|
<Button
|
|
text={`${registerFormID ? 'Modifier' : 'Créer'} le dossier`}
|
|
onClick={submit}
|
|
className={`px-6 py-2 rounded-md shadow ${
|
|
isSubmitDisabled()
|
|
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
|
|
: 'bg-emerald-500 text-white hover:bg-emerald-600'
|
|
}`}
|
|
primary
|
|
disabled={isSubmitDisabled()}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|