From 7f816c165b59e61c075b7d3660563da971a0f1df Mon Sep 17 00:00:00 2001 From: Emma781227 Date: Thu, 23 Jul 2026 13:45:11 +0200 Subject: [PATCH] changement --- frontend/index.html | 3 +- frontend/src/App.jsx | 2 - frontend/src/pages/Admin.jsx | 157 +++++++++++++++++- frontend/src/pages/Connexion.jsx | 6 +- frontend/src/pages/Contact.jsx | 12 +- frontend/src/pages/Employe.jsx | 2 + frontend/src/pages/Inscription.jsx | 17 +- frontend/src/pages/Participation.jsx | 61 ------- frontend/src/pages/Profile.jsx | 3 +- frontend/src/pages/SaisirCodeUnique.jsx | 2 + frontend/src/services/admin.js | 7 + .../src/test/fonctionnel/Connexion.test.jsx | 16 +- .../src/test/fonctionnel/Inscription.test.jsx | 16 +- 13 files changed, 208 insertions(+), 96 deletions(-) delete mode 100644 frontend/src/pages/Participation.jsx diff --git a/frontend/index.html b/frontend/index.html index a9d49dd..ee46fd3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,9 +1,10 @@ - + + Jeu concours - Thé Tip Top diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 6fe2b02..4cbd83f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -7,7 +7,6 @@ import Connexion from './pages/Connexion' import Inscription from './pages/Inscription' import Accueil from './pages/Accueil' import ReglesJeu from './pages/ReglesJeu' -import Participation from './pages/Participation' import MesGains from './pages/MesGains' import Employe from './pages/Employe' import Admin from './pages/Admin' @@ -32,7 +31,6 @@ export default function App() { {/* Pages publiques */} } /> } /> - } /> } /> } /> } /> diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index 67fe444..4272e81 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { getStatsApi, getUsersApi, drawWinnerApi, getWinnerApi } from '../services/admin' +import { getStatsApi, getUsersApi, drawWinnerApi, getWinnerApi, getEmailingContactsApi } from '../services/admin' import { searchTicketApi, deliverClaimApi } from '../services/employee' import { useHandleAuthError } from '../hooks/useHandleAuthError' @@ -40,6 +40,11 @@ export default function Admin() { const [ticketDeliverErr, setTicketDeliverErr] = useState('') const [ticketSuccess, setTicketSuccess] = useState(false) + // Contacts emailing + const [emailingContacts, setEmailingContacts] = useState(null) + const [emailingLoading, setEmailingLoading] = useState(false) + const [emailingError, setEmailingError] = useState('') + useEffect(() => { Promise.all([getStatsApi(), getUsersApi(), getWinnerApi()]) .then(([s, u, w]) => { @@ -67,6 +72,37 @@ export default function Admin() { } } + const handleLoadEmailingContacts = async () => { + setEmailingLoading(true) + setEmailingError('') + try { + const data = await getEmailingContactsApi() + setEmailingContacts(data.contacts) + } catch (e) { + if (!handleAuthError(e)) setEmailingError(e.message) + } finally { + setEmailingLoading(false) + } + } + + const handleExportEmailingCsv = () => { + if (!emailingContacts?.length) return + const header = ['Prénom', 'Nom', 'Email', 'Inscrit le'] + const rows = emailingContacts.map((c) => [ + c.firstname, c.lastname, c.email, new Date(c.createdAt).toLocaleDateString('fr-FR'), + ]) + const csv = [header, ...rows] + .map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(';')) + .join('\n') + const blob = new Blob([`${csv}`], { type: 'text/csv;charset=utf-8;' }) + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = `contacts-emailing-${new Date().toISOString().slice(0, 10)}.csv` + link.click() + URL.revokeObjectURL(url) + } + const handleTicketSearch = async (e) => { e.preventDefault() setTicketResult(null) @@ -127,6 +163,25 @@ export default function Admin() {

Tableau de bord Admin

+ {/* Fenêtre du jeu-concours */} + {stats?.gameWindow?.configured && ( +
+
+
+

+ Jeu-concours {stats.gameWindow.isOpen ? 'en cours' : 'terminé'} +

+

+ Début : {formatDate(stats.gameWindow.startDate)} — Fin des participations et réclamations : {formatDate(stats.gameWindow.claimDeadline)} +

+
+ + {stats.gameWindow.isOpen ? 'Ouvert' : 'Fermé'} + +
+
+ )} + {/* Stats principales */}
@@ -142,6 +197,39 @@ export default function Admin() {
+ {/* Stats démographiques */} + {stats?.genderStats && stats?.ageStats && ( +
+
+

Genre des participants

+
+ {[ + { label: 'Femmes', value: stats.genderStats.women }, + { label: 'Hommes', value: stats.genderStats.men }, + { label: 'Autre / non renseigné', value: stats.genderStats.other }, + ].map((row) => ( +
+ {row.label} + {row.value} +
+ ))} +
+
+ +
+

Âge des participants

+
+ {['-18', '18-24', '25-34', '35-44', '45-54', '55-64', '65+', 'unknown'].map((bracket) => ( +
+ {bracket === 'unknown' ? 'Non renseigné' : `${bracket} ans`} + {stats.ageStats[bracket] ?? 0} +
+ ))} +
+
+
+ )} + {/* Répartition par lot */} {stats?.lotsStats?.length > 0 && (
@@ -171,13 +259,80 @@ export default function Admin() {
)} + {/* ── Contacts emailing ── */} +
+
+

Contacts emailing

+
+ + {emailingContacts?.length > 0 && ( + + )} +
+
+ + {emailingError &&

{emailingError}

} + +

+ Clients ayant accepté d'être contactés à des fins marketing. + {emailingContacts && <> — {emailingContacts.length} contact(s)} +

+ + {emailingContacts && ( +
+ + + + + + + + + + {emailingContacts.length === 0 && ( + + + + )} + {emailingContacts.map((contact) => ( + + + + + + ))} + +
NomEmailInscrit le
+ Aucun contact n'a accepté l'emailing pour le moment +
+ {contact.firstname} {contact.lastname} + {contact.email} + {new Date(contact.createdAt).toLocaleDateString('fr-FR')} +
+
+ )} +
+ {/* ── Remise de lot ── */}

Remise de lot

+ setTicketCode(e.target.value.toUpperCase())} placeholder="Code ticket (10 caractères)" diff --git a/frontend/src/pages/Connexion.jsx b/frontend/src/pages/Connexion.jsx index acf2a7f..3a9b1d2 100644 --- a/frontend/src/pages/Connexion.jsx +++ b/frontend/src/pages/Connexion.jsx @@ -55,11 +55,12 @@ export default function Connexion() {
-