Checkpoint: Ajout de badges Actif/Inactif dans la liste des apprenants et création d'une page dédiée aux établissements avec statistiques de participation

This commit is contained in:
Manus Sandbox
2025-11-23 17:28:12 -05:00
parent 9a38fc9dd3
commit a0049436cc
8 changed files with 287 additions and 2 deletions

View File

@@ -280,7 +280,24 @@ export async function getApprenants() {
const db = await getDb();
if (!db) return [];
return await db.select().from(apprenants);
const apprenantsData = await db.select().from(apprenants);
// Pour chaque apprenant, récupérer ses inscriptions
const apprenantsWithInscriptions = await Promise.all(
apprenantsData.map(async (apprenant) => {
const inscriptionsData = await db
.select()
.from(inscriptions)
.where(eq(inscriptions.apprenantId, apprenant.id));
return {
...apprenant,
inscriptions: inscriptionsData,
};
})
);
return apprenantsWithInscriptions;
}
export async function getApprenantById(id: number): Promise<Apprenant | undefined> {

110
server/etablissementsDb.ts Normal file
View File

@@ -0,0 +1,110 @@
import { eq, sql } from "drizzle-orm";
import { getDb } from "./db";
import { apprenants, inscriptions } from "../drizzle/schema";
export async function getEtablissementsStats() {
const db = await getDb();
if (!db) return [];
// Récupérer tous les apprenants groupés par établissement
const apprenantsData = await db.select().from(apprenants);
// Grouper par code établissement
const etablissementsMap = new Map<string, {
codeEtablissement: string;
nombreApprenants: number;
apprenantsActifs: number;
apprenantIds: number[];
}>();
for (const apprenant of apprenantsData) {
const code = apprenant.codeEtablissement;
if (!etablissementsMap.has(code)) {
etablissementsMap.set(code, {
codeEtablissement: code,
nombreApprenants: 0,
apprenantsActifs: 0,
apprenantIds: [],
});
}
const etab = etablissementsMap.get(code)!;
etab.nombreApprenants++;
etab.apprenantIds.push(apprenant.id);
}
// Pour chaque établissement, compter les apprenants actifs
const etablissementsStats = await Promise.all(
Array.from(etablissementsMap.values()).map(async (etab) => {
// Compter le nombre d'apprenants ayant au moins une inscription
const apprenantsActifsCount = await Promise.all(
etab.apprenantIds.map(async (apprenantId) => {
const inscriptionsData = await db
.select()
.from(inscriptions)
.where(eq(inscriptions.apprenantId, apprenantId))
.limit(1);
return inscriptionsData.length > 0 ? 1 : 0;
})
);
const nombreActifs = apprenantsActifsCount.reduce((sum, count) => sum + count, 0);
const tauxParticipation = etab.nombreApprenants > 0
? Math.round((nombreActifs / etab.nombreApprenants) * 100)
: 0;
return {
codeEtablissement: etab.codeEtablissement,
nombreApprenants: etab.nombreApprenants,
apprenantsActifs: nombreActifs,
tauxParticipation,
};
})
);
// Trier par nombre d'apprenants décroissant
return etablissementsStats.sort((a, b) => b.nombreApprenants - a.nombreApprenants);
}
export async function getEtablissementDetail(codeEtablissement: string) {
const db = await getDb();
if (!db) return null;
// Récupérer tous les apprenants de cet établissement
const apprenantsData = await db
.select()
.from(apprenants)
.where(eq(apprenants.codeEtablissement, codeEtablissement));
// Pour chaque apprenant, récupérer ses inscriptions
const apprenantsWithInscriptions = await Promise.all(
apprenantsData.map(async (apprenant) => {
const inscriptionsData = await db
.select()
.from(inscriptions)
.where(eq(inscriptions.apprenantId, apprenant.id));
return {
...apprenant,
inscriptions: inscriptionsData,
};
})
);
const nombreApprenants = apprenantsData.length;
const apprenantsActifs = apprenantsWithInscriptions.filter(
(a) => a.inscriptions.length > 0
).length;
const tauxParticipation = nombreApprenants > 0
? Math.round((apprenantsActifs / nombreApprenants) * 100)
: 0;
return {
codeEtablissement,
nombreApprenants,
apprenantsActifs,
tauxParticipation,
apprenants: apprenantsWithInscriptions,
};
}

View File

@@ -890,6 +890,21 @@ export const appRouter = router({
};
}),
}),
// ===== ÉTABLISSEMENTS =====
etablissements: router({
list: adminProcedure.query(async () => {
const { getEtablissementsStats } = await import("./etablissementsDb");
return getEtablissementsStats();
}),
getDetail: adminProcedure
.input(z.object({ codeEtablissement: z.string() }))
.query(async ({ input }) => {
const { getEtablissementDetail } = await import("./etablissementsDb");
return getEtablissementDetail(input.codeEtablissement);
}),
}),
});
export type AppRouter = typeof appRouter;