mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
feat: Preparation des modèles Settings pour l'enregistrement SMTP [#17]
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import { usePlanning, RecurrenceType } from '@/context/PlanningContext';
|
||||
import { format } from 'date-fns';
|
||||
import React from 'react';
|
||||
import { useNotification } from '@/context/NotificationContext';
|
||||
|
||||
export default function EventModal({
|
||||
isOpen,
|
||||
@ -10,6 +11,7 @@ export default function EventModal({
|
||||
}) {
|
||||
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } =
|
||||
usePlanning();
|
||||
const { showNotification } = useNotification();
|
||||
|
||||
// S'assurer que planning est défini lors du premier rendu
|
||||
React.useEffect(() => {
|
||||
@ -46,7 +48,11 @@ export default function EventModal({
|
||||
e.preventDefault();
|
||||
|
||||
if (!eventData.planning) {
|
||||
alert('Veuillez sélectionner un planning');
|
||||
showNotification(
|
||||
'Veuillez sélectionner un planning',
|
||||
'warning',
|
||||
'Attention'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
70
Front-End/src/components/FlashNotification.js
Normal file
70
Front-End/src/components/FlashNotification.js
Normal file
@ -0,0 +1,70 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from 'lucide-react';
|
||||
|
||||
const typeStyles = {
|
||||
success: {
|
||||
icon: <CheckCircle className="h-6 w-6 text-green-500" />,
|
||||
bg: 'bg-green-300',
|
||||
},
|
||||
error: {
|
||||
icon: <AlertCircle className="h-6 w-6 text-red-500" />,
|
||||
bg: 'bg-red-300',
|
||||
},
|
||||
info: {
|
||||
icon: <Info className="h-6 w-6 text-blue-500" />,
|
||||
bg: 'bg-blue-300',
|
||||
},
|
||||
warning: {
|
||||
icon: <AlertTriangle className="h-6 w-6 text-yellow-500" />,
|
||||
bg: 'bg-yellow-300',
|
||||
},
|
||||
};
|
||||
|
||||
export default function FlashNotification({
|
||||
title,
|
||||
message,
|
||||
type = 'info',
|
||||
onClose,
|
||||
}) {
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setIsVisible(false); // Déclenche la disparition
|
||||
setTimeout(onClose, 300); // Appelle onClose après l'animation
|
||||
}, 3000); // Notification visible pendant 3 secondes
|
||||
return () => clearTimeout(timer);
|
||||
}, [onClose]);
|
||||
|
||||
if (!message || !isVisible) return null;
|
||||
|
||||
const { icon, bg } = typeStyles[type] || typeStyles.info;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 50 }} // Animation d'entrée
|
||||
animate={{ opacity: 1, x: 0 }} // Animation visible
|
||||
exit={{ opacity: 0, x: 50 }} // Animation de sortie
|
||||
transition={{ duration: 0.3 }} // Durée des animations
|
||||
className="fixed top-5 right-5 flex items-stretch w-96 rounded-lg shadow-lg bg-white z-50 border border-gray-200"
|
||||
>
|
||||
{/* Rectangle gauche avec l'icône */}
|
||||
<div className={`flex items-center justify-center w-12 ${bg}`}>
|
||||
{icon}
|
||||
</div>
|
||||
{/* Zone de texte */}
|
||||
<div className="flex-1 p-4">
|
||||
<p className="font-bold text-black">{title}</p>
|
||||
<p className="text-gray-700">{message}</p>
|
||||
</div>
|
||||
{/* Bouton de fermeture */}
|
||||
<button
|
||||
onClick={() => setIsVisible(false)}
|
||||
className="text-gray-500 hover:text-gray-700 focus:outline-none p-2"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@ -31,7 +31,7 @@ export default function InputTextIcon({
|
||||
!enable ? 'bg-gray-100 cursor-not-allowed' : ''
|
||||
}`}
|
||||
>
|
||||
<span className="inline-flex items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
|
||||
<span className="inline-flex min-h-9 items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
|
||||
{IconItem && <IconItem />}
|
||||
</span>
|
||||
<input
|
||||
|
||||
@ -4,6 +4,7 @@ import { SessionProvider } from 'next-auth/react';
|
||||
import { CsrfProvider } from '@/context/CsrfContext';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
import { EstablishmentProvider } from '@/context/EstablishmentContext';
|
||||
import { NotificationProvider } from '@/context/NotificationContext';
|
||||
import { ClassesProvider } from '@/context/ClassesContext';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
@ -14,18 +15,20 @@ export default function Providers({ children, messages, locale, session }) {
|
||||
locale = 'fr'; // Valeur par défaut
|
||||
}
|
||||
return (
|
||||
<SessionProvider session={session}>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<CsrfProvider>
|
||||
<EstablishmentProvider>
|
||||
<ClassesProvider>
|
||||
<NextIntlClientProvider messages={messages} locale={locale}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</ClassesProvider>
|
||||
</EstablishmentProvider>
|
||||
</CsrfProvider>
|
||||
</DndProvider>
|
||||
</SessionProvider>
|
||||
<NotificationProvider>
|
||||
<SessionProvider session={session}>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<CsrfProvider>
|
||||
<EstablishmentProvider>
|
||||
<ClassesProvider>
|
||||
<NextIntlClientProvider messages={messages} locale={locale}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</ClassesProvider>
|
||||
</EstablishmentProvider>
|
||||
</CsrfProvider>
|
||||
</DndProvider>
|
||||
</SessionProvider>
|
||||
</NotificationProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
const SidebarTabs = ({ tabs, onTabChange }) => {
|
||||
const [activeTab, setActiveTab] = useState(tabs[0].id);
|
||||
@ -30,15 +31,24 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
|
||||
</div>
|
||||
|
||||
{/* Tabs Content */}
|
||||
<div className="flex-1 overflow-y-auto p-4 rounded-b-lg shadow-inner">
|
||||
{tabs.map((tab) => (
|
||||
<div
|
||||
key={tab.id}
|
||||
className={`${activeTab === tab.id ? 'block' : 'hidden'}`}
|
||||
>
|
||||
{tab.content}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex-1 overflow-y-auto p-4 rounded-b-lg shadow-inner relative">
|
||||
<AnimatePresence mode="wait">
|
||||
{tabs.map(
|
||||
(tab) =>
|
||||
activeTab === tab.id && (
|
||||
<motion.div
|
||||
key={tab.id}
|
||||
initial={{ opacity: 0, x: 50 }} // Animation d'entrée
|
||||
animate={{ opacity: 1, x: 0 }} // Animation visible
|
||||
exit={{ opacity: 0, x: -50 }} // Animation de sortie
|
||||
transition={{ duration: 0.3 }} // Durée des animations
|
||||
className="absolute w-full h-full"
|
||||
>
|
||||
{tab.content}
|
||||
</motion.div>
|
||||
)
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -29,6 +29,7 @@ import ParentFilesSection from '@/components/Structure/Files/ParentFilesSection'
|
||||
import SectionHeader from '@/components/SectionHeader';
|
||||
import Popup from '@/components/Popup';
|
||||
import Loader from '@/components/Loader';
|
||||
import { useNotification } from '@/context/NotificationContext';
|
||||
|
||||
export default function FilesGroupsManagement({
|
||||
csrfToken,
|
||||
@ -52,6 +53,7 @@ export default function FilesGroupsManagement({
|
||||
const [removePopupMessage, setRemovePopupMessage] = useState('');
|
||||
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { showNotification } = useNotification();
|
||||
|
||||
const handleReloadTemplates = () => {
|
||||
setReloadTemplates(true);
|
||||
@ -136,15 +138,20 @@ export default function FilesGroupsManagement({
|
||||
(fichier) => fichier.id !== templateMaster.id
|
||||
)
|
||||
);
|
||||
setPopupMessage(
|
||||
`Le document "${templateMaster.name}" a été correctement supprimé.`
|
||||
showNotification(
|
||||
`Le document "${templateMaster.name}" a été correctement supprimé.`,
|
||||
'success',
|
||||
'Succès'
|
||||
);
|
||||
|
||||
setPopupVisible(true);
|
||||
setRemovePopupVisible(false);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
setPopupMessage(
|
||||
`Erreur lors de la suppression du document "${templateMaster.name}".`
|
||||
showNotification(
|
||||
`Erreur lors de la suppression du document "${templateMaster.name}".`,
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
setPopupVisible(true);
|
||||
setRemovePopupVisible(false);
|
||||
@ -153,16 +160,20 @@ export default function FilesGroupsManagement({
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error deleting file from database:', error);
|
||||
setPopupMessage(
|
||||
`Erreur lors de la suppression du document "${templateMaster.name}".`
|
||||
showNotification(
|
||||
`Erreur lors de la suppression du document "${templateMaster.name}".`,
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
setPopupVisible(true);
|
||||
setRemovePopupVisible(false);
|
||||
setIsLoading(false);
|
||||
});
|
||||
} else {
|
||||
setPopupMessage(
|
||||
`Erreur lors de la suppression du document "${templateMaster.name}".`
|
||||
showNotification(
|
||||
`Erreur lors de la suppression du document "${templateMaster.name}".`,
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
setPopupVisible(true);
|
||||
setRemovePopupVisible(false);
|
||||
@ -171,8 +182,10 @@ export default function FilesGroupsManagement({
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error removing template from DocuSeal:', error);
|
||||
setPopupMessage(
|
||||
`Erreur lors de la suppression du document "${templateMaster.name}".`
|
||||
showNotification(
|
||||
`Erreur lors de la suppression du document "${templateMaster.name}".`,
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
setPopupVisible(true);
|
||||
setRemovePopupVisible(false);
|
||||
@ -253,7 +266,11 @@ export default function FilesGroupsManagement({
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error editing file:', error);
|
||||
alert('Erreur lors de la modification du fichier');
|
||||
showNotification(
|
||||
'Erreur lors de la modification du fichier',
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -271,7 +288,11 @@ export default function FilesGroupsManagement({
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error handling group:', error);
|
||||
alert("Erreur lors de l'opération sur le groupe");
|
||||
showNotification(
|
||||
"Erreur lors de l'opération sur le groupe",
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Ajouter l'établissement sélectionné lors de la création d'un nouveau groupe
|
||||
@ -287,7 +308,11 @@ export default function FilesGroupsManagement({
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error handling group:', error);
|
||||
alert("Erreur lors de l'opération sur le groupe");
|
||||
showNotification(
|
||||
"Erreur lors de l'opération sur le groupe",
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -303,8 +328,10 @@ export default function FilesGroupsManagement({
|
||||
(file) => file.group && file.group.id === groupId
|
||||
);
|
||||
if (filesInGroup.length > 0) {
|
||||
alert(
|
||||
"Impossible de supprimer ce groupe car il contient des schoolFileMasters. Veuillez d'abord retirer tous les schoolFileMasters de ce groupe."
|
||||
showNotification(
|
||||
"Impossible de supprimer ce groupe car il contient des schoolFileMasters. Veuillez d'abord retirer tous les schoolFileMasters de ce groupe.",
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -319,13 +346,15 @@ export default function FilesGroupsManagement({
|
||||
throw new Error('Erreur lors de la suppression du groupe.');
|
||||
}
|
||||
setGroups(groups.filter((group) => group.id !== groupId));
|
||||
alert('Groupe supprimé avec succès.');
|
||||
showNotification('Groupe supprimé avec succès.', 'success', 'Succès');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error deleting group:', error);
|
||||
alert(
|
||||
showNotification(
|
||||
error.message ||
|
||||
"Erreur lors de la suppression du groupe. Vérifiez qu'aucune inscription n'utilise ce groupe."
|
||||
"Erreur lors de la suppression du groupe. Vérifiez qu'aucune inscription n'utilise ce groupe.",
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -342,8 +371,10 @@ export default function FilesGroupsManagement({
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('Erreur lors de la création du document parent:', error);
|
||||
alert(
|
||||
'Une erreur est survenue lors de la création du document parent.'
|
||||
showNotification(
|
||||
'Une erreur est survenue lors de la création du document parent.',
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
throw error;
|
||||
});
|
||||
@ -365,8 +396,10 @@ export default function FilesGroupsManagement({
|
||||
'Erreur lors de la modification du document parent:',
|
||||
error
|
||||
);
|
||||
alert(
|
||||
'Une erreur est survenue lors de la modification du document parent.'
|
||||
showNotification(
|
||||
'Une erreur est survenue lors de la modification du document parent.',
|
||||
'error',
|
||||
'Erreur'
|
||||
);
|
||||
throw error;
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { usePlanning } from '@/context/PlanningContext';
|
||||
import { format } from 'date-fns';
|
||||
import React from 'react';
|
||||
import { useNotification } from '@/context/NotificationContext';
|
||||
|
||||
export default function ScheduleEventModal({
|
||||
isOpen,
|
||||
@ -13,6 +14,7 @@ export default function ScheduleEventModal({
|
||||
}) {
|
||||
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } =
|
||||
usePlanning();
|
||||
const { showNotification } = useNotification();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!eventData?.planning && schedules.length > 0) {
|
||||
@ -78,22 +80,34 @@ export default function ScheduleEventModal({
|
||||
e.preventDefault();
|
||||
|
||||
if (!eventData.speciality) {
|
||||
alert('Veuillez sélectionner une matière');
|
||||
showNotification(
|
||||
'Veuillez sélectionner une matière',
|
||||
'warning',
|
||||
'Attention'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventData.teacher) {
|
||||
alert('Veuillez sélectionner un professeur');
|
||||
showNotification(
|
||||
'Veuillez sélectionner un professeur',
|
||||
'warning',
|
||||
'Attention'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventData.planning) {
|
||||
alert('Veuillez sélectionner un planning');
|
||||
showNotification(
|
||||
'Veuillez sélectionner un planning',
|
||||
'warning',
|
||||
'Attention'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventData.location) {
|
||||
alert('Veuillez saisir un lieu');
|
||||
showNotification('Veuillez saisir un lieu', 'warning', 'Attention');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user