Checkpoint: Modification de la boîte de dialogue de test de rappels

- Ajout d'un champ de saisie pour l'email du destinataire
- Modification du texte : "Tous les rappels configurés pour cette séquence seront envoyés à l'adresse email que vous indiquez ci-dessous"
- Validation de l'email avant envoi
- Backend modifié pour envoyer à l'email saisi au lieu de tous les inscrits
- Les logs enregistrent correctement l'email de test utilisé
This commit is contained in:
Manus
2026-01-22 08:33:07 -05:00
parent 58cbc2d85c
commit 94fed67d19
3 changed files with 45 additions and 13 deletions

View File

@@ -35,6 +35,7 @@ export default function AdminSequences() {
const [isCompactView, setIsCompactView] = useState(true); const [isCompactView, setIsCompactView] = useState(true);
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 [formData, setFormData] = useState({ const [formData, setFormData] = useState({
formationId: "", formationId: "",
@@ -94,17 +95,20 @@ export default function AdminSequences() {
toast.success("Email de test envoyé avec succès"); toast.success("Email de test envoyé avec succès");
setIsConfirmTestOpen(false); setIsConfirmTestOpen(false);
setSequenceToTest(null); setSequenceToTest(null);
setTestEmail("");
}, },
onError: (error) => { onError: (error) => {
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);
setTestEmail("");
}, },
}); });
const handleTestRappel = (sequenceId: number, sequence: any) => { const handleTestRappel = (sequenceId: number, sequence: any) => {
if (sequence.dates && sequence.dates.length > 0) { if (sequence.dates && sequence.dates.length > 0) {
setSequenceToTest(sequenceId); setSequenceToTest(sequenceId);
setTestEmail(""); // Réinitialiser l'email
setIsConfirmTestOpen(true); setIsConfirmTestOpen(true);
} else { } else {
toast.error("Cette séquence n'a pas de dates configurées"); toast.error("Cette séquence n'a pas de dates configurées");
@@ -113,7 +117,14 @@ export default function AdminSequences() {
const confirmTestRappel = () => { const confirmTestRappel = () => {
if (sequenceToTest !== null) { if (sequenceToTest !== null) {
testerRappelMutation.mutate({ sequenceId: sequenceToTest }); if (!testEmail || !testEmail.includes('@')) {
toast.error("Veuillez saisir une adresse email valide");
return;
}
testerRappelMutation.mutate({
sequenceId: sequenceToTest,
emailTest: testEmail
});
} }
}; };
@@ -1076,17 +1087,31 @@ export default function AdminSequences() {
<Dialog open={isConfirmTestOpen} onOpenChange={setIsConfirmTestOpen}> <Dialog open={isConfirmTestOpen} onOpenChange={setIsConfirmTestOpen}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Confirmer l'envoi du test</DialogTitle> <DialogTitle>Tester l'envoi de rappels</DialogTitle>
<DialogDescription> <DialogDescription>
Êtes-vous sûr de vouloir envoyer un email de test de rappel à tous les apprenants inscrits confirmés de cette séquence ? Tous les rappels configurés pour cette séquence seront envoyés à l'adresse email que vous indiquez ci-dessous.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="flex justify-end gap-2 pt-4"> <div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="test-email">Adresse email du destinataire</Label>
<Input
id="test-email"
type="email"
placeholder="exemple@domaine.com"
value={testEmail}
onChange={(e) => setTestEmail(e.target.value)}
disabled={testerRappelMutation.isPending}
/>
</div>
</div>
<div className="flex justify-end gap-2">
<Button <Button
variant="outline" variant="outline"
onClick={() => { onClick={() => {
setIsConfirmTestOpen(false); setIsConfirmTestOpen(false);
setSequenceToTest(null); setSequenceToTest(null);
setTestEmail("");
}} }}
disabled={testerRappelMutation.isPending} disabled={testerRappelMutation.isPending}
> >

View File

@@ -1418,6 +1418,7 @@ export const appRouter = router({
testerRappel: adminProcedure testerRappel: adminProcedure
.input(z.object({ .input(z.object({
sequenceId: z.number(), sequenceId: z.number(),
emailTest: z.string().email().optional(),
})) }))
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
const sequence = await db.getSequenceById(input.sequenceId); const sequence = await db.getSequenceById(input.sequenceId);
@@ -1463,26 +1464,31 @@ export const appRouter = router({
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Aucun apprenant inscrit confirmé pour cette séquence' }); throw new TRPCError({ code: 'BAD_REQUEST', message: 'Aucun apprenant inscrit confirmé pour cette séquence' });
} }
// Envoyer le rappel de test à l'administrateur connecté // Envoyer le rappel de test
// Envoyer le rappel de test à tous les inscrits confirmés
const { sendRappelEmail } = await import('./emailService'); const { sendRappelEmail } = await import('./emailService');
let nbEnvoyes = 0; let nbEnvoyes = 0;
let nbEchecs = 0; let nbEchecs = 0;
const erreurs: string[] = []; const erreurs: string[] = [];
for (const inscrit of inscritsConfirmes) { // Si un email de test est fourni, envoyer à cet email uniquement
const apprenant = inscrit.apprenant; // Sinon, envoyer à tous les inscrits confirmés
const destinataires = input.emailTest
if (!apprenant || !apprenant.email) { ? [{ apprenant: inscritsConfirmes[0].apprenant, emailOverride: input.emailTest }]
: inscritsConfirmes.map(i => ({ apprenant: i.apprenant, emailOverride: null }));
for (const { apprenant, emailOverride } of destinataires) {
if (!apprenant || (!apprenant.email && !emailOverride)) {
nbEchecs++; nbEchecs++;
erreurs.push(`Apprenant ${apprenant?.nom || 'inconnu'}: email manquant`); erreurs.push(`Apprenant ${apprenant?.nom || 'inconnu'}: email manquant`);
continue; continue;
} }
const emailDestinataire = emailOverride || apprenant.email;
try { try {
await sendRappelEmail({ await sendRappelEmail({
apprenantEmail: apprenant.email, apprenantEmail: emailDestinataire,
apprenantPrenom: apprenant.prenom, apprenantPrenom: apprenant.prenom,
apprenantNom: apprenant.nom, apprenantNom: apprenant.nom,
apprenantFonction: apprenant.fonction || '', apprenantFonction: apprenant.fonction || '',
@@ -1506,7 +1512,7 @@ export const appRouter = router({
rappelId: rappelAUtiliser.id, rappelId: rappelAUtiliser.id,
sequenceId: input.sequenceId, sequenceId: input.sequenceId,
apprenantId: apprenant.id, apprenantId: apprenant.id,
email: apprenant.email, email: emailDestinataire,
type: rappelAUtiliser.templateType, type: rappelAUtiliser.templateType,
typeEnvoi: 'test', typeEnvoi: 'test',
statut: 'succes', statut: 'succes',
@@ -1524,7 +1530,7 @@ export const appRouter = router({
rappelId: rappelAUtiliser.id, rappelId: rappelAUtiliser.id,
sequenceId: input.sequenceId, sequenceId: input.sequenceId,
apprenantId: apprenant.id, apprenantId: apprenant.id,
email: apprenant.email, email: emailDestinataire,
type: rappelAUtiliser.templateType, type: rappelAUtiliser.templateType,
typeEnvoi: 'test', typeEnvoi: 'test',
statut: 'echec', statut: 'echec',

View File

@@ -1188,3 +1188,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