mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
1214 lines
40 KiB
JavaScript
1214 lines
40 KiB
JavaScript
'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: (
|
|
<span title="Envoyer le dossier">
|
|
<Send className="w-5 h-5 text-green-500 hover:text-green-700" />
|
|
</span>
|
|
),
|
|
onClick: () =>
|
|
sendConfirmRegisterForm(
|
|
row.student.id,
|
|
row.student.last_name,
|
|
row.student.first_name
|
|
),
|
|
},
|
|
],
|
|
2: [
|
|
{
|
|
icon: (
|
|
<span title="Editer le dossier">
|
|
<Edit className="w-5 h-5 text-blue-500 hover:text-blue-700" />
|
|
</span>
|
|
),
|
|
onClick: () =>
|
|
router.push(
|
|
`${FE_ADMIN_SUBSCRIPTIONS_EDIT_URL}?studentId=${row.student.id}`
|
|
),
|
|
},
|
|
],
|
|
3: [
|
|
{
|
|
icon: (
|
|
<span title="Voir les fichiers">
|
|
<FileText className="w-5 h-5 text-cyan-500 hover:text-cyan-700" />
|
|
</span>
|
|
),
|
|
onClick: () => openFilesModal(row),
|
|
},
|
|
{
|
|
icon: (
|
|
<span title="Valider le dossier">
|
|
<CheckCircle className="w-5 h-5 text-green-500 hover:text-green-700" />
|
|
</span>
|
|
),
|
|
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: (
|
|
<span title="Voir les fichiers">
|
|
<FileText className="w-5 h-5 text-cyan-500 hover:text-cyan-700" />
|
|
</span>
|
|
),
|
|
onClick: () => openFilesModal(row),
|
|
},
|
|
],
|
|
7: [
|
|
{
|
|
icon: (
|
|
<span title="Voir les fichiers">
|
|
<FileText className="w-5 h-5 text-cyan-500 hover:text-cyan-700" />
|
|
</span>
|
|
),
|
|
onClick: () => openFilesModal(row),
|
|
},
|
|
],
|
|
8: [
|
|
{
|
|
icon: (
|
|
<span title="Voir les fichiers">
|
|
<FileText className="w-5 h-5 text-cyan-500 hover:text-cyan-700" />
|
|
</span>
|
|
),
|
|
onClick: () => openFilesModal(row),
|
|
},
|
|
{
|
|
icon: (
|
|
<span title="Uploader un mandat SEPA">
|
|
<Upload className="w-5 h-5 text-emerald-500 hover:text-emerald-700" />
|
|
</span>
|
|
),
|
|
onClick: () => openSepaUploadModal(row),
|
|
},
|
|
],
|
|
default: [
|
|
{
|
|
icon: (
|
|
<span title="Archiver le dossier">
|
|
<Archive className="w-5 h-5 text-gray-500 hover:text-gray-700" />
|
|
</span>
|
|
),
|
|
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) => (
|
|
<div className="flex justify-center items-center">
|
|
{row.student.photo ? (
|
|
<a
|
|
href={`${BASE_URL}${row.student.photo}`} // Lien vers la photo
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<img
|
|
src={`${BASE_URL}${row.student.photo}`}
|
|
alt={`${row.student.first_name} ${row.student.last_name}`}
|
|
className="w-10 h-10 object-cover transition-transform duration-200 hover:scale-125 cursor-pointer"
|
|
/>
|
|
</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.student.first_name[0]}
|
|
{row.student.last_name[0]}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
),
|
|
},
|
|
{ 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
|
|
) : (
|
|
<div className="flex justify-center h-full">
|
|
<button
|
|
className="flex items-center gap-2 text-blue-600 font-semibold hover:text-blue-800 transition duration-200 underline decoration-blue-600 hover:decoration-blue-800"
|
|
onClick={() => handleOpenAddGuardian(row.student)}
|
|
>
|
|
<span className="px-3 py-1 bg-blue-100 rounded-full hover:bg-blue-200 transition duration-200">
|
|
Ajouter un responsable
|
|
</span>
|
|
</button>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
name: t('phone'),
|
|
transform: (row) => (
|
|
<PhoneLabel phoneNumber={row.student.guardians[0]?.phone} />
|
|
),
|
|
},
|
|
{
|
|
name: t('lastUpdateDate'),
|
|
transform: (row) => row.formatted_last_update,
|
|
},
|
|
{
|
|
name: t('registrationFileStatus'),
|
|
transform: (row) => (
|
|
<div className="flex justify-center items-center h-full">
|
|
<StatusLabel
|
|
status={row.status}
|
|
onChange={(newStatus) =>
|
|
updateStatusAction(row.student.id, newStatus)
|
|
}
|
|
showDropdown={false}
|
|
/>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
name: 'Actions',
|
|
transform: (row) => (
|
|
<div className="flex justify-center space-x-2">
|
|
{getActionsByStatus(row).map((action, index) => (
|
|
<button
|
|
key={index}
|
|
onClick={action.onClick}
|
|
className="p-2 rounded-full hover:bg-gray-100 transition"
|
|
>
|
|
{action.icon}
|
|
</button>
|
|
))}
|
|
</div>
|
|
),
|
|
},
|
|
];
|
|
|
|
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) => (
|
|
<div className="flex justify-center items-center h-full">
|
|
<StatusLabel
|
|
status={row.status}
|
|
onChange={(newStatus) =>
|
|
updateStatusAction(row.student.id, newStatus)
|
|
}
|
|
showDropdown={false}
|
|
/>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
name: 'Actions',
|
|
transform: (row) => (
|
|
<DropdownMenu
|
|
buttonContent={
|
|
<MoreVertical
|
|
size={20}
|
|
className="text-gray-400 hover:text-gray-600"
|
|
/>
|
|
}
|
|
items={[
|
|
{
|
|
label: (
|
|
<>
|
|
<CheckCircle size={16} className="mr-2" /> Rattacher
|
|
</>
|
|
),
|
|
onClick: () => openModalAssociationEleve(row.student),
|
|
},
|
|
{
|
|
label: (
|
|
<>
|
|
<Archive size={16} className="mr-2 text-red-700" /> 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 <Loader />;
|
|
} else {
|
|
if (
|
|
registrationForms.length === 0 &&
|
|
registrationFormsDataArchived.length === 0 &&
|
|
alertPage
|
|
) {
|
|
return (
|
|
<div className="p-8">
|
|
<AlertWithModal
|
|
title={t('information')}
|
|
message={t('no_records') + ' ' + t('create_first_record')}
|
|
buttonText={t('add_button')}
|
|
/>
|
|
</div>
|
|
);
|
|
} else {
|
|
return (
|
|
<div className="p-8">
|
|
<div className="border-b border-gray-200 mb-6">
|
|
<div className="flex items-center gap-8">
|
|
<Tab
|
|
text={
|
|
<>
|
|
{t('pending')}
|
|
<span className="ml-2 text-sm text-gray-400">
|
|
({totalPending})
|
|
</span>
|
|
</>
|
|
}
|
|
active={activeTab === 'pending'}
|
|
onClick={() => setActiveTab('pending')}
|
|
/>
|
|
<Tab
|
|
text={
|
|
<>
|
|
{t('subscribed')}
|
|
<span className="ml-2 text-sm text-gray-400">
|
|
({totalSubscribed})
|
|
</span>
|
|
</>
|
|
}
|
|
active={activeTab === 'subscribed'}
|
|
onClick={() => setActiveTab('subscribed')}
|
|
/>
|
|
<Tab
|
|
text={
|
|
<>
|
|
{t('archived')}
|
|
<span className="ml-2 text-sm text-gray-400">
|
|
({totalArchives})
|
|
</span>
|
|
</>
|
|
}
|
|
active={activeTab === 'archived'}
|
|
onClick={() => setActiveTab('archived')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="border-b border-gray-200 mb-6 w-full">
|
|
{/*SI STATE == pending || subscribe || archived */}
|
|
{activeTab === 'pending' ||
|
|
activeTab === 'subscribed' ||
|
|
activeTab === 'archived' ? (
|
|
<React.Fragment>
|
|
<div className="flex justify-between items-center mb-4 w-full">
|
|
<div className="relative flex-grow">
|
|
<Search
|
|
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
|
|
size={20}
|
|
/>
|
|
<input
|
|
type="text"
|
|
placeholder={t('searchStudent')}
|
|
className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-md"
|
|
value={searchTerm}
|
|
onChange={handleSearchChange}
|
|
/>
|
|
</div>
|
|
<button
|
|
onClick={openModal}
|
|
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200 ml-4"
|
|
>
|
|
<Plus className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="w-full">
|
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
|
<Table
|
|
key={`${currentPage}-${searchTerm}`}
|
|
data={
|
|
activeTab === 'pending'
|
|
? [
|
|
...registrationFormsDataPending,
|
|
...registrationFormsDataSubscribed,
|
|
]
|
|
: activeTab === 'subscribed'
|
|
? registrationFormsDataSubscribed
|
|
: registrationFormsDataArchived
|
|
}
|
|
columns={
|
|
activeTab === 'subscribed' ? columnsSubscribed : columns
|
|
}
|
|
itemsPerPage={itemsPerPage}
|
|
currentPage={currentPage}
|
|
totalPages={totalPages}
|
|
onPageChange={handlePageChange}
|
|
/>
|
|
</div>
|
|
</React.Fragment>
|
|
) : null}
|
|
</div>
|
|
<Popup
|
|
visible={popupVisible}
|
|
message={popupMessage}
|
|
onConfirm={() => setPopupVisible(false)}
|
|
uniqueConfirmButton={true}
|
|
/>
|
|
<Popup
|
|
visible={confirmPopupVisible}
|
|
message={confirmPopupMessage}
|
|
onConfirm={confirmPopupOnConfirm}
|
|
onCancel={() => setConfirmPopupVisible(false)}
|
|
/>
|
|
|
|
{isOpen && (
|
|
<Modal
|
|
isOpen={isOpen}
|
|
setIsOpen={setIsOpen}
|
|
title={"Nouveau dossier d'inscription"}
|
|
ContentComponent={() => (
|
|
<InscriptionForm
|
|
students={students}
|
|
registrationDiscounts={registrationDiscounts}
|
|
tuitionDiscounts={tuitionDiscounts}
|
|
registrationFees={registrationFees.filter(
|
|
(fee) => fee.is_active
|
|
)}
|
|
tuitionFees={tuitionFees.filter((fee) => fee.is_active)}
|
|
groups={groups}
|
|
profiles={profiles}
|
|
onSubmit={createRF}
|
|
/>
|
|
)}
|
|
/>
|
|
)}
|
|
{isOpenAffectationClasse && (
|
|
<Modal
|
|
isOpen={isOpenAffectationClasse}
|
|
setIsOpen={setIsOpenAffectationClasse}
|
|
title="Affectation à une classe"
|
|
ContentComponent={() => (
|
|
<AffectationClasseForm
|
|
students={students}
|
|
onSubmit={affectationClassFormSubmitHandler}
|
|
classes={classes}
|
|
/>
|
|
)}
|
|
/>
|
|
)}
|
|
{isOpenAddGuardian && (
|
|
<Modal
|
|
isOpen={isOpenAddGuardian}
|
|
setIsOpen={setIsOpenAddGuardian}
|
|
title="Ajouter un responsable"
|
|
ContentComponent={() => (
|
|
<InscriptionForm
|
|
students={students}
|
|
profiles={profiles}
|
|
onSubmit={updateRF}
|
|
currentStep={2}
|
|
showOnlyStep2={true}
|
|
/>
|
|
)}
|
|
/>
|
|
)}
|
|
{isSepaUploadModalOpen && (
|
|
<Modal
|
|
isOpen={isSepaUploadModalOpen}
|
|
setIsOpen={setIsSepaUploadModalOpen}
|
|
title="Uploader un mandat SEPA"
|
|
ContentComponent={() => (
|
|
<FileUpload
|
|
selectionMessage="Sélectionnez un mandat SEPA à uploader"
|
|
onFileSelect={(file) =>
|
|
handleSepaFileUpload(file, selectedRowForUpload)
|
|
}
|
|
/>
|
|
)}
|
|
/>
|
|
)}
|
|
{isFilesModalOpen && (
|
|
<FilesModal
|
|
isOpen={isFilesModalOpen}
|
|
setIsOpen={setIsFilesModalOpen}
|
|
selectedRegisterForm={selectedRegisterForm}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
}
|