fix: Uniformisation des Modales et Popup [#35]

This commit is contained in:
Luc SORIGNET
2025-05-17 11:38:26 +02:00
parent c6bc0d0b51
commit f252efdef4
9 changed files with 467 additions and 427 deletions

View File

@ -99,7 +99,8 @@ export default function Page() {
return ( return (
<> <>
<Popup <Popup
visible={popupVisible} isOpen={popupVisible}
setIsOpen={setPopupVisible}
message={popupMessage} message={popupMessage}
onConfirm={() => { onConfirm={() => {
setPopupVisible(false); setPopupVisible(false);
@ -107,6 +108,7 @@ export default function Page() {
}} }}
onCancel={() => setPopupVisible(false)} onCancel={() => setPopupVisible(false)}
uniqueConfirmButton={true} uniqueConfirmButton={true}
popupClassName="w-full max-w-xs sm:max-w-md"
/> />
<div className="container max mx-auto p-4"> <div className="container max mx-auto p-4">
<div className="flex justify-center mb-4"> <div className="flex justify-center mb-4">

View File

@ -144,7 +144,8 @@ export default function Page() {
</div> </div>
</div> </div>
<Popup <Popup
visible={popupVisible} isOpen={popupVisible}
setIsOpen={setPopupVisible}
message={popupMessage} message={popupMessage}
onConfirm={() => { onConfirm={() => {
setPopupVisible(false); setPopupVisible(false);
@ -152,6 +153,7 @@ export default function Page() {
}} }}
onCancel={() => setPopupVisible(false)} onCancel={() => setPopupVisible(false)}
uniqueConfirmButton={true} uniqueConfirmButton={true}
popupClassName="w-full max-w-xs sm:max-w-md"
/> />
</> </>
); );

View File

@ -1,12 +1,14 @@
import { import {
BE_GESTIONMESSAGERIE_CONVERSATIONS_URL,
BE_GESTIONMESSAGERIE_CONVERSATION_MESSAGES_URL,
BE_GESTIONMESSAGERIE_MARK_AS_READ_URL,
BE_GESTIONMESSAGERIE_MESSAGES_URL, BE_GESTIONMESSAGERIE_MESSAGES_URL,
BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL,
BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL, BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL,
} from '@/utils/Url'; } from '@/utils/Url';
import { errorHandler, requestResponseHandler } from './actionsHandlers'; import { errorHandler, requestResponseHandler } from './actionsHandlers';
export const fetchMessages = (id) => { export const fetchConversations = (profileId) => {
return fetch(`${BE_GESTIONMESSAGERIE_MESSAGES_URL}/${id}`, { return fetch(`${BE_GESTIONMESSAGERIE_CONVERSATIONS_URL}/${profileId}/`, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
@ -15,12 +17,24 @@ export const fetchMessages = (id) => {
.catch(errorHandler); .catch(errorHandler);
}; };
export const sendMessage = (data, csrfToken) => { export const fetchMessages = (conversationId) => {
return fetch(`${BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL}`, { return fetch(
`${BE_GESTIONMESSAGERIE_CONVERSATION_MESSAGES_URL}/${conversationId}/`,
{
headers: {
'Content-Type': 'application/json',
},
}
)
.then(requestResponseHandler)
.catch(errorHandler);
};
export const sendMessage = (data) => {
return fetch(`${BE_GESTIONMESSAGERIE_MESSAGES_URL}`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
}, },
body: JSON.stringify(data), body: JSON.stringify(data),
}) })
@ -28,6 +42,18 @@ export const sendMessage = (data, csrfToken) => {
.catch(errorHandler); .catch(errorHandler);
}; };
export const markAsRead = (conversationId, profileId) => {
return fetch(`${BE_GESTIONMESSAGERIE_MARK_AS_READ_URL}/${conversationId}/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ profile_id: profileId }),
})
.then(requestResponseHandler)
.catch(errorHandler);
};
export const searchRecipients = (establishmentId, query) => { export const searchRecipients = (establishmentId, query) => {
const url = `${BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL}/?establishment_id=${establishmentId}&q=${encodeURIComponent(query)}`; const url = `${BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL}/?establishment_id=${establishmentId}&q=${encodeURIComponent(query)}`;
return fetch(url, { return fetch(url, {

View File

@ -2,6 +2,7 @@ import { usePlanning, RecurrenceType } from '@/context/PlanningContext';
import { format } from 'date-fns'; import { format } from 'date-fns';
import React from 'react'; import React from 'react';
import { useNotification } from '@/context/NotificationContext'; import { useNotification } from '@/context/NotificationContext';
import Modal from '@/components/Modal';
export default function EventModal({ export default function EventModal({
isOpen, isOpen,
@ -86,12 +87,12 @@ export default function EventModal({
}; };
return ( return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <Modal
<div className="bg-white p-6 rounded-lg w-full max-w-md"> isOpen={isOpen}
<h2 className="text-xl font-semibold mb-4"> setIsOpen={onClose}
{eventData.id ? "Modifier l'événement" : 'Nouvel événement'} title={eventData.id ? "Modifier l'événement" : 'Nouvel événement'}
</h2> modalClassName="w-full max-w-md"
>
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
{/* Titre */} {/* Titre */}
<div> <div>
@ -334,7 +335,6 @@ export default function EventModal({
</div> </div>
</div> </div>
</form> </form>
</div> </Modal>
</div>
); );
} }

View File

@ -96,7 +96,7 @@ const FilesModal = ({
{title} {title}
</span> </span>
} }
ContentComponent={() => ( >
<div className="space-y-6"> <div className="space-y-6">
{/* Section Fiche élève */} {/* Section Fiche élève */}
{files.registrationFile && ( {files.registrationFile && (
@ -203,9 +203,7 @@ const FilesModal = ({
</li> </li>
)) ))
) : ( ) : (
<p className="text-gray-500"> <p className="text-gray-500">Aucun fichier parent disponible.</p>
Aucun fichier parent disponible.
</p>
)} )}
</ul> </ul>
</div> </div>
@ -234,8 +232,7 @@ const FilesModal = ({
)} )}
</div> </div>
</div> </div>
)} </Modal>
/>
); );
}; };

View File

@ -4,7 +4,7 @@ const Modal = ({
isOpen, isOpen,
setIsOpen, setIsOpen,
title, title,
ContentComponent, children,
modalClassName, modalClassName,
}) => { }) => {
return ( return (
@ -12,13 +12,14 @@ const Modal = ({
<Dialog.Portal> <Dialog.Portal>
{/* Overlay avec un z-index élevé et un effet de flou */} {/* Overlay avec un z-index élevé et un effet de flou */}
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm z-[9998] transition-opacity" /> <Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm z-[9998] transition-opacity" />
<Dialog.Content className="fixed inset-0 flex items-center justify-center p-4 z-[9999]"> <Dialog.Content className="fixed inset-0 flex items-center justify-center p-2 sm:p-4 z-[9999]">
<div <div
className={`relative bg-white rounded-xl px-8 py-6 text-left shadow-2xl transform transition-all sm:my-8 ${modalClassName ? modalClassName : 'min-w-[500px] max-w-[90%] max-h-[80vh] overflow-auto'}`} className={`relative bg-white rounded-xl px-2 py-4 sm:px-8 sm:py-6 text-left shadow-2xl transform transition-all w-full max-w-lg sm:min-w-[400px] sm:max-w-[90%] max-h-[90vh] overflow-auto ${modalClassName ? modalClassName : ''}`}
> >
{/* En-tête de la modale */} {/* En-tête de la modale si title fourni */}
<div className="flex justify-between items-center mb-6"> {title && (
<Dialog.Title className="text-2xl font-semibold text-gray-800"> <div className="flex justify-between items-center mb-4 sm:mb-6">
<Dialog.Title className="text-lg sm:text-2xl font-semibold text-gray-800">
{title} {title}
</Dialog.Title> </Dialog.Title>
<Dialog.Close asChild> <Dialog.Close asChild>
@ -44,11 +45,9 @@ const Modal = ({
</button> </button>
</Dialog.Close> </Dialog.Close>
</div> </div>
)}
{/* Contenu de la modale */} {/* Contenu de la modale */}
<div className="w-full h-full"> <div className="w-full h-full">{children}</div>
<ContentComponent />
</div>
</div> </div>
</Dialog.Content> </Dialog.Content>
</Dialog.Portal> </Dialog.Portal>

View File

@ -1,23 +1,31 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
const Popup = ({ const Popup = ({
visible, isOpen,
setIsOpen,
message, message,
onConfirm, onConfirm,
onCancel, onCancel,
uniqueConfirmButton = false, uniqueConfirmButton = false,
popupClassName,
}) => { }) => {
if (!visible) return null; if (!isOpen) return null;
// Vérifier si le message est une chaîne de caractères // Vérifier si le message est une chaîne de caractères
const isStringMessage = typeof message === 'string'; const isStringMessage = typeof message === 'string';
// Diviser le message en lignes seulement si c'est une chaîne
const messageLines = isStringMessage ? message.split('\n') : null; const messageLines = isStringMessage ? message.split('\n') : null;
return ReactDOM.createPortal( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60"> <div className="fixed inset-0 z-[9999] flex items-center justify-center">
<div className="bg-white p-6 rounded-xl shadow-2xl max-w-lg w-full"> {/* Overlay noir semi-transparent */}
<div
className="fixed inset-0 bg-black bg-opacity-60 transition-opacity z-[9998]"
onClick={() => setIsOpen(false)}
/>
<div
className={`relative bg-white p-4 sm:p-6 rounded-xl shadow-2xl w-full max-w-xs sm:max-w-md ${popupClassName ? popupClassName : ''}`}
style={{ zIndex: 9999 }}
>
{/* Titre ou message */} {/* Titre ou message */}
<div className="mb-6"> <div className="mb-6">
{isStringMessage {isStringMessage
@ -28,27 +36,31 @@ const Popup = ({
)) ))
: message} : message}
</div> </div>
{/* Boutons d'action */} {/* Boutons d'action */}
<div className="flex justify-end space-x-4"> <div className="flex justify-end space-x-4">
{!uniqueConfirmButton && ( {!uniqueConfirmButton && (
<button <button
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 focus:outline-none transition" className="px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 focus:outline-none transition"
onClick={onCancel} onClick={() => {
setIsOpen(false);
if (onCancel) onCancel();
}}
> >
Annuler Annuler
</button> </button>
)} )}
<button <button
className="px-4 py-2 bg-emerald-500 text-white rounded-lg hover:bg-emerald-600 focus:outline-none transition" className="px-4 py-2 bg-emerald-500 text-white rounded-lg hover:bg-emerald-600 focus:outline-none transition"
onClick={onConfirm} onClick={() => {
setIsOpen(false);
if (onConfirm) onConfirm();
}}
> >
{uniqueConfirmButton ? 'Fermer' : 'Confirmer'} {uniqueConfirmButton ? 'Fermer' : 'Confirmer'}
</button> </button>
</div> </div>
</div> </div>
</div>, </div>
document.body
); );
}; };

View File

@ -331,7 +331,7 @@ export default function FilesGroupsManagement({
setRemovePopupVisible(true); setRemovePopupVisible(true);
setRemovePopupMessage( setRemovePopupMessage(
`Attentions ! \nÊtes-vous sûr de vouloir supprimer ce groupe ?` 'Attentions ! \nÊtes-vous sûr de vouloir supprimer ce groupe ?'
); );
setRemovePopupOnConfirm(() => () => { setRemovePopupOnConfirm(() => () => {
setIsLoading(true); setIsLoading(true);
@ -508,16 +508,15 @@ export default function FilesGroupsManagement({
} }
}} }}
title={isEditing ? 'Modification du document' : 'Ajouter un document'} title={isEditing ? 'Modification du document' : 'Ajouter un document'}
ContentComponent={() => ( modalClassName="w-4/5 h-4/5"
>
<FileUploadDocuSeal <FileUploadDocuSeal
handleCreateTemplateMaster={handleCreateTemplateMaster} handleCreateTemplateMaster={handleCreateTemplateMaster}
handleEditTemplateMaster={handleEditTemplateMaster} handleEditTemplateMaster={handleEditTemplateMaster}
fileToEdit={fileToEdit} fileToEdit={fileToEdit}
onSuccess={handleReloadTemplates} onSuccess={handleReloadTemplates}
/> />
)} </Modal>
modalClassName="w-4/5 h-4/5"
/>
{/* Modal pour les groupes */} {/* Modal pour les groupes */}
<Modal <Modal
@ -526,13 +525,12 @@ export default function FilesGroupsManagement({
title={ title={
groupToEdit ? 'Modifier le dossier' : "Création d'un nouveau dossier" groupToEdit ? 'Modifier le dossier' : "Création d'un nouveau dossier"
} }
ContentComponent={() => ( >
<RegistrationFileGroupForm <RegistrationFileGroupForm
onSubmit={handleGroupSubmit} onSubmit={handleGroupSubmit}
initialData={groupToEdit} initialData={groupToEdit}
/> />
)} </Modal>
/>
{/* Section Groupes de fichiers */} {/* Section Groupes de fichiers */}
<div className="mt-8 w-3/5"> <div className="mt-8 w-3/5">

View File

@ -334,17 +334,21 @@ const FeesSection = ({
renderCell={renderFeeCell} renderCell={renderFeeCell}
/> />
<Popup <Popup
visible={popupVisible} isOpen={popupVisible}
setIsOpen={setPopupVisible}
message={popupMessage} message={popupMessage}
onConfirm={() => setPopupVisible(false)} onConfirm={() => setPopupVisible(false)}
onCancel={() => setPopupVisible(false)} onCancel={() => setPopupVisible(false)}
uniqueConfirmButton={true} uniqueConfirmButton={true}
popupClassName="w-full max-w-xs sm:max-w-md"
/> />
<Popup <Popup
visible={removePopupVisible} isOpen={removePopupVisible}
setIsOpen={setRemovePopupVisible}
message={removePopupMessage} message={removePopupMessage}
onConfirm={removePopupOnConfirm} onConfirm={removePopupOnConfirm}
onCancel={() => setRemovePopupVisible(false)} onCancel={() => setRemovePopupVisible(false)}
popupClassName="w-full max-w-xs sm:max-w-md"
/> />
</div> </div>
); );