Checkpoint: Implémentation de trois fonctionnalités majeures :
1. Personnalisation des templates d'emails avec variables {{lieu}} et {{formateur}} (déjà disponible)
2. Aperçu avant envoi avec données réelles d'un apprenant (modal avec iframe)
3. Email de rappel J-1 avec bouton d'envoi et d'aperçu
Les templates personnalisés supportent maintenant toutes les variables disponibles.
L'aperçu permet de vérifier le rendu final avant l'envoi groupé.
Le rappel J-1 complète le système de communication avec les apprenants.
This commit is contained in:
@@ -4,13 +4,20 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { trpc } from "@/lib/trpc";
|
||||
import { ArrowLeft, Download, Mail, Search } from "lucide-react";
|
||||
import { ArrowLeft, Download, Mail, Search, Eye } from "lucide-react";
|
||||
import { useLocation, useRoute } from "wouter";
|
||||
import { useState, useMemo } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
export default function AdminSequenceInscrits() {
|
||||
const [, setLocation] = useLocation();
|
||||
@@ -41,11 +48,17 @@ export default function AdminSequenceInscrits() {
|
||||
const [filterEtablissement, setFilterEtablissement] = useState<string>("all");
|
||||
const [filterFonction, setFilterFonction] = useState<string>("all");
|
||||
const [sortBy, setSortBy] = useState<"date" | "nom">("date");
|
||||
const [showPreviewModal, setShowPreviewModal] = useState(false);
|
||||
const [previewType, setPreviewType] = useState<"teaser" | "rappel">("teaser");
|
||||
|
||||
const utils = trpc.useUtils();
|
||||
const { data: sequence, isLoading: loadingSequence } = trpc.sequences.getById.useQuery({ id: sequenceId });
|
||||
const { data: inscriptions, isLoading: loadingInscriptions } = trpc.inscriptions.listBySequence.useQuery({ sequenceId });
|
||||
const { data: formations } = trpc.formations.list.useQuery();
|
||||
const { data: emailPreview, isLoading: loadingPreview } = trpc.inscriptions.emailPreview.useQuery(
|
||||
{ sequenceId, type: previewType },
|
||||
{ enabled: showPreviewModal }
|
||||
);
|
||||
|
||||
const updateStatutMutation = trpc.inscriptions.updateStatut.useMutation({
|
||||
onSuccess: () => {
|
||||
@@ -143,8 +156,9 @@ export default function AdminSequenceInscrits() {
|
||||
exportFeuilleMutation.mutate({ sequenceId });
|
||||
};
|
||||
|
||||
const handleSendEmails = (type: "teaser" | "rappel") => {
|
||||
if (confirm(`Êtes-vous sûr de vouloir envoyer les emails ${type === "teaser" ? "teaser" : "de rappel J-7"} à tous les inscrits confirmés ?`)) {
|
||||
const handleSendEmails = (type: "teaser" | "rappel" | "rappel_j1") => {
|
||||
const typeLabel = type === "teaser" ? "teaser" : type === "rappel" ? "de rappel J-7" : "de rappel J-1";
|
||||
if (confirm(`Êtes-vous sûr de vouloir envoyer les emails ${typeLabel} à tous les inscrits confirmés ?`)) {
|
||||
sendEmailsMutation.mutate({ sequenceId, type });
|
||||
}
|
||||
};
|
||||
@@ -354,6 +368,17 @@ export default function AdminSequenceInscrits() {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setPreviewType("teaser");
|
||||
setShowPreviewModal(true);
|
||||
}}
|
||||
disabled={nbConfirmes === 0}
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-2" />
|
||||
Aperçu teaser
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => handleSendEmails("teaser")}
|
||||
@@ -362,6 +387,17 @@ export default function AdminSequenceInscrits() {
|
||||
<Mail className="h-4 w-4 mr-2" />
|
||||
Envoyer email teaser
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setPreviewType("rappel");
|
||||
setShowPreviewModal(true);
|
||||
}}
|
||||
disabled={nbConfirmes === 0}
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-2" />
|
||||
Aperçu rappel J-7
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => handleSendEmails("rappel")}
|
||||
@@ -370,6 +406,25 @@ export default function AdminSequenceInscrits() {
|
||||
<Mail className="h-4 w-4 mr-2" />
|
||||
Envoyer rappel J-7
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setPreviewType("rappel_j1");
|
||||
setShowPreviewModal(true);
|
||||
}}
|
||||
disabled={nbConfirmes === 0}
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-2" />
|
||||
Aperçu rappel J-1
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => handleSendEmails("rappel_j1")}
|
||||
disabled={sendEmailsMutation.isPending || nbConfirmes === 0}
|
||||
>
|
||||
<Mail className="h-4 w-4 mr-2" />
|
||||
Envoyer rappel J-1
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleExportExcel}
|
||||
@@ -556,6 +611,49 @@ export default function AdminSequenceInscrits() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Modal d'aperçu d'email */}
|
||||
<Dialog open={showPreviewModal} onOpenChange={setShowPreviewModal}>
|
||||
<DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Aperçu de l'email {previewType === "teaser" ? "teaser" : previewType === "rappel" ? "de rappel J-7" : "de rappel J-1"}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{loadingPreview ? (
|
||||
"Chargement de l'aperçu..."
|
||||
) : emailPreview ? (
|
||||
`Destinataire : ${emailPreview.recipient.prenom} ${emailPreview.recipient.nom} (${emailPreview.recipient.email})`
|
||||
) : (
|
||||
"Erreur lors du chargement de l'aperçu"
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{loadingPreview ? (
|
||||
<div className="flex justify-center p-8">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
||||
</div>
|
||||
) : emailPreview ? (
|
||||
<div className="space-y-4">
|
||||
<div className="bg-muted p-3 rounded-md">
|
||||
<p className="text-sm font-medium">Sujet :</p>
|
||||
<p className="text-sm">{emailPreview.subject}</p>
|
||||
</div>
|
||||
<div className="border rounded-md p-4 bg-white">
|
||||
<iframe
|
||||
srcDoc={emailPreview.html}
|
||||
className="w-full h-[500px] border-0"
|
||||
title="Aperçu de l'email"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center p-8 text-muted-foreground">
|
||||
Impossible de charger l'aperçu. Vérifiez qu'il y a des apprenants confirmés.
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user