mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
fix: Uniformisation des Modales et Popup [#35]
This commit is contained in:
@ -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">
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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, {
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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'é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'é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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user