mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Preparation des modèles Settings pour l'enregistrement SMTP [#17]
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
.venv/
|
.venv/
|
||||||
|
.env
|
||||||
node_modules/
|
node_modules/
|
||||||
hardcoded-strings-report.md
|
hardcoded-strings-report.md
|
||||||
@ -8,14 +8,8 @@ WORKDIR /Back-End
|
|||||||
# Allows docker to cache installed dependencies between builds
|
# Allows docker to cache installed dependencies between builds
|
||||||
COPY requirements.txt requirements.txt
|
COPY requirements.txt requirements.txt
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
RUN pip install pymupdf
|
|
||||||
|
|
||||||
# Mounts the application code to the image
|
# Mounts the application code to the image
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
ENV DJANGO_SETTINGS_MODULE N3wtSchool.settings
|
|
||||||
ENV DJANGO_SUPERUSER_PASSWORD=admin
|
|
||||||
ENV DJANGO_SUPERUSER_USERNAME=admin
|
|
||||||
ENV DJANGO_SUPERUSER_EMAIL=admin@n3wtschool.com
|
|
||||||
|
|||||||
@ -45,6 +45,7 @@ INSTALLED_APPS = [
|
|||||||
'School.apps.SchoolConfig',
|
'School.apps.SchoolConfig',
|
||||||
'Planning.apps.PlanningConfig',
|
'Planning.apps.PlanningConfig',
|
||||||
'Establishment.apps.EstablishmentConfig',
|
'Establishment.apps.EstablishmentConfig',
|
||||||
|
'Settings.apps.SettingsConfig',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
|
|||||||
@ -47,6 +47,7 @@ urlpatterns = [
|
|||||||
path("DocuSeal/", include(("DocuSeal.urls", 'DocuSeal'), namespace='DocuSeal')),
|
path("DocuSeal/", include(("DocuSeal.urls", 'DocuSeal'), namespace='DocuSeal')),
|
||||||
path("Planning/", include(("Planning.urls", 'Planning'), namespace='Planning')),
|
path("Planning/", include(("Planning.urls", 'Planning'), namespace='Planning')),
|
||||||
path("Establishment/", include(("Establishment.urls", 'Establishment'), namespace='Establishment')),
|
path("Establishment/", include(("Establishment.urls", 'Establishment'), namespace='Establishment')),
|
||||||
|
path("Settings/", include(("Settings.urls", 'Settings'), namespace='Settings')),
|
||||||
# Documentation Api
|
# Documentation Api
|
||||||
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||||
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class RecursionType(models.IntegerChoices):
|
|||||||
RECURSION_CUSTOM = 4, _('Personnalisé')
|
RECURSION_CUSTOM = 4, _('Personnalisé')
|
||||||
|
|
||||||
class Planning(models.Model):
|
class Planning(models.Model):
|
||||||
establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT)
|
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE)
|
||||||
school_class = models.ForeignKey(
|
school_class = models.ForeignKey(
|
||||||
SchoolClass,
|
SchoolClass,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
|||||||
1
Back-End/Settings/__init__.py
Normal file
1
Back-End/Settings/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'Settings.apps.SettingsConfig'
|
||||||
3
Back-End/Settings/admin.py
Normal file
3
Back-End/Settings/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
5
Back-End/Settings/apps.py
Normal file
5
Back-End/Settings/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
class SettingsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'Settings'
|
||||||
17
Back-End/Settings/models.py
Normal file
17
Back-End/Settings/models.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.conf import settings
|
||||||
|
from Establishment.models import Establishment
|
||||||
|
|
||||||
|
class SMTPSettings(models.Model):
|
||||||
|
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE)
|
||||||
|
smtp_server = models.CharField(max_length=255)
|
||||||
|
smtp_port = models.PositiveIntegerField()
|
||||||
|
smtp_user = models.CharField(max_length=255)
|
||||||
|
smtp_password = models.CharField(max_length=255)
|
||||||
|
use_tls = models.BooleanField(default=True)
|
||||||
|
use_ssl = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"SMTP Settings ({self.smtp_server}:{self.smtp_port})"
|
||||||
7
Back-End/Settings/serializers.py
Normal file
7
Back-End/Settings/serializers.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from .models import SMTPSettings
|
||||||
|
|
||||||
|
class SMTPSettingsSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = SMTPSettings
|
||||||
|
fields = '__all__'
|
||||||
6
Back-End/Settings/urls.py
Normal file
6
Back-End/Settings/urls.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from .views import SMTPSettingsView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('smtp-settings/', SMTPSettingsView.as_view(), name='smtp_settings'),
|
||||||
|
]
|
||||||
55
Back-End/Settings/views.py
Normal file
55
Back-End/Settings/views.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
|
from drf_yasg import openapi
|
||||||
|
from .models import SMTPSettings
|
||||||
|
from .serializers import SMTPSettingsSerializer
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
class SMTPSettingsView(APIView):
|
||||||
|
"""
|
||||||
|
API pour gérer les paramètres SMTP.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Récupérer les paramètres SMTP",
|
||||||
|
responses={
|
||||||
|
200: SMTPSettingsSerializer(),
|
||||||
|
404: openapi.Response(description="Aucun paramètre SMTP trouvé."),
|
||||||
|
500: openapi.Response(description="Erreur interne du serveur."),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def get(self, request):
|
||||||
|
try:
|
||||||
|
smtp_settings = SMTPSettings.objects.first()
|
||||||
|
if not smtp_settings:
|
||||||
|
return Response({'error': 'Aucun paramètre SMTP trouvé.'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
serializer = SMTPSettingsSerializer(smtp_settings)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
except Exception as e:
|
||||||
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
operation_description="Créer ou mettre à jour les paramètres SMTP",
|
||||||
|
request_body=SMTPSettingsSerializer,
|
||||||
|
responses={
|
||||||
|
200: SMTPSettingsSerializer(),
|
||||||
|
400: openapi.Response(description="Données invalides."),
|
||||||
|
500: openapi.Response(description="Erreur interne du serveur."),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def post(self, request):
|
||||||
|
data = request.data
|
||||||
|
try:
|
||||||
|
smtp_settings = SMTPSettings.objects.first()
|
||||||
|
if smtp_settings:
|
||||||
|
serializer = SMTPSettingsSerializer(smtp_settings, data=data)
|
||||||
|
else:
|
||||||
|
serializer = SMTPSettingsSerializer(data=data)
|
||||||
|
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
except Exception as e:
|
||||||
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
Binary file not shown.
@ -16,6 +16,7 @@ commands = [
|
|||||||
["python", "manage.py", "collectstatic", "--noinput"],
|
["python", "manage.py", "collectstatic", "--noinput"],
|
||||||
["python", "manage.py", "flush", "--noinput"],
|
["python", "manage.py", "flush", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "Establishment", "--noinput"],
|
["python", "manage.py", "makemigrations", "Establishment", "--noinput"],
|
||||||
|
["python", "manage.py", "makemigrations", "Settings", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "Subscriptions", "--noinput"],
|
["python", "manage.py", "makemigrations", "Subscriptions", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "Planning", "--noinput"],
|
["python", "manage.py", "makemigrations", "Planning", "--noinput"],
|
||||||
["python", "manage.py", "makemigrations", "GestionNotification", "--noinput"],
|
["python", "manage.py", "makemigrations", "GestionNotification", "--noinput"],
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import {
|
|||||||
FE_ADMIN_GRADES_URL,
|
FE_ADMIN_GRADES_URL,
|
||||||
FE_ADMIN_PLANNING_URL,
|
FE_ADMIN_PLANNING_URL,
|
||||||
FE_ADMIN_SETTINGS_URL,
|
FE_ADMIN_SETTINGS_URL,
|
||||||
FE_ADMIN_MESSAGERIE_URL
|
FE_ADMIN_MESSAGERIE_URL,
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
|
|
||||||
import { disconnect } from '@/app/actions/authAction';
|
import { disconnect } from '@/app/actions/authAction';
|
||||||
@ -38,7 +38,6 @@ import Footer from '@/components/Footer';
|
|||||||
import { getRightStr, RIGHTS } from '@/utils/rights';
|
import { getRightStr, RIGHTS } from '@/utils/rights';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
|
|
||||||
export default function Layout({ children }) {
|
export default function Layout({ children }) {
|
||||||
const t = useTranslations('sidebar');
|
const t = useTranslations('sidebar');
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import logger from '@/utils/logger';
|
|||||||
import { fetchRegisterForms } from '@/app/actions/subscriptionAction';
|
import { fetchRegisterForms } from '@/app/actions/subscriptionAction';
|
||||||
import { fetchUpcomingEvents } from '@/app/actions/planningAction';
|
import { fetchUpcomingEvents } from '@/app/actions/planningAction';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
// Composant EventCard pour afficher les événements
|
// Composant EventCard pour afficher les événements
|
||||||
const EventCard = ({ title, date, description, type }) => (
|
const EventCard = ({ title, date, description, type }) => (
|
||||||
<div className="bg-stone-50 p-4 rounded-lg shadow-sm border border-gray-100 mb-4">
|
<div className="bg-stone-50 p-4 rounded-lg shadow-sm border border-gray-100 mb-4">
|
||||||
@ -39,6 +39,7 @@ export default function DashboardPage() {
|
|||||||
|
|
||||||
const [classes, setClasses] = useState([]);
|
const [classes, setClasses] = useState([]);
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
const { showNotification } = useNotification();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedEstablishmentId) return;
|
if (!selectedEstablishmentId) return;
|
||||||
@ -64,6 +65,11 @@ export default function DashboardPage() {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error('Error fetching classes:', error);
|
logger.error('Error fetching classes:', error);
|
||||||
|
showNotification(
|
||||||
|
'Error fetching classes: ' + error.message,
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch des formulaires d'inscription
|
// Fetch des formulaires d'inscription
|
||||||
|
|||||||
@ -1,10 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Tab from '@/components/Tab';
|
import Tab from '@/components/Tab';
|
||||||
import TabContent from '@/components/TabContent';
|
import TabContent from '@/components/TabContent';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import {
|
||||||
|
fetchSmtpSettings,
|
||||||
|
editSmtpSettings,
|
||||||
|
} from '@/app/actions/settingsAction';
|
||||||
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
import { useCsrfToken } from '@/context/CsrfContext'; // Import du hook pour récupérer le csrfToken
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const [activeTab, setActiveTab] = useState('structure');
|
const [activeTab, setActiveTab] = useState('structure');
|
||||||
@ -15,11 +22,35 @@ export default function SettingsPage() {
|
|||||||
const [smtpPort, setSmtpPort] = useState('');
|
const [smtpPort, setSmtpPort] = useState('');
|
||||||
const [smtpUser, setSmtpUser] = useState('');
|
const [smtpUser, setSmtpUser] = useState('');
|
||||||
const [smtpPassword, setSmtpPassword] = useState('');
|
const [smtpPassword, setSmtpPassword] = useState('');
|
||||||
|
const [useTls, setUseTls] = useState(true);
|
||||||
|
const [useSsl, setUseSsl] = useState(false);
|
||||||
|
const [statusMessage, setStatusMessage] = useState('');
|
||||||
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
|
const csrfToken = useCsrfToken(); // Récupération du csrfToken
|
||||||
|
const { showNotification } = useNotification();
|
||||||
const handleTabClick = (tab) => {
|
const handleTabClick = (tab) => {
|
||||||
setActiveTab(tab);
|
setActiveTab(tab);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Charger les paramètres SMTP existants
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab === 'smtp') {
|
||||||
|
fetchSmtpSettings(csrfToken) // Passer le csrfToken ici
|
||||||
|
.then((data) => {
|
||||||
|
setSmtpServer(data.smtp_server || '');
|
||||||
|
setSmtpPort(data.smtp_port || '');
|
||||||
|
setSmtpUser(data.smtp_user || '');
|
||||||
|
setSmtpPassword(data.smtp_password || '');
|
||||||
|
setUseTls(data.use_tls || false);
|
||||||
|
setUseSsl(data.use_ssl || false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error('Erreur lors du chargement des paramètres SMTP:', error);
|
||||||
|
setStatusMessage('Erreur lors du chargement des paramètres SMTP.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [activeTab, csrfToken]); // Ajouter csrfToken comme dépendance
|
||||||
|
|
||||||
const handleEmailChange = (e) => {
|
const handleEmailChange = (e) => {
|
||||||
setEmail(e.target.value);
|
setEmail(e.target.value);
|
||||||
};
|
};
|
||||||
@ -48,24 +79,50 @@ export default function SettingsPage() {
|
|||||||
setSmtpPassword(e.target.value);
|
setSmtpPassword(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUseTlsChange = (e) => {
|
||||||
|
setUseTls(e.target.checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUseSslChange = (e) => {
|
||||||
|
setUseSsl(e.target.checked);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (password !== confirmPassword) {
|
if (password !== confirmPassword) {
|
||||||
alert('Les mots de passe ne correspondent pas');
|
showNotification(
|
||||||
|
'Les mots de passe ne correspondent pas',
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Logique pour mettre à jour l'email et le mot de passe
|
|
||||||
logger.debug('Email:', email);
|
|
||||||
logger.debug('Password:', password);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSmtpSubmit = (e) => {
|
const handleSmtpSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// Logique pour mettre à jour les paramètres SMTP
|
const smtpData = {
|
||||||
logger.debug('SMTP Server:', smtpServer);
|
establishment: selectedEstablishmentId,
|
||||||
logger.debug('SMTP Port:', smtpPort);
|
smtp_server: smtpServer,
|
||||||
logger.debug('SMTP User:', smtpUser);
|
smtp_port: smtpPort,
|
||||||
logger.debug('SMTP Password:', smtpPassword);
|
smtp_user: smtpUser,
|
||||||
|
smtp_password: smtpPassword,
|
||||||
|
use_tls: useTls,
|
||||||
|
use_ssl: useSsl,
|
||||||
|
};
|
||||||
|
|
||||||
|
editSmtpSettings(smtpData, csrfToken) // Passer le csrfToken ici
|
||||||
|
.then(() => {
|
||||||
|
setStatusMessage('Paramètres SMTP mis à jour avec succès.');
|
||||||
|
logger.debug('SMTP Settings Updated:', smtpData);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.error(
|
||||||
|
'Erreur lors de la mise à jour des paramètres SMTP:',
|
||||||
|
error
|
||||||
|
);
|
||||||
|
setStatusMessage('Erreur lors de la mise à jour des paramètres SMTP.');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -128,8 +185,27 @@ export default function SettingsPage() {
|
|||||||
value={smtpPassword}
|
value={smtpPassword}
|
||||||
onChange={handleSmtpPasswordChange}
|
onChange={handleSmtpPasswordChange}
|
||||||
/>
|
/>
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={useTls}
|
||||||
|
onChange={handleUseTlsChange}
|
||||||
|
/>
|
||||||
|
Utiliser TLS
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={useSsl}
|
||||||
|
onChange={handleUseSslChange}
|
||||||
|
/>
|
||||||
|
Utiliser SSL
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<Button type="submit" primary text="Mettre à jour"></Button>
|
<Button type="submit" primary text="Mettre à jour"></Button>
|
||||||
</form>
|
</form>
|
||||||
|
{statusMessage && <p className="mt-4 text-sm">{statusMessage}</p>}
|
||||||
</TabContent>
|
</TabContent>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import StructureManagement from '@/components/Structure/Configuration/StructureManagement';
|
import StructureManagement from '@/components/Structure/Configuration/StructureManagement';
|
||||||
import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement';
|
import ScheduleManagement from '@/components/Structure/Planning/ScheduleManagement';
|
||||||
import FeesManagement from '@/components/Structure/Tarification/FeesManagement';
|
import FeesManagement from '@/components/Structure/Tarification/FeesManagement';
|
||||||
|
|||||||
@ -160,7 +160,7 @@ export default function CreateSubscriptionPage() {
|
|||||||
|
|
||||||
const requestErrorHandler = (err) => {
|
const requestErrorHandler = (err) => {
|
||||||
logger.error('Error fetching data:', err);
|
logger.error('Error fetching data:', err);
|
||||||
setErrors(err);
|
//setErrors(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -3,11 +3,13 @@ import React, { useState } from 'react';
|
|||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import InputText from '@/components/InputText';
|
import InputText from '@/components/InputText';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [confirmPassword, setConfirmPassword] = useState('');
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
const { showNotification } = useNotification();
|
||||||
|
|
||||||
const handleEmailChange = (e) => {
|
const handleEmailChange = (e) => {
|
||||||
setEmail(e.target.value);
|
setEmail(e.target.value);
|
||||||
@ -24,7 +26,11 @@ export default function SettingsPage() {
|
|||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (password !== confirmPassword) {
|
if (password !== confirmPassword) {
|
||||||
alert('Les mots de passe ne correspondent pas');
|
showNotification(
|
||||||
|
'Les mots de passe ne correspondent pas',
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Logique pour mettre à jour l'email et le mot de passe
|
// Logique pour mettre à jour l'email et le mot de passe
|
||||||
|
|||||||
@ -9,6 +9,7 @@ const requestResponseHandler = async (response) => {
|
|||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
// Throw an error with the JSON body containing the form errors
|
// Throw an error with the JSON body containing the form errors
|
||||||
|
|
||||||
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||||
error.details = body;
|
error.details = body;
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
37
Front-End/src/app/actions/settingsAction.js
Normal file
37
Front-End/src/app/actions/settingsAction.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { BE_SETTINGS_SMTP_URL } from '@/utils/Url';
|
||||||
|
|
||||||
|
export const PENDING = 'pending';
|
||||||
|
export const SUBSCRIBED = 'subscribed';
|
||||||
|
export const ARCHIVED = 'archived';
|
||||||
|
|
||||||
|
const requestResponseHandler = async (response) => {
|
||||||
|
const body = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
// Throw an error with the JSON body containing the form errors
|
||||||
|
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||||
|
error.details = body;
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchSmtpSettings = (csrfToken) => {
|
||||||
|
return fetch(`${BE_SETTINGS_SMTP_URL}/`, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
}).then(requestResponseHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const editSmtpSettings = (data, csrfToken) => {
|
||||||
|
return fetch(`${BE_SETTINGS_SMTP_URL}/`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: 'include',
|
||||||
|
}).then(requestResponseHandler);
|
||||||
|
};
|
||||||
@ -7,6 +7,7 @@ import {
|
|||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
|
|
||||||
import { CURRENT_YEAR_FILTER } from '@/utils/constants';
|
import { CURRENT_YEAR_FILTER } from '@/utils/constants';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
const requestResponseHandler = async (response) => {
|
const requestResponseHandler = async (response) => {
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
@ -16,6 +17,7 @@ const requestResponseHandler = async (response) => {
|
|||||||
// Throw an error with the JSON body containing the form errors
|
// Throw an error with the JSON body containing the form errors
|
||||||
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
const error = new Error(body?.errorMessage || 'Une erreur est survenue');
|
||||||
error.details = body;
|
error.details = body;
|
||||||
|
showNotification('Une erreur inattendue est survenue.', 'error', 'Erreur');
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { usePlanning, RecurrenceType } from '@/context/PlanningContext';
|
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';
|
||||||
|
|
||||||
export default function EventModal({
|
export default function EventModal({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -10,6 +11,7 @@ export default function EventModal({
|
|||||||
}) {
|
}) {
|
||||||
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } =
|
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } =
|
||||||
usePlanning();
|
usePlanning();
|
||||||
|
const { showNotification } = useNotification();
|
||||||
|
|
||||||
// S'assurer que planning est défini lors du premier rendu
|
// S'assurer que planning est défini lors du premier rendu
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -46,7 +48,11 @@ export default function EventModal({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!eventData.planning) {
|
if (!eventData.planning) {
|
||||||
alert('Veuillez sélectionner un planning');
|
showNotification(
|
||||||
|
'Veuillez sélectionner un planning',
|
||||||
|
'warning',
|
||||||
|
'Attention'
|
||||||
|
);
|
||||||
return;
|
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' : ''
|
!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 />}
|
{IconItem && <IconItem />}
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { SessionProvider } from 'next-auth/react';
|
|||||||
import { CsrfProvider } from '@/context/CsrfContext';
|
import { CsrfProvider } from '@/context/CsrfContext';
|
||||||
import { NextIntlClientProvider } from 'next-intl';
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
import { EstablishmentProvider } from '@/context/EstablishmentContext';
|
import { EstablishmentProvider } from '@/context/EstablishmentContext';
|
||||||
|
import { NotificationProvider } from '@/context/NotificationContext';
|
||||||
import { ClassesProvider } from '@/context/ClassesContext';
|
import { ClassesProvider } from '@/context/ClassesContext';
|
||||||
import { DndProvider } from 'react-dnd';
|
import { DndProvider } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
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
|
locale = 'fr'; // Valeur par défaut
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<SessionProvider session={session}>
|
<NotificationProvider>
|
||||||
<DndProvider backend={HTML5Backend}>
|
<SessionProvider session={session}>
|
||||||
<CsrfProvider>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<EstablishmentProvider>
|
<CsrfProvider>
|
||||||
<ClassesProvider>
|
<EstablishmentProvider>
|
||||||
<NextIntlClientProvider messages={messages} locale={locale}>
|
<ClassesProvider>
|
||||||
{children}
|
<NextIntlClientProvider messages={messages} locale={locale}>
|
||||||
</NextIntlClientProvider>
|
{children}
|
||||||
</ClassesProvider>
|
</NextIntlClientProvider>
|
||||||
</EstablishmentProvider>
|
</ClassesProvider>
|
||||||
</CsrfProvider>
|
</EstablishmentProvider>
|
||||||
</DndProvider>
|
</CsrfProvider>
|
||||||
</SessionProvider>
|
</DndProvider>
|
||||||
|
</SessionProvider>
|
||||||
|
</NotificationProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
|
||||||
const SidebarTabs = ({ tabs, onTabChange }) => {
|
const SidebarTabs = ({ tabs, onTabChange }) => {
|
||||||
const [activeTab, setActiveTab] = useState(tabs[0].id);
|
const [activeTab, setActiveTab] = useState(tabs[0].id);
|
||||||
@ -30,15 +31,24 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tabs Content */}
|
{/* Tabs Content */}
|
||||||
<div className="flex-1 overflow-y-auto p-4 rounded-b-lg shadow-inner">
|
<div className="flex-1 overflow-y-auto p-4 rounded-b-lg shadow-inner relative">
|
||||||
{tabs.map((tab) => (
|
<AnimatePresence mode="wait">
|
||||||
<div
|
{tabs.map(
|
||||||
key={tab.id}
|
(tab) =>
|
||||||
className={`${activeTab === tab.id ? 'block' : 'hidden'}`}
|
activeTab === tab.id && (
|
||||||
>
|
<motion.div
|
||||||
{tab.content}
|
key={tab.id}
|
||||||
</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="absolute w-full h-full"
|
||||||
|
>
|
||||||
|
{tab.content}
|
||||||
|
</motion.div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import ParentFilesSection from '@/components/Structure/Files/ParentFilesSection'
|
|||||||
import SectionHeader from '@/components/SectionHeader';
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import Loader from '@/components/Loader';
|
import Loader from '@/components/Loader';
|
||||||
|
import { useNotification } from '@/context/NotificationContext';
|
||||||
|
|
||||||
export default function FilesGroupsManagement({
|
export default function FilesGroupsManagement({
|
||||||
csrfToken,
|
csrfToken,
|
||||||
@ -52,6 +53,7 @@ export default function FilesGroupsManagement({
|
|||||||
const [removePopupMessage, setRemovePopupMessage] = useState('');
|
const [removePopupMessage, setRemovePopupMessage] = useState('');
|
||||||
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { showNotification } = useNotification();
|
||||||
|
|
||||||
const handleReloadTemplates = () => {
|
const handleReloadTemplates = () => {
|
||||||
setReloadTemplates(true);
|
setReloadTemplates(true);
|
||||||
@ -136,15 +138,20 @@ export default function FilesGroupsManagement({
|
|||||||
(fichier) => fichier.id !== templateMaster.id
|
(fichier) => fichier.id !== templateMaster.id
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
setPopupMessage(
|
showNotification(
|
||||||
`Le document "${templateMaster.name}" a été correctement supprimé.`
|
`Le document "${templateMaster.name}" a été correctement supprimé.`,
|
||||||
|
'success',
|
||||||
|
'Succès'
|
||||||
);
|
);
|
||||||
|
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
setRemovePopupVisible(false);
|
setRemovePopupVisible(false);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} else {
|
} else {
|
||||||
setPopupMessage(
|
showNotification(
|
||||||
`Erreur lors de la suppression du document "${templateMaster.name}".`
|
`Erreur lors de la suppression du document "${templateMaster.name}".`,
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
);
|
);
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
setRemovePopupVisible(false);
|
setRemovePopupVisible(false);
|
||||||
@ -153,16 +160,20 @@ export default function FilesGroupsManagement({
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error deleting file from database:', error);
|
console.error('Error deleting file from database:', error);
|
||||||
setPopupMessage(
|
showNotification(
|
||||||
`Erreur lors de la suppression du document "${templateMaster.name}".`
|
`Erreur lors de la suppression du document "${templateMaster.name}".`,
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
);
|
);
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
setRemovePopupVisible(false);
|
setRemovePopupVisible(false);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setPopupMessage(
|
showNotification(
|
||||||
`Erreur lors de la suppression du document "${templateMaster.name}".`
|
`Erreur lors de la suppression du document "${templateMaster.name}".`,
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
);
|
);
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
setRemovePopupVisible(false);
|
setRemovePopupVisible(false);
|
||||||
@ -171,8 +182,10 @@ export default function FilesGroupsManagement({
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error removing template from DocuSeal:', error);
|
console.error('Error removing template from DocuSeal:', error);
|
||||||
setPopupMessage(
|
showNotification(
|
||||||
`Erreur lors de la suppression du document "${templateMaster.name}".`
|
`Erreur lors de la suppression du document "${templateMaster.name}".`,
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
);
|
);
|
||||||
setPopupVisible(true);
|
setPopupVisible(true);
|
||||||
setRemovePopupVisible(false);
|
setRemovePopupVisible(false);
|
||||||
@ -253,7 +266,11 @@ export default function FilesGroupsManagement({
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error editing file:', 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) => {
|
.catch((error) => {
|
||||||
console.error('Error handling group:', 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 {
|
} else {
|
||||||
// Ajouter l'établissement sélectionné lors de la création d'un nouveau groupe
|
// Ajouter l'établissement sélectionné lors de la création d'un nouveau groupe
|
||||||
@ -287,7 +308,11 @@ export default function FilesGroupsManagement({
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error handling group:', 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
|
(file) => file.group && file.group.id === groupId
|
||||||
);
|
);
|
||||||
if (filesInGroup.length > 0) {
|
if (filesInGroup.length > 0) {
|
||||||
alert(
|
showNotification(
|
||||||
"Impossible de supprimer ce groupe car il contient des schoolFileMasters. Veuillez d'abord retirer tous les schoolFileMasters de ce groupe."
|
"Impossible de supprimer ce groupe car il contient des schoolFileMasters. Veuillez d'abord retirer tous les schoolFileMasters de ce groupe.",
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -319,13 +346,15 @@ export default function FilesGroupsManagement({
|
|||||||
throw new Error('Erreur lors de la suppression du groupe.');
|
throw new Error('Erreur lors de la suppression du groupe.');
|
||||||
}
|
}
|
||||||
setGroups(groups.filter((group) => group.id !== groupId));
|
setGroups(groups.filter((group) => group.id !== groupId));
|
||||||
alert('Groupe supprimé avec succès.');
|
showNotification('Groupe supprimé avec succès.', 'success', 'Succès');
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error deleting group:', error);
|
console.error('Error deleting group:', error);
|
||||||
alert(
|
showNotification(
|
||||||
error.message ||
|
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) => {
|
.catch((error) => {
|
||||||
logger.error('Erreur lors de la création du document parent:', error);
|
logger.error('Erreur lors de la création du document parent:', error);
|
||||||
alert(
|
showNotification(
|
||||||
'Une erreur est survenue lors de la création du document parent.'
|
'Une erreur est survenue lors de la création du document parent.',
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
);
|
);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
@ -365,8 +396,10 @@ export default function FilesGroupsManagement({
|
|||||||
'Erreur lors de la modification du document parent:',
|
'Erreur lors de la modification du document parent:',
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
alert(
|
showNotification(
|
||||||
'Une erreur est survenue lors de la modification du document parent.'
|
'Une erreur est survenue lors de la modification du document parent.',
|
||||||
|
'error',
|
||||||
|
'Erreur'
|
||||||
);
|
);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { usePlanning } from '@/context/PlanningContext';
|
import { usePlanning } 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';
|
||||||
|
|
||||||
export default function ScheduleEventModal({
|
export default function ScheduleEventModal({
|
||||||
isOpen,
|
isOpen,
|
||||||
@ -13,6 +14,7 @@ export default function ScheduleEventModal({
|
|||||||
}) {
|
}) {
|
||||||
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } =
|
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } =
|
||||||
usePlanning();
|
usePlanning();
|
||||||
|
const { showNotification } = useNotification();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!eventData?.planning && schedules.length > 0) {
|
if (!eventData?.planning && schedules.length > 0) {
|
||||||
@ -78,22 +80,34 @@ export default function ScheduleEventModal({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!eventData.speciality) {
|
if (!eventData.speciality) {
|
||||||
alert('Veuillez sélectionner une matière');
|
showNotification(
|
||||||
|
'Veuillez sélectionner une matière',
|
||||||
|
'warning',
|
||||||
|
'Attention'
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!eventData.teacher) {
|
if (!eventData.teacher) {
|
||||||
alert('Veuillez sélectionner un professeur');
|
showNotification(
|
||||||
|
'Veuillez sélectionner un professeur',
|
||||||
|
'warning',
|
||||||
|
'Attention'
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!eventData.planning) {
|
if (!eventData.planning) {
|
||||||
alert('Veuillez sélectionner un planning');
|
showNotification(
|
||||||
|
'Veuillez sélectionner un planning',
|
||||||
|
'warning',
|
||||||
|
'Attention'
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!eventData.location) {
|
if (!eventData.location) {
|
||||||
alert('Veuillez saisir un lieu');
|
showNotification('Veuillez saisir un lieu', 'warning', 'Attention');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
36
Front-End/src/context/NotificationContext.js
Normal file
36
Front-End/src/context/NotificationContext.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import React, { createContext, useState, useContext } from 'react';
|
||||||
|
import FlashNotification from '@/components/FlashNotification';
|
||||||
|
|
||||||
|
const NotificationContext = createContext();
|
||||||
|
|
||||||
|
export const NotificationProvider = ({ children }) => {
|
||||||
|
const [notification, setNotification] = useState({
|
||||||
|
message: '',
|
||||||
|
type: '',
|
||||||
|
title: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const showNotification = (message, type = 'info', title = '') => {
|
||||||
|
setNotification({ message, type, title });
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearNotification = () => {
|
||||||
|
setNotification({ message: '', type: '', title: '' });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NotificationContext.Provider value={{ showNotification }}>
|
||||||
|
{notification.message && (
|
||||||
|
<FlashNotification
|
||||||
|
title={notification.title}
|
||||||
|
message={notification.message}
|
||||||
|
type={notification.type}
|
||||||
|
onClose={clearNotification}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</NotificationContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useNotification = () => useContext(NotificationContext);
|
||||||
@ -55,6 +55,9 @@ export const BE_GESTIONMESSAGERIE_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/
|
|||||||
export const BE_GESTIONMESSAGERIE_MESSAGERIE_URL = `${BASE_URL}/GestionMessagerie/messagerie`;
|
export const BE_GESTIONMESSAGERIE_MESSAGERIE_URL = `${BASE_URL}/GestionMessagerie/messagerie`;
|
||||||
export const BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL = `${BASE_URL}/GestionMessagerie/send-email/`;
|
export const BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL = `${BASE_URL}/GestionMessagerie/send-email/`;
|
||||||
|
|
||||||
|
// SETTINGS
|
||||||
|
export const BE_SETTINGS_SMTP_URL = `${BASE_URL}/Settings/smtp-settings`;
|
||||||
|
|
||||||
// URL FRONT-END
|
// URL FRONT-END
|
||||||
export const FE_HOME_URL = `/`;
|
export const FE_HOME_URL = `/`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user