dans la fonction "envoyer des emails", ajoute une case à cocher pour n'envoyer qu'aux participants n"ayant pas voté (utilie pour les rappels aux retardataires)

This commit is contained in:
Yannick Le Duc
2025-09-21 21:01:38 +02:00
parent 8274722518
commit 25ccb43272
4 changed files with 146 additions and 34 deletions

View File

@@ -2,8 +2,8 @@
import { useState, useEffect } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { Campaign, Participant } from '@/types';
import { campaignService, participantService, settingsService } from '@/lib/services';
import { Campaign, Participant, ParticipantWithVoteStatus } from '@/types';
import { campaignService, participantService, settingsService, voteService } from '@/lib/services';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Textarea } from '@/components/ui/textarea';
@@ -11,6 +11,7 @@ import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Progress } from '@/components/ui/progress';
import { Badge } from '@/components/ui/badge';
import { Checkbox } from '@/components/ui/checkbox';
import { ArrowLeft, Mail, Send, CheckCircle, XCircle, Clock, Users } from 'lucide-react';
import AuthGuard from '@/components/AuthGuard';
import Footer from '@/components/Footer';
@@ -29,13 +30,14 @@ function SendEmailsPageContent() {
const campaignId = params.id as string;
const [campaign, setCampaign] = useState<Campaign | null>(null);
const [participants, setParticipants] = useState<Participant[]>([]);
const [participants, setParticipants] = useState<ParticipantWithVoteStatus[]>([]);
const [loading, setLoading] = useState(true);
const [sending, setSending] = useState(false);
const [emailProgress, setEmailProgress] = useState<EmailProgress[]>([]);
const [defaultSubject, setDefaultSubject] = useState('');
const [defaultMessage, setDefaultMessage] = useState('');
const [smtpConfigured, setSmtpConfigured] = useState(false);
const [onlyNonVoters, setOnlyNonVoters] = useState(true);
useEffect(() => {
loadData();
@@ -46,7 +48,7 @@ function SendEmailsPageContent() {
setLoading(true);
const [campaignData, participantsData, smtpSettings] = await Promise.all([
campaignService.getById(campaignId),
participantService.getByCampaign(campaignId),
voteService.getParticipantVoteStatus(campaignId),
settingsService.getSmtpSettings()
]);
@@ -85,15 +87,24 @@ Cordialement,`);
}
};
// Fonction pour obtenir les participants filtrés selon l'option sélectionnée
const getFilteredParticipants = () => {
if (onlyNonVoters) {
return participants.filter(participant => !participant.has_voted);
}
return participants;
};
const handleSendAllEmails = async () => {
if (!campaign || !defaultSubject.trim() || !defaultMessage.trim()) {
return;
}
setSending(true);
const filteredParticipants = getFilteredParticipants();
for (let i = 0; i < participants.length; i++) {
const participant = participants[i];
for (let i = 0; i < filteredParticipants.length; i++) {
const participant = filteredParticipants[i];
// Mettre à jour le statut à "sending"
setEmailProgress(prev => prev.map(p =>
@@ -157,7 +168,7 @@ Cordialement,`);
}
// Attendre 1 seconde avant l'email suivant
if (i < participants.length - 1) {
if (i < filteredParticipants.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
@@ -191,9 +202,10 @@ Cordialement,`);
}
};
const filteredParticipants = getFilteredParticipants();
const sentCount = emailProgress.filter(p => p.status === 'sent').length;
const errorCount = emailProgress.filter(p => p.status === 'error').length;
const progressPercentage = participants.length > 0 ? (sentCount / participants.length) * 100 : 0;
const progressPercentage = filteredParticipants.length > 0 ? (sentCount / filteredParticipants.length) * 100 : 0;
if (loading) {
return (
@@ -308,7 +320,7 @@ Cordialement,`);
</div>
{/* Statistiques */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<Card>
<CardContent className="p-6">
<div className="flex items-center">
@@ -321,6 +333,18 @@ Cordialement,`);
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center">
<Users className="h-8 w-8 text-blue-600 dark:text-blue-400 mr-3" />
<div>
<p className="text-sm font-medium text-slate-600 dark:text-slate-400">Destinataires</p>
<p className="text-2xl font-bold text-blue-600">{filteredParticipants.length}</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center">
@@ -379,14 +403,25 @@ Cordialement,`);
/>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="only-non-voters"
checked={onlyNonVoters}
onCheckedChange={(checked) => setOnlyNonVoters(checked as boolean)}
/>
<Label htmlFor="only-non-voters" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
N'envoyer qu'aux participants n'ayant pas encore voté
</Label>
</div>
<div className="flex gap-4">
<Button
onClick={handleSendAllEmails}
disabled={sending || !defaultSubject.trim() || !defaultMessage.trim() || participants.length === 0}
disabled={sending || !defaultSubject.trim() || !defaultMessage.trim() || filteredParticipants.length === 0}
className="flex-1"
>
<Send className="w-4 h-4 mr-2" />
{sending ? 'Envoi en cours...' : 'Envoyer à tous'}
{sending ? 'Envoi en cours...' : `Envoyer à ${filteredParticipants.length} participant${filteredParticipants.length > 1 ? 's' : ''}`}
</Button>
</div>
</CardContent>
@@ -402,7 +437,7 @@ Cordialement,`);
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
{sentCount} / {participants.length} emails envoyés
{sentCount} / {filteredParticipants.length} emails envoyés
</span>
<span className="text-sm text-slate-600 dark:text-slate-400">
{Math.round(progressPercentage)}%
@@ -424,27 +459,42 @@ Cordialement,`);
</CardHeader>
<CardContent>
<div className="space-y-3">
{emailProgress.map((progress) => (
<div key={progress.participant.id} className="flex items-center justify-between p-3 border border-slate-200 dark:border-slate-700 rounded-lg">
<div className="flex items-center space-x-3">
{getStatusIcon(progress.status)}
<div>
<p className="font-medium text-slate-900 dark:text-slate-100">
{progress.participant.first_name} {progress.participant.last_name}
</p>
<p className="text-sm text-slate-600 dark:text-slate-400">
{progress.participant.email}
</p>
{progress.error && (
<p className="text-sm text-red-600 dark:text-red-400">
{progress.error}
</p>
)}
{emailProgress
.filter(progress => {
const participant = participants.find(p => p.id === progress.participant.id);
return participant && (!onlyNonVoters || !participant.has_voted);
})
.map((progress) => {
const participant = participants.find(p => p.id === progress.participant.id);
return (
<div key={progress.participant.id} className="flex items-center justify-between p-3 border border-slate-200 dark:border-slate-700 rounded-lg">
<div className="flex items-center space-x-3">
{getStatusIcon(progress.status)}
<div>
<div className="flex items-center gap-2">
<p className="font-medium text-slate-900 dark:text-slate-100">
{progress.participant.first_name} {progress.participant.last_name}
</p>
{participant?.has_voted && (
<Badge variant="secondary" className="text-xs">
A voté
</Badge>
)}
</div>
<p className="text-sm text-slate-600 dark:text-slate-400">
{progress.participant.email}
</p>
{progress.error && (
<p className="text-sm text-red-600 dark:text-red-400">
{progress.error}
</p>
)}
</div>
</div>
{getStatusBadge(progress.status)}
</div>
</div>
{getStatusBadge(progress.status)}
</div>
))}
);
})}
</div>
</CardContent>
</Card>