🐛 Problème identifié :
- Le code utilisait `dateInfo.ordre` (1, 2, 3...) pour chercher les présences
- Mais les présences sont indexées par `dateFormationId` (ID réel de la date en base)
- Résultat : aucune correspondance trouvée, signatures non affichées
✅ Corrections apportées :
- Ajout du champ `id` dans l'interface `FeuillePresenceInfo` (exportService.ts)
- Modification de la logique de recherche : `dateInfo.ordre` → `dateInfo.id` (ligne 228)
- Ajout de l'ID dans le mapping des dates (routers.ts, ligne 1178)
- Déploiement sur VPS (98.71.216.234)
- Application redémarrée avec PM2
📋 Fonctionnement corrigé :
- Les dates incluent maintenant leur ID réel de la base de données
- La recherche des présences utilise cet ID pour la correspondance
- Les signatures sont chargées et affichées dans les bonnes colonnes du PDF
🔍 Test requis :
- Exporter une feuille de présence pour une séquence avec signatures
- Vérifier que les signatures apparaissent dans le PDF généré
✅ Modifications implémentées :
- Interface ApprenantPresence étendue pour inclure les présences avec signatures
- Fonction generateFeuillePresence modifiée pour charger et afficher les images de signatures
- Hook didDrawCell ajouté pour insérer les images dans les cellules du tableau PDF
- Procédure exportFeuillePresence modifiée pour récupérer toutes les présences avec signatures
- Signatures chargées depuis le système de fichiers local (uploads/signatures/)
- Gestion automatique des cas où la signature n'existe pas (cellule vide)
- Hauteur des cellules augmentée (minCellHeight: 15) pour accommoder les signatures
📋 Fonctionnement :
- Lors de l'export PDF, le système récupère toutes les présences de la séquence
- Pour chaque apprenant, les signatures sont associées à leurs dates respectives
- Les images PNG sont chargées depuis le disque et converties en base64
- Les signatures sont insérées dans les colonnes Matin/Après-midi correspondantes
- Si aucune signature n'existe, la cellule reste vide pour signature manuscrite
🔧 Technique :
- Utilisation de jsPDF avec jspdf-autotable
- Chargement des images avec fs.readFileSync
- Conversion en data URL base64 pour insertion dans le PDF
- Gestion des erreurs avec console.warn si fichier manquant
✅ Fonctionnalités implémentées :
- Signature tactile obligatoire lors de l'émargement via QR code sur smartphone
- Stockage des signatures en local dans uploads/signatures/[formation-id]-[sequence-id]/
- Affichage des informations de validation dans l'interface formateur : "Prénom Nom a validé(e) le [date et heure]"
- Icône cliquable pour visualiser la signature en grand format dans une modale
- Correction du bug Drizzle ORM avec sql.raw() pour les colonnes de signature
✅ Corrections techniques :
- Retrait des logs de debug de routers.ts
- Utilisation de sql.raw() au lieu de l'accès direct aux colonnes dans presenceDb.ts
- Gestion robuste des signatures nulles dans l'affichage
✅ Déploiement :
- Système testé et fonctionnel sur le VPS (formations.itinova.org)
- Signatures stockées localement (S3 abandonné pour problème DNS avec forge.manus.im)
📝 Améliorations futures possibles :
- Intégration des signatures dans les exports PDF des feuilles de présence
- Configuration Nginx pour servir /uploads/signatures/ avec les bons headers
- Validation de signature (nombre minimum de traits, taille minimale)
- Historique/audit trail des signatures
- Ajout des champs signatureUrl, signatureS3Key et dateSigned dans getPresencesBySequence
- L'icône de signature s'affiche maintenant correctement quand une signature existe
- Ajout des champs signatureUrl, signatureS3Key et dateSigned dans getPresencesByInscription
- Affichage du nom du signataire, date/heure de validation à côté de chaque puce matin/après-midi
- Icône cliquable pour visualiser la signature en grand format
- Modale Dialog pour afficher la signature manuscrite
- Fonction getPresenceInfo pour récupérer les détails de présence
- Interface moderne avec icône de stylo SVG pour les signatures
- Remplacement de storagePut par fs.writeFileSync dans routers.ts
- Structure de dossiers : uploads/signatures/[formation-id]-[sequence-id]/
- Noms de fichiers uniques avec crypto.randomBytes
- URLs publiques relatives : /uploads/signatures/[formation-id]-[sequence-id]/[fichier].png
- Correction de l'erreur "fetch failed" causée par l'inaccessibilité de forge.manus.im sur le VPS
- Intégration du composant SignaturePad dans EmargementScan.tsx
- Ajout des champs de signature dans la table presences (signatureUrl, signatureS3Key, dateSigned)
- Modification de la procédure presences.valider pour accepter et uploader la signature en S3
- Stockage automatique de l'URL et de la clé S3 dans la base de données
- Correction des types nullables dans exportService.ts (codeEtablissement, fonction)
- Les apprenants doivent maintenant signer avec le doigt sur l'écran avant de valider leur présence
- Modification du backend pour inclure la première date de chaque séquence
- Tri des séquences par ordre chronologique (première date en premier)
- Les séquences sont maintenant affichées dans l'ordre temporel pour une meilleure organisation
- Remplacement de adminProcedure par protectedProcedure pour getApprenantsWithStatus
- Les formateurs peuvent maintenant voir la liste des apprenants et gérer les attestations
- Ajout du champ 'periode' dans la table presences
- Modification des procédures backend pour gérer les deux périodes
- Mise à jour de l'interface EmargementScan avec choix de période
- Mise à jour de l'interface FormateurEmargement avec validation séparée matin/après-midi
- La feuille de présence PDF était déjà configurée avec deux colonnes
- Ajout d'un badge de compteur indiquant le nombre de rappels actifs (ex: "1")
- Désactivation visuelle du bouton pour les séquences sans rappels actifs (icône grisée)
- Tooltip informatif adapté selon le contexte (nombre de rappels ou message d'erreur)
- Amélioration de l'expérience utilisateur avec feedback visuel clair
- Modification du backend pour envoyer un email par inscrit confirmé (tous à l'adresse de test)
- Ajout d'un indicateur de progression moderne avec animation (spinner + effet ping)
- Message de succès affichant le nombre d'emails envoyés
- Validation avec séquence "test 3" : 3 emails envoyés correctement (4ème inscrit sans email ignoré)
- Synchronisation complète de la structure de la table logsrappels avec le VPS
- 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é
**Problème identifié :**
- Incohérence entre le schéma Drizzle (`drizzle/schema.ts`) et le code (`notificationLogsDb.ts`)
- Le schéma utilisait des noms de colonnes et des types d'enum différents de ceux utilisés dans le code
- Cela causait l'erreur "Cannot convert undefined or null to object" lors de la récupération des données
**Corrections appliquées :**
1. **Mise à jour du schéma Drizzle (drizzle/schema.ts) :**
- Renommé `destinataire` en `emailDestinataire` pour correspondre au code
- Ajouté la colonne `sujet` (VARCHAR 500)
- Ajouté la colonne `formateurId` (INT NULL)
- Ajouté la colonne `metadata` (TEXT NULL)
- Mis à jour l'enum `type` avec les bonnes valeurs : `remerciement`, `notification_formateur_inscription`, `notification_formateur_annulation`, `alerte_capacite`, `notification_liste_attente`
- Mis à jour l'enum `statut` avec les bonnes valeurs : `success`, `failed`
2. **Amélioration de la gestion des valeurs null (notificationLogsDb.ts) :**
- Ajout de vérifications null-safe dans `getNotificationStats()` pour éviter les erreurs lors du calcul du taux de succès
- Ajout de valeurs par défaut pour `parType` et `evolutionParJour` (tableaux vides si undefined)
- Ajout d'un mapping des logs pour s'assurer que tous les champs sont bien définis (null au lieu d'undefined)
**Tests réussis :**
- ✅ Onglet "Statistiques" : affiche correctement 24 envois, 100% de succès, répartition par type et évolution par jour
- ✅ Onglet "Historique" : affiche correctement les 24 notifications avec toutes les colonnes (Date, Type, Destinataire, Sujet, Formation/Séquence, Statut)
- ✅ Pagination fonctionnelle (Page 1 sur 2)
- ✅ Filtres disponibles (Type, Statut, Email, Date début, Date fin)
**Résultat :**
✅ L'erreur "Cannot convert undefined or null to object" est complètement résolue
✅ La page notifications fonctionne correctement avec toutes ses fonctionnalités
**1. Correction erreur Fail2Ban (fail2banDb.ts) :**
- Ajout de vérifications pour détecter si Fail2Ban est installé (commande `which fail2ban-client`)
- Retour de valeurs par défaut (0, tableaux vides) au lieu de throw d'erreur dans l'environnement sandbox
- Corrections appliquées aux 3 fonctions : getFail2BanStatus(), getBanHistory(), countRecentBans()
- La page /admin/fail2ban s'affiche maintenant correctement sans erreur dans la sandbox
**2. Correction erreur SQL DATE_FORMAT (analyticsDb.ts) :**
- Ajout d'un filtre `IS NOT NULL` sur la colonne dateInscription dans getInscriptionsByMonth()
- Exclusion des inscriptions avec dateInscription NULL qui causaient des erreurs SQL
- Le graphique "Évolution des inscriptions par mois" affiche maintenant "Aucune donnée disponible" au lieu d'une erreur
**Tests réussis :**
- ✅ Page Fail2Ban : affiche correctement les statistiques à 0 et les messages appropriés
- ✅ Tableau de bord analytique : tous les graphiques fonctionnent sans erreur SQL
- ✅ Les autres pages (rapport-public-cible, suivi questionnaires) continuent de fonctionner correctement
**Résultat :**
✅ Toutes les erreurs signalées sont corrigées
✅ L'application fonctionne correctement dans l'environnement sandbox
✅ Sur le VPS de production, Fail2Ban affichera les vraies valeurs
**Corrections d'erreurs :**
1. **Erreurs SQL DATE_FORMAT corrigées** (analyticsDb.ts) :
- Remplacement des sous-requêtes corrélées par des JOIN dans getTauxRemplissageByMonth()
- Utilisation de COUNT(CASE WHEN...) au lieu de SUM(SELECT COUNT(*))
- Ajout de leftJoin pour éviter les erreurs de sous-requêtes
2. **Erreur React "Cannot read properties of null" corrigée** (AdminRapportPublicCible.tsx) :
- Ajout de l'opérateur null-safe `?.` pour accéder à `insc.apprenant?.fonction`
- Vérification explicite si la fonction est null/undefined
- Gestion du cas "public cible = tous" qui correspond toujours
3. **Tests réussis** :
- Page rapport-public-cible : affiche correctement les statistiques et recommandations
- Tableau de bord analytique : graphiques et statistiques fonctionnels
- Page suivi questionnaires : affiche correctement les données
**Harmonisation charte graphique :**
- Application du style bleu clair (#6B9FE8) avec ombre portée à tous les titres des 32 pages
- Création de la classe CSS `.page-title` réutilisable dans index.css
- Cohérence visuelle sur toute l'application
**Résultat :**
✅ Toutes les erreurs SQL et React sont corrigées
✅ Les pages fonctionnent correctement sans erreur
✅ Charte graphique harmonisée sur toutes les pages
**Fonctionnalité ajoutée :**
- Bouton "Ajouter un inscrit" avec icône UserPlus dans le header de la liste des inscrits
- Dialogue de sélection avec liste déroulante des apprenants disponibles (filtrés pour exclure ceux déjà inscrits)
- Sélection du statut (Confirmé / Liste d'attente)
- Mutation backend `inscriptions.create` pour l'ajout manuel par l'admin (sans vérifications de blocage/capacité)
**Fichiers modifiés :**
- client/src/pages/AdminSequenceInscrits.tsx : Ajout du bouton et du dialogue
- client/src/components/AddInscritForm.tsx : Nouveau composant de formulaire
- server/routers.ts : Ajout de la mutation inscriptions.create
**Résultat :**
Les administrateurs peuvent maintenant ajouter manuellement des apprenants à une séquence depuis l'interface des inscrits.
**Problème résolu :**
Erreur "Invalid option: expected one of 'rappel1', 'rappel2', 'rappel3', 'rappel4', 'rappel5', 'rappel6'" lors de la création d'un rappel.
**Modifications apportées :**
- Correction des enums dans `sendGroupEmail` et `emailPreview` (lignes 926 et 986)
- Remplacement de ['teaser', 'rappel', 'rappel_j1'] par ['teaser', 'rappel1', 'rappel2', 'rappel3', 'rappel4', 'rappel5', 'rappel6']
- Harmonisation complète avec le renommage précédent des types de rappels
**Résultat :**
La création de rappels fonctionne maintenant correctement sans erreur de validation.
**Modifications du schéma Drizzle :**
- logsrappels.type : enum('rappel1','rappel2','rappel3','rappel4','rappel5','rappel6')
- emailTemplates.type : enum('inscription','teaser','rappel1','rappel2','rappel3','rappel4','rappel5','rappel6','reset_password')
- rappels.templateType : enum('rappel1','rappel2','rappel3','rappel4','rappel5','rappel6')
**Modifications du code :**
- server/routers.ts : mise à jour des enums Zod
- server/db.ts : renommage des templates par défaut (rappel → rappel1, rappelJ1 → rappel2)
- server/rappelRetry.ts : correction de la condition rappelJ1 → rappel2
- drizzle/schema.ts : mise à jour de tous les enums
**Migration de la base de données MySQL :**
- Script SQL créé et exécuté avec succès sur le VPS
- Migration en 3 étapes : élargir enum → mettre à jour données → restreindre enum
- Toutes les données existantes migrées correctement
**Résultat :**
- ✅ Plus d'erreur "Unknown column 'type'" dans les logs
- ✅ Cohérence parfaite entre l'interface (Rappel 1, Rappel 2) et le backend (rappel1, rappel2)
- ✅ Système de rappels fonctionnel
- ✅ Déployé et testé sur le VPS de production (https://formations.itinova.org)
1. Ajout de la vérification de l'heure configurée avant l'envoi (tolérance de 20 minutes)
2. Réduction de l'intervalle du scheduler de 1 heure à 15 minutes pour plus de précision
3. Ajout de logs détaillés pour le débogage
4. Les rappels sont maintenant envoyés à l'heure exacte configurée (ex: 13h51)
1. Ajout de l'enregistrement dans l'historique lors de l'envoi d'attestations (backend + frontend)
2. Correction du bouton Renvoyer inactif (selectedSequenceId -> apprenant.sequence.id)
3. Gestion des erreurs avec enregistrement dans l'historique en cas d'échec
4. Support des chemins locaux et URLs pour les fichiers PDF