## Problème identifié
Redéclaration de la variable `signatureS3Key` à la ligne 2645 du fichier `server/routers.ts` qui causait une erreur JavaScript et faisait planter la mutation tRPC.
## Solution implémentée
Changement de `const signatureS3Key = ...` en `signatureS3Key = ...` pour utiliser l'assignation au lieu de la redéclaration.
## Déploiement
✅ Serveur de production (98.71.216.234) - Compilé et redémarré
✅ Application online et fonctionnelle
La validation formateur devrait maintenant fonctionner correctement.
## Problème identifié
La recherche du formateur par email était sensible à la casse et aux espaces, ce qui empêchait de trouver le formateur même s'il existait dans la base de données.
## Solution implémentée
Modification de la logique de recherche dans `server/routers.ts` (lignes 2617-2623) :
- Normalisation de l'email saisi : `trim().toLowerCase()`
- Utilisation de SQL `LOWER(TRIM())` pour la comparaison en base de données
- La recherche est maintenant insensible à la casse et aux espaces
## Déploiement
✅ Serveur de production (98.71.216.234) - Bundle: index-C6kRdcg-.js
✅ Application redémarrée avec PM2
Le formateur peut maintenant être trouvé quelle que soit la casse utilisée lors de la saisie de l'email.
## Bug 1 : Erreur "inscription requise pour un apprenant"
**Problème :** Le paramètre typeUtilisateur: "formateur" n'était pas passé dans l'appel à la mutation validerPresence
**Solution :** Ajout du paramètre manquant dans EmargementFormateurScan.tsx (ligne 101)
## Bug 2 : Erreur "Aucune date de formation ne correspond à aujourd'hui"
**Problème :** Le backend cherchait automatiquement la date du jour avec CURDATE() au lieu d'utiliser le dateFormationId sélectionné par le formateur
**Solution :** Remplacement de la logique de recherche automatique par l'utilisation directe du dateFormationId fourni (routers.ts lignes 2631-2635)
## Déploiements effectués
✅ Serveur de production (98.71.216.234) - Bundle: index-C6kRdcg-.js
✅ Serveur de recette (108.143.67.56) - Bundle: index-CHmE9ro_.js
Les formateurs peuvent maintenant :
- Signer leur présence sans erreur d'inscription
- Sélectionner n'importe quelle date de la séquence (pas seulement aujourd'hui)
Modifications apportées :
- Backend : Ajout de la fonction getSignaturesFormateursBySequence dans presenceDb.ts
- Backend : Ajout de la procédure tRPC presences.listSignaturesFormateurs dans routers.ts
- Frontend : Ajout de l'encart visuel des signatures formateurs dans FormateurEmargement.tsx
- L'encart affiche les signatures avec date, période (matin/après-midi), heure et image de signature
- Rafraîchissement automatique toutes les 30 secondes
- Design distinct avec bordure bleue pour différencier des signatures apprenants
- Déployé sur recette (108.143.67.56) et production (98.71.216.234)
- Procédure generateQRCodeFormateur dans routers.ts
- Fonction validerPresenceFormateur dans presenceDb.ts
- Bouton "QR Code Formateur" dans FormateurEmargement.tsx
- Page EmargementFormateurScan.tsx pour le scan du QR code
- Route /emargement-formateur/{token}
- Gestion automatique de la date du jour
- Stockage de la signature sur S3
- Champ qrCodeFormateurToken dans le schéma sequences
Prêt pour déploiement et tests sur le VPS.
- Création de la page /emargement-formateur/:token avec formulaire simplifié
- Procédure backend presences.validerFormateur dédiée
- Deux boutons QR code dans l'interface formateur (apprenants + formateur)
- Génération automatique des URL distinctes pour chaque type d'utilisateur
- Déploiement réussi sur le VPS de production
- Ajout de la détection automatique formateur/apprenant dans EmargementScan.tsx
- Nouvelle procédure tRPC getSequenceByToken pour récupérer les infos de séquence
- Interface adaptée selon le type d'utilisateur (apprenant ou formateur)
- Déploiement réussi sur le VPS de production
Le système détecte maintenant automatiquement si l'email correspond à un apprenant ou un formateur, et affiche l'interface appropriée pour chaque cas.
Modifications principales :
- Ajout des champs typeUtilisateur et formateurId dans la table presences pour distinguer formateurs et apprenants
- Détection automatique du rôle (formateur/apprenant) via l'email lors du scan du QR code
- Interface de signature tactile réutilisée pour les formateurs
- Intégration des signatures tactiles formateur dans la génération PDF de la feuille de présence
- QR code dans l'espace formateur accessible pour l'émargement
- Tests unitaires validés avec succès
Le système permet maintenant aux formateurs d'émarger (matin et après-midi) via le même QR code que les apprenants, avec détection automatique du rôle et intégration des signatures dans le PDF.
🐛 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 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
- 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é
**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 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
- Correction du nom de table dans la procédure viderHistoriqueRappels (logsRappels → logsrappels)
- Le bouton de vidage fonctionne maintenant correctement sur le VPS de production
**Fonctionnalités ajoutées :**
1. **Procédure tRPC `viderHistoriqueRappels`** (server/routers.ts) :
- Mutation admin protégée pour supprimer tous les logs de la table logsRappels
- Retourne le nombre de logs supprimés
- Utilise une requête SQL directe pour optimiser les performances
2. **Bouton de vidage dans l'interface** (client/src/pages/admin/AdminRappelsHistorique.tsx) :
- Bouton rouge "Vider l'historique" avec icône de corbeille
- Dialogue de confirmation AlertDialog avec message d'avertissement
- Invalidation automatique de la requête historique après suppression
- Toast de succès avec le nombre de logs supprimés
3. **Correction d'une erreur TypeScript** :
- Ajout d'une parenthèse fermante manquante dans le router gestionAttestations
- Correction de l'erreur TS1135: Argument expression expected
**Fichiers modifiés :**
- server/routers.ts : Ajout de la procédure viderHistoriqueRappels
- client/src/pages/admin/AdminRappelsHistorique.tsx : Ajout du bouton avec dialogue
Déployé sur le VPS de production (https://formations.itinova.org)