Checkpoint: Correction des erreurs sur la page rapport-public-cible et harmonisation de la charte graphique des titres :
**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
This commit is contained in:
@@ -50,23 +50,14 @@ export async function getTauxRemplissageByMonth() {
|
||||
.select({
|
||||
mois: sql<string>`DATE_FORMAT(${sequences.createdAt}, '%Y-%m')`,
|
||||
capaciteTotale: sql<number>`SUM(${sequences.capaciteMax})`,
|
||||
inscritsConfirmes: sql<number>`SUM(
|
||||
(SELECT COUNT(*)
|
||||
FROM ${inscriptions}
|
||||
WHERE ${inscriptions.sequenceId} = ${sequences.id}
|
||||
AND ${inscriptions.statut} = 'confirmee')
|
||||
)`,
|
||||
inscritsConfirmes: sql<number>`COUNT(CASE WHEN ${inscriptions.statut} = 'confirmee' THEN ${inscriptions.id} END)`,
|
||||
tauxRemplissage: sql<number>`ROUND(
|
||||
(SUM(
|
||||
(SELECT COUNT(*)
|
||||
FROM ${inscriptions}
|
||||
WHERE ${inscriptions.sequenceId} = ${sequences.id}
|
||||
AND ${inscriptions.statut} = 'confirmee')
|
||||
) / SUM(${sequences.capaciteMax})) * 100,
|
||||
(COUNT(CASE WHEN ${inscriptions.statut} = 'confirmee' THEN ${inscriptions.id} END) / SUM(${sequences.capaciteMax})) * 100,
|
||||
2
|
||||
)`,
|
||||
})
|
||||
.from(sequences)
|
||||
.leftJoin(inscriptions, eq(sequences.id, inscriptions.sequenceId))
|
||||
.groupBy(sql`DATE_FORMAT(${sequences.createdAt}, '%Y-%m')`)
|
||||
.orderBy(sql`DATE_FORMAT(${sequences.createdAt}, '%Y-%m')`);
|
||||
|
||||
|
||||
14
server/db.ts
14
server/db.ts
@@ -619,12 +619,14 @@ export async function createPasswordResetToken(email: string, token: string) {
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setHours(expiresAt.getHours() + 24); // Token valide 24h
|
||||
|
||||
const result = await db.insert(passwordResetTokens).values({
|
||||
email,
|
||||
token,
|
||||
expiresAt,
|
||||
used: false,
|
||||
});
|
||||
// Convertir la date au format MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
|
||||
const mysqlDate = expiresAt.toISOString().slice(0, 19).replace('T', ' ');
|
||||
|
||||
// Utiliser une requête SQL brute pour éviter les problèmes avec Drizzle
|
||||
const result = await db.execute(sql`
|
||||
INSERT INTO passwordResetTokens (email, token, expiresAt)
|
||||
VALUES (${email}, ${token}, ${mysqlDate})
|
||||
`);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -361,9 +361,13 @@ export const appRouter = router({
|
||||
update: adminProcedure.input(z.object({
|
||||
id: z.number(),
|
||||
nom: z.string().min(1).optional(),
|
||||
email: z.string().email().nullable().optional(),
|
||||
email: z.string().email().or(z.literal('')).nullable().optional(),
|
||||
})).mutation(async ({ input }) => {
|
||||
const { id, ...data } = input;
|
||||
// Convertir les chaînes vides en null
|
||||
if (data.email === '') {
|
||||
data.email = null;
|
||||
}
|
||||
await db.updateFormateur(id, data);
|
||||
return { success: true };
|
||||
}),
|
||||
@@ -1257,12 +1261,8 @@ export const appRouter = router({
|
||||
const crypto = await import('crypto');
|
||||
const token = crypto.randomBytes(32).toString('hex');
|
||||
|
||||
// Définir l'expiration à 24h
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setHours(expiresAt.getHours() + 24);
|
||||
|
||||
// Sauvegarder le token en base
|
||||
await db.createPasswordResetToken(user.id, token, expiresAt);
|
||||
// Sauvegarder le token en base (la fonction gère l'expiration automatiquement)
|
||||
await db.createPasswordResetToken(user.email, token);
|
||||
|
||||
// Générer le lien de réinitialisation
|
||||
// TODO: Remplacer par l'URL réelle de votre application en production
|
||||
|
||||
Reference in New Issue
Block a user