From 4c56cb647470f86151b1ff78c318e0b6468f009d Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Sat, 4 Apr 2026 14:26:23 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20Suppression=20envoi=20mail=20/=20cr?= =?UTF-8?q?=C3=A9ation=20page=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Back-End/Auth/serializers.py | 2 +- Back-End/GestionEmail/urls.py | 3 +- Back-End/GestionEmail/views.py | 82 +++++++++++ Front-End/messages/en/feedback.json | 21 +++ Front-End/messages/en/sidebar.json | 3 +- Front-End/messages/fr/feedback.json | 21 +++ Front-End/messages/fr/sidebar.json | 3 +- .../src/app/[locale]/admin/feedback/page.js | 136 ++++++++++++++++++ Front-End/src/app/[locale]/admin/layout.js | 8 ++ .../src/app/[locale]/admin/messagerie/page.js | 7 - Front-End/src/app/actions/emailAction.js | 11 ++ Front-End/src/utils/Url.js | 4 + 12 files changed, 290 insertions(+), 11 deletions(-) create mode 100644 Front-End/messages/en/feedback.json create mode 100644 Front-End/messages/fr/feedback.json create mode 100644 Front-End/src/app/[locale]/admin/feedback/page.js diff --git a/Back-End/Auth/serializers.py b/Back-End/Auth/serializers.py index 6f1db3e..3846534 100644 --- a/Back-End/Auth/serializers.py +++ b/Back-End/Auth/serializers.py @@ -14,7 +14,7 @@ class ProfileSerializer(serializers.ModelSerializer): class Meta: model = Profile - fields = ['id', 'password', 'email', 'code', 'datePeremption', 'username', 'roles', 'roleIndexLoginDefault'] + fields = ['id', 'password', 'email', 'code', 'datePeremption', 'username', 'roles', 'roleIndexLoginDefault', 'first_name', 'last_name'] extra_kwargs = {'password': {'write_only': True}} def get_roles(self, obj): diff --git a/Back-End/GestionEmail/urls.py b/Back-End/GestionEmail/urls.py index e9ffee7..4798d6d 100644 --- a/Back-End/GestionEmail/urls.py +++ b/Back-End/GestionEmail/urls.py @@ -1,9 +1,10 @@ from django.urls import path from .views import ( - SendEmailView, search_recipients + SendEmailView, search_recipients, SendFeedbackView ) urlpatterns = [ path('send-email/', SendEmailView.as_view(), name='send_email'), path('search-recipients/', search_recipients, name='search_recipients'), + path('send-feedback/', SendFeedbackView.as_view(), name='send_feedback'), ] \ No newline at end of file diff --git a/Back-End/GestionEmail/views.py b/Back-End/GestionEmail/views.py index 9084539..f83ce1e 100644 --- a/Back-End/GestionEmail/views.py +++ b/Back-End/GestionEmail/views.py @@ -5,6 +5,7 @@ from rest_framework import status from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from django.db.models import Q +from django.conf import settings from Auth.models import Profile, ProfileRole import N3wtSchool.mailManager as mailer @@ -119,3 +120,84 @@ def search_recipients(request): }) return JsonResponse(results, safe=False) + + +class SendFeedbackView(APIView): + """ + API pour envoyer un feedback au support (EMAIL_HOST_USER). + """ + permission_classes = [IsAuthenticated] + + def post(self, request): + data = request.data + category = data.get('category', '') + subject = data.get('subject', 'Feedback') + message = data.get('message', '') + user_email = data.get('user_email', '') + user_name = data.get('user_name', '') + establishment = data.get('establishment', {}) + + logger.info(f"Feedback received - Category: {category}, Subject: {subject}") + + if not message or not subject or not category: + return Response( + {'error': 'La catégorie, le sujet et le message sont requis.'}, + status=status.HTTP_400_BAD_REQUEST + ) + + try: + # Construire le message formaté + category_labels = { + 'bug': 'Signalement de bug', + 'feature': 'Proposition de fonctionnalité', + 'question': 'Question', + 'other': 'Autre' + } + category_label = category_labels.get(category, category) + + # Construire les infos établissement + establishment_id = establishment.get('id', 'N/A') + establishment_name = establishment.get('name', 'N/A') + establishment_capacity = establishment.get('total_capacity', 'N/A') + establishment_frequency = establishment.get('evaluation_frequency', 'N/A') + + formatted_message = f""" +

Nouveau Feedback - {category_label}

+

De: {user_name} ({user_email})

+

Établissement

+ +
+

Sujet: {subject}

+
+ Message:
+ {message} +
+ """ + + formatted_subject = f"[N3WT School Feedback] [{category_label}] {subject}" + + # Envoyer à EMAIL_HOST_USER avec la configuration SMTP par défaut + result = mailer.sendMail( + subject=formatted_subject, + message=formatted_message, + recipients=[settings.EMAIL_HOST_USER], + cc=[], + bcc=[], + attachments=[], + connection=None # Utilise la configuration SMTP par défaut + ) + + logger.info("Feedback envoyé avec succès") + return result + + except Exception as e: + logger.error(f"Erreur lors de l'envoi du feedback: {str(e)}", exc_info=True) + return Response( + {'error': "Erreur lors de l'envoi du feedback"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) diff --git a/Front-End/messages/en/feedback.json b/Front-End/messages/en/feedback.json new file mode 100644 index 0000000..4b6ed56 --- /dev/null +++ b/Front-End/messages/en/feedback.json @@ -0,0 +1,21 @@ +{ + "title": "Feedback", + "description": "Share your feedback, report a bug, or suggest an improvement. We read every message!", + "category_label": "Category", + "category_placeholder": "Select a category", + "category_bug": "Report a bug", + "category_feature": "Suggest a feature", + "category_question": "Ask a question", + "category_other": "Other", + "subject_label": "Subject", + "subject_placeholder": "Summarize your request", + "message_label": "Message", + "message_placeholder": "Describe your feedback in detail...", + "send": "Send", + "sending": "Sending...", + "success": "Success", + "success_message": "Your feedback has been sent. Thank you!", + "error": "Error", + "error_required_fields": "Please fill in all required fields.", + "error_sending": "An error occurred while sending your feedback." +} diff --git a/Front-End/messages/en/sidebar.json b/Front-End/messages/en/sidebar.json index 16cc0fe..0572e96 100644 --- a/Front-End/messages/en/sidebar.json +++ b/Front-End/messages/en/sidebar.json @@ -7,5 +7,6 @@ "educational_monitoring": "Educational Monitoring", "settings": "Settings", "schoolAdmin": "School Administration", - "messagerie": "Messenger" + "messagerie": "Messenger", + "feedback": "Feedback" } diff --git a/Front-End/messages/fr/feedback.json b/Front-End/messages/fr/feedback.json new file mode 100644 index 0000000..4a8bfcb --- /dev/null +++ b/Front-End/messages/fr/feedback.json @@ -0,0 +1,21 @@ +{ + "title": "Feedback", + "description": "Partagez vos retours, signalez un bug ou proposez une amélioration. Nous lisons chaque message !", + "category_label": "Catégorie", + "category_placeholder": "Sélectionnez une catégorie", + "category_bug": "Signaler un bug", + "category_feature": "Proposer une fonctionnalité", + "category_question": "Poser une question", + "category_other": "Autre", + "subject_label": "Sujet", + "subject_placeholder": "Résumez votre demande", + "message_label": "Message", + "message_placeholder": "Décrivez en détail votre retour...", + "send": "Envoyer", + "sending": "Envoi en cours...", + "success": "Succès", + "success_message": "Votre feedback a bien été envoyé. Merci !", + "error": "Erreur", + "error_required_fields": "Veuillez remplir tous les champs obligatoires.", + "error_sending": "Une erreur est survenue lors de l'envoi du feedback." +} diff --git a/Front-End/messages/fr/sidebar.json b/Front-End/messages/fr/sidebar.json index aba45e7..5d88c04 100644 --- a/Front-End/messages/fr/sidebar.json +++ b/Front-End/messages/fr/sidebar.json @@ -7,5 +7,6 @@ "educational_monitoring": "Suivi pédagogique", "settings": "Paramètres", "schoolAdmin": "Administration Scolaire", - "messagerie": "Messagerie" + "messagerie": "Messagerie", + "feedback": "Feedback" } diff --git a/Front-End/src/app/[locale]/admin/feedback/page.js b/Front-End/src/app/[locale]/admin/feedback/page.js new file mode 100644 index 0000000..605f167 --- /dev/null +++ b/Front-End/src/app/[locale]/admin/feedback/page.js @@ -0,0 +1,136 @@ +'use client'; + +import React, { useState } from 'react'; +import { sendFeedback } from '@/app/actions/emailAction'; +import { useNotification } from '@/context/NotificationContext'; +import { useEstablishment } from '@/context/EstablishmentContext'; +import { useTranslations } from 'next-intl'; +import WisiwigTextArea from '@/components/Form/WisiwigTextArea'; +import InputText from '@/components/Form/InputText'; +import Button from '@/components/Form/Button'; +import SelectChoice from '@/components/Form/SelectChoice'; +import logger from '@/utils/logger'; + +export default function FeedbackPage() { + const t = useTranslations('feedback'); + const { showNotification } = useNotification(); + const { selectedEstablishmentId, establishments, user } = useEstablishment(); + + // Récupérer les infos complètes de l'établissement sélectionné + const selectedEstablishment = establishments?.find( + (e) => e.id === selectedEstablishmentId + ); + + const [category, setCategory] = useState(''); + const [subject, setSubject] = useState(''); + const [message, setMessage] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + + const categoryChoices = [ + { value: 'bug', label: t('category_bug') }, + { value: 'feature', label: t('category_feature') }, + { value: 'question', label: t('category_question') }, + { value: 'other', label: t('category_other') }, + ]; + + const handleSubmit = async () => { + if (!category || !subject || !message) { + showNotification(t('error_required_fields'), 'error', t('error')); + return; + } + + setIsSubmitting(true); + + // Construire le nom de l'utilisateur (fallback vers l'email si nom indisponible) + const userName = user + ? user.first_name && user.last_name + ? `${user.first_name} ${user.last_name}` + : user.username || user.email?.split('@')[0] || '' + : ''; + + const feedbackData = { + category, + subject, + message, + establishment: selectedEstablishment + ? { + id: selectedEstablishment.id, + name: selectedEstablishment.name, + total_capacity: selectedEstablishment.total_capacity, + evaluation_frequency: selectedEstablishment.evaluation_frequency, + } + : { id: selectedEstablishmentId }, + user_email: user?.email || '', + user_name: userName, + }; + + try { + await sendFeedback(feedbackData); + showNotification(t('success_message'), 'success', t('success')); + // Réinitialiser les champs après succès + setCategory(''); + setSubject(''); + setMessage(''); + } catch (error) { + logger.error("Erreur lors de l'envoi du feedback:", { error }); + showNotification(t('error_sending'), 'error', t('error')); + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+
+

+ {t('title')} +

+

{t('description')}

+ +
+ {/* Catégorie */} + setCategory(e.target.value)} + choices={categoryChoices} + placeHolder={t('category_placeholder')} + required + /> + + {/* Sujet */} + setSubject(e.target.value)} + placeholder={t('subject_placeholder')} + required + className="mb-4 mt-4" + /> + + {/* Message */} +
+ +
+ + {/* Bouton d'envoi */} +
+
+
+
+
+ ); +} diff --git a/Front-End/src/app/[locale]/admin/layout.js b/Front-End/src/app/[locale]/admin/layout.js index 75587bb..3550808 100644 --- a/Front-End/src/app/[locale]/admin/layout.js +++ b/Front-End/src/app/[locale]/admin/layout.js @@ -12,6 +12,7 @@ import { Calendar, Settings, MessageSquare, + MessageCircleHeart, } from 'lucide-react'; import Popup from '@/components/Popup'; @@ -24,6 +25,7 @@ import { FE_ADMIN_PLANNING_URL, FE_ADMIN_SETTINGS_URL, FE_ADMIN_MESSAGERIE_URL, + FE_ADMIN_FEEDBACK_URL, } from '@/utils/Url'; import { disconnect } from '@/app/actions/authAction'; @@ -82,6 +84,12 @@ export default function Layout({ children }) { url: FE_ADMIN_MESSAGERIE_URL, icon: MessageSquare, }, + feedback: { + id: 'feedback', + name: t('feedback'), + url: FE_ADMIN_FEEDBACK_URL, + icon: MessageCircleHeart, + }, 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 index eeb5b37..f187024 100644 --- a/Front-End/src/app/[locale]/admin/messagerie/page.js +++ b/Front-End/src/app/[locale]/admin/messagerie/page.js @@ -1,17 +1,10 @@ 'use client'; import React from 'react'; import SidebarTabs from '@/components/SidebarTabs'; -import EmailSender from '@/components/Admin/EmailSender'; import InstantMessaging from '@/components/Admin/InstantMessaging'; -import logger from '@/utils/logger'; export default function MessageriePage({ csrfToken }) { const tabs = [ - { - id: 'email', - label: 'Envoyer un Mail', - content: , - }, { id: 'instant', label: 'Messagerie Instantanée', diff --git a/Front-End/src/app/actions/emailAction.js b/Front-End/src/app/actions/emailAction.js index 8fdc3bb..8c38a90 100644 --- a/Front-End/src/app/actions/emailAction.js +++ b/Front-End/src/app/actions/emailAction.js @@ -1,6 +1,7 @@ import { BE_GESTIONEMAIL_SEARCH_RECIPIENTS_URL, BE_GESTIONEMAIL_SEND_EMAIL_URL, + BE_GESTIONEMAIL_SEND_FEEDBACK_URL, } from '@/utils/Url'; import { fetchWithAuth } from '@/utils/fetchWithAuth'; import { getCsrfToken } from '@/utils/getCsrfToken'; @@ -19,3 +20,13 @@ export const sendEmail = async (messageData) => { body: JSON.stringify(messageData), }); }; + +// Envoyer un feedback au support +export const sendFeedback = async (feedbackData) => { + const csrfToken = getCsrfToken(); + return fetchWithAuth(BE_GESTIONEMAIL_SEND_FEEDBACK_URL, { + method: 'POST', + headers: { 'X-CSRFToken': csrfToken }, + body: JSON.stringify(feedbackData), + }); +}; diff --git a/Front-End/src/utils/Url.js b/Front-End/src/utils/Url.js index 78aa9cf..d6763ab 100644 --- a/Front-End/src/utils/Url.js +++ b/Front-End/src/utils/Url.js @@ -54,6 +54,7 @@ export const BE_PLANNING_EVENTS_URL = `${BASE_URL}/Planning/events`; // GESTION EMAIL export const BE_GESTIONEMAIL_SEND_EMAIL_URL = `${BASE_URL}/GestionEmail/send-email/`; export const BE_GESTIONEMAIL_SEARCH_RECIPIENTS_URL = `${BASE_URL}/GestionEmail/search-recipients`; +export const BE_GESTIONEMAIL_SEND_FEEDBACK_URL = `${BASE_URL}/GestionEmail/send-feedback/`; // GESTION MESSAGERIE export const BE_GESTIONMESSAGERIE_CONVERSATIONS_URL = `${BASE_URL}/GestionMessagerie/conversations`; @@ -123,6 +124,9 @@ export const FE_ADMIN_SETTINGS_URL = '/admin/settings'; //ADMIN/MESSAGERIE URL export const FE_ADMIN_MESSAGERIE_URL = '/admin/messagerie'; +//ADMIN/FEEDBACK URL +export const FE_ADMIN_FEEDBACK_URL = '/admin/feedback'; + // PARENT HOME export const FE_PARENTS_HOME_URL = '/parents'; export const FE_PARENTS_MESSAGERIE_URL = '/parents/messagerie';