diff --git a/client/src/pages/AdminRappels.tsx b/client/src/pages/AdminRappels.tsx
index 1398e72..3b5761e 100644
--- a/client/src/pages/AdminRappels.tsx
+++ b/client/src/pages/AdminRappels.tsx
@@ -22,7 +22,7 @@ import {
} from "@/components/ui/select";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { trpc } from "@/lib/trpc";
-import { Bell, Edit, Plus, Trash2 } from "lucide-react";
+import { Bell, Download, Edit, Plus, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { FileUpload } from "@/components/FileUpload";
import { DateFormationMultiSelect } from "@/components/DateFormationMultiSelect";
@@ -41,6 +41,8 @@ export default function AdminRappels() {
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [isEditOpen, setIsEditOpen] = useState(false);
const [editingRappel, setEditingRappel] = useState(null);
+ const [filterFormation, setFilterFormation] = useState("");
+ const [filterSequence, setFilterSequence] = useState("");
const [formData, setFormData] = useState({
nom: "",
templateType: "",
@@ -183,6 +185,76 @@ export default function AdminRappels() {
return TEMPLATE_TYPES.find(t => t.value === type)?.label || type;
};
+ const exportToCSV = () => {
+ if (!rappels || rappels.length === 0) {
+ toast.error("Aucun rappel à exporter");
+ return;
+ }
+
+ // Appliquer les mêmes filtres que le tableau
+ const filteredRappels = rappels.filter((rappel) => {
+ if (filterFormation && filterFormation !== "all") {
+ if (!rappel.datesDetails || rappel.datesDetails.length === 0) {
+ return true;
+ }
+ const hasFormation = rappel.datesDetails.some((d: any) => d.formationNom === filterFormation);
+ if (!hasFormation) return false;
+ }
+ if (filterSequence && filterSequence !== "all") {
+ if (!rappel.datesDetails || rappel.datesDetails.length === 0) {
+ return true;
+ }
+ const hasSequence = rappel.datesDetails.some((d: any) => d.sequenceNom === filterSequence);
+ if (!hasSequence) return false;
+ }
+ return true;
+ });
+
+ // Créer le CSV
+ const headers = ["Nom", "Type", "Template", "Délai", "Heure", "Formations", "Séquences", "Dates", "Statut"];
+ const rows = filteredRappels.map((rappel) => {
+ const type = rappel.datesDetails && rappel.datesDetails.length > 0 ? "Ciblé" : "Global";
+ const templateName = getTemplateName(rappel.templateType);
+ const delai = rappel.timing === "post_formation" ? `J+${rappel.joursAvant}` : `J-${rappel.joursAvant}`;
+ const statut = rappel.actif ? "Actif" : "Inactif";
+
+ let formations = "Toutes";
+ let sequences = "Toutes";
+ let dates = "Toutes";
+
+ if (rappel.datesDetails && rappel.datesDetails.length > 0) {
+ const formationNames = Array.from(new Set(rappel.datesDetails.map((d: any) => d.formationNom)));
+ const sequenceNames = Array.from(new Set(rappel.datesDetails.map((d: any) => d.sequenceNom)));
+ const dateOrdres = rappel.datesDetails.map((d: any) => `Date ${d.ordre}`).join(", ");
+
+ formations = formationNames.join("; ");
+ sequences = sequenceNames.join("; ");
+ dates = dateOrdres;
+ }
+
+ return [rappel.nom, type, templateName, delai, rappel.heureEnvoi, formations, sequences, dates, statut];
+ });
+
+ // Convertir en CSV
+ const csvContent = [
+ headers.join(","),
+ ...rows.map(row => row.map(cell => `\"${cell}\"`).join(","))
+ ].join("\n");
+
+ // Télécharger le fichier
+ const blob = new Blob(["\ufeff" + csvContent], { type: "text/csv;charset=utf-8;" });
+ const link = document.createElement("a");
+ const url = URL.createObjectURL(blob);
+ link.setAttribute("href", url);
+ link.setAttribute("download", `rappels_${new Date().toISOString().split('T')[0]}.csv`);
+ link.style.visibility = "hidden";
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ toast.success(`Export réussi : ${filteredRappels.length} rappel(s)`);
+ };
+
const formatDatesInfo = (rappel: any) => {
if (!rappel.datesDetails || rappel.datesDetails.length === 0) {
return Toutes les dates ;
@@ -227,10 +299,16 @@ export default function AdminRappels() {
Configurez les rappels envoyés automatiquement avant les séquences
- setIsCreateOpen(true)}>
-
- Nouveau rappel
-
+
+
+
+ Export CSV
+
+
setIsCreateOpen(true)}>
+
+ Nouveau rappel
+
+
@@ -244,6 +322,41 @@ export default function AdminRappels() {
+ {/* Filtres */}
+
+
+ Filtrer par formation
+
+
+
+
+
+ Toutes les formations
+ {sequences && Array.from(new Set(sequences.map(s => s.formation?.nom).filter(Boolean))).map((formationNom) => (
+
+ {formationNom}
+
+ ))}
+
+
+
+
+ Filtrer par séquence
+
+
+
+
+
+ Toutes les séquences
+ {sequences && sequences.map((seq) => (
+
+ {seq.nom}
+
+ ))}
+
+
+
+
@@ -258,9 +371,42 @@ export default function AdminRappels() {
{rappels && rappels.length > 0 ? (
- rappels.map((rappel) => (
+ rappels
+ .filter((rappel) => {
+ // Filtrer par formation
+ if (filterFormation && filterFormation !== "all") {
+ if (!rappel.datesDetails || rappel.datesDetails.length === 0) {
+ return true; // Les rappels globaux passent tous les filtres
+ }
+ const hasFormation = rappel.datesDetails.some((d: any) => d.formationNom === filterFormation);
+ if (!hasFormation) return false;
+ }
+ // Filtrer par séquence
+ if (filterSequence && filterSequence !== "all") {
+ if (!rappel.datesDetails || rappel.datesDetails.length === 0) {
+ return true; // Les rappels globaux passent tous les filtres
+ }
+ const hasSequence = rappel.datesDetails.some((d: any) => d.sequenceNom === filterSequence);
+ if (!hasSequence) return false;
+ }
+ return true;
+ })
+ .map((rappel) => (
- {rappel.nom}
+
+
+ {rappel.datesDetails && rappel.datesDetails.length > 0 ? (
+
+ 🎯 Ciblé
+
+ ) : (
+
+ 🌍 Global
+
+ )}
+ {rappel.nom}
+
+
{getTemplateName(rappel.templateType)}
{rappel.timing === "post_formation" ? `J+${rappel.joursAvant}` : `J-${rappel.joursAvant}`}
diff --git a/todo.md b/todo.md
index 0ab7136..424bbb4 100644
--- a/todo.md
+++ b/todo.md
@@ -566,3 +566,9 @@
- [x] Ajouter une colonne "Dates concernées" avec formations, séquences et dates
- [x] Modifier la procédure tRPC pour inclure les informations de dates
- [x] Tester l'affichage avec plusieurs rappels
+
+## Améliorations avancées du tableau des rappels
+- [x] Ajouter des badges visuels Global/Ciblé dans la colonne Nom
+- [x] Implémenter les filtres par formation et séquence
+- [x] Créer la fonction d'export CSV des rappels
+- [x] Tester toutes les fonctionnalités