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,255 +87,254 @@ 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>
<label className="block text-sm font-medium text-gray-700 mb-1">
Titre
</label>
<input
type="text"
value={eventData.title || ''}
onChange={(e) =>
setEventData({ ...eventData, title: e.target.value })
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
required
/>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
{/* Titre */}
{/* Description */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Description
</label>
<textarea
value={eventData.description || ''}
onChange={(e) =>
setEventData({ ...eventData, description: e.target.value })
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
rows="3"
/>
</div>
{/* Planning */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Planning
</label>
<select
value={eventData.planning || schedules[0]?.id}
onChange={(e) => {
const selectedSchedule = schedules.find(
(s) => s.id === e.target.value
);
setEventData({
...eventData,
planning: e.target.value,
color: selectedSchedule?.color || '#10b981',
});
}}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
required
>
{schedules.map((schedule) => (
<option key={schedule.id} value={schedule.id}>
{schedule.name}
</option>
))}
</select>
</div>
{/* Couleur */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Couleur
</label>
<input
type="color"
value={
eventData.color ||
schedules.find((s) => s.id === eventData.planning)?.color ||
'#10b981'
}
onChange={(e) =>
setEventData({ ...eventData, color: e.target.value })
}
className="w-full h-10 p-1 rounded border"
/>
</div>
{/* Récurrence */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Récurrence
</label>
<select
value={eventData.recursionType || RecurrenceType.NONE}
onChange={(e) => {
return setEventData({
...eventData,
recursionType: e.target.value,
});
}}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
>
{recurrenceOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
{/* Paramètres de récurrence personnalisée */}
{eventData.recursionType == RecurrenceType.CUSTOM && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Jours de répétition
</label>
<div className="flex gap-2 flex-wrap">
{daysOfWeek.map((day) => (
<button
key={day.value}
type="button"
onClick={() => {
const days = eventData.selectedDays || [];
const newDays = days.includes(day.value)
? days.filter((d) => d !== day.value)
: [...days, day.value];
setEventData({ ...eventData, selectedDays: newDays });
}}
className={`px-3 py-1 rounded-full text-sm ${
(eventData.selectedDays || []).includes(day.value)
? 'bg-emerald-100 text-emerald-800'
: 'bg-gray-100 text-gray-600'
}`}
>
{day.label}
</button>
))}
</div>
</div>
)}
{/* Date de fin de récurrence */}
{eventData.recursionType != RecurrenceType.NONE && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Titre
Fin de récurrence
</label>
<input
type="text"
value={eventData.title || ''}
onChange={(e) =>
setEventData({ ...eventData, title: e.target.value })
type="date"
value={
eventData.recursionEnd
? format(new Date(eventData.recursionEnd), 'yyyy-MM-dd')
: ''
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
required
/>
</div>
{/* Description */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Description
</label>
<textarea
value={eventData.description || ''}
onChange={(e) =>
setEventData({ ...eventData, description: e.target.value })
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
rows="3"
/>
</div>
{/* Planning */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Planning
</label>
<select
value={eventData.planning || schedules[0]?.id}
onChange={(e) => {
const selectedSchedule = schedules.find(
(s) => s.id === e.target.value
);
setEventData({
...eventData,
planning: e.target.value,
color: selectedSchedule?.color || '#10b981',
});
}}
recursionEnd: e.target.value
? new Date(e.target.value).toISOString()
: null,
})
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
/>
</div>
)}
{/* Dates */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Début
</label>
<input
type="datetime-local"
value={format(new Date(eventData.start), "yyyy-MM-dd'T'HH:mm")}
onChange={(e) =>
setEventData({
...eventData,
start: new Date(e.target.value).toISOString(),
})
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
required
>
{schedules.map((schedule) => (
<option key={schedule.id} value={schedule.id}>
{schedule.name}
</option>
))}
</select>
</div>
{/* Couleur */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Couleur
</label>
<input
type="color"
value={
eventData.color ||
schedules.find((s) => s.id === eventData.planning)?.color ||
'#10b981'
}
onChange={(e) =>
setEventData({ ...eventData, color: e.target.value })
}
className="w-full h-10 p-1 rounded border"
/>
</div>
{/* Récurrence */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Récurrence
Fin
</label>
<select
value={eventData.recursionType || RecurrenceType.NONE}
onChange={(e) => {
return setEventData({
<input
type="datetime-local"
value={format(new Date(eventData.end), "yyyy-MM-dd'T'HH:mm")}
onChange={(e) =>
setEventData({
...eventData,
recursionType: e.target.value,
});
}}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
>
{recurrenceOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
{/* Paramètres de récurrence personnalisée */}
{eventData.recursionType == RecurrenceType.CUSTOM && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Jours de répétition
</label>
<div className="flex gap-2 flex-wrap">
{daysOfWeek.map((day) => (
<button
key={day.value}
type="button"
onClick={() => {
const days = eventData.selectedDays || [];
const newDays = days.includes(day.value)
? days.filter((d) => d !== day.value)
: [...days, day.value];
setEventData({ ...eventData, selectedDays: newDays });
}}
className={`px-3 py-1 rounded-full text-sm ${
(eventData.selectedDays || []).includes(day.value)
? 'bg-emerald-100 text-emerald-800'
: 'bg-gray-100 text-gray-600'
}`}
>
{day.label}
</button>
))}
</div>
</div>
)}
{/* Date de fin de récurrence */}
{eventData.recursionType != RecurrenceType.NONE && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Fin de récurrence
</label>
<input
type="date"
value={
eventData.recursionEnd
? format(new Date(eventData.recursionEnd), 'yyyy-MM-dd')
: ''
}
onChange={(e) =>
setEventData({
...eventData,
recursionEnd: e.target.value
? new Date(e.target.value).toISOString()
: null,
})
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
/>
</div>
)}
{/* Dates */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Début
</label>
<input
type="datetime-local"
value={format(new Date(eventData.start), "yyyy-MM-dd'T'HH:mm")}
onChange={(e) =>
setEventData({
...eventData,
start: new Date(e.target.value).toISOString(),
})
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Fin
</label>
<input
type="datetime-local"
value={format(new Date(eventData.end), "yyyy-MM-dd'T'HH:mm")}
onChange={(e) =>
setEventData({
...eventData,
end: new Date(e.target.value).toISOString(),
})
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
required
/>
</div>
</div>
{/* Lieu */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Lieu
</label>
<input
type="text"
value={eventData.location || ''}
onChange={(e) =>
setEventData({ ...eventData, location: e.target.value })
end: new Date(e.target.value).toISOString(),
})
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
required
/>
</div>
</div>
{/* Boutons */}
<div className="flex justify-between gap-2 mt-6">
<div>
{eventData.id && (
<button
type="button"
onClick={handleDelete}
className="px-4 py-2 text-red-600 hover:bg-red-50 rounded"
>
Supprimer
</button>
)}
</div>
<div className="flex gap-2">
{/* Lieu */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Lieu
</label>
<input
type="text"
value={eventData.location || ''}
onChange={(e) =>
setEventData({ ...eventData, location: e.target.value })
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
/>
</div>
{/* Boutons */}
<div className="flex justify-between gap-2 mt-6">
<div>
{eventData.id && (
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
onClick={handleDelete}
className="px-4 py-2 text-red-600 hover:bg-red-50 rounded"
>
Annuler
Supprimer
</button>
<button
type="submit"
className="px-4 py-2 bg-emerald-600 text-white rounded hover:bg-emerald-700"
>
{eventData.id ? 'Modifier' : 'Créer'}
</button>
</div>
)}
</div>
</form>
</div>
</div>
<div className="flex gap-2">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
>
Annuler
</button>
<button
type="submit"
className="px-4 py-2 bg-emerald-600 text-white rounded hover:bg-emerald-700"
>
{eventData.id ? 'Modifier' : 'Créer'}
</button>
</div>
</div>
</form>
</Modal>
);
}

View File

@ -96,146 +96,143 @@ const FilesModal = ({
{title}
</span>
}
ContentComponent={() => (
<div className="space-y-6">
{/* Section Fiche élève */}
{files.registrationFile && (
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Fiche élève
</h3>
<div className="flex items-center gap-2">
<FileText className="w-5 h-5 text-gray-500" />
<a
href={files.registrationFile.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 underline"
>
{files.registrationFile.name}
</a>
</div>
>
<div className="space-y-6">
{/* Section Fiche élève */}
{files.registrationFile && (
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Fiche élève
</h3>
<div className="flex items-center gap-2">
<FileText className="w-5 h-5 text-gray-500" />
<a
href={files.registrationFile.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 underline"
>
{files.registrationFile.name}
</a>
</div>
)}
</div>
)}
{/* Section Documents fusionnés */}
{files.fusionFile && (
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Documents fusionnés
</h3>
<div className="flex items-center gap-2">
<FileText className="w-5 h-5 text-gray-500" />
<a
href={files.fusionFile.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 underline"
>
{files.fusionFile.name}
</a>
</div>
{/* Section Documents fusionnés */}
{files.fusionFile && (
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Documents fusionnés
</h3>
<div className="flex items-center gap-2">
<FileText className="w-5 h-5 text-gray-500" />
<a
href={files.fusionFile.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 underline"
>
{files.fusionFile.name}
</a>
</div>
)}
<hr className="border-t border-gray-300" />
{/* Section Fichiers École */}
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Formulaires de l&apos;établissement
</h3>
<ul className="space-y-2">
{files.schoolFiles.length > 0 ? (
files.schoolFiles.map((file, index) => (
<li key={index} className="flex items-center gap-2">
<FileText className="w-5 h-5 text-gray-500" />
{file.url ? (
<a
href={file.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 underline"
>
{file.name}
</a>
) : (
<span className="text-gray-400">
{file.name} (Non disponible)
</span>
)}
</li>
))
) : (
<p className="text-gray-500">
Aucun fichier scolaire disponible.
</p>
)}
</ul>
</div>
)}
<hr className="border-t border-gray-300" />
<hr className="border-t border-gray-300" />
{/* Section Fichiers Parent */}
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Pièces fournies
</h3>
<ul className="space-y-2">
{files.parentFiles.length > 0 ? (
files.parentFiles.map((file, index) => (
<li key={index} className="flex items-center gap-2">
<FileText className="w-5 h-5 text-gray-500" />
{file.url ? (
<a
href={file.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 underline"
>
{file.name}
</a>
) : (
<span className="text-gray-400">
{file.name} (Non disponible)
</span>
)}
</li>
))
) : (
<p className="text-gray-500">
Aucun fichier parent disponible.
</p>
)}
</ul>
</div>
<hr className="border-t border-gray-300" />
{/* Section Mandat SEPA */}
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Mandat SEPA
</h3>
{files.sepaFile ? (
<div className="flex items-center gap-2">
<FileText className="w-5 h-5 text-gray-500" />
<a
href={files.sepaFile.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 underline"
>
{files.sepaFile.name}
</a>
</div>
{/* Section Fichiers École */}
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Formulaires de l&apos;établissement
</h3>
<ul className="space-y-2">
{files.schoolFiles.length > 0 ? (
files.schoolFiles.map((file, index) => (
<li key={index} className="flex items-center gap-2">
<FileText className="w-5 h-5 text-gray-500" />
{file.url ? (
<a
href={file.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 underline"
>
{file.name}
</a>
) : (
<span className="text-gray-400">
{file.name} (Non disponible)
</span>
)}
</li>
))
) : (
<p className="text-gray-500">Aucun mandat SEPA disponible.</p>
<p className="text-gray-500">
Aucun fichier scolaire disponible.
</p>
)}
</div>
</ul>
</div>
)}
/>
<hr className="border-t border-gray-300" />
{/* Section Fichiers Parent */}
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Pièces fournies
</h3>
<ul className="space-y-2">
{files.parentFiles.length > 0 ? (
files.parentFiles.map((file, index) => (
<li key={index} className="flex items-center gap-2">
<FileText className="w-5 h-5 text-gray-500" />
{file.url ? (
<a
href={file.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 underline"
>
{file.name}
</a>
) : (
<span className="text-gray-400">
{file.name} (Non disponible)
</span>
)}
</li>
))
) : (
<p className="text-gray-500">Aucun fichier parent disponible.</p>
)}
</ul>
</div>
<hr className="border-t border-gray-300" />
{/* Section Mandat SEPA */}
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Mandat SEPA
</h3>
{files.sepaFile ? (
<div className="flex items-center gap-2">
<FileText className="w-5 h-5 text-gray-500" />
<a
href={files.sepaFile.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 underline"
>
{files.sepaFile.name}
</a>
</div>
) : (
<p className="text-gray-500">Aucun mandat SEPA disponible.</p>
)}
</div>
</div>
</Modal>
);
};

View File

@ -4,7 +4,7 @@ const Modal = ({
isOpen,
setIsOpen,
title,
ContentComponent,
children,
modalClassName,
}) => {
return (
@ -12,43 +12,42 @@ 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">
{title}
</Dialog.Title>
<Dialog.Close asChild>
<button
onClick={() => setIsOpen(false)}
className="text-gray-500 hover:text-gray-700 focus:outline-none"
>
<span className="sr-only">Fermer</span>
<svg
className="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
{/* 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>
<button
onClick={() => setIsOpen(false)}
className="text-gray-500 hover:text-gray-700 focus:outline-none"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</Dialog.Close>
</div>
<span className="sr-only">Fermer</span>
<svg
className="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</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={() => (
<FileUploadDocuSeal
handleCreateTemplateMaster={handleCreateTemplateMaster}
handleEditTemplateMaster={handleEditTemplateMaster}
fileToEdit={fileToEdit}
onSuccess={handleReloadTemplates}
/>
)}
modalClassName="w-4/5 h-4/5"
/>
>
<FileUploadDocuSeal
handleCreateTemplateMaster={handleCreateTemplateMaster}
handleEditTemplateMaster={handleEditTemplateMaster}
fileToEdit={fileToEdit}
onSuccess={handleReloadTemplates}
/>
</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}
/>
)}
/>
>
<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>
);