'use client'; import React, { useState, useEffect } from 'react'; import Table from '@/components/Table'; import Tab from '@/components/Tab'; import { useTranslations } from 'next-intl'; import StatusLabel from '@/components/StatusLabel'; import { Search } from 'lucide-react'; import Popup from '@/components/Popup'; import Loader from '@/components/Loader'; import AlertWithModal from '@/components/AlertWithModal'; import { useRouter } from 'next/navigation'; import DropdownMenu from '@/components/DropdownMenu'; import { MoreVertical, Send, Edit, Archive, FileText, CheckCircle, Plus, Upload, } from 'lucide-react'; import Modal from '@/components/Modal'; import InscriptionForm from '@/components/Inscription/InscriptionForm'; import AffectationClasseForm from '@/components/AffectationClasseForm'; import { useEstablishment } from '@/context/EstablishmentContext'; import { PENDING, SUBSCRIBED, ARCHIVED, fetchRegisterForms, createRegisterForm, sendRegisterForm, archiveRegisterForm, fetchStudents, editRegisterForm, editRegisterFormWithBinaryFile, } from '@/app/actions/subscriptionAction'; import { fetchRegistrationSchoolFileMasters, fetchRegistrationParentFileMasters, createRegistrationSchoolFileTemplate, createRegistrationParentFileTemplate, fetchRegistrationFileGroups, cloneTemplate, } from '@/app/actions/registerFileGroupAction'; import { fetchClasses, fetchRegistrationDiscounts, fetchTuitionDiscounts, fetchRegistrationFees, fetchTuitionFees, } from '@/app/actions/schoolAction'; import { fetchProfiles } from '@/app/actions/authAction'; import { FE_ADMIN_SUBSCRIPTIONS_EDIT_URL, FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL, BASE_URL, } from '@/utils/Url'; import DjangoCSRFToken from '@/components/DjangoCSRFToken'; import { useCsrfToken } from '@/context/CsrfContext'; import logger from '@/utils/logger'; import { PhoneLabel } from '@/components/PhoneLabel'; import FileUpload from '@/components/FileUpload'; import FilesModal from '@/components/Inscription/FilesModal'; export default function Page({ params: { locale } }) { const t = useTranslations('subscriptions'); const [registrationForms, setRegistrationForms] = useState([]); const [registrationFormsDataPending, setRegistrationFormsDataPending] = useState([]); const [registrationFormsDataSubscribed, setRegistrationFormsDataSubscribed] = useState([]); const [registrationFormsDataArchived, setRegistrationFormsDataArchived] = useState([]); // const [filter, setFilter] = useState('*'); const [searchTerm, setSearchTerm] = useState(''); const [alertPage, setAlertPage] = useState(false); const [isLoading, setIsLoading] = useState(false); const [activeTab, setActiveTab] = useState('pending'); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalPending, setTotalPending] = useState(0); const [totalSubscribed, setTotalSubscribed] = useState(0); const [totalArchives, setTotalArchives] = useState(0); const [itemsPerPage, setItemsPerPage] = useState(10); // Définir le nombre d'éléments par page const [schoolFileMasters, setSchoolFileMasters] = useState([]); const [parentFileMasters, setParentFileMasters] = useState([]); const [isOpen, setIsOpen] = useState(false); const [isOpenAffectationClasse, setIsOpenAffectationClasse] = useState(false); const [student, setStudent] = useState(''); const [classes, setClasses] = useState([]); const [students, setEleves] = useState([]); const [reloadFetch, setReloadFetch] = useState(false); const [registrationDiscounts, setRegistrationDiscounts] = useState([]); const [tuitionDiscounts, setTuitionDiscounts] = useState([]); const [registrationFees, setRegistrationFees] = useState([]); const [tuitionFees, setTuitionFees] = useState([]); const [groups, setGroups] = useState([]); const [profiles, setProfiles] = useState([]); const [isOpenAddGuardian, setIsOpenAddGuardian] = useState(false); const [isFilesModalOpen, setIsFilesModalOpen] = useState(false); const [selectedRegisterForm, setSelectedRegisterForm] = useState([]); const [popupVisible, setPopupVisible] = useState(false); const [popupMessage, setPopupMessage] = useState(''); const [confirmPopupVisible, setConfirmPopupVisible] = useState(false); const [confirmPopupMessage, setConfirmPopupMessage] = useState(''); const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {}); const [isSepaUploadModalOpen, setIsSepaUploadModalOpen] = useState(false); const [selectedRowForUpload, setSelectedRowForUpload] = useState(null); const csrfToken = useCsrfToken(); const router = useRouter(); const { selectedEstablishmentId } = useEstablishment(); const openSepaUploadModal = (row) => { setSelectedRowForUpload(row); setIsSepaUploadModalOpen(true); }; const closeSepaUploadModal = () => { setSelectedRowForUpload(null); setIsSepaUploadModalOpen(false); }; const openModal = () => { setIsOpen(true); }; const closeModal = () => { setIsOpen(false); }; const handleOpenAddGuardian = (eleveSelected) => { setIsOpenAddGuardian(true); setStudent(eleveSelected); }; const handleCloseAddGuardian = () => { setIsOpenAddGuardian(false); }; const openModalAssociationEleve = (eleveSelected) => { setIsOpenAffectationClasse(true); setStudent(eleveSelected); }; const openFilesModal = (row) => { setSelectedRegisterForm(row || []); setIsFilesModalOpen(true); }; const requestErrorHandler = (err) => { logger.error('Error fetching data:', err); }; /** * Handles the pending data for the registration form. * * @param {Object} data - The data object containing registration forms and count. * @param {Array} data.registerForms - The array of registration forms. * @param {number} data.count - The total count of registration forms. */ const registerFormPendingDataHandler = (data) => { if (data) { const { registerForms, count, page_size } = data; if (registerForms) { setRegistrationFormsDataPending(registerForms); } const calculatedTotalPages = count === 0 ? 1 : Math.ceil(count / page_size); setTotalPending(count); setTotalPages(calculatedTotalPages); } }; /** * Handles the data received from the subscription registration form. * * @param {Object} data - The data object received from the subscription registration form. * @param {Array} data.registerForms - An array of registration forms. * @param {number} data.count - The total count of subscribed forms. */ const registerFormSubscribedDataHandler = (data) => { if (data) { const { registerForms, count, page_size } = data; setTotalSubscribed(count); if (registerForms) { setRegistrationFormsDataSubscribed(registerForms); } } }; /** * Handles the archived data for the register form. * * @param {Object} data - The data object containing archived register forms and count. * @param {Array} data.registerForms - The array of archived register forms. * @param {number} data.count - The total count of archived register forms. */ const registerFormArchivedDataHandler = (data) => { if (data) { const { registerForms, count, page_size } = data; setTotalArchives(count); if (registerForms) { setRegistrationFormsDataArchived(registerForms); } } }; useEffect(() => { if (selectedEstablishmentId) { const fetchDataAndSetState = () => { setIsLoading(true); Promise.all([ fetchRegisterForms( selectedEstablishmentId, PENDING, currentPage, itemsPerPage, searchTerm ) .then(registerFormPendingDataHandler) .catch(requestErrorHandler), fetchClasses(selectedEstablishmentId) .then((classesData) => { setClasses(classesData); }) .catch(requestErrorHandler), fetchStudents(selectedEstablishmentId) .then((studentsData) => { setEleves(studentsData); }) .catch(requestErrorHandler), fetchRegisterForms(selectedEstablishmentId, SUBSCRIBED) .then(registerFormSubscribedDataHandler) .catch(requestErrorHandler), fetchRegisterForms(selectedEstablishmentId, ARCHIVED) .then(registerFormArchivedDataHandler) .catch(requestErrorHandler), fetchRegistrationSchoolFileMasters() .then((data) => { setSchoolFileMasters(data); }) .catch((err) => { logger.debug(err.message); }), fetchRegistrationParentFileMasters() .then((data) => { setParentFileMasters(data); }) .catch((err) => { logger.debug(err.message); }), fetchRegistrationDiscounts(selectedEstablishmentId) .then((data) => { setRegistrationDiscounts(data); }) .catch(requestErrorHandler), fetchTuitionDiscounts(selectedEstablishmentId) .then((data) => { setTuitionDiscounts(data); }) .catch(requestErrorHandler), fetchRegistrationFees(selectedEstablishmentId) .then((data) => { setRegistrationFees(data); }) .catch(requestErrorHandler), fetchTuitionFees(selectedEstablishmentId) .then((data) => { setTuitionFees(data); }) .catch(requestErrorHandler), fetchRegistrationFileGroups(selectedEstablishmentId) .then((data) => { setGroups(data); }) .catch((error) => { logger.error('Error fetching file groups:', error); }), fetchProfiles() .then((data) => { setProfiles(data); }) .catch((error) => { logger.error('Error fetching profileRoles:', error); }), ]) .then(() => { setIsLoading(false); setReloadFetch(false); }) .catch((err) => { logger.error(err); setIsLoading(false); setReloadFetch(false); }); }; fetchDataAndSetState(); } }, [selectedEstablishmentId, reloadFetch, currentPage, searchTerm]); useEffect(() => { if (selectedEstablishmentId) { const fetchDataAndSetState = () => { setIsLoading(true); fetchRegisterForms( selectedEstablishmentId, PENDING, currentPage, itemsPerPage, searchTerm ) .then(registerFormPendingDataHandler) .catch(requestErrorHandler); fetchRegisterForms(selectedEstablishmentId, SUBSCRIBED) .then(registerFormSubscribedDataHandler) .catch(requestErrorHandler); fetchRegisterForms(selectedEstablishmentId, ARCHIVED) .then(registerFormArchivedDataHandler) .catch(requestErrorHandler); fetchRegistrationSchoolFileMasters() .then((data) => { setSchoolFileMasters(data); }) .catch((err) => { err = err.message; logger.debug(err); }); setIsLoading(false); setReloadFetch(false); }; const timeoutId = setTimeout(() => { fetchDataAndSetState(); }, 500); // Debounce la recherche return () => clearTimeout(timeoutId); } }, [searchTerm]); /** * UseEffect to update page count of tab */ useEffect(() => { if (activeTab === 'pending') { setTotalPages(Math.ceil(totalPending / itemsPerPage)); } else if (activeTab === 'subscribed') { setTotalPages(Math.ceil(totalSubscribed / itemsPerPage)); } else if (activeTab === 'archived') { setTotalPages(Math.ceil(totalArchives / itemsPerPage)); } }, [currentPage]); const handleSepaFileUpload = (file, row) => { if (!file || !row) { logger.error("Aucun fichier ou ligne sélectionnée pour l'upload."); return; } const formData = new FormData(); formData.append('status', 7); formData.append('sepa_file', file); // Appeler l'API pour uploader le fichier SEPA editRegisterFormWithBinaryFile(row.student.id, formData, csrfToken) .then((response) => { logger.debug('Mandat SEPA uploadé avec succès :', response); setPopupMessage('Le mandat SEPA a été uploadé avec succès.'); setPopupVisible(true); setReloadFetch(true); closeSepaUploadModal(); }) .catch((error) => { logger.error("Erreur lors de l'upload du mandat SEPA :", error); setPopupMessage("Erreur lors de l'upload du mandat SEPA."); setPopupVisible(true); }); }; /** * Archives a registration form after user confirmation. * * @param {number} id - The ID of the registration form to be archived. * @param {string} nom - The last name of the person whose registration form is being archived. * @param {string} prenom - The first name of the person whose registration form is being archived. */ const archiveFicheInscription = (id, nom, prenom) => { setConfirmPopupMessage( `Attentions ! \nVous êtes sur le point d'archiver le dossier d'inscription de ${nom} ${prenom}\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?` ); setConfirmPopupOnConfirm(() => () => { archiveRegisterForm(id) .then((data) => { logger.debug('Success:', data); setPopupMessage( `Le dossier d'inscription a été correctement archivé` ); setPopupVisible(true); setRegistrationForms( registrationForms.filter((fiche) => fiche.id !== id) ); setReloadFetch(true); }) .catch((error) => { logger.error('Error archiving data:', error); setPopupMessage( `Erreur lors de l'archivage du dossier d'inscription.\nContactez l'administrateur.` ); setPopupVisible(true); }); setConfirmPopupVisible(false); }); setConfirmPopupVisible(true); }; const sendConfirmRegisterForm = (id, nom, prenom) => { setConfirmPopupMessage( `Avertissement ! \nVous êtes sur le point d'envoyer un dossier d'inscription à ${nom} ${prenom}\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?` ); setConfirmPopupOnConfirm(() => () => { sendRegisterForm(id) .then((data) => { logger.debug('Success:', data); setPopupMessage(`Le dossier d'inscription a été envoyé avec succès`); setPopupVisible(true); setReloadFetch(true); }) .catch((error) => { logger.error('Error archiving data:', error); setPopupMessage( `Erreur lors de l'envoi du dossier d'inscription.\nContactez l'administrateur.` ); setPopupVisible(true); }); setConfirmPopupVisible(false); }); setConfirmPopupVisible(true); }; const affectationClassFormSubmitHandler = (formdata) => { editRegisterForm(student.id, formData, csrfToken) .then((data) => { logger.debug('Success:', data); setReloadFetch(true); }) .catch((error) => { logger.error('Error :', error); }); }; const updateStatusAction = (id, newStatus) => { logger.debug( `Mise à jour du statut du dossier d'inscription avec l'ID : ${id} vers le statut : ${newStatus}` ); }; const handleSearchChange = (event) => { setSearchTerm(event.target.value); }; const handlePageChange = (newPage) => { setCurrentPage(newPage); }; const createRF = (updatedData) => { logger.debug('createRF updatedData:', updatedData); const selectedRegistrationFeesIds = updatedData.selectedRegistrationFees.map((feeId) => feeId); const selectedRegistrationDiscountsIds = updatedData.selectedRegistrationDiscounts.map((discountId) => discountId); const selectedTuitionFeesIds = updatedData.selectedTuitionFees.map( (feeId) => feeId ); const selectedTuitionDiscountsIds = updatedData.selectedTuitionDiscounts.map((discountId) => discountId); const selectedFileGroup = updatedData.selectedFileGroup; const allFeesIds = [ ...selectedRegistrationFeesIds, ...selectedTuitionFeesIds, ]; const allDiscountsds = [ ...selectedRegistrationDiscountsIds, ...selectedTuitionDiscountsIds, ]; const data = { student: { last_name: updatedData.studentLastName, first_name: updatedData.studentFirstName, guardians: updatedData.selectedGuardians.length !== 0 ? updatedData.selectedGuardians.map((guardianId) => ({ id: guardianId, })) : (() => { if (updatedData.isExistingParentProfile) { return [ { profile_role_data: { establishment: selectedEstablishmentId, role_type: 2, is_active: false, profile: updatedData.existingProfileId, // Associer au profil existant }, last_name: updatedData.guardianLastName, first_name: updatedData.guardianFirstName, birth_date: updatedData.guardianBirthDate, address: updatedData.guardianAddress, phone: updatedData.guardianPhone, profession: updatedData.guardianProfession, }, ]; } // Si aucun profil existant n'est trouvé, créer un nouveau profil return [ { profile_role_data: { establishment: selectedEstablishmentId, role_type: 2, is_active: false, profile_data: { email: updatedData.guardianEmail, password: 'Provisoire01!', username: updatedData.guardianEmail, }, }, last_name: updatedData.guardianLastName, first_name: updatedData.guardianFirstName, birth_date: updatedData.guardianBirthDate, address: updatedData.guardianAddress, phone: updatedData.guardianPhone, profession: updatedData.guardianProfession, }, ]; })(), sibling: [], }, fees: allFeesIds, discounts: allDiscountsds, fileGroup: selectedFileGroup, establishment: selectedEstablishmentId, }; setIsLoading(true); createRegisterForm(data, csrfToken) .then((data) => { // Cloner les schoolFileTemplates pour chaque templateMaster du fileGroup const masters = schoolFileMasters.filter((file) => file.groups.includes(selectedFileGroup) ); const parent_masters = parentFileMasters.filter((file) => file.groups.includes(selectedFileGroup) ); const clonePromises = masters.map((templateMaster) => { return cloneTemplate( templateMaster.id, updatedData.guardianEmail, templateMaster.is_required ) .then((clonedDocument) => { // Sauvegarde des schoolFileTemplates clonés dans la base de données const cloneData = { name: `${templateMaster.name}_${updatedData.studentFirstName}_${updatedData.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 ); }); }) .catch((error) => { setIsLoading(false); logger.error('Error during cloning or sending:', error); }); }); // Créer les parentFileTemplates pour chaque parentMaster const parentClonePromises = parent_masters.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 ); }); }); // Attendre que tous les clones (school et parent) soient créés Promise.all([...clonePromises, ...parentClonePromises]) .then(() => { // Mise à jour immédiate des données setRegistrationFormsDataPending((prevState) => [ ...(prevState || []), data, ]); setTotalPending((prev) => prev + 1); if (updatedData.autoMail) { sendConfirmRegisterForm( data.student.id, updatedData.studentLastName, updatedData.studentFirstName ); } closeModal(); // Appeler closeModal ici après que tout soit terminé // Forcer le rechargement complet des données setReloadFetch(true); setIsLoading(false); }) .catch((error) => { setIsLoading(false); logger.error('Error during cloning or sending:', error); }); }) .catch((error) => { setIsLoading(false); logger.error('Error:', error); }); }; const updateRF = (updatedData) => { logger.debug('updateRF updatedData:', updatedData); const data = { student: { guardians: updatedData.selectedGuardians.length !== 0 ? updatedData.selectedGuardians.map((guardianId) => ({ id: guardianId, })) : (() => { if (updatedData.isExistingParentProfile) { return [ { profile_role_data: { establishment: selectedEstablishmentId, role_type: 2, is_active: false, profile: updatedData.existingProfileId, // Associer au profil existant }, last_name: updatedData.guardianLastName, first_name: updatedData.guardianFirstName, birth_date: updatedData.guardianBirthDate, address: updatedData.guardianAddress, phone: updatedData.guardianPhone, profession: updatedData.guardianProfession, }, ]; } // Si aucun profil existant n'est trouvé, créer un nouveau profil return [ { profile_role_data: { establishment: selectedEstablishmentId, role_type: 2, is_active: false, profile_data: { email: updatedData.guardianEmail, password: 'Provisoire01!', username: updatedData.guardianEmail, }, }, last_name: updatedData.guardianLastName, first_name: updatedData.guardianFirstName, birth_date: updatedData.guardianBirthDate, address: updatedData.guardianAddress, phone: updatedData.guardianPhone, profession: updatedData.guardianProfession, }, ]; })(), }, establishment: selectedEstablishmentId, }; editRegisterForm(student.id, data, csrfToken) .then((data) => { // Mise à jour immédiate des données setRegistrationFormsDataPending((prevState) => [ ...(prevState || []), data, ]); setTotalPending((prev) => prev + 1); if (updatedData.autoMail) { sendConfirmRegisterForm( data.student.id, updatedData.studentLastName, updatedData.studentFirstName ); } handleCloseAddGuardian(); // Forcer le rechargement complet des données setReloadFetch(true); }) .catch((error) => { logger.error('Error during updating registration form:', error); }); }; const getActionsByStatus = (row) => { const actions = { 1: [ { icon: ( ), onClick: () => sendConfirmRegisterForm( row.student.id, row.student.last_name, row.student.first_name ), }, ], 2: [ { icon: ( ), onClick: () => router.push( `${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?studentId=${row.student.id}` ), }, ], 3: [ { icon: ( ), onClick: () => openFilesModal(row), }, { icon: ( ), onClick: () => { const url = `${FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL}?studentId=${row.student.id}&firstName=${row.student.first_name}&lastName=${row.student.last_name}&sepa_file=${row.sepa_file}&student_file=${row.registration_file}`; router.push(`${url}`); }, }, ], 5: [ { icon: ( ), onClick: () => openFilesModal(row), }, ], 7: [ { icon: ( ), onClick: () => openFilesModal(row), }, ], 8: [ { icon: ( ), onClick: () => openFilesModal(row), }, { icon: ( ), onClick: () => openSepaUploadModal(row), }, ], default: [ { icon: ( ), onClick: () => archiveFicheInscription( row.student.id, row.student.last_name, row.student.first_name ), }, ], }; // Combine actions for the specific status and default actions return [ ...(actions[row.status] || []), ...(row.status !== 6 ? actions.default : []), ]; }; const columns = [ { name: t('photo'), transform: (row) => (
{row.student.photo ? ( {`${row.student.first_name} ) : (
{row.student.first_name[0]} {row.student.last_name[0]}
)}
), }, { name: t('studentName'), transform: (row) => row.student.last_name }, { name: t('studentFistName'), transform: (row) => row.student.first_name }, { name: t('mainContactMail'), transform: (row) => row.student.guardians && row.student.guardians.length > 0 ? ( row.student.guardians[0].associated_profile_email ) : (
), }, { name: t('phone'), transform: (row) => ( ), }, { name: t('lastUpdateDate'), transform: (row) => row.formatted_last_update, }, { name: t('registrationFileStatus'), transform: (row) => (
updateStatusAction(row.student.id, newStatus) } showDropdown={false} />
), }, { name: 'Actions', transform: (row) => (
{getActionsByStatus(row).map((action, index) => ( ))}
), }, ]; const columnsSubscribed = [ { name: t('studentName'), transform: (row) => row.student.last_name }, { name: t('studentFistName'), transform: (row) => row.student.first_name }, { name: t('lastUpdateDate'), transform: (row) => row.updated_date_formated, }, { name: t('class'), transform: (row) => row.student.first_name }, { name: t('registrationFileStatus'), transform: (row) => (
updateStatusAction(row.student.id, newStatus) } showDropdown={false} />
), }, { name: 'Actions', transform: (row) => ( } items={[ { label: ( <> Rattacher ), onClick: () => openModalAssociationEleve(row.student), }, { label: ( <> Archiver ), onClick: () => archiveFicheInscription( row.student.id, row.student.last_name, row.student.first_name ), }, ]} buttonClassName="text-gray-400 hover:text-gray-600" menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-10 flex flex-col items-center" /> ), }, ]; if (isLoading) { return ; } else { if ( registrationForms.length === 0 && registrationFormsDataArchived.length === 0 && alertPage ) { return (
); } else { return (
{t('pending')} ({totalPending}) } active={activeTab === 'pending'} onClick={() => setActiveTab('pending')} /> {t('subscribed')} ({totalSubscribed}) } active={activeTab === 'subscribed'} onClick={() => setActiveTab('subscribed')} /> {t('archived')} ({totalArchives}) } active={activeTab === 'archived'} onClick={() => setActiveTab('archived')} />
{/*SI STATE == pending || subscribe || archived */} {activeTab === 'pending' || activeTab === 'subscribed' || activeTab === 'archived' ? (
) : null} setPopupVisible(false)} uniqueConfirmButton={true} /> setConfirmPopupVisible(false)} /> {isOpen && ( ( fee.is_active )} tuitionFees={tuitionFees.filter((fee) => fee.is_active)} groups={groups} profiles={profiles} onSubmit={createRF} /> )} /> )} {isOpenAffectationClasse && ( ( )} /> )} {isOpenAddGuardian && ( ( )} /> )} {isSepaUploadModalOpen && ( ( handleSepaFileUpload(file, selectedRowForUpload) } /> )} /> )} {isFilesModalOpen && ( )} ); } } }