feat: Messagerie WIP [#17]

This commit is contained in:
Luc SORIGNET
2025-05-11 14:02:04 +02:00
parent c6d75281a1
commit 23a593dbc7
28 changed files with 1177 additions and 391 deletions

View File

@ -25,7 +25,7 @@ from django.db.models import Q
from Auth.serializers import ProfileSerializer, ProfileRoleSerializer from Auth.serializers import ProfileSerializer, ProfileRoleSerializer
from Subscriptions.models import RegistrationForm, Guardian from Subscriptions.models import RegistrationForm, Guardian
import Subscriptions.mailManager as mailer import N3wtSchool.mailManager as mailer
import Subscriptions.util as util import Subscriptions.util as util
import logging import logging
from N3wtSchool import bdd, error, settings from N3wtSchool import bdd, error, settings

View File

@ -1,5 +1,5 @@
from django.urls import path, re_path from django.urls import path, re_path
from .views import SendEmailView from .views import SendEmailView, search_recipients
from GestionMessagerie.views import MessagerieView, MessageView, MessageSimpleView from GestionMessagerie.views import MessagerieView, MessageView, MessageSimpleView
urlpatterns = [ urlpatterns = [
@ -7,4 +7,5 @@ urlpatterns = [
re_path(r'^messages$', MessageView.as_view(), name="messages"), re_path(r'^messages$', MessageView.as_view(), name="messages"),
re_path(r'^messages/(?P<id>[0-9]+)$', MessageSimpleView.as_view(), name="messages"), re_path(r'^messages/(?P<id>[0-9]+)$', MessageSimpleView.as_view(), name="messages"),
path('send-email/', SendEmailView.as_view(), name='send_email'), path('send-email/', SendEmailView.as_view(), name='send_email'),
path('search-recipients/', search_recipients, name='search_recipients'),
] ]

View File

@ -6,12 +6,18 @@ from django.utils.html import strip_tags
from django.conf import settings from django.conf import settings
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status from rest_framework import status
from django.db.models import Q
from Auth.models import Profile # Assurez-vous que le modèle Profile contient les informations nécessaires
from .models import * from .models import *
from School.models import Teacher, ProfileRole
from Settings.models import SMTPSettings # Assurez-vous que le chemin est correct
from GestionMessagerie.serializers import MessageSerializer from GestionMessagerie.serializers import MessageSerializer
from School.serializers import TeacherSerializer
from N3wtSchool import bdd from N3wtSchool import bdd
import N3wtSchool.mailManager as mailer
class MessagerieView(APIView): class MessagerieView(APIView):
def get(self, request, profile_id): def get(self, request, profile_id):
@ -46,21 +52,89 @@ class SendEmailView(APIView):
recipients = data.get('recipients', []) recipients = data.get('recipients', [])
subject = data.get('subject', 'Notification') subject = data.get('subject', 'Notification')
message = data.get('message', '') message = data.get('message', '')
establishment_id = data.get('establishment_id', '')
if not recipients or not message: if not recipients or not message:
return Response({'error': 'Les destinataires et le message sont requis.'}, status=status.HTTP_400_BAD_REQUEST) return Response({'error': 'Les destinataires et le message sont requis.'}, status=status.HTTP_400_BAD_REQUEST)
try: try:
plain_message = strip_tags(message) # Récupérer la connexion SMTP
send_mail( connection = mailer.getConnection(establishment_id)
subject,
plain_message, # Envoyer l'email
settings.EMAIL_HOST_USER, return mailer.sendMail(
recipients, recipients=recipients,
html_message=message, subject=subject,
fail_silently=False, message=message,
connection=connection
) )
return Response({'message': 'Email envoyé avec succès.'}, status=status.HTTP_200_OK) except NotFound as e:
return Response({'error': str(e)}, status=status.HTTP_404_NOT_FOUND)
except Exception as e: except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class ContactsView(APIView):
"""
API pour récupérer les contacts associés à un établissement.
"""
def get(self, request, establishment_id):
try:
# Récupérer les enseignants associés à l'établissement
teachers = Teacher.objects.filter(profile_role__establishment_id=establishment_id)
teachers_serializer = TeacherSerializer(teachers, many=True)
# Ajouter un contact pour l'administration
admin_contact = {
"id": "admin",
"name": "Administration",
"email": "admin@etablissement.com",
"profilePic": "https://www.gravatar.com/avatar/admin"
}
contacts = [admin_contact] + teachers_serializer.data
return Response(contacts, status=status.HTTP_200_OK)
except Exception as e:
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def search_recipients(request):
"""
API pour rechercher des destinataires en fonction d'un terme de recherche et d'un établissement.
"""
query = request.GET.get('q', '').strip() # Récupérer le terme de recherche depuis les paramètres GET
establishment_id = request.GET.get('establishment_id', None) # Récupérer l'ID de l'établissement
if not query:
return JsonResponse([], safe=False) # Retourner une liste vide si aucun terme n'est fourni
if not establishment_id:
return JsonResponse({'error': 'establishment_id est requis'}, safe=False, status=status.HTTP_400_BAD_REQUEST)
# Rechercher dans les champs pertinents (nom, prénom, email) et filtrer par establishment_id
profiles = Profile.objects.filter(
Q(first_name__icontains=query) |
Q(last_name__icontains=query) |
Q(email__icontains=query),
roles__establishment_id=establishment_id, # Utiliser 'roles' au lieu de 'profilerole'
roles__is_active=True # Filtrer uniquement les ProfileRole actifs
).distinct()
# Construire la réponse avec les rôles associés
results = []
for profile in profiles:
profile_roles = ProfileRole.objects.filter(
profile=profile,
establishment_id=establishment_id,
is_active=True # Inclure uniquement les ProfileRole actifs
).values(
'id', 'role_type', 'establishment__name', 'is_active'
)
results.append({
'id': profile.id,
'first_name': profile.first_name,
'last_name': profile.last_name,
'email': profile.email,
'roles': list(profile_roles) # Inclure tous les rôles actifs associés pour cet établissement
})
return JsonResponse(results, safe=False)

View File

@ -0,0 +1,8 @@
{
"hostSMTP": "",
"portSMTP": 25,
"username": "",
"password": "",
"useSSL": false,
"useTLS": false
}

View File

@ -1,8 +1,50 @@
from django.core.mail import send_mail, EmailMultiAlternatives, EmailMessage from django.core.mail import send_mail, get_connection, EmailMultiAlternatives, EmailMessage
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.html import strip_tags from django.utils.html import strip_tags
from django.conf import settings
import re import re
from N3wtSchool import settings from rest_framework.response import Response
from rest_framework import status
from rest_framework.exceptions import NotFound
from Settings.models import SMTPSettings
from Establishment.models import Establishment # Importer le modèle Establishment
def getConnection(id_establishement):
try:
# Récupérer l'instance de l'établissement
establishment = Establishment.objects.get(id=id_establishement)
# Récupérer les paramètres SMTP associés à l'établissement
smtp_settings = SMTPSettings.objects.get(establishment=establishment)
# Créer une connexion SMTP avec les paramètres récupérés
connection = get_connection(
host=smtp_settings.smtp_server,
port=smtp_settings.smtp_port,
username=smtp_settings.smtp_user,
password=smtp_settings.smtp_password,
use_tls=smtp_settings.use_tls,
use_ssl=smtp_settings.use_ssl
)
return connection
except Establishment.DoesNotExist:
raise NotFound(f"Aucun établissement trouvé avec l'ID {id_establishement}")
except SMTPSettings.DoesNotExist:
raise NotFound(f"Aucun paramètre SMTP trouvé pour l'établissement {id_establishement}")
def sendMail(recipients, subject, message, connection=None):
try:
plain_message = strip_tags(message)
from_email = settings.EMAIL_HOST_USER
if connection is None:
send_mail(subject, plain_message, from_email, recipients, html_message=message, fail_silently=False)
else:
send_mail(subject, plain_message, from_email, recipients, html_message=message, connection=connection, fail_silently=False)
return Response({'message': 'Email envoyé avec succès.'}, status=status.HTTP_200_OK)
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def envoieReinitMotDePasse(recipients, code): def envoieReinitMotDePasse(recipients, code):
errorMessage = '' errorMessage = ''
@ -14,9 +56,8 @@ def envoieReinitMotDePasse(recipients, code):
} }
subject = EMAIL_REINIT_SUBJECT subject = EMAIL_REINIT_SUBJECT
html_message = render_to_string('emails/resetPassword.html', context) html_message = render_to_string('emails/resetPassword.html', context)
plain_message = strip_tags(html_message) sendMail(recipients, subject, html_message)
from_email = settings.EMAIL_HOST_USER
send_mail(subject, plain_message, from_email, [recipients], html_message=html_message)
except Exception as e: except Exception as e:
errorMessage = str(e) errorMessage = str(e)
@ -36,10 +77,8 @@ def sendRegisterForm(recipients, establishment_id):
subject = EMAIL_INSCRIPTION_SUBJECT subject = EMAIL_INSCRIPTION_SUBJECT
html_message = render_to_string('emails/inscription.html', context) html_message = render_to_string('emails/inscription.html', context)
plain_message = strip_tags(html_message) sendMail(recipients, subject, html_message)
from_email = settings.EMAIL_HOST_USER
send_mail(subject, plain_message, from_email, [recipients], html_message=html_message)
except Exception as e: except Exception as e:
errorMessage = str(e) errorMessage = str(e)
@ -59,10 +98,7 @@ def sendMandatSEPA(recipients, establishment_id):
subject = EMAIL_INSCRIPTION_SUBJECT subject = EMAIL_INSCRIPTION_SUBJECT
html_message = render_to_string('emails/sepa.html', context) html_message = render_to_string('emails/sepa.html', context)
plain_message = strip_tags(html_message) sendMail(recipients, subject, html_message)
from_email = settings.EMAIL_HOST_USER
send_mail(subject, plain_message, from_email, [recipients], html_message=html_message)
except Exception as e: except Exception as e:
errorMessage = str(e) errorMessage = str(e)
@ -74,13 +110,8 @@ def envoieRelanceDossierInscription(recipients, code):
EMAIL_RELANCE_CORPUS = 'Bonjour,\nN\'ayant pas eu de retour de votre part, nous vous renvoyons le lien vers le formulaire d\'inscription : ' + BASE_URL + '/users/login\nCordialement' EMAIL_RELANCE_CORPUS = 'Bonjour,\nN\'ayant pas eu de retour de votre part, nous vous renvoyons le lien vers le formulaire d\'inscription : ' + BASE_URL + '/users/login\nCordialement'
errorMessage = '' errorMessage = ''
try: try:
send_mail( sendMail(recipients, EMAIL_RELANCE_SUBJECT, EMAIL_RELANCE_CORPUS%str(code))
EMAIL_RELANCE_SUBJECT,
EMAIL_RELANCE_CORPUS%str(code),
settings.EMAIL_HOST_USER,
[recipients],
fail_silently=False,
)
except Exception as e: except Exception as e:
errorMessage = str(e) errorMessage = str(e)

View File

@ -14,6 +14,10 @@ from pathlib import Path
import json import json
import os import os
from datetime import timedelta from datetime import timedelta
import logging
# Configuration du logger
logger = logging.getLogger(__name__)
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@ -219,23 +223,29 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
#################### Application Settings ############################## #################### Application Settings ##############################
######################################################################## ########################################################################
with open('Subscriptions/Configuration/application.json', 'r') as f:
jsonObject = json.load(f)
DJANGO_SUPERUSER_PASSWORD='admin' DJANGO_SUPERUSER_PASSWORD='admin'
DJANGO_SUPERUSER_USERNAME='admin' DJANGO_SUPERUSER_USERNAME='admin'
DJANGO_SUPERUSER_EMAIL='admin@n3wtschool.com' DJANGO_SUPERUSER_EMAIL='admin@n3wtschool.com'
# Configuration de l'email de l'application
smtp_config_file = 'N3wtSchool/Configuration/application.json'
EMAIL_HOST='smtp.gmail.com' if os.path.exists(smtp_config_file):
EMAIL_PORT=587 try:
EMAIL_HOST_USER=jsonObject['mailFrom'] with open(smtp_config_file, 'r') as f:
EMAIL_HOST_PASSWORD=jsonObject['password'] smtpSettings = json.load(f)
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = smtpSettings.get('hostSMTP', '')
EMAIL_USE_TLS = True EMAIL_PORT = smtpSettings.get('portSMTP', 587)
EMAIL_USE_SSL = False EMAIL_HOST_USER = smtpSettings.get('username', '')
EMAIL_HOST_PASSWORD = smtpSettings.get('password', '')
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = smtpSettings.get('useTLS', True)
EMAIL_USE_SSL = smtpSettings.get('useSSL', False)
except Exception as e:
logger.error(f"Erreur lors de la lecture du fichier de configuration SMTP : {e}")
else:
logger.error(f"Fichier de configuration SMTP introuvable : {smtp_config_file}")
DOCUMENT_DIR = 'documents' DOCUMENT_DIR = 'documents'

View File

@ -12,25 +12,51 @@ class SMTPSettingsView(APIView):
""" """
@swagger_auto_schema( @swagger_auto_schema(
operation_description="Récupérer les paramètres SMTP", operation_description="Récupérer les paramètres SMTP pour un établissement spécifique ou tous les paramètres si aucun ID n'est fourni",
manual_parameters=[
openapi.Parameter(
'establishment_id',
openapi.IN_QUERY,
description="ID de l'établissement (facultatif)",
type=openapi.TYPE_INTEGER,
required=False
)
],
responses={ responses={
200: SMTPSettingsSerializer(), 200: SMTPSettingsSerializer(many=True),
404: openapi.Response(description="Aucun paramètre SMTP trouvé."), 404: openapi.Response(description="Aucun paramètre SMTP trouvé."),
500: openapi.Response(description="Erreur interne du serveur."), 500: openapi.Response(description="Erreur interne du serveur."),
}, },
) )
def get(self, request): def get(self, request):
establishment_id = request.query_params.get('establishment_id')
try: try:
smtp_settings = SMTPSettings.objects.first() if establishment_id:
if not smtp_settings: # Récupérer les paramètres SMTP pour un établissement spécifique
return Response({'error': 'Aucun paramètre SMTP trouvé.'}, status=status.HTTP_404_NOT_FOUND) smtp_settings = SMTPSettings.objects.filter(establishment_id=establishment_id).first()
serializer = SMTPSettingsSerializer(smtp_settings) if not smtp_settings:
return Response(serializer.data, status=status.HTTP_200_OK) return Response(
{'error': f"Aucun paramètre SMTP trouvé pour l'établissement {establishment_id}."},
status=status.HTTP_404_NOT_FOUND
)
serializer = SMTPSettingsSerializer(smtp_settings)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
# Récupérer tous les paramètres SMTP
smtp_settings = SMTPSettings.objects.all()
if not smtp_settings.exists():
return Response(
{'error': "Aucun paramètre SMTP trouvé."},
status=status.HTTP_404_NOT_FOUND
)
serializer = SMTPSettingsSerializer(smtp_settings, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e: except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@swagger_auto_schema( @swagger_auto_schema(
operation_description="Créer ou mettre à jour les paramètres SMTP", operation_description="Créer ou mettre à jour les paramètres SMTP pour un établissement spécifique",
request_body=SMTPSettingsSerializer, request_body=SMTPSettingsSerializer,
responses={ responses={
200: SMTPSettingsSerializer(), 200: SMTPSettingsSerializer(),

View File

@ -1,4 +0,0 @@
{
"mailFrom":"",
"password":""
}

View File

@ -11,7 +11,7 @@ import json
import os import os
from django.core.files import File from django.core.files import File
import Subscriptions.mailManager as mailer import N3wtSchool.mailManager as mailer
import Subscriptions.util as util import Subscriptions.util as util
from Subscriptions.serializers import RegistrationFormSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileTemplateSerializer from Subscriptions.serializers import RegistrationFormSerializer, RegistrationSchoolFileTemplateSerializer, RegistrationParentFileTemplateSerializer
@ -519,4 +519,3 @@ def get_parent_file_templates_by_rf(request, id):
return JsonResponse(serializer.data, safe=False) return JsonResponse(serializer.data, safe=False)
except RegistrationParentFileTemplate.DoesNotExist: except RegistrationParentFileTemplate.DoesNotExist:
return JsonResponse({'error': 'Aucune pièce à fournir trouvée pour ce dossier d\'inscription'}, status=status.HTTP_404_NOT_FOUND) return JsonResponse({'error': 'Aucune pièce à fournir trouvée pour ce dossier d\'inscription'}, status=status.HTTP_404_NOT_FOUND)

View File

@ -11,7 +11,6 @@
"@docuseal/react": "^1.0.56", "@docuseal/react": "^1.0.56",
"@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2",
"@tailwindcss/forms": "^0.5.9", "@tailwindcss/forms": "^0.5.9",
"@tinymce/tinymce-react": "^6.1.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"framer-motion": "^11.11.11", "framer-motion": "^11.11.11",
"ics": "^3.8.1", "ics": "^3.8.1",
@ -29,6 +28,7 @@
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18", "react-dom": "^18",
"react-international-phone": "^4.5.0", "react-international-phone": "^4.5.0",
"react-quill": "^2.0.0",
"react-tooltip": "^5.28.0" "react-tooltip": "^5.28.0"
}, },
"devDependencies": { "devDependencies": {
@ -1055,25 +1055,6 @@
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
} }
}, },
"node_modules/@tinymce/tinymce-react": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@tinymce/tinymce-react/-/tinymce-react-6.1.0.tgz",
"integrity": "sha512-K0MP3yYVKe8+etUwsg6zyRq+q9TGLaVf005WiBHiB8JZEomAwbBPERGunhU9uOqNQ5gJs8yVOPZ68Xcd1UHclA==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": "^19.0.0 || ^18.0.0 || ^17.0.1 || ^16.7.0",
"react-dom": "^19.0.0 || ^18.0.0 || ^17.0.1 || ^16.7.0",
"tinymce": "^7.0.0 || ^6.0.0 || ^5.5.1"
},
"peerDependenciesMeta": {
"tinymce": {
"optional": true
}
}
},
"node_modules/@tybys/wasm-util": { "node_modules/@tybys/wasm-util": {
"version": "0.9.0", "version": "0.9.0",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
@ -1110,6 +1091,15 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true "dev": true
}, },
"node_modules/@types/quill": {
"version": "1.3.10",
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
"integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==",
"license": "MIT",
"dependencies": {
"parchment": "^1.1.2"
}
},
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.1.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.0.tgz",
@ -1990,7 +1980,6 @@
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
"dev": true,
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.0", "call-bind-apply-helpers": "^1.0.0",
"es-define-property": "^1.0.0", "es-define-property": "^1.0.0",
@ -2008,7 +1997,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@ -2021,7 +2009,6 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0" "get-intrinsic": "^1.3.0"
@ -2129,6 +2116,15 @@
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
}, },
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"license": "MIT",
"engines": {
"node": ">=0.8"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -2284,6 +2280,26 @@
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==" "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="
}, },
"node_modules/deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
"license": "MIT",
"dependencies": {
"is-arguments": "^1.1.1",
"is-date-object": "^1.0.5",
"is-regex": "^1.1.4",
"object-is": "^1.1.5",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.5.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-is": { "node_modules/deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -2294,7 +2310,6 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"dependencies": { "dependencies": {
"es-define-property": "^1.0.0", "es-define-property": "^1.0.0",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@ -2311,7 +2326,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"dependencies": { "dependencies": {
"define-data-property": "^1.0.1", "define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0", "has-property-descriptors": "^1.0.0",
@ -2377,7 +2391,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.1", "call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@ -2480,7 +2493,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@ -2489,7 +2501,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@ -2525,7 +2536,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"dependencies": { "dependencies": {
"es-errors": "^1.3.0" "es-errors": "^1.3.0"
}, },
@ -3049,11 +3059,29 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
"license": "MIT"
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
}, },
"node_modules/fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==",
"license": "Apache-2.0"
},
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@ -3287,7 +3315,6 @@
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"dev": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
@ -3296,7 +3323,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1", "es-define-property": "^1.0.1",
@ -3328,7 +3354,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"dependencies": { "dependencies": {
"dunder-proto": "^1.0.1", "dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0" "es-object-atoms": "^1.0.0"
@ -3469,7 +3494,6 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
}, },
@ -3513,7 +3537,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"dependencies": { "dependencies": {
"es-define-property": "^1.0.0" "es-define-property": "^1.0.0"
}, },
@ -3540,7 +3563,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
}, },
@ -3552,7 +3574,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"dependencies": { "dependencies": {
"has-symbols": "^1.0.3" "has-symbols": "^1.0.3"
}, },
@ -3668,6 +3689,22 @@
"tslib": "^2.8.0" "tslib": "^2.8.0"
} }
}, },
"node_modules/is-arguments": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-array-buffer": { "node_modules/is-array-buffer": {
"version": "3.0.5", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@ -3802,7 +3839,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
"integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
"dev": true,
"dependencies": { "dependencies": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2" "has-tostringtag": "^1.0.2"
@ -3923,7 +3959,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
"dev": true,
"dependencies": { "dependencies": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
"gopd": "^1.2.0", "gopd": "^1.2.0",
@ -4381,7 +4416,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@ -4706,11 +4740,26 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": { "node_modules/object-keys": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@ -4903,6 +4952,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==",
"license": "BSD-3-Clause"
},
"node_modules/parent-module": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -5239,6 +5294,7 @@
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@ -5283,6 +5339,34 @@
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
}, },
"node_modules/quill": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
"license": "BSD-3-Clause",
"dependencies": {
"clone": "^2.1.1",
"deep-equal": "^1.0.1",
"eventemitter3": "^2.0.3",
"extend": "^3.0.2",
"parchment": "^1.1.4",
"quill-delta": "^3.6.2"
}
},
"node_modules/quill-delta": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
"license": "MIT",
"dependencies": {
"deep-equal": "^1.0.1",
"extend": "^3.0.2",
"fast-diff": "1.1.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/react": { "node_modules/react": {
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
@ -5369,6 +5453,21 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/react-quill": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-quill/-/react-quill-2.0.0.tgz",
"integrity": "sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==",
"license": "MIT",
"dependencies": {
"@types/quill": "^1.3.10",
"lodash": "^4.17.4",
"quill": "^1.3.7"
},
"peerDependencies": {
"react": "^16 || ^17 || ^18",
"react-dom": "^16 || ^17 || ^18"
}
},
"node_modules/react-remove-scroll": { "node_modules/react-remove-scroll": {
"version": "2.6.3", "version": "2.6.3",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz",
@ -5514,7 +5613,6 @@
"version": "1.5.4", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
"integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
"dev": true,
"dependencies": { "dependencies": {
"call-bind": "^1.0.8", "call-bind": "^1.0.8",
"define-properties": "^1.2.1", "define-properties": "^1.2.1",
@ -5742,7 +5840,6 @@
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dev": true,
"dependencies": { "dependencies": {
"define-data-property": "^1.1.4", "define-data-property": "^1.1.4",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@ -5759,7 +5856,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
"dev": true,
"dependencies": { "dependencies": {
"define-data-property": "^1.1.4", "define-data-property": "^1.1.4",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@ -7548,14 +7644,6 @@
"mini-svg-data-uri": "^1.2.3" "mini-svg-data-uri": "^1.2.3"
} }
}, },
"@tinymce/tinymce-react": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@tinymce/tinymce-react/-/tinymce-react-6.1.0.tgz",
"integrity": "sha512-K0MP3yYVKe8+etUwsg6zyRq+q9TGLaVf005WiBHiB8JZEomAwbBPERGunhU9uOqNQ5gJs8yVOPZ68Xcd1UHclA==",
"requires": {
"prop-types": "^15.6.2"
}
},
"@tybys/wasm-util": { "@tybys/wasm-util": {
"version": "0.9.0", "version": "0.9.0",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
@ -7592,6 +7680,14 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true "dev": true
}, },
"@types/quill": {
"version": "1.3.10",
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
"integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==",
"requires": {
"parchment": "^1.1.2"
}
},
"@types/react": { "@types/react": {
"version": "19.1.0", "version": "19.1.0",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.0.tgz",
@ -8138,7 +8234,6 @@
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
"dev": true,
"requires": { "requires": {
"call-bind-apply-helpers": "^1.0.0", "call-bind-apply-helpers": "^1.0.0",
"es-define-property": "^1.0.0", "es-define-property": "^1.0.0",
@ -8150,7 +8245,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"requires": { "requires": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@ -8160,7 +8254,6 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
"requires": { "requires": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0" "get-intrinsic": "^1.3.0"
@ -8227,6 +8320,11 @@
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
}, },
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
},
"color-convert": { "color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -8334,6 +8432,19 @@
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==" "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="
}, },
"deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
"requires": {
"is-arguments": "^1.1.1",
"is-date-object": "^1.0.5",
"is-regex": "^1.1.4",
"object-is": "^1.1.5",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.5.1"
}
},
"deep-is": { "deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -8344,7 +8455,6 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"requires": { "requires": {
"es-define-property": "^1.0.0", "es-define-property": "^1.0.0",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@ -8355,7 +8465,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"requires": { "requires": {
"define-data-property": "^1.0.1", "define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0", "has-property-descriptors": "^1.0.0",
@ -8409,7 +8518,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"requires": { "requires": {
"call-bind-apply-helpers": "^1.0.1", "call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@ -8502,14 +8610,12 @@
"es-define-property": { "es-define-property": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
"dev": true
}, },
"es-errors": { "es-errors": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
"dev": true
}, },
"es-iterator-helpers": { "es-iterator-helpers": {
"version": "1.2.1", "version": "1.2.1",
@ -8539,7 +8645,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"requires": { "requires": {
"es-errors": "^1.3.0" "es-errors": "^1.3.0"
} }
@ -8920,11 +9025,26 @@
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true "dev": true
}, },
"eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"fast-deep-equal": { "fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
}, },
"fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
},
"fast-glob": { "fast-glob": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@ -9084,14 +9204,12 @@
"functions-have-names": { "functions-have-names": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="
"dev": true
}, },
"get-intrinsic": { "get-intrinsic": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"requires": { "requires": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1", "es-define-property": "^1.0.1",
@ -9114,7 +9232,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"requires": { "requires": {
"dunder-proto": "^1.0.1", "dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0" "es-object-atoms": "^1.0.0"
@ -9211,8 +9328,7 @@
"gopd": { "gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
"dev": true
}, },
"graceful-fs": { "graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
@ -9241,7 +9357,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"requires": { "requires": {
"es-define-property": "^1.0.0" "es-define-property": "^1.0.0"
} }
@ -9258,14 +9373,12 @@
"has-symbols": { "has-symbols": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
"dev": true
}, },
"has-tostringtag": { "has-tostringtag": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"requires": { "requires": {
"has-symbols": "^1.0.3" "has-symbols": "^1.0.3"
} }
@ -9356,6 +9469,15 @@
"tslib": "^2.8.0" "tslib": "^2.8.0"
} }
}, },
"is-arguments": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
"requires": {
"call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2"
}
},
"is-array-buffer": { "is-array-buffer": {
"version": "3.0.5", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@ -9445,7 +9567,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
"integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
"dev": true,
"requires": { "requires": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2" "has-tostringtag": "^1.0.2"
@ -9521,7 +9642,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
"dev": true,
"requires": { "requires": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
"gopd": "^1.2.0", "gopd": "^1.2.0",
@ -9862,8 +9982,7 @@
"math-intrinsics": { "math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
"dev": true
}, },
"merge2": { "merge2": {
"version": "1.4.1", "version": "1.4.1",
@ -10055,11 +10174,19 @@
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"dev": true "dev": true
}, },
"object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"requires": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
}
},
"object-keys": { "object-keys": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
"dev": true
}, },
"object.assign": { "object.assign": {
"version": "4.1.7", "version": "4.1.7",
@ -10195,6 +10322,11 @@
"p-limit": "^3.0.2" "p-limit": "^3.0.2"
} }
}, },
"parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg=="
},
"parent-module": { "parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -10397,6 +10529,7 @@
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"requires": { "requires": {
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@ -10424,6 +10557,29 @@
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
}, },
"quill": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
"requires": {
"clone": "^2.1.1",
"deep-equal": "^1.0.1",
"eventemitter3": "^2.0.3",
"extend": "^3.0.2",
"parchment": "^1.1.4",
"quill-delta": "^3.6.2"
}
},
"quill-delta": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
"requires": {
"deep-equal": "^1.0.1",
"extend": "^3.0.2",
"fast-diff": "1.1.2"
}
},
"react": { "react": {
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
@ -10482,6 +10638,16 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"react-quill": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-quill/-/react-quill-2.0.0.tgz",
"integrity": "sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==",
"requires": {
"@types/quill": "^1.3.10",
"lodash": "^4.17.4",
"quill": "^1.3.7"
}
},
"react-remove-scroll": { "react-remove-scroll": {
"version": "2.6.3", "version": "2.6.3",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz",
@ -10575,7 +10741,6 @@
"version": "1.5.4", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
"integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
"dev": true,
"requires": { "requires": {
"call-bind": "^1.0.8", "call-bind": "^1.0.8",
"define-properties": "^1.2.1", "define-properties": "^1.2.1",
@ -10711,7 +10876,6 @@
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dev": true,
"requires": { "requires": {
"define-data-property": "^1.1.4", "define-data-property": "^1.1.4",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@ -10725,7 +10889,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
"dev": true,
"requires": { "requires": {
"define-data-property": "^1.1.4", "define-data-property": "^1.1.4",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",

View File

@ -13,7 +13,6 @@
"@docuseal/react": "^1.0.56", "@docuseal/react": "^1.0.56",
"@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2",
"@tailwindcss/forms": "^0.5.9", "@tailwindcss/forms": "^0.5.9",
"@tinymce/tinymce-react": "^6.1.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"framer-motion": "^11.11.11", "framer-motion": "^11.11.11",
"ics": "^3.8.1", "ics": "^3.8.1",
@ -31,6 +30,7 @@
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18", "react-dom": "^18",
"react-international-phone": "^4.5.0", "react-international-phone": "^4.5.0",
"react-quill": "^2.0.0",
"react-tooltip": "^5.28.0" "react-tooltip": "^5.28.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -151,7 +151,7 @@ export default function Layout({ children }) {
return ( return (
<ProtectedRoute requiredRight={[RIGHTS.ADMIN, RIGHTS.TEACHER]}> <ProtectedRoute requiredRight={[RIGHTS.ADMIN, RIGHTS.TEACHER]}>
{/* Topbar */} {/* Topbar */}
<header className="absolute top-0 left-0 right-0 h-16 bg-white border-b border-gray-200 px-4 md:px-8 flex items-center justify-between z-10 box-border"> <header className="absolute top-0 left-64 right-0 h-16 bg-white border-b border-gray-200 px-4 md:px-8 flex items-center justify-between z-10 box-border">
<div className="flex items-center"> <div className="flex items-center">
<button <button
className="mr-4 md:hidden text-gray-600 hover:text-gray-900" className="mr-4 md:hidden text-gray-600 hover:text-gray-900"
@ -180,7 +180,7 @@ export default function Layout({ children }) {
{/* Sidebar */} {/* Sidebar */}
<div <div
className={`absolute top-16 bottom-16 left-0 z-30 w-64 bg-white border-r border-gray-200 box-border ${ className={`absolute top-0 bottom-0 left-0 z-30 w-64 bg-white border-r border-gray-200 box-border ${
isSidebarOpen ? 'block' : 'hidden md:block' isSidebarOpen ? 'block' : 'hidden md:block'
}`} }`}
> >

View File

@ -1,10 +1,35 @@
'use client';
import React from 'react'; import React from 'react';
import SidebarTabs from '@/components/SidebarTabs';
import EmailSender from '@/components/Admin/EmailSender'; import EmailSender from '@/components/Admin/EmailSender';
import InstantMessaging from '@/components/Admin/InstantMessaging';
import AnnouncementScheduler from '@/components/Admin/AnnouncementScheduler';
export default function MessageriePage({ csrfToken }) { export default function MessageriePage({ csrfToken }) {
const tabs = [
{
id: 'email',
label: 'Envoyer un Mail',
content: <EmailSender csrfToken={csrfToken} />,
},
{
id: 'instant',
label: 'Messagerie Instantanée',
content: <InstantMessaging csrfToken={csrfToken} />,
},
{
id: 'announcement',
label: 'Planifier une Annonce',
content: <AnnouncementScheduler csrfToken={csrfToken} />,
},
];
return ( return (
<div className="p-6"> <div className="flex h-full w-full">
<h1 className="text-2xl font-bold mb-6">Messagerie Admin</h1> <SidebarTabs
<EmailSender csrfToken={csrfToken} /> tabs={tabs}
onTabChange={(tabId) => console.log(`Onglet actif : ${tabId}`)}
/>
</div> </div>
); );
} }

View File

@ -4,6 +4,7 @@ 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 CheckBox from '@/components/CheckBox'; // Import du composant CheckBox
import logger from '@/utils/logger'; import logger from '@/utils/logger';
import { import {
fetchSmtpSettings, fetchSmtpSettings,
@ -24,7 +25,6 @@ export default function SettingsPage() {
const [smtpPassword, setSmtpPassword] = useState(''); const [smtpPassword, setSmtpPassword] = useState('');
const [useTls, setUseTls] = useState(true); const [useTls, setUseTls] = useState(true);
const [useSsl, setUseSsl] = useState(false); const [useSsl, setUseSsl] = useState(false);
const [statusMessage, setStatusMessage] = useState('');
const { selectedEstablishmentId } = useEstablishment(); const { selectedEstablishmentId } = useEstablishment();
const csrfToken = useCsrfToken(); // Récupération du csrfToken const csrfToken = useCsrfToken(); // Récupération du csrfToken
const { showNotification } = useNotification(); const { showNotification } = useNotification();
@ -35,7 +35,7 @@ export default function SettingsPage() {
// Charger les paramètres SMTP existants // Charger les paramètres SMTP existants
useEffect(() => { useEffect(() => {
if (activeTab === 'smtp') { if (activeTab === 'smtp') {
fetchSmtpSettings(csrfToken) // Passer le csrfToken ici fetchSmtpSettings(csrfToken, selectedEstablishmentId) // Passer le csrfToken ici
.then((data) => { .then((data) => {
setSmtpServer(data.smtp_server || ''); setSmtpServer(data.smtp_server || '');
setSmtpPort(data.smtp_port || ''); setSmtpPort(data.smtp_port || '');
@ -46,7 +46,11 @@ export default function SettingsPage() {
}) })
.catch((error) => { .catch((error) => {
logger.error('Erreur lors du chargement des paramètres SMTP:', error); logger.error('Erreur lors du chargement des paramètres SMTP:', error);
setStatusMessage('Erreur lors du chargement des paramètres SMTP.'); showNotification(
'Erreur lors du chargement des paramètres SMTP.',
'error',
'Erreur'
);
}); });
} }
}, [activeTab, csrfToken]); // Ajouter csrfToken comme dépendance }, [activeTab, csrfToken]); // Ajouter csrfToken comme dépendance
@ -113,7 +117,11 @@ export default function SettingsPage() {
editSmtpSettings(smtpData, csrfToken) // Passer le csrfToken ici editSmtpSettings(smtpData, csrfToken) // Passer le csrfToken ici
.then(() => { .then(() => {
setStatusMessage('Paramètres SMTP mis à jour avec succès.'); showNotification(
'Paramètres SMTP mis à jour avec succès.',
'success',
'Succès'
);
logger.debug('SMTP Settings Updated:', smtpData); logger.debug('SMTP Settings Updated:', smtpData);
}) })
.catch((error) => { .catch((error) => {
@ -121,7 +129,11 @@ export default function SettingsPage() {
'Erreur lors de la mise à jour des paramètres SMTP:', 'Erreur lors de la mise à jour des paramètres SMTP:',
error error
); );
setStatusMessage('Erreur lors de la mise à jour des paramètres SMTP.'); showNotification(
'Erreur lors de la mise à jour des paramètres SMTP.',
'error',
'Erreur'
);
}); });
}; };
@ -164,48 +176,54 @@ export default function SettingsPage() {
</TabContent> </TabContent>
<TabContent isActive={activeTab === 'smtp'}> <TabContent isActive={activeTab === 'smtp'}>
<form onSubmit={handleSmtpSubmit}> <form onSubmit={handleSmtpSubmit}>
<InputText <div className="grid grid-cols-2 gap-4">
label="Serveur SMTP" <InputText
value={smtpServer} label="Serveur SMTP"
onChange={handleSmtpServerChange} value={smtpServer}
/> onChange={handleSmtpServerChange}
<InputText />
label="Port SMTP" <InputText
value={smtpPort} label="Port SMTP"
onChange={handleSmtpPortChange} value={smtpPort}
/> onChange={handleSmtpPortChange}
<InputText />
label="Utilisateur SMTP" <InputText
value={smtpUser} label="Utilisateur SMTP"
onChange={handleSmtpUserChange} value={smtpUser}
/> onChange={handleSmtpUserChange}
<InputText />
label="Mot de passe SMTP" <InputText
type="password" label="Mot de passe SMTP"
value={smtpPassword} type="password"
onChange={handleSmtpPasswordChange} value={smtpPassword}
/> 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> </div>
<Button type="submit" primary text="Mettre à jour"></Button> <div className="mt-6 border-t pt-4">
<div className="flex items-center space-x-4">
<CheckBox
item={{ id: 'useTls' }}
formData={{ useTls }}
handleChange={() => setUseTls((prev) => !prev)} // Inverser la valeur booléenne
fieldName="useTls"
itemLabelFunc={() => 'Utiliser TLS'}
/>
<CheckBox
item={{ id: 'useSsl' }}
formData={{ useSsl }}
handleChange={() => setUseSsl((prev) => !prev)} // Inverser la valeur booléenne
fieldName="useSsl"
itemLabelFunc={() => 'Utiliser SSL'}
/>
</div>
</div>
<Button
type="submit"
primary
text="Mettre à jour"
className="mt-6"
></Button>
</form> </form>
{statusMessage && <p className="mt-4 text-sm">{statusMessage}</p>}
</TabContent> </TabContent>
</div> </div>
</div> </div>

View File

@ -1,7 +1,6 @@
'use client'; 'use client';
import React, { useState, useRef, useEffect } from 'react'; import React from 'react';
import { SendHorizontal } from 'lucide-react'; import Chat from '@/components/Chat';
import Image from 'next/image';
import { getGravatarUrl } from '@/utils/gravatar'; import { getGravatarUrl } from '@/utils/gravatar';
const contacts = [ const contacts = [
@ -23,45 +22,7 @@ const contacts = [
]; ];
export default function MessageriePage() { export default function MessageriePage() {
const [selectedContact, setSelectedContact] = useState(null); const simulateResponse = (contactId, setMessages) => {
const [messages, setMessages] = useState({});
const [newMessage, setNewMessage] = useState('');
const messagesEndRef = useRef(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleSendMessage = () => {
if (newMessage.trim() && selectedContact) {
const contactMessages = messages[selectedContact.id] || [];
setMessages({
...messages,
[selectedContact.id]: [
...contactMessages,
{
id: contactMessages.length + 1,
text: newMessage,
date: new Date(),
},
],
});
setNewMessage('');
simulateContactResponse(selectedContact.id);
}
};
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
handleSendMessage();
}
};
const simulateContactResponse = (contactId) => {
setTimeout(() => { setTimeout(() => {
setMessages((prevMessages) => { setMessages((prevMessages) => {
const contactMessages = prevMessages[contactId] || []; const contactMessages = prevMessages[contactId] || [];
@ -81,79 +42,5 @@ export default function MessageriePage() {
}, 2000); }, 2000);
}; };
return ( return <Chat contacts={contacts} simulateResponse={simulateResponse} />;
<div className="flex" style={{ height: 'calc(100vh - 128px )' }}>
{' '}
{/* Utilisation de calc pour soustraire la hauteur de l'entête */}
<div className="w-1/4 border-r border-gray-200 p-4 overflow-y-auto h-full ">
{contacts.map((contact) => (
<div
key={contact.id}
className={`p-2 cursor-pointer ${selectedContact?.id === contact.id ? 'bg-gray-200' : ''}`}
onClick={() => setSelectedContact(contact)}
>
<Image
src={contact.profilePic}
alt={`${contact.name}'s profile`}
className="w-8 h-8 rounded-full inline-block mr-2"
width={150}
height={150}
/>
{contact.name}
</div>
))}
</div>
<div className="flex-1 flex flex-col h-full">
<div className="flex-1 overflow-y-auto p-4 h-full">
{selectedContact &&
(messages[selectedContact.id] || []).map((message) => (
<div
key={message.id}
className={`mb-2 p-2 rounded max-w-xs ${message.isResponse ? 'bg-gray-200 justify-self-end' : 'bg-emerald-200 justify-self-start'}`}
style={{
borderRadius: message.isResponse
? '20px 20px 0 20px'
: '20px 20px 20px 0',
minWidth: '25%',
}}
>
<div className="flex items-center mb-1">
<img
src={selectedContact.profilePic}
alt={`${selectedContact.name}'s profile`}
className="w-8 h-8 rounded-full inline-block mr-2"
width={150}
height={150}
/>
<span className="text-xs text-gray-600">
{selectedContact.name}
</span>
<span className="text-xs text-gray-400 ml-2">
{new Date(message.date).toLocaleTimeString()}
</span>
</div>
{message.text}
</div>
))}
<div ref={messagesEndRef} />
</div>
<div className="p-4 border-t border-gray-200 flex">
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
className="w-full p-2 border border-gray-300 rounded"
placeholder="Écrire un message..."
onKeyDown={handleKeyPress}
/>
<button
onClick={handleSendMessage}
className="p-2 bg-emerald-500 text-white rounded mr-2"
>
<SendHorizontal />
</button>
</div>
</div>
</div>
);
} }

View File

@ -1,6 +1,7 @@
import { import {
BE_GESTIONMESSAGERIE_MESSAGES_URL, BE_GESTIONMESSAGERIE_MESSAGES_URL,
BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL, BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL,
BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL,
} from '@/utils/Url'; } from '@/utils/Url';
const requestResponseHandler = async (response) => { const requestResponseHandler = async (response) => {
@ -8,8 +9,6 @@ const requestResponseHandler = async (response) => {
if (response.ok) { if (response.ok) {
return body; return body;
} }
// 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;
@ -33,3 +32,13 @@ export const sendMessage = (data, csrfToken) => {
body: JSON.stringify(data), body: JSON.stringify(data),
}).then(requestResponseHandler); }).then(requestResponseHandler);
}; };
export const searchRecipients = (establishmentId, query) => {
const url = `${BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL}/?establishment_id=${establishmentId}&q=${encodeURIComponent(query)}`;
return fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}).then(requestResponseHandler);
};

View File

@ -15,8 +15,12 @@ const requestResponseHandler = async (response) => {
throw error; throw error;
}; };
export const fetchSmtpSettings = (csrfToken) => { export const fetchSmtpSettings = (csrfToken, establishment_id = null) => {
return fetch(`${BE_SETTINGS_SMTP_URL}/`, { let url = `${BE_SETTINGS_SMTP_URL}/`;
if (establishment_id) {
url += `?establishment_id=${establishment_id}`;
}
return fetch(`${url}`, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRFToken': csrfToken, 'X-CSRFToken': csrfToken,

View File

@ -0,0 +1,52 @@
'use client';
import React, { useState } from 'react';
export default function AnnouncementScheduler({ csrfToken }) {
const [title, setTitle] = useState('');
const [date, setDate] = useState('');
const [message, setMessage] = useState('');
const handleSchedule = () => {
// Logique pour planifier une annonce
console.log('Annonce planifiée:', { title, date, message });
};
return (
<div className="p-4 bg-white rounded shadow">
<h2 className="text-xl font-bold mb-4">Planifier une Annonce</h2>
<div className="mb-4">
<label className="block font-medium">Titre</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="w-full p-2 border rounded"
/>
</div>
<div className="mb-4">
<label className="block font-medium">Date</label>
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
className="w-full p-2 border rounded"
/>
</div>
<div className="mb-4">
<label className="block font-medium">Message</label>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
className="w-full p-2 border rounded"
rows="5"
/>
</div>
<button
onClick={handleSchedule}
className="bg-blue-500 text-white px-4 py-2 rounded"
>
Planifier
</button>
</div>
);
}

View File

@ -1,75 +1,163 @@
'use client'; 'use client';
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { Editor } from '@tinymce/tinymce-react'; import dynamic from 'next/dynamic';
import { sendMessage } from '@/app/actions/messagerieAction'; import { sendMessage, searchRecipients } from '@/app/actions/messagerieAction';
import { fetchSmtpSettings } from '@/app/actions/settingsAction';
import { useNotification } from '@/context/NotificationContext';
import { useEstablishment } from '@/context/EstablishmentContext';
import AlertMessage from '@/components/AlertMessage';
import RecipientInput from '@/components/RecipientInput';
// Charger Quill dynamiquement pour éviter les problèmes de SSR
const ReactQuill = dynamic(() => import('react-quill'), { ssr: false });
import 'react-quill/dist/quill.snow.css'; // Importer les styles de Quill
export default function EmailSender({ csrfToken }) { export default function EmailSender({ csrfToken }) {
const [recipients, setRecipients] = useState(''); const [recipients, setRecipients] = useState([]);
const [fromEmail, setFromEmail] = useState('');
const [cc, setCc] = useState([]);
const [bcc, setBcc] = useState([]);
const [subject, setSubject] = useState(''); const [subject, setSubject] = useState('');
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');
const [status, setStatus] = useState(''); const [smtpConfigured, setSmtpConfigured] = useState(false); // État pour vérifier si SMTP est configuré
const { showNotification } = useNotification();
const { selectedEstablishmentId } = useEstablishment(); // Récupérer l'establishment_id depuis le contexte
useEffect(() => {
// Vérifier si les paramètres SMTP sont configurés
fetchSmtpSettings(csrfToken, selectedEstablishmentId)
.then((data) => {
if (data.smtp_server && data.smtp_port && data.smtp_user) {
setFromEmail(data.smtp_user);
setSmtpConfigured(true);
} else {
setSmtpConfigured(false);
}
})
.catch((error) => {
console.error(
'Erreur lors de la vérification des paramètres SMTP:',
error
);
setSmtpConfigured(false);
});
}, [csrfToken, selectedEstablishmentId]);
const handleSendEmail = async () => { const handleSendEmail = async () => {
const data = { const data = {
recipients: recipients.split(',').map((email) => email.trim()), recipients,
cc,
bcc,
subject, subject,
message, message,
establishment_id: selectedEstablishmentId, // Ajouter l'establishment_id à la payload
}; };
sendMessage(data);
try {
await sendMessage(data);
showNotification('Email envoyé avec succès.', 'success', 'Succès');
// Réinitialiser les champs après succès
setRecipients([]);
setCc([]);
setBcc([]);
setSubject('');
setMessage('');
} catch (error) {
console.error("Erreur lors de l'envoi de l'email:", error);
showNotification(
"Une erreur est survenue lors de l'envoi de l'email.",
'error',
'Erreur'
);
}
}; };
if (!smtpConfigured) {
return (
<AlertMessage
type="warning"
title="Configuration SMTP requise"
message="Les paramètres SMTP de cet établissement ne sont pas configurés. Veuillez les configurer dans la page des paramètres."
actionLabel="Aller aux paramètres"
onAction={() => (window.location.href = '/admin/settings')} // Redirige vers la page des paramètres
/>
);
}
return ( return (
<div className="p-4 bg-white rounded shadow"> <div className="max-w-3xl mx-auto bg-white rounded-lg shadow-md">
<h2 className="text-xl font-bold mb-4">Envoyer un Email</h2> {/* Header */}
<div className="mb-4"> <div className="flex items-center justify-between px-4 py-2 border-b">
<label className="block font-medium"> <h2 className="text-sm font-medium text-gray-700">
Destinataires (séparés par des virgules) Email from {fromEmail}
</label> </h2>
<input
type="text"
value={recipients}
onChange={(e) => setRecipients(e.target.value)}
className="w-full p-2 border rounded"
/>
</div> </div>
<div className="mb-4">
<label className="block font-medium">Sujet</label> {/* Form */}
<input <div className="p-4">
type="text" {/* To */}
value={subject} <RecipientInput
onChange={(e) => setSubject(e.target.value)} label="Destinataires"
className="w-full p-2 border rounded" recipients={recipients}
setRecipients={setRecipients}
searchRecipients={searchRecipients} // Passer l'action de recherche
establishmentId={selectedEstablishmentId} // Passer l'ID de l'établissement
/> />
{/* Cc and Bcc */}
<div className="flex space-x-4">
<RecipientInput
label="Cc"
placeholder="Add Cc"
recipients={cc}
setRecipients={setCc}
/>
<RecipientInput
label="Bcc"
placeholder="Add Bcc"
recipients={bcc}
setRecipients={setBcc}
/>
</div>
{/* Subject */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700">
Subject
</label>
<input
type="text"
value={subject}
onChange={(e) => setSubject(e.target.value)}
placeholder="Enter subject"
className="w-full p-2 border rounded"
/>
</div>
{/* Email Body */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700">
Your email
</label>
<ReactQuill
theme="snow"
value={message}
onChange={setMessage}
placeholder="Write your email here..."
/>
</div>
{/* Footer */}
<div className="flex justify-between items-center">
<button
onClick={handleSendEmail}
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Send email
</button>
</div>
</div> </div>
<div className="mb-4">
<label className="block font-medium">Message</label>
<Editor
apiKey="8ftyao41dcp1et0p409ipyrdtp14wxs0efqdofvrjq1vo2gi" // Remplacez par votre clé API TinyMCE
value={message}
init={{
height: 300,
menubar: false,
plugins: [
'advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste code help wordcount',
],
toolbar:
'undo redo | formatselect | bold italic backcolor | \
alignleft aligncenter alignright alignjustify | \
bullist numlist outdent indent | removeformat | help',
}}
onEditorChange={(content) => setMessage(content)}
/>
</div>
<button
onClick={handleSendEmail}
className="bg-blue-500 text-white px-4 py-2 rounded"
>
Envoyer
</button>
{status && <p className="mt-4 text-sm">{status}</p>}
</div> </div>
); );
} }

View File

@ -0,0 +1,25 @@
// filepath: d:\Dev\n3wt-innov\n3wt-school\Front-End\src\components\Admin\InstantMessaging.js
import React from 'react';
import Chat from '@/components/Chat';
import { getGravatarUrl } from '@/utils/gravatar';
const contacts = [
{
id: 1,
name: 'Parent 1',
profilePic: getGravatarUrl('parent1@n3wtschool.com'),
},
{
id: 2,
name: 'Parent 2',
profilePic: getGravatarUrl('parent2@n3wtschool.com'),
},
];
export default function InstantMessaging({ csrfToken }) {
const handleSendMessage = (contact, message) => {
console.log(`Message envoyé à ${contact.name}: ${message}`);
};
return <Chat contacts={contacts} onSendMessage={handleSendMessage} />;
}

View File

@ -1,21 +1,36 @@
import React from 'react'; import React from 'react';
const AlertMessage = ({ title, message, buttonText, buttonLink }) => { const AlertMessage = ({
type = 'info',
title,
message,
actionLabel,
onAction,
}) => {
// Définir les styles en fonction du type d'alerte
const typeStyles = {
info: 'bg-blue-100 border-blue-500 text-blue-700',
warning: 'bg-yellow-100 border-yellow-500 text-yellow-700',
error: 'bg-red-100 border-red-500 text-red-700',
success: 'bg-green-100 border-green-500 text-green-700',
};
const alertStyle = typeStyles[type] || typeStyles.info;
return ( return (
<div <div className={`alert centered border-l-4 p-4 ${alertStyle}`} role="alert">
className="alert centered bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4"
role="alert"
>
<h3 className="font-bold">{title}</h3> <h3 className="font-bold">{title}</h3>
<p className="mt-2">{message}</p> <p className="mt-2">{message}</p>
<div className="alert-actions mt-4"> {actionLabel && onAction && (
<a <div className="alert-actions mt-4">
className="btn primary bg-emerald-500 text-white rounded-md px-4 py-2 hover:bg-emerald-600" <button
href={buttonLink} className="btn primary bg-emerald-500 text-white rounded-md px-4 py-2 hover:bg-emerald-600"
> onClick={onAction}
{buttonText} <i className="icon profile-add"></i> >
</a> {actionLabel}
</div> </button>
</div>
)}
</div> </div>
); );
}; };

View File

@ -0,0 +1,205 @@
import React, { useState, useRef, useEffect } from 'react';
import { SendHorizontal } from 'lucide-react';
import Image from 'next/image';
export default function Chat({
discussions,
setDiscussions,
onSendMessage,
simulateResponse,
}) {
const [selectedDiscussion, setSelectedDiscussion] = useState(null);
const [messages, setMessages] = useState({});
const [newMessage, setNewMessage] = useState('');
const [showCreateForm, setShowCreateForm] = useState(false);
const [newDiscussionName, setNewDiscussionName] = useState('');
const [newDiscussionProfilePic, setNewDiscussionProfilePic] = useState('');
const messagesEndRef = useRef(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleSendMessage = () => {
if (newMessage.trim() && selectedDiscussion) {
const discussionMessages = messages[selectedDiscussion.id] || [];
const newMessages = {
...messages,
[selectedDiscussion.id]: [
...discussionMessages,
{
id: discussionMessages.length + 1,
text: newMessage,
date: new Date(),
isResponse: false,
},
],
};
setMessages(newMessages);
setNewMessage('');
onSendMessage && onSendMessage(selectedDiscussion, newMessage);
simulateResponse && simulateResponse(selectedDiscussion.id, setMessages);
}
};
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
handleSendMessage();
}
};
const handleCreateDiscussion = () => {
if (newDiscussionName.trim()) {
const newDiscussion = {
id: discussions.length + 1,
name: newDiscussionName,
profilePic: newDiscussionProfilePic || '/default-profile.png', // Image par défaut si aucune n'est fournie
lastMessage: '',
lastMessageDate: new Date(),
};
setDiscussions([...discussions, newDiscussion]);
setNewDiscussionName('');
setNewDiscussionProfilePic('');
setShowCreateForm(false);
}
};
return (
<div className="flex h-full">
{/* Liste des discussions */}
<div className="w-1/4 bg-gray-100 border-r border-gray-300 p-4 overflow-y-auto">
<h2 className="text-lg font-bold mb-4">Discussions</h2>
<button
onClick={() => setShowCreateForm(!showCreateForm)}
className="w-full p-2 mb-4 bg-blue-500 text-white rounded-lg"
>
{showCreateForm ? 'Annuler' : 'Créer une discussion'}
</button>
{showCreateForm && (
<div className="mb-4 p-2 border rounded-lg bg-white">
<input
type="text"
value={newDiscussionName}
onChange={(e) => setNewDiscussionName(e.target.value)}
placeholder="Nom de la discussion"
className="w-full p-2 mb-2 border rounded"
/>
<input
type="text"
value={newDiscussionProfilePic}
onChange={(e) => setNewDiscussionProfilePic(e.target.value)}
placeholder="URL de la photo de profil (optionnel)"
className="w-full p-2 mb-2 border rounded"
/>
<button
onClick={handleCreateDiscussion}
className="w-full p-2 bg-green-500 text-white rounded-lg"
>
Ajouter
</button>
</div>
)}
{discussions && discussions.length > 0 ? (
discussions.map((discussion) => (
<div
key={discussion.id}
className={`flex items-center p-2 mb-2 cursor-pointer rounded ${
selectedDiscussion?.id === discussion.id
? 'bg-blue-100'
: 'hover:bg-gray-200'
}`}
onClick={() => setSelectedDiscussion(discussion)}
>
<Image
src={discussion.profilePic}
alt={`${discussion.name}'s profile`}
className="w-10 h-10 rounded-full mr-3"
width={40}
height={40}
/>
<div className="flex-1">
<p className="font-medium">{discussion.name}</p>
<p className="text-sm text-gray-500 truncate">
{discussion.lastMessage}
</p>
</div>
<span className="text-xs text-gray-400">
{new Date(discussion.lastMessageDate).toLocaleTimeString()}
</span>
</div>
))
) : (
<p className="text-gray-500">Aucune discussion disponible.</p>
)}
</div>
{/* Zone de chat */}
<div className="flex-1 flex flex-col bg-white">
{/* En-tête du chat */}
{selectedDiscussion && (
<div className="flex items-center p-4 border-b border-gray-300">
<Image
src={selectedDiscussion.profilePic}
alt={`${selectedDiscussion.name}'s profile`}
className="w-10 h-10 rounded-full mr-3"
width={40}
height={40}
/>
<h2 className="text-lg font-bold">{selectedDiscussion.name}</h2>
</div>
)}
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4">
{selectedDiscussion &&
(messages[selectedDiscussion.id] || []).map((message) => (
<div
key={message.id}
className={`flex mb-4 ${
message.isResponse ? 'justify-start' : 'justify-end'
}`}
>
<div
className={`p-3 rounded-lg max-w-xs ${
message.isResponse
? 'bg-gray-200 text-gray-800'
: 'bg-blue-500 text-white'
}`}
>
<p>{message.text}</p>
<span className="text-xs text-gray-500 block mt-1">
{new Date(message.date).toLocaleTimeString()}
</span>
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
{/* Champ de saisie */}
{selectedDiscussion && (
<div className="p-4 border-t border-gray-300 flex items-center">
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
className="flex-1 p-2 border border-gray-300 rounded-lg mr-2"
placeholder="Écrire un message..."
onKeyDown={handleKeyPress}
/>
<button
onClick={handleSendMessage}
className="p-2 bg-blue-500 text-white rounded-lg"
>
<SendHorizontal />
</button>
</div>
)}
</div>
</div>
);
}

View File

@ -9,7 +9,12 @@ const CheckBox = ({
horizontal, horizontal,
}) => { }) => {
console.log(formData); console.log(formData);
const isChecked = formData[fieldName].includes(parseInt(item.id));
// Vérifier si formData[fieldName] est un tableau ou une valeur booléenne
const isChecked = Array.isArray(formData[fieldName])
? formData[fieldName].includes(parseInt(item.id)) // Si c'est un tableau, vérifier si l'élément est inclus
: formData[fieldName]; // Si c'est une valeur booléenne, l'utiliser directement
return ( return (
<div <div
key={item.id} key={item.id}

View File

@ -2,7 +2,7 @@ import Logo from '@/components/Logo';
export default function Footer({ softwareName, softwareVersion }) { export default function Footer({ softwareName, softwareVersion }) {
return ( return (
<footer className="absolute bottom-0 left-0 right-0 h-16 bg-white border-t border-gray-200 flex items-center justify-center box-border"> <footer className="absolute bottom-0 left-64 right-0 h-16 bg-white border-t border-gray-200 flex items-center justify-center box-border">
<div className="text-sm font-light"> <div className="text-sm font-light">
<span> <span>
&copy; {new Date().getFullYear()} N3WT-INNOV Tous droits réservés. &copy; {new Date().getFullYear()} N3WT-INNOV Tous droits réservés.

View File

@ -0,0 +1,144 @@
import React, { useState } from 'react';
import { getGravatarUrl } from '@/utils/gravatar'; // Assurez-vous que cette fonction est définie pour générer les URLs Gravatar
import { getRightStr } from '@/utils/rights'; // Fonction existante pour récupérer le nom des rôles
export default function RecipientInput({
label,
recipients,
setRecipients,
searchRecipients, // Fonction pour effectuer la recherche
establishmentId, // ID de l'établissement
}) {
const [inputValue, setInputValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const [selectedIndex, setSelectedIndex] = useState(-1);
const handleInputChange = async (e) => {
const value = e.target.value;
setInputValue(value);
if (value.trim() !== '') {
try {
const results = await searchRecipients(establishmentId, value);
setSuggestions(results);
} catch (error) {
console.error('Erreur lors de la recherche des destinataires:', error);
setSuggestions([]);
}
} else {
setSuggestions([]);
}
};
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
if (selectedIndex >= 0 && selectedIndex < suggestions.length) {
handleSuggestionClick(suggestions[selectedIndex]);
} else {
const trimmedValue = inputValue.trim();
if (trimmedValue && !recipients.some((r) => r.email === trimmedValue)) {
setRecipients([...recipients, { email: trimmedValue }]);
setInputValue('');
setSuggestions([]);
}
}
} else if (e.key === 'ArrowDown') {
e.preventDefault();
setSelectedIndex((prevIndex) =>
prevIndex < suggestions.length - 1 ? prevIndex + 1 : 0
);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setSelectedIndex((prevIndex) =>
prevIndex > 0 ? prevIndex - 1 : suggestions.length - 1
);
}
};
const handleSuggestionClick = (suggestion) => {
if (!recipients.some((r) => r.email === suggestion.email)) {
setRecipients([...recipients, suggestion]);
}
setInputValue('');
setSuggestions([]);
};
const handleRemoveRecipient = (email) => {
setRecipients(recipients.filter((recipient) => recipient.email !== email));
};
return (
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700">{label}</label>
<div className="flex flex-wrap items-center gap-2 p-2 border rounded">
{recipients.map((recipient, index) => (
<div
key={index}
className="flex items-center bg-gray-100 text-gray-700 px-2 py-1 rounded-full"
>
<img
src={getGravatarUrl(recipient.email)}
alt={recipient.email}
className="w-6 h-6 rounded-full mr-2"
/>
<span className="mr-2">
{recipient.first_name && recipient.last_name
? `${recipient.first_name} ${recipient.last_name}`
: recipient.email}
</span>
<button
type="button"
onClick={() => handleRemoveRecipient(recipient.email)}
className="text-gray-500 hover:text-gray-700"
>
&times;
</button>
</div>
))}
<input
type="text"
value={inputValue}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder="Rechercher des destinataires"
className="flex-1 p-1 outline-none"
/>
</div>
{suggestions.length > 0 && (
<ul className="border rounded mt-2 bg-white shadow">
{suggestions.map((suggestion, index) => (
<li
key={suggestion.id}
className={`p-2 cursor-pointer ${
index === selectedIndex ? 'bg-gray-200' : ''
}`}
onClick={() => handleSuggestionClick(suggestion)}
>
<div className="flex items-center gap-2">
<img
src={getGravatarUrl(suggestion.email)}
alt={suggestion.email}
className="w-8 h-8 rounded-full"
/>
<div>
<p className="font-medium">
{suggestion.first_name && suggestion.last_name
? `${suggestion.first_name} ${suggestion.last_name}`
: suggestion.email}
</p>
<p className="text-sm text-gray-500">{suggestion.email}</p>
<p className="text-xs text-gray-400">
{suggestion.roles
.map((role) => getRightStr(role.role_type) || 'Inconnu')
.join(', ')}
</p>
</div>
</div>
</li>
))}
</ul>
)}
</div>
);
}

View File

@ -12,7 +12,7 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
}; };
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full w-full">
{/* Tabs Header */} {/* Tabs Header */}
<div className="flex h-14 bg-gray-50 border-b border-gray-200 shadow-sm"> <div className="flex h-14 bg-gray-50 border-b border-gray-200 shadow-sm">
{tabs.map((tab) => ( {tabs.map((tab) => (
@ -31,7 +31,7 @@ const SidebarTabs = ({ tabs, onTabChange }) => {
</div> </div>
{/* Tabs Content */} {/* Tabs Content */}
<div className="flex-1 overflow-y-auto p-4 rounded-b-lg shadow-inner relative"> <div className="flex-1 overflow-y-auto rounded-b-lg shadow-inner relative">
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
{tabs.map( {tabs.map(
(tab) => (tab) =>

View File

@ -54,6 +54,7 @@ export const BE_PLANNING_EVENTS_URL = `${BASE_URL}/Planning/events`;
export const BE_GESTIONMESSAGERIE_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/messages`; export const BE_GESTIONMESSAGERIE_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/messages`;
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/`;
export const BE_GESTIONMESSAGERIE_SEARCH_RECIPIENTS_URL = `${BASE_URL}/GestionMessagerie/search-recipients`;
// SETTINGS // SETTINGS
export const BE_SETTINGS_SMTP_URL = `${BASE_URL}/Settings/smtp-settings`; export const BE_SETTINGS_SMTP_URL = `${BASE_URL}/Settings/smtp-settings`;

View File

@ -16,7 +16,7 @@
"@commitlint/cli": "^19.5.0", "@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0", "@commitlint/config-conventional": "^19.5.0",
"husky": "^9.1.6", "husky": "^9.1.6",
"standard-version": "^9.5.0", "prettier": "^3.5.3",
"prettier": "^3.5.3" "standard-version": "^9.5.0"
} }
} }