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:
Manus
2026-01-22 08:50:00 -05:00
parent 94fed67d19
commit e3dffb85f4
5 changed files with 112 additions and 5 deletions

View 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
}

View 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
}

View File

@@ -36,6 +36,7 @@ export default function AdminSequences() {
const [isConfirmTestOpen, setIsConfirmTestOpen] = useState(false);
const [sequenceToTest, setSequenceToTest] = useState<number | null>(null);
const [testEmail, setTestEmail] = useState("");
const [sendingProgress, setSendingProgress] = useState<{ current: number; total: number } | null>(null);
const [formData, setFormData] = useState({
formationId: "",
@@ -91,13 +92,15 @@ export default function AdminSequences() {
});
const testerRappelMutation = trpc.rappels.testerRappel.useMutation({
onSuccess: () => {
toast.success("Email de test envoyé avec succès");
onSuccess: (data) => {
setSendingProgress(null);
toast.success(`${data.nbEnvoyes} email(s) de test envoyé(s) avec succès`);
setIsConfirmTestOpen(false);
setSequenceToTest(null);
setTestEmail("");
},
onError: (error) => {
setSendingProgress(null);
toast.error(`Erreur lors de l'envoi du test : ${error.message}`);
setIsConfirmTestOpen(false);
setSequenceToTest(null);
@@ -1104,6 +1107,27 @@ export default function AdminSequences() {
disabled={testerRappelMutation.isPending}
/>
</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 className="flex justify-end gap-2">
<Button

View File

@@ -1471,10 +1471,10 @@ export const appRouter = router({
let nbEchecs = 0;
const erreurs: string[] = [];
// Si un email de test est fourni, envoyer à cet email uniquement
// Sinon, envoyer à tous les inscrits confirmés
// Si un email de test est fourni, envoyer un email par inscrit confirmé à cet email
// Sinon, envoyer à l'email réel de chaque inscrit
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 }));
for (const { apprenant, emailOverride } of destinataires) {

View File

@@ -1189,3 +1189,4 @@
- [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] 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