Checkpoint: ✅ Amélioration du test de rappels avec envoi multiple et indicateur de progression
- Modification du backend pour envoyer un email par inscrit confirmé (tous à l'adresse de test) - Ajout d'un indicateur de progression moderne avec animation (spinner + effet ping) - Message de succès affichant le nombre d'emails envoyés - Validation avec séquence "test 3" : 3 emails envoyés correctement (4ème inscrit sans email ignoré) - Synchronisation complète de la structure de la table logsrappels avec le VPS
This commit is contained in:
9
.manus/db/db-query-1769089721346.json
Normal file
9
.manus/db/db-query-1769089721346.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"query": "SELECT i.id, i.apprenantId, i.statut, a.nom, a.prenom, a.email \nFROM inscriptions i \nLEFT JOIN apprenants a ON i.apprenantId = a.id \nWHERE i.sequenceId = 330001 AND i.statut = 'confirme';",
|
||||||
|
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute SELECT i.id, i.apprenantId, i.statut, a.nom, a.prenom, a.email \nFROM inscriptions i \nLEFT JOIN apprenants a ON i.apprenantId = a.id \nWHERE i.sequenceId = 330001 AND i.statut = 'confirme';",
|
||||||
|
"rows": [],
|
||||||
|
"messages": [],
|
||||||
|
"stdout": "",
|
||||||
|
"stderr": "",
|
||||||
|
"execution_time_ms": 80
|
||||||
|
}
|
||||||
73
.manus/db/db-query-1769089734156.json
Normal file
73
.manus/db/db-query-1769089734156.json
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"query": "SELECT i.id, i.apprenantId, i.statut, i.dateInscription, a.nom, a.prenom, a.email \nFROM inscriptions i \nLEFT JOIN apprenants a ON i.apprenantId = a.id \nWHERE i.sequenceId = 330001;",
|
||||||
|
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute SELECT i.id, i.apprenantId, i.statut, i.dateInscription, a.nom, a.prenom, a.email \nFROM inscriptions i \nLEFT JOIN apprenants a ON i.apprenantId = a.id \nWHERE i.sequenceId = 330001;",
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"id": "300001",
|
||||||
|
"apprenantId": "1",
|
||||||
|
"statut": "confirmee",
|
||||||
|
"dateInscription": "2025-11-30 21:54:11",
|
||||||
|
"nom": "JOHANSSON ",
|
||||||
|
"prenom": "Scarlett",
|
||||||
|
"email": "opareige@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "450001",
|
||||||
|
"apprenantId": "60001",
|
||||||
|
"statut": "confirmee",
|
||||||
|
"dateInscription": "2026-01-22 12:39:20",
|
||||||
|
"nom": "PITT",
|
||||||
|
"prenom": "Brad",
|
||||||
|
"email": "test2@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "420003",
|
||||||
|
"apprenantId": "240001",
|
||||||
|
"statut": "confirmee",
|
||||||
|
"dateInscription": "2025-12-03 09:32:09",
|
||||||
|
"nom": "monroe",
|
||||||
|
"prenom": "marylin",
|
||||||
|
"email": "o.pareige@itinova.org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "420002",
|
||||||
|
"apprenantId": "240001",
|
||||||
|
"statut": "annulee",
|
||||||
|
"dateInscription": "2025-12-03 09:23:59",
|
||||||
|
"nom": "monroe",
|
||||||
|
"prenom": "marylin",
|
||||||
|
"email": "o.pareige@itinova.org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "330001",
|
||||||
|
"apprenantId": "180001",
|
||||||
|
"statut": "annulee",
|
||||||
|
"dateInscription": "2025-12-01 08:40:38",
|
||||||
|
"nom": "NULL",
|
||||||
|
"prenom": "NULL",
|
||||||
|
"email": "NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "360001",
|
||||||
|
"apprenantId": "180001",
|
||||||
|
"statut": "annulee",
|
||||||
|
"dateInscription": "2025-12-01 09:47:25",
|
||||||
|
"nom": "NULL",
|
||||||
|
"prenom": "NULL",
|
||||||
|
"email": "NULL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "360002",
|
||||||
|
"apprenantId": "180001",
|
||||||
|
"statut": "confirmee",
|
||||||
|
"dateInscription": "2025-12-01 09:48:55",
|
||||||
|
"nom": "NULL",
|
||||||
|
"prenom": "NULL",
|
||||||
|
"email": "NULL"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"messages": [],
|
||||||
|
"stdout": "id\tapprenantId\tstatut\tdateInscription\tnom\tprenom\temail\n300001\t1\tconfirmee\t2025-11-30 21:54:11\tJOHANSSON \tScarlett\topareige@gmail.com\n450001\t60001\tconfirmee\t2026-01-22 12:39:20\tPITT\tBrad\ttest2@gmail.com\n420003\t240001\tconfirmee\t2025-12-03 09:32:09\tmonroe\tmarylin\to.pareige@itinova.org\n420002\t240001\tannulee\t2025-12-03 09:23:59\tmonroe\tmarylin\to.pareige@itinova.org\n330001\t180001\tannulee\t2025-12-01 08:40:38\tNULL\tNULL\tNULL\n360001\t180001\tannulee\t2025-12-01 09:47:25\tNULL\tNULL\tNULL\n360002\t180001\tconfirmee\t2025-12-01 09:48:55\tNULL\tNULL\tNULL\n",
|
||||||
|
"stderr": "",
|
||||||
|
"execution_time_ms": 54
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ export default function AdminSequences() {
|
|||||||
const [isConfirmTestOpen, setIsConfirmTestOpen] = useState(false);
|
const [isConfirmTestOpen, setIsConfirmTestOpen] = useState(false);
|
||||||
const [sequenceToTest, setSequenceToTest] = useState<number | null>(null);
|
const [sequenceToTest, setSequenceToTest] = useState<number | null>(null);
|
||||||
const [testEmail, setTestEmail] = useState("");
|
const [testEmail, setTestEmail] = useState("");
|
||||||
|
const [sendingProgress, setSendingProgress] = useState<{ current: number; total: number } | null>(null);
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
formationId: "",
|
formationId: "",
|
||||||
@@ -91,13 +92,15 @@ export default function AdminSequences() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const testerRappelMutation = trpc.rappels.testerRappel.useMutation({
|
const testerRappelMutation = trpc.rappels.testerRappel.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: (data) => {
|
||||||
toast.success("Email de test envoyé avec succès");
|
setSendingProgress(null);
|
||||||
|
toast.success(`${data.nbEnvoyes} email(s) de test envoyé(s) avec succès`);
|
||||||
setIsConfirmTestOpen(false);
|
setIsConfirmTestOpen(false);
|
||||||
setSequenceToTest(null);
|
setSequenceToTest(null);
|
||||||
setTestEmail("");
|
setTestEmail("");
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
setSendingProgress(null);
|
||||||
toast.error(`Erreur lors de l'envoi du test : ${error.message}`);
|
toast.error(`Erreur lors de l'envoi du test : ${error.message}`);
|
||||||
setIsConfirmTestOpen(false);
|
setIsConfirmTestOpen(false);
|
||||||
setSequenceToTest(null);
|
setSequenceToTest(null);
|
||||||
@@ -1104,6 +1107,27 @@ export default function AdminSequences() {
|
|||||||
disabled={testerRappelMutation.isPending}
|
disabled={testerRappelMutation.isPending}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Indicateur de progression */}
|
||||||
|
{testerRappelMutation.isPending && (
|
||||||
|
<div className="space-y-3 p-4 bg-muted/50 rounded-lg border">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="relative flex h-10 w-10 shrink-0">
|
||||||
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
|
||||||
|
<span className="relative inline-flex rounded-full h-10 w-10 bg-primary items-center justify-center">
|
||||||
|
<svg className="animate-spin h-5 w-5 text-primary-foreground" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-sm font-medium">Envoi en cours...</p>
|
||||||
|
<p className="text-xs text-muted-foreground">Veuillez patienter pendant l'envoi des emails de test</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1471,10 +1471,10 @@ export const appRouter = router({
|
|||||||
let nbEchecs = 0;
|
let nbEchecs = 0;
|
||||||
const erreurs: string[] = [];
|
const erreurs: string[] = [];
|
||||||
|
|
||||||
// Si un email de test est fourni, envoyer à cet email uniquement
|
// Si un email de test est fourni, envoyer un email par inscrit confirmé à cet email
|
||||||
// Sinon, envoyer à tous les inscrits confirmés
|
// Sinon, envoyer à l'email réel de chaque inscrit
|
||||||
const destinataires = input.emailTest
|
const destinataires = input.emailTest
|
||||||
? [{ apprenant: inscritsConfirmes[0].apprenant, emailOverride: input.emailTest }]
|
? inscritsConfirmes.map(i => ({ apprenant: i.apprenant, emailOverride: input.emailTest }))
|
||||||
: inscritsConfirmes.map(i => ({ apprenant: i.apprenant, emailOverride: null }));
|
: inscritsConfirmes.map(i => ({ apprenant: i.apprenant, emailOverride: null }));
|
||||||
|
|
||||||
for (const { apprenant, emailOverride } of destinataires) {
|
for (const { apprenant, emailOverride } of destinataires) {
|
||||||
|
|||||||
1
todo.md
1
todo.md
@@ -1189,3 +1189,4 @@
|
|||||||
- [x] Corriger l'enregistrement des logs de rappels (table logsrappels reste vide après test)
|
- [x] Corriger l'enregistrement des logs de rappels (table logsrappels reste vide après test)
|
||||||
- [x] Ajouter une boîte de dialogue de confirmation avant l'envoi de test des rappels
|
- [x] Ajouter une boîte de dialogue de confirmation avant l'envoi de test des rappels
|
||||||
- [x] Modifier la boîte de dialogue de test de rappels pour permettre de saisir l'email du destinataire
|
- [x] Modifier la boîte de dialogue de test de rappels pour permettre de saisir l'email du destinataire
|
||||||
|
- [x] Modifier le test de rappels pour envoyer un email par inscrit à l'adresse de test avec indicateur de progression
|
||||||
|
|||||||
Reference in New Issue
Block a user