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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ const Modal = ({
isOpen,
setIsOpen,
title,
ContentComponent,
children,
modalClassName,
}) => {
return (
@ -12,13 +12,14 @@ const Modal = ({
<Dialog.Portal>
{/* 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.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
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 */}
<div className="flex justify-between items-center mb-6">
<Dialog.Title className="text-2xl font-semibold text-gray-800">
{/* En-tête de la modale si title fourni */}
{title && (
<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}
</Dialog.Title>
<Dialog.Close asChild>
@ -44,11 +45,9 @@ const Modal = ({
</button>
</Dialog.Close>
</div>
)}
{/* Contenu de la modale */}
<div className="w-full h-full">
<ContentComponent />
</div>
<div className="w-full h-full">{children}</div>
</div>
</Dialog.Content>
</Dialog.Portal>

View File

@ -1,23 +1,31 @@
import React from 'react';
import ReactDOM from 'react-dom';
const Popup = ({
visible,
isOpen,
setIsOpen,
message,
onConfirm,
onCancel,
uniqueConfirmButton = false,
popupClassName,
}) => {
if (!visible) return null;
if (!isOpen) return null;
// Vérifier si le message est une chaîne de caractères
const isStringMessage = typeof message === 'string';
// Diviser le message en lignes seulement si c'est une chaîne
const messageLines = isStringMessage ? message.split('\n') : null;
return ReactDOM.createPortal(
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60">
<div className="bg-white p-6 rounded-xl shadow-2xl max-w-lg w-full">
return (
<div className="fixed inset-0 z-[9999] flex items-center justify-center">
{/* 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 */}
<div className="mb-6">
{isStringMessage
@ -28,27 +36,31 @@ const Popup = ({
))
: message}
</div>
{/* Boutons d'action */}
<div className="flex justify-end space-x-4">
{!uniqueConfirmButton && (
<button
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
</button>
)}
<button
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'}
</button>
</div>
</div>
</div>,
document.body
</div>
);
};

View File

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

View File

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