Checkpoint: Correction du bug d'émargement formateur bloqué sur la saisie d'email

- 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.
This commit is contained in:
Manus
2026-02-02 14:18:46 -05:00
parent e8410230a9
commit f9983adada
81 changed files with 3734 additions and 27745 deletions

View File

@@ -1,110 +0,0 @@
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: number, 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,
};
}