Checkpoint: Création d'un tableau de bord analytique avancé avec graphiques interactifs (Recharts). Affiche les statistiques globales, l'évolution temporelle des inscriptions par mois, le taux de remplissage des séquences, le top 10 des établissements participants et la répartition par fonction. Accessible via le menu "Tableau de bord analytique".
This commit is contained in:
147
server/analyticsDb.ts
Normal file
147
server/analyticsDb.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 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>`SUM(
|
||||
(SELECT COUNT(*)
|
||||
FROM ${inscriptions}
|
||||
WHERE ${inscriptions.sequenceId} = ${sequences.id}
|
||||
AND ${inscriptions.statut} = 'confirmee')
|
||||
)`,
|
||||
tauxRemplissage: sql<number>`ROUND(
|
||||
(SUM(
|
||||
(SELECT COUNT(*)
|
||||
FROM ${inscriptions}
|
||||
WHERE ${inscriptions.sequenceId} = ${sequences.id}
|
||||
AND ${inscriptions.statut} = 'confirmee')
|
||||
) / SUM(${sequences.capaciteMax})) * 100,
|
||||
2
|
||||
)`,
|
||||
})
|
||||
.from(sequences)
|
||||
.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;
|
||||
}
|
||||
Reference in New Issue
Block a user