diff --git a/.manus/db/db-query-1766057305408.json b/.manus/db/db-query-1766057305408.json new file mode 100644 index 0000000..ea08da2 --- /dev/null +++ b/.manus/db/db-query-1766057305408.json @@ -0,0 +1,226 @@ +{ + "query": "SELECT \n type,\n emailDestinataire,\n sujet,\n dateEnvoi,\n statut,\n sequenceId,\n apprenantId\nFROM logsNotifications \nWHERE type = 'remerciement'\nORDER BY dateEnvoi DESC\nLIMIT 50;", + "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 \n type,\n emailDestinataire,\n sujet,\n dateEnvoi,\n statut,\n sequenceId,\n apprenantId\nFROM logsNotifications \nWHERE type = 'remerciement'\nORDER BY dateEnvoi DESC\nLIMIT 50;", + "rows": [ + { + "type": "remerciement", + "emailDestinataire": "alerte2.test@example.com", + "sujet": "Merci pour votre participation - Formation manager Itinova", + "dateEnvoi": "2025-12-18 11:23:55", + "statut": "success", + "sequenceId": "30001", + "apprenantId": "90002" + }, + { + "type": "remerciement", + "emailDestinataire": "test.alerte@example.com", + "sujet": "Merci pour votre participation - Formation manager Itinova", + "dateEnvoi": "2025-12-18 09:42:30", + "statut": "success", + "sequenceId": "30001", + "apprenantId": "90001" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation manager Itinova", + "dateEnvoi": "2025-12-18 09:42:27", + "statut": "success", + "sequenceId": "30001", + "apprenantId": "1" + }, + { + "type": "remerciement", + "emailDestinataire": "o.pareige@itinova.org", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 21:16:59", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "240001" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 21:16:32", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "1" + }, + { + "type": "remerciement", + "emailDestinataire": "o.pareige@itinova.org", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 21:14:32", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "240001" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 21:14:25", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "1" + }, + { + "type": "remerciement", + "emailDestinataire": "o.pareige@itinova.org", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 21:00:43", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "240001" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 21:00:40", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "1" + }, + { + "type": "remerciement", + "emailDestinataire": "o.pareige@itinova.org", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:34:28", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "240001" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:34:00", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "1" + }, + { + "type": "remerciement", + "emailDestinataire": "o.pareige@itinova.org", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:33:28", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "240001" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:33:21", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "1" + }, + { + "type": "remerciement", + "emailDestinataire": "o.pareige@itinova.org", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:26:15", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "240001" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:26:13", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "1" + }, + { + "type": "remerciement", + "emailDestinataire": "o.pareige@itinova.org", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:25:51", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "240001" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:25:49", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "1" + }, + { + "type": "remerciement", + "emailDestinataire": "o.pareige@itinova.org", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:25:31", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "240001" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:25:28", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "1" + }, + { + "type": "remerciement", + "emailDestinataire": "o.pareige@itinova.org", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:25:14", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "240001" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:25:12", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "1" + }, + { + "type": "remerciement", + "emailDestinataire": "o.pareige@itinova.org", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:24:58", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "240001" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:24:55", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "1" + }, + { + "type": "remerciement", + "emailDestinataire": "opareige@gmail.com", + "sujet": "Merci pour votre participation - Formation Jetski", + "dateEnvoi": "2025-12-15 20:24:49", + "statut": "success", + "sequenceId": "330001", + "apprenantId": "1" + } + ], + "messages": [], + "stdout": "type\temailDestinataire\tsujet\tdateEnvoi\tstatut\tsequenceId\tapprenantId\nremerciement\talerte2.test@example.com\tMerci pour votre participation - Formation manager Itinova\t2025-12-18 11:23:55\tsuccess\t30001\t90002\nremerciement\ttest.alerte@example.com\tMerci pour votre participation - Formation manager Itinova\t2025-12-18 09:42:30\tsuccess\t30001\t90001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation manager Itinova\t2025-12-18 09:42:27\tsuccess\t30001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:16:59\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:16:32\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:14:32\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:14:25\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:00:43\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:00:40\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:34:28\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:34:00\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:33:28\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:33:21\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:26:15\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:26:13\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:51\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:49\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:31\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:28\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:14\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:12\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:24:58\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:24:55\tsuccess\t330001\t1\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:24:49\tsuccess\t330001\t1\n", + "stderr": "", + "execution_time_ms": 168 +} \ No newline at end of file diff --git a/.manus/db/db-query-1766057309842.json b/.manus/db/db-query-1766057309842.json new file mode 100644 index 0000000..97236b6 --- /dev/null +++ b/.manus/db/db-query-1766057309842.json @@ -0,0 +1,24 @@ +{ + "query": "SELECT \n emailDestinataire,\n sequenceId,\n COUNT(*) as nb_envois,\n MIN(dateEnvoi) as premier_envoi,\n MAX(dateEnvoi) as dernier_envoi\nFROM logsNotifications \nWHERE type = 'remerciement' AND statut = 'success'\nGROUP BY emailDestinataire, sequenceId\nHAVING COUNT(*) > 1\nORDER BY nb_envois DESC, dernier_envoi DESC;", + "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 \n emailDestinataire,\n sequenceId,\n COUNT(*) as nb_envois,\n MIN(dateEnvoi) as premier_envoi,\n MAX(dateEnvoi) as dernier_envoi\nFROM logsNotifications \nWHERE type = 'remerciement' AND statut = 'success'\nGROUP BY emailDestinataire, sequenceId\nHAVING COUNT(*) > 1\nORDER BY nb_envois DESC, dernier_envoi DESC;", + "rows": [ + { + "emailDestinataire": "opareige@gmail.com", + "sequenceId": "330001", + "nb_envois": "11", + "premier_envoi": "2025-12-15 20:24:49", + "dernier_envoi": "2025-12-15 21:16:32" + }, + { + "emailDestinataire": "o.pareige@itinova.org", + "sequenceId": "330001", + "nb_envois": "10", + "premier_envoi": "2025-12-15 20:24:58", + "dernier_envoi": "2025-12-15 21:16:59" + } + ], + "messages": [], + "stdout": "emailDestinataire\tsequenceId\tnb_envois\tpremier_envoi\tdernier_envoi\nopareige@gmail.com\t330001\t11\t2025-12-15 20:24:49\t2025-12-15 21:16:32\no.pareige@itinova.org\t330001\t10\t2025-12-15 20:24:58\t2025-12-15 21:16:59\n", + "stderr": "", + "execution_time_ms": 61 +} \ No newline at end of file diff --git a/.manus/db/db-query-1767770875139.json b/.manus/db/db-query-1767770875139.json new file mode 100644 index 0000000..3b7622f --- /dev/null +++ b/.manus/db/db-query-1767770875139.json @@ -0,0 +1,9 @@ +{ + "query": "CREATE TABLE IF NOT EXISTS `attestations` (\n `id` int AUTO_INCREMENT NOT NULL,\n `inscriptionId` int NOT NULL,\n `apprenantId` int NOT NULL,\n `sequenceId` int NOT NULL,\n `s3Key` varchar(500) NOT NULL,\n `pdfUrl` varchar(1000) NOT NULL,\n `dateGeneration` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `emailEnvoye` boolean NOT NULL DEFAULT false,\n `dateEnvoiEmail` timestamp NULL,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n CONSTRAINT `attestations_id` PRIMARY KEY(`id`)\n);\n\nCREATE TABLE IF NOT EXISTS `configAttestation` (\n `id` int AUTO_INCREMENT NOT NULL,\n `logoS3Key` varchar(500),\n `logoUrl` varchar(1000),\n `signatureS3Key` varchar(500),\n `signatureUrl` varchar(1000),\n `nomSignataire` varchar(255),\n `fonctionSignataire` varchar(255),\n `texteAttestation` text,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updatedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `configAttestation_id` PRIMARY KEY(`id`)\n);", + "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 CREATE TABLE IF NOT EXISTS `attestations` (\n `id` int AUTO_INCREMENT NOT NULL,\n `inscriptionId` int NOT NULL,\n `apprenantId` int NOT NULL,\n `sequenceId` int NOT NULL,\n `s3Key` varchar(500) NOT NULL,\n `pdfUrl` varchar(1000) NOT NULL,\n `dateGeneration` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `emailEnvoye` boolean NOT NULL DEFAULT false,\n `dateEnvoiEmail` timestamp NULL,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n CONSTRAINT `attestations_id` PRIMARY KEY(`id`)\n);\n\nCREATE TABLE IF NOT EXISTS `configAttestation` (\n `id` int AUTO_INCREMENT NOT NULL,\n `logoS3Key` varchar(500),\n `logoUrl` varchar(1000),\n `signatureS3Key` varchar(500),\n `signatureUrl` varchar(1000),\n `nomSignataire` varchar(255),\n `fonctionSignataire` varchar(255),\n `texteAttestation` text,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updatedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `configAttestation_id` PRIMARY KEY(`id`)\n);", + "rows": [], + "messages": [], + "stdout": "", + "stderr": "", + "execution_time_ms": 665 +} \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index 561de3f..944af09 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -29,6 +29,7 @@ import AdminRappelsHistorique from "./pages/admin/AdminRappelsHistorique"; import AdminRappelsStats from "./pages/admin/AdminRappelsStats"; import AdminFormateurs from "./pages/admin/AdminFormateurs"; import AdminNotifications from "./pages/admin/AdminNotifications"; +import AdminAttestations from "./pages/admin/AdminAttestations"; import QuestionnaireReponse from "./pages/QuestionnaireReponse"; import Inscription from "./pages/Inscription"; import Login from "./pages/Login"; @@ -53,6 +54,7 @@ function Router() { + diff --git a/client/src/components/DashboardLayout.tsx b/client/src/components/DashboardLayout.tsx index b9026e7..bee1721 100644 --- a/client/src/components/DashboardLayout.tsx +++ b/client/src/components/DashboardLayout.tsx @@ -57,6 +57,7 @@ const menuSections = [ { icon: Mail, label: "Templates d'emails", path: "/admin/email-templates" }, { icon: Settings, label: "Configuration SMTP", path: "/admin/email-config" }, { icon: Mail, label: "Notifications", path: "/admin/notifications" }, + { icon: FileText, label: "Attestations", path: "/admin/attestations" }, ] }, { diff --git a/client/src/pages/admin/AdminAttestations.tsx b/client/src/pages/admin/AdminAttestations.tsx new file mode 100644 index 0000000..8bdc118 --- /dev/null +++ b/client/src/pages/admin/AdminAttestations.tsx @@ -0,0 +1,218 @@ +import { useState, useEffect } from "react"; +import { trpc } from "@/lib/trpc"; +import DashboardLayout from "@/components/DashboardLayout"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { toast } from "sonner"; +import { Upload, FileText, Save } from "lucide-react"; + +export default function AdminAttestations() { + const { data: config, isLoading, refetch } = trpc.attestations.getConfig.useQuery(); + const updateConfig = trpc.attestations.updateConfig.useMutation(); + + const [formData, setFormData] = useState({ + nomSignataire: "", + fonctionSignataire: "", + texteAttestation: "", + }); + + // Mettre à jour le formulaire quand les données sont chargées + useEffect(() => { + if (config) { + setFormData({ + nomSignataire: config.nomSignataire || "", + fonctionSignataire: config.fonctionSignataire || "", + texteAttestation: config.texteAttestation || "", + }); + } + }, [config]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + try { + await updateConfig.mutateAsync(formData); + toast.success("Configuration enregistrée avec succès"); + refetch(); + } catch (error: any) { + toast.error("Erreur lors de l'enregistrement : " + error.message); + } + }; + + if (isLoading) { + return ( + +
Chargement...
+
+ ); + } + + return ( + +
+
+

Configuration des Attestations

+

+ Personnalisez le template des attestations de formation +

+
+ +
+ {/* Logo et signature */} +
+ + + + + Logo + + + Logo affiché en haut de l'attestation + + + + {config?.logoUrl ? ( +
+ Logo + +
+ ) : ( +
+ +

+ Aucun logo configuré +

+ +
+ )} +
+
+ + + + + + Signature + + + Signature affichée en bas de l'attestation + + + + {config?.signatureUrl ? ( +
+ Signature + +
+ ) : ( +
+ +

+ Aucune signature configurée +

+ +
+ )} +
+
+
+ + {/* Informations du signataire */} + + + Informations du signataire + + Nom et fonction affichés en bas de l'attestation + + + +
+
+ + + setFormData({ ...formData, nomSignataire: e.target.value }) + } + placeholder="Ex: Jean Dupont" + /> +
+
+ + + setFormData({ ...formData, fonctionSignataire: e.target.value }) + } + placeholder="Ex: Directeur de la formation" + /> +
+
+
+
+ + {/* Texte de l'attestation */} + + + Texte de l'attestation + + Personnalisez le texte principal de l'attestation. Variables disponibles : + + {"{"}{"{"} nomComplet {"}"}{"}"} + + + {"{"}{"{"} nomFormation {"}"}{"}"} + + + {"{"}{"{"} dateDebut {"}"}{"}"} + + + {"{"}{"{"} dateFin {"}"}{"}"} + + + + +