Checkpoint: Refonte majeure : transformation du système de sessions en séquences avec support de jusqu'à 4 dates par séquence. L'inscription à une séquence inscrit automatiquement l'apprenant à toutes les dates. Mise à jour complète du backend (schéma DB, procédures tRPC, emails, exports) et du frontend (AdminSequences, AdminSequenceInscrits, Inscription, Admin). Toutes les fonctionnalités existantes (filtres, statistiques, exports PDF/Excel, emails automatiques) sont préservées et adaptées au nouveau modèle.

This commit is contained in:
Manus Sandbox
2025-11-12 09:57:58 -05:00
parent ca9b1c3d37
commit 5c9b6637c4
20 changed files with 2445 additions and 1410 deletions

View File

@@ -16,23 +16,35 @@ interface InscriptionExport {
dateInscription: Date;
}
interface SessionInfo {
interface SequenceInfo {
formationNom: string;
sessionNom: string;
dateDebut: Date;
dateFin: Date;
sequenceNom: string;
dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>;
lieu: string;
inscriptions: InscriptionExport[];
}
interface ApprenantPresence {
nom: string;
prenom: string;
codeEtablissement: string;
fonction: string;
}
interface FeuillePresenceInfo {
formationNom: string;
sequenceNom: string;
dates: Array<{ dateDebut: Date; dateFin: Date; ordre: number }>;
lieu: string;
apprenants: ApprenantPresence[];
}
/**
* Génère un fichier Excel avec la liste des inscrits
*/
export function generateExcelExport(
sessionInfo: SessionInfo,
inscriptions: InscriptionExport[]
): Buffer {
export function generateExcelExport(sequenceInfo: SequenceInfo): Buffer {
// Préparer les données
const data = inscriptions.map(i => ({
const data = sequenceInfo.inscriptions.map(i => ({
'Nom': i.nom,
'Prénom': i.prenom,
'Email': i.email,
@@ -45,31 +57,42 @@ export function generateExcelExport(
// Créer le workbook
const wb = XLSX.utils.book_new();
// Créer la feuille avec les informations de session
// Créer la feuille avec les informations de séquence
const infoData = [
['Formation', sessionInfo.formationNom],
['Session', sessionInfo.sessionNom],
['Date de début', sessionInfo.dateDebut.toLocaleDateString('fr-FR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})],
['Date de fin', sessionInfo.dateFin.toLocaleDateString('fr-FR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})],
['Lieu', sessionInfo.lieu],
['Nombre d\'inscrits', inscriptions.length.toString()],
['Formation', sequenceInfo.formationNom],
['Séquence', sequenceInfo.sequenceNom],
['Lieu', sequenceInfo.lieu],
['Nombre de dates', sequenceInfo.dates.length.toString()],
[],
['DATES DE FORMATION'],
];
// Ajouter les dates
sequenceInfo.dates.forEach(date => {
infoData.push([
`Date ${date.ordre}`,
`Du ${date.dateDebut.toLocaleDateString('fr-FR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})} au ${date.dateFin.toLocaleDateString('fr-FR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}`
]);
});
infoData.push([]);
infoData.push(['Nombre d\'inscrits', sequenceInfo.inscriptions.length.toString()]);
infoData.push([]);
// Créer la feuille principale
const ws = XLSX.utils.aoa_to_sheet(infoData);
@@ -88,38 +111,45 @@ export function generateExcelExport(
/**
* Génère un fichier PDF avec la liste des inscrits
*/
export function generatePDFExport(
sessionInfo: SessionInfo,
inscriptions: InscriptionExport[]
): Buffer {
export function generatePDFExport(sequenceInfo: SequenceInfo): Buffer {
const doc = new jsPDF();
// Titre
doc.setFontSize(18);
doc.text('Liste des inscrits', 14, 20);
// Informations de session
// Informations de séquence
doc.setFontSize(10);
let yPos = 35;
doc.text(`Formation : ${sessionInfo.formationNom}`, 14, yPos);
doc.text(`Formation : ${sequenceInfo.formationNom}`, 14, yPos);
yPos += 6;
doc.text(`Session : ${sessionInfo.sessionNom}`, 14, yPos);
doc.text(`Séquence : ${sequenceInfo.sequenceNom}`, 14, yPos);
yPos += 6;
doc.text(`Date : ${sessionInfo.dateDebut.toLocaleDateString('fr-FR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}`, 14, yPos);
doc.text(`Lieu : ${sequenceInfo.lieu}`, 14, yPos);
yPos += 6;
doc.text(`Lieu : ${sessionInfo.lieu}`, 14, yPos);
yPos += 6;
doc.text(`Nombre d'inscrits : ${inscriptions.length}`, 14, yPos);
doc.text(`Nombre de dates : ${sequenceInfo.dates.length}`, 14, yPos);
yPos += 8;
// Dates de formation
doc.setFontSize(9);
sequenceInfo.dates.forEach(date => {
doc.text(`Date ${date.ordre} : ${date.dateDebut.toLocaleDateString('fr-FR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}`, 14, yPos);
yPos += 5;
});
yPos += 3;
doc.setFontSize(10);
doc.text(`Nombre d'inscrits : ${sequenceInfo.inscriptions.length}`, 14, yPos);
// Tableau des inscrits
const tableData = inscriptions.map(i => [
const tableData = sequenceInfo.inscriptions.map(i => [
i.nom,
i.prenom,
i.email,
@@ -142,37 +172,39 @@ export function generatePDFExport(
/**
* Génère une feuille de présence PDF
*/
export function generateFeuillePresence(
sessionInfo: SessionInfo,
inscriptions: InscriptionExport[]
): Buffer {
export function generateFeuillePresence(feuilleInfo: FeuillePresenceInfo): Buffer {
const doc = new jsPDF();
// Titre
doc.setFontSize(18);
doc.text('Feuille de présence', 14, 20);
// Informations de session
// Informations de séquence
doc.setFontSize(10);
let yPos = 35;
doc.text(`Formation : ${sessionInfo.formationNom}`, 14, yPos);
doc.text(`Formation : ${feuilleInfo.formationNom}`, 14, yPos);
yPos += 6;
doc.text(`Session : ${sessionInfo.sessionNom}`, 14, yPos);
doc.text(`Séquence : ${feuilleInfo.sequenceNom}`, 14, yPos);
yPos += 6;
doc.text(`Date : ${sessionInfo.dateDebut.toLocaleDateString('fr-FR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}`, 14, yPos);
yPos += 6;
doc.text(`Lieu : ${sessionInfo.lieu}`, 14, yPos);
doc.text(`Lieu : ${feuilleInfo.lieu}`, 14, yPos);
yPos += 8;
// Tableau de présence (uniquement les inscrits confirmés)
const inscritsConfirmes = inscriptions.filter(i => i.statut === 'confirmee');
const tableData = inscritsConfirmes.map(i => [
// Dates de formation
doc.setFontSize(9);
feuilleInfo.dates.forEach(date => {
doc.text(`Date ${date.ordre} : ${date.dateDebut.toLocaleDateString('fr-FR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}`, 14, yPos);
yPos += 5;
});
// Tableau de présence
const tableData = feuilleInfo.apprenants.map(i => [
i.nom,
i.prenom,
i.codeEtablissement,