**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
139 lines
5.1 KiB
TypeScript
139 lines
5.1 KiB
TypeScript
/**
|
|
* Requêtes analytiques pour le tableau de bord
|
|
*/
|
|
|
|
import { eq, sql, and, gte, lte, desc } from "drizzle-orm";
|
|
import { getDb } from "./db";
|
|
import { inscriptions, sequences, apprenants, datesFormation, formations } from "../drizzle/schema";
|
|
|
|
/**
|
|
* Récupère les statistiques d'inscriptions par mois
|
|
* @param startDate - Date de début de la période
|
|
* @param endDate - Date de fin de la période
|
|
*/
|
|
export async function getInscriptionsByMonth(startDate?: Date, endDate?: Date) {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const conditions = [];
|
|
if (startDate) {
|
|
conditions.push(gte(inscriptions.dateInscription, startDate));
|
|
}
|
|
if (endDate) {
|
|
conditions.push(lte(inscriptions.dateInscription, endDate));
|
|
}
|
|
|
|
const result = await db
|
|
.select({
|
|
mois: sql<string>`DATE_FORMAT(${inscriptions.dateInscription}, '%Y-%m')`,
|
|
total: sql<number>`COUNT(*)`,
|
|
confirmees: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'confirmee' THEN 1 ELSE 0 END)`,
|
|
listeAttente: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'liste_attente' THEN 1 ELSE 0 END)`,
|
|
annulees: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'annulee' THEN 1 ELSE 0 END)`,
|
|
})
|
|
.from(inscriptions)
|
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
.groupBy(sql`DATE_FORMAT(${inscriptions.dateInscription}, '%Y-%m')`)
|
|
.orderBy(sql`DATE_FORMAT(${inscriptions.dateInscription}, '%Y-%m')`);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Récupère le taux de remplissage des séquences par mois
|
|
*/
|
|
export async function getTauxRemplissageByMonth() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const result = await db
|
|
.select({
|
|
mois: sql<string>`DATE_FORMAT(${sequences.createdAt}, '%Y-%m')`,
|
|
capaciteTotale: sql<number>`SUM(${sequences.capaciteMax})`,
|
|
inscritsConfirmes: sql<number>`COUNT(CASE WHEN ${inscriptions.statut} = 'confirmee' THEN ${inscriptions.id} END)`,
|
|
tauxRemplissage: sql<number>`ROUND(
|
|
(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')`);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Récupère les statistiques de participation par établissement
|
|
*/
|
|
export async function getParticipationByEtablissement() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const result = await db
|
|
.select({
|
|
codeEtablissement: apprenants.codeEtablissement,
|
|
totalApprenants: sql<number>`COUNT(DISTINCT ${apprenants.id})`,
|
|
totalInscriptions: sql<number>`COUNT(${inscriptions.id})`,
|
|
inscriptionsConfirmees: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'confirmee' THEN 1 ELSE 0 END)`,
|
|
inscriptionsListeAttente: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'liste_attente' THEN 1 ELSE 0 END)`,
|
|
inscriptionsAnnulees: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'annulee' THEN 1 ELSE 0 END)`,
|
|
})
|
|
.from(apprenants)
|
|
.leftJoin(inscriptions, eq(apprenants.id, inscriptions.apprenantId))
|
|
.groupBy(apprenants.codeEtablissement)
|
|
.orderBy(desc(sql<number>`COUNT(${inscriptions.id})`));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Récupère les statistiques globales du tableau de bord
|
|
*/
|
|
export async function getGlobalStats() {
|
|
const db = await getDb();
|
|
if (!db) return null;
|
|
|
|
const [stats] = await db
|
|
.select({
|
|
totalFormations: sql<number>`(SELECT COUNT(*) FROM ${formations} WHERE ${formations.actif} = 1)`,
|
|
totalSequences: sql<number>`(SELECT COUNT(*) FROM ${sequences})`,
|
|
sequencesOuvertes: sql<number>`(SELECT COUNT(*) FROM ${sequences} WHERE ${sequences.statut} = 'ouverte')`,
|
|
totalApprenants: sql<number>`(SELECT COUNT(*) FROM ${apprenants})`,
|
|
totalInscriptions: sql<number>`(SELECT COUNT(*) FROM ${inscriptions})`,
|
|
inscriptionsConfirmees: sql<number>`(SELECT COUNT(*) FROM ${inscriptions} WHERE ${inscriptions.statut} = 'confirmee')`,
|
|
inscriptionsListeAttente: sql<number>`(SELECT COUNT(*) FROM ${inscriptions} WHERE ${inscriptions.statut} = 'liste_attente')`,
|
|
tauxRemplissageMoyen: sql<number>`ROUND(
|
|
(SELECT COUNT(*) FROM ${inscriptions} WHERE ${inscriptions.statut} = 'confirmee') /
|
|
(SELECT SUM(${sequences.capaciteMax}) FROM ${sequences}) * 100,
|
|
2
|
|
)`,
|
|
})
|
|
.from(sql`dual`);
|
|
|
|
return stats;
|
|
}
|
|
|
|
/**
|
|
* Récupère les statistiques de participation par fonction
|
|
*/
|
|
export async function getParticipationByFonction() {
|
|
const db = await getDb();
|
|
if (!db) return [];
|
|
|
|
const result = await db
|
|
.select({
|
|
fonction: apprenants.fonction,
|
|
totalApprenants: sql<number>`COUNT(DISTINCT ${apprenants.id})`,
|
|
totalInscriptions: sql<number>`COUNT(${inscriptions.id})`,
|
|
inscriptionsConfirmees: sql<number>`SUM(CASE WHEN ${inscriptions.statut} = 'confirmee' THEN 1 ELSE 0 END)`,
|
|
})
|
|
.from(apprenants)
|
|
.leftJoin(inscriptions, eq(apprenants.id, inscriptions.apprenantId))
|
|
.groupBy(apprenants.fonction)
|
|
.orderBy(desc(sql<number>`COUNT(${inscriptions.id})`));
|
|
|
|
return result;
|
|
}
|