From 99a882a64acfd9340d6849edd1766de5173a2341 Mon Sep 17 00:00:00 2001 From: Luc SORIGNET Date: Sun, 4 May 2025 14:52:47 +0200 Subject: [PATCH] feat: Ajout de l'envoie de mail [#17] --- Back-End/GestionMessagerie/urls.py | 3 +- Back-End/GestionMessagerie/views.py | 32 ++++++++ Front-End/messages/en/sidebar.json | 5 +- Front-End/messages/fr/sidebar.json | 5 +- Front-End/package-lock.json | 30 +++++++- Front-End/package.json | 1 + Front-End/src/app/[locale]/admin/layout.js | 9 +++ .../src/app/[locale]/admin/messagerie/page.js | 10 +++ Front-End/src/app/actions/messagerieAction.js | 16 +++- Front-End/src/components/Admin/EmailSender.js | 74 +++++++++++++++++++ Front-End/src/utils/Url.js | 4 + 11 files changed, 181 insertions(+), 8 deletions(-) create mode 100644 Front-End/src/app/[locale]/admin/messagerie/page.js create mode 100644 Front-End/src/components/Admin/EmailSender.js diff --git a/Back-End/GestionMessagerie/urls.py b/Back-End/GestionMessagerie/urls.py index cdbd50b..cb9660a 100644 --- a/Back-End/GestionMessagerie/urls.py +++ b/Back-End/GestionMessagerie/urls.py @@ -1,9 +1,10 @@ from django.urls import path, re_path - +from .views import SendEmailView from GestionMessagerie.views import MessagerieView, MessageView, MessageSimpleView urlpatterns = [ re_path(r'^messagerie/(?P[0-9]+)$', MessagerieView.as_view(), name="messagerie"), re_path(r'^messages$', MessageView.as_view(), name="messages"), re_path(r'^messages/(?P[0-9]+)$', MessageSimpleView.as_view(), name="messages"), + path('send-email/', SendEmailView.as_view(), name='send_email'), ] \ No newline at end of file diff --git a/Back-End/GestionMessagerie/views.py b/Back-End/GestionMessagerie/views.py index 9b51042..c7c4a7c 100644 --- a/Back-End/GestionMessagerie/views.py +++ b/Back-End/GestionMessagerie/views.py @@ -1,6 +1,11 @@ from django.http.response import JsonResponse from rest_framework.views import APIView from rest_framework.parsers import JSONParser +from django.core.mail import send_mail +from django.utils.html import strip_tags +from django.conf import settings +from rest_framework.response import Response +from rest_framework import status from .models import * @@ -32,3 +37,30 @@ class MessageSimpleView(APIView): message_serializer=MessageSerializer(message) return JsonResponse(message_serializer.data, safe=False) +class SendEmailView(APIView): + """ + API pour envoyer des emails aux parents et professeurs. + """ + def post(self, request): + data = request.data + recipients = data.get('recipients', []) + subject = data.get('subject', 'Notification') + message = data.get('message', '') + + if not recipients or not message: + return Response({'error': 'Les destinataires et le message sont requis.'}, status=status.HTTP_400_BAD_REQUEST) + + try: + plain_message = strip_tags(message) + send_mail( + subject, + plain_message, + settings.EMAIL_HOST_USER, + recipients, + html_message=message, + 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) + diff --git a/Front-End/messages/en/sidebar.json b/Front-End/messages/en/sidebar.json index e56085e..5d1f90c 100644 --- a/Front-End/messages/en/sidebar.json +++ b/Front-End/messages/en/sidebar.json @@ -6,5 +6,6 @@ "events": "Events", "educational_monitoring": "Educational Monitoring", "settings": "Settings", - "schoolAdmin": "School Administration" -} + "schoolAdmin": "School Administration", + "messagerie": "Messenger" +} \ No newline at end of file diff --git a/Front-End/messages/fr/sidebar.json b/Front-End/messages/fr/sidebar.json index 364db2d..8779ee8 100644 --- a/Front-End/messages/fr/sidebar.json +++ b/Front-End/messages/fr/sidebar.json @@ -6,5 +6,6 @@ "events": "Evenements", "educational_monitoring": "Suivi pédagogique", "settings": "Paramètres", - "schoolAdmin": "Administration Scolaire" -} + "schoolAdmin": "Administration Scolaire", + "messagerie": "Messagerie" +} \ No newline at end of file diff --git a/Front-End/package-lock.json b/Front-End/package-lock.json index a5bb5e4..3b22305 100644 --- a/Front-End/package-lock.json +++ b/Front-End/package-lock.json @@ -11,6 +11,7 @@ "@docuseal/react": "^1.0.56", "@radix-ui/react-dialog": "^1.1.2", "@tailwindcss/forms": "^0.5.9", + "@tinymce/tinymce-react": "^6.1.0", "date-fns": "^4.1.0", "framer-motion": "^11.11.11", "ics": "^3.8.1", @@ -1054,6 +1055,25 @@ "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": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -5219,7 +5239,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -7529,6 +7548,14 @@ "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": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -10370,7 +10397,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", diff --git a/Front-End/package.json b/Front-End/package.json index 7e2fe59..b865403 100644 --- a/Front-End/package.json +++ b/Front-End/package.json @@ -13,6 +13,7 @@ "@docuseal/react": "^1.0.56", "@radix-ui/react-dialog": "^1.1.2", "@tailwindcss/forms": "^0.5.9", + "@tinymce/tinymce-react": "^6.1.0", "date-fns": "^4.1.0", "framer-motion": "^11.11.11", "ics": "^3.8.1", diff --git a/Front-End/src/app/[locale]/admin/layout.js b/Front-End/src/app/[locale]/admin/layout.js index 90e0907..1d6fae5 100644 --- a/Front-End/src/app/[locale]/admin/layout.js +++ b/Front-End/src/app/[locale]/admin/layout.js @@ -15,6 +15,7 @@ import { LogOut, Menu, X, + Mail, } from 'lucide-react'; import DropdownMenu from '@/components/DropdownMenu'; @@ -27,6 +28,7 @@ import { FE_ADMIN_GRADES_URL, FE_ADMIN_PLANNING_URL, FE_ADMIN_SETTINGS_URL, + FE_ADMIN_MESSAGERIE_URL } from '@/utils/Url'; import { disconnect } from '@/app/actions/authAction'; @@ -36,6 +38,7 @@ import Footer from '@/components/Footer'; import { getRightStr, RIGHTS } from '@/utils/rights'; import { useEstablishment } from '@/context/EstablishmentContext'; + export default function Layout({ children }) { const t = useTranslations('sidebar'); const [isSidebarOpen, setIsSidebarOpen] = useState(false); @@ -79,6 +82,12 @@ export default function Layout({ children }) { url: FE_ADMIN_PLANNING_URL, icon: Calendar, }, + messagerie: { + id: 'messagerie', + name: t('messagerie'), + url: FE_ADMIN_MESSAGERIE_URL, + icon: Mail, + }, settings: { id: 'settings', name: t('settings'), diff --git a/Front-End/src/app/[locale]/admin/messagerie/page.js b/Front-End/src/app/[locale]/admin/messagerie/page.js new file mode 100644 index 0000000..2d77602 --- /dev/null +++ b/Front-End/src/app/[locale]/admin/messagerie/page.js @@ -0,0 +1,10 @@ +import React from 'react'; +import EmailSender from '@/components/Admin/EmailSender'; +export default function MessageriePage({ csrfToken }) { + return ( +
+

Messagerie Admin

+ +
+ ); +} \ No newline at end of file diff --git a/Front-End/src/app/actions/messagerieAction.js b/Front-End/src/app/actions/messagerieAction.js index 5b06962..2ae326a 100644 --- a/Front-End/src/app/actions/messagerieAction.js +++ b/Front-End/src/app/actions/messagerieAction.js @@ -1,4 +1,7 @@ -import { BE_GESTIONMESSAGERIE_MESSAGES_URL } from '@/utils/Url'; +import { + BE_GESTIONMESSAGERIE_MESSAGES_URL, + BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL, +} from '@/utils/Url'; const requestResponseHandler = async (response) => { const body = await response.json(); @@ -18,3 +21,14 @@ export const fetchMessages = (id) => { }, }).then(requestResponseHandler); }; + +export const sendMessage = (data, csrfToken) => { + return fetch(`${BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken, + }, + body: JSON.stringify(data), + }).then(requestResponseHandler); +}; diff --git a/Front-End/src/components/Admin/EmailSender.js b/Front-End/src/components/Admin/EmailSender.js new file mode 100644 index 0000000..ee55ec1 --- /dev/null +++ b/Front-End/src/components/Admin/EmailSender.js @@ -0,0 +1,74 @@ +'use client'; + +import React, { useState } from 'react'; +import { Editor } from '@tinymce/tinymce-react'; +import { sendMessage } from '@/app/actions/messagerieAction'; + +export default function EmailSender({ csrfToken }) { + const [recipients, setRecipients] = useState(''); + const [subject, setSubject] = useState(''); + const [message, setMessage] = useState(''); + const [status, setStatus] = useState(''); + + const handleSendEmail = async () => { + const data = { + recipients: recipients.split(',').map((email) => email.trim()), + subject, + message, + }; + sendMessage(data); + + }; + + return ( +
+

Envoyer un Email

+
+ + setRecipients(e.target.value)} + className="w-full p-2 border rounded" + /> +
+
+ + setSubject(e.target.value)} + className="w-full p-2 border rounded" + /> +
+
+ + setMessage(content)} + /> +
+ + {status &&

{status}

} +
+ ); +} \ No newline at end of file diff --git a/Front-End/src/utils/Url.js b/Front-End/src/utils/Url.js index 72cc8a4..70e3a4a 100644 --- a/Front-End/src/utils/Url.js +++ b/Front-End/src/utils/Url.js @@ -53,6 +53,7 @@ export const BE_PLANNING_EVENTS_URL = `${BASE_URL}/Planning/events`; // GESTION MESSAGERIE export const BE_GESTIONMESSAGERIE_MESSAGES_URL = `${BASE_URL}/GestionMessagerie/messages`; export const BE_GESTIONMESSAGERIE_MESSAGERIE_URL = `${BASE_URL}/GestionMessagerie/messagerie`; +export const BE_GESTIONMESSAGERIE_SEND_MESSAGE_URL = `${BASE_URL}/GestionMessagerie/send-email/`; // URL FRONT-END export const FE_HOME_URL = `/`; @@ -94,6 +95,9 @@ export const FE_ADMIN_PLANNING_URL = `/admin/planning`; //ADMIN/SETTINGS URL export const FE_ADMIN_SETTINGS_URL = `/admin/settings`; +//ADMIN/MESSAGERIE URL +export const FE_ADMIN_MESSAGERIE_URL = `/admin/messagerie`; + // PARENT HOME export const FE_PARENTS_HOME_URL = `/parents`; export const FE_PARENTS_MESSAGERIE_URL = `/parents/messagerie`;