mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-05 12:41:27 +00:00
fix: Suppression envoi mail / création page feedback
This commit is contained in:
@ -14,7 +14,7 @@ class ProfileSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Profile
|
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}}
|
extra_kwargs = {'password': {'write_only': True}}
|
||||||
|
|
||||||
def get_roles(self, obj):
|
def get_roles(self, obj):
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from .views import (
|
from .views import (
|
||||||
SendEmailView, search_recipients
|
SendEmailView, search_recipients, SendFeedbackView
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
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'),
|
path('search-recipients/', search_recipients, name='search_recipients'),
|
||||||
|
path('send-feedback/', SendFeedbackView.as_view(), name='send_feedback'),
|
||||||
]
|
]
|
||||||
@ -5,6 +5,7 @@ from rest_framework import status
|
|||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.conf import settings
|
||||||
from Auth.models import Profile, ProfileRole
|
from Auth.models import Profile, ProfileRole
|
||||||
|
|
||||||
import N3wtSchool.mailManager as mailer
|
import N3wtSchool.mailManager as mailer
|
||||||
@ -119,3 +120,84 @@ def search_recipients(request):
|
|||||||
})
|
})
|
||||||
|
|
||||||
return JsonResponse(results, safe=False)
|
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"""
|
||||||
|
<h2>Nouveau Feedback - {category_label}</h2>
|
||||||
|
<p><strong>De:</strong> {user_name} ({user_email})</p>
|
||||||
|
<h3>Établissement</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>ID:</strong> {establishment_id}</li>
|
||||||
|
<li><strong>Nom:</strong> {establishment_name}</li>
|
||||||
|
<li><strong>Capacité:</strong> {establishment_capacity}</li>
|
||||||
|
<li><strong>Fréquence d'évaluation:</strong> {establishment_frequency}</li>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
|
<p><strong>Sujet:</strong> {subject}</p>
|
||||||
|
<div>
|
||||||
|
<strong>Message:</strong><br>
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|||||||
21
Front-End/messages/en/feedback.json
Normal file
21
Front-End/messages/en/feedback.json
Normal file
@ -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."
|
||||||
|
}
|
||||||
@ -7,5 +7,6 @@
|
|||||||
"educational_monitoring": "Educational Monitoring",
|
"educational_monitoring": "Educational Monitoring",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"schoolAdmin": "School Administration",
|
"schoolAdmin": "School Administration",
|
||||||
"messagerie": "Messenger"
|
"messagerie": "Messenger",
|
||||||
|
"feedback": "Feedback"
|
||||||
}
|
}
|
||||||
|
|||||||
21
Front-End/messages/fr/feedback.json
Normal file
21
Front-End/messages/fr/feedback.json
Normal file
@ -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."
|
||||||
|
}
|
||||||
@ -7,5 +7,6 @@
|
|||||||
"educational_monitoring": "Suivi pédagogique",
|
"educational_monitoring": "Suivi pédagogique",
|
||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"schoolAdmin": "Administration Scolaire",
|
"schoolAdmin": "Administration Scolaire",
|
||||||
"messagerie": "Messagerie"
|
"messagerie": "Messagerie",
|
||||||
|
"feedback": "Feedback"
|
||||||
}
|
}
|
||||||
|
|||||||
136
Front-End/src/app/[locale]/admin/feedback/page.js
Normal file
136
Front-End/src/app/[locale]/admin/feedback/page.js
Normal file
@ -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 (
|
||||||
|
<div className="h-full flex flex-col p-4">
|
||||||
|
<div className="max-w-3xl mx-auto w-full">
|
||||||
|
<h1 className="text-2xl font-headline font-bold text-gray-800 mb-2">
|
||||||
|
{t('title')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-600 mb-6">{t('description')}</p>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg shadow-md p-6">
|
||||||
|
{/* Catégorie */}
|
||||||
|
<SelectChoice
|
||||||
|
name="category"
|
||||||
|
label={t('category_label')}
|
||||||
|
selected={category}
|
||||||
|
callback={(e) => setCategory(e.target.value)}
|
||||||
|
choices={categoryChoices}
|
||||||
|
placeHolder={t('category_placeholder')}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Sujet */}
|
||||||
|
<InputText
|
||||||
|
name="subject"
|
||||||
|
label={t('subject_label')}
|
||||||
|
value={subject}
|
||||||
|
onChange={(e) => setSubject(e.target.value)}
|
||||||
|
placeholder={t('subject_placeholder')}
|
||||||
|
required
|
||||||
|
className="mb-4 mt-4"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Message */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<WisiwigTextArea
|
||||||
|
label={t('message_label')}
|
||||||
|
value={message}
|
||||||
|
onChange={setMessage}
|
||||||
|
placeholder={t('message_placeholder')}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bouton d'envoi */}
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
text={isSubmitting ? t('sending') : t('send')}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
Calendar,
|
Calendar,
|
||||||
Settings,
|
Settings,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
|
MessageCircleHeart,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
@ -24,6 +25,7 @@ import {
|
|||||||
FE_ADMIN_PLANNING_URL,
|
FE_ADMIN_PLANNING_URL,
|
||||||
FE_ADMIN_SETTINGS_URL,
|
FE_ADMIN_SETTINGS_URL,
|
||||||
FE_ADMIN_MESSAGERIE_URL,
|
FE_ADMIN_MESSAGERIE_URL,
|
||||||
|
FE_ADMIN_FEEDBACK_URL,
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
|
|
||||||
import { disconnect } from '@/app/actions/authAction';
|
import { disconnect } from '@/app/actions/authAction';
|
||||||
@ -82,6 +84,12 @@ export default function Layout({ children }) {
|
|||||||
url: FE_ADMIN_MESSAGERIE_URL,
|
url: FE_ADMIN_MESSAGERIE_URL,
|
||||||
icon: MessageSquare,
|
icon: MessageSquare,
|
||||||
},
|
},
|
||||||
|
feedback: {
|
||||||
|
id: 'feedback',
|
||||||
|
name: t('feedback'),
|
||||||
|
url: FE_ADMIN_FEEDBACK_URL,
|
||||||
|
icon: MessageCircleHeart,
|
||||||
|
},
|
||||||
settings: {
|
settings: {
|
||||||
id: 'settings',
|
id: 'settings',
|
||||||
name: t('settings'),
|
name: t('settings'),
|
||||||
|
|||||||
@ -1,17 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SidebarTabs from '@/components/SidebarTabs';
|
import SidebarTabs from '@/components/SidebarTabs';
|
||||||
import EmailSender from '@/components/Admin/EmailSender';
|
|
||||||
import InstantMessaging from '@/components/Admin/InstantMessaging';
|
import InstantMessaging from '@/components/Admin/InstantMessaging';
|
||||||
import logger from '@/utils/logger';
|
|
||||||
|
|
||||||
export default function MessageriePage({ csrfToken }) {
|
export default function MessageriePage({ csrfToken }) {
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
|
||||||
id: 'email',
|
|
||||||
label: 'Envoyer un Mail',
|
|
||||||
content: <EmailSender csrfToken={csrfToken} />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'instant',
|
id: 'instant',
|
||||||
label: 'Messagerie Instantanée',
|
label: 'Messagerie Instantanée',
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
BE_GESTIONEMAIL_SEARCH_RECIPIENTS_URL,
|
BE_GESTIONEMAIL_SEARCH_RECIPIENTS_URL,
|
||||||
BE_GESTIONEMAIL_SEND_EMAIL_URL,
|
BE_GESTIONEMAIL_SEND_EMAIL_URL,
|
||||||
|
BE_GESTIONEMAIL_SEND_FEEDBACK_URL,
|
||||||
} from '@/utils/Url';
|
} from '@/utils/Url';
|
||||||
import { fetchWithAuth } from '@/utils/fetchWithAuth';
|
import { fetchWithAuth } from '@/utils/fetchWithAuth';
|
||||||
import { getCsrfToken } from '@/utils/getCsrfToken';
|
import { getCsrfToken } from '@/utils/getCsrfToken';
|
||||||
@ -19,3 +20,13 @@ export const sendEmail = async (messageData) => {
|
|||||||
body: JSON.stringify(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),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export const BE_PLANNING_EVENTS_URL = `${BASE_URL}/Planning/events`;
|
|||||||
// GESTION EMAIL
|
// GESTION EMAIL
|
||||||
export const BE_GESTIONEMAIL_SEND_EMAIL_URL = `${BASE_URL}/GestionEmail/send-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_SEARCH_RECIPIENTS_URL = `${BASE_URL}/GestionEmail/search-recipients`;
|
||||||
|
export const BE_GESTIONEMAIL_SEND_FEEDBACK_URL = `${BASE_URL}/GestionEmail/send-feedback/`;
|
||||||
|
|
||||||
// GESTION MESSAGERIE
|
// GESTION MESSAGERIE
|
||||||
export const BE_GESTIONMESSAGERIE_CONVERSATIONS_URL = `${BASE_URL}/GestionMessagerie/conversations`;
|
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
|
//ADMIN/MESSAGERIE URL
|
||||||
export const FE_ADMIN_MESSAGERIE_URL = '/admin/messagerie';
|
export const FE_ADMIN_MESSAGERIE_URL = '/admin/messagerie';
|
||||||
|
|
||||||
|
//ADMIN/FEEDBACK URL
|
||||||
|
export const FE_ADMIN_FEEDBACK_URL = '/admin/feedback';
|
||||||
|
|
||||||
// PARENT HOME
|
// PARENT HOME
|
||||||
export const FE_PARENTS_HOME_URL = '/parents';
|
export const FE_PARENTS_HOME_URL = '/parents';
|
||||||
export const FE_PARENTS_MESSAGERIE_URL = '/parents/messagerie';
|
export const FE_PARENTS_MESSAGERIE_URL = '/parents/messagerie';
|
||||||
|
|||||||
Reference in New Issue
Block a user