Checkpoint: Trois améliorations majeures pour la gestion des séquences :
✅ **1. Indicateur visuel de remplissage** - Composant CapacityProgressBar avec barre de progression colorée - Affichage "X / Y" avec pourcentage - Couleurs automatiques : vert (<70%), orange (70-90%), rouge (>90%) - Intégré dans la colonne capacité du tableau des séquences ✅ **2. Vue calendrier mensuel** - Nouvelle page AdminCalendrier accessible depuis le menu - Calendrier mensuel avec navigation (mois précédent/suivant, aujourd'hui) - Affichage des séquences sur leurs dates respectives - Dialog de détails au clic sur une séquence - Vue complète avec toutes les informations (lieu, formateur, capacité, dates) ✅ **3. Système de rappels automatiques personnalisables** - Tables rappels et rappelsEnvoyes créées - Procédures tRPC complètes (list, create, update, delete) - Page AdminRappels avec interface de gestion - Configuration de rappels multiples (J-15, J-7, J-1, etc.) - Templates d'emails personnalisables avec variables dynamiques - Activation/désactivation des rappels - Infrastructure prête pour l'envoi automatique (nécessite service de planification externe) Ces améliorations facilitent grandement la planification et le suivi des formations.
This commit is contained in:
84
server/db.ts
84
server/db.ts
@@ -20,6 +20,7 @@ import {
|
||||
InsertFormation,
|
||||
InsertApprenant,
|
||||
InsertFormateur,
|
||||
InsertRappel,
|
||||
Sequence,
|
||||
DateFormation,
|
||||
Apprenant,
|
||||
@@ -693,3 +694,86 @@ export async function deleteFormateur(id: number) {
|
||||
|
||||
await db.update(formateurs).set({ actif: false }).where(eq(formateurs.id, id));
|
||||
}
|
||||
|
||||
// ===== RAPPELS =====
|
||||
|
||||
/**
|
||||
* Récupérer tous les rappels
|
||||
*/
|
||||
export async function getRappels() {
|
||||
const db = await getDb();
|
||||
if (!db) return [];
|
||||
|
||||
const { rappels } = await import("../drizzle/schema");
|
||||
const result = await db.select().from(rappels);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer un nouveau rappel
|
||||
*/
|
||||
export async function createRappel(data: InsertRappel) {
|
||||
const db = await getDb();
|
||||
if (!db) return null;
|
||||
|
||||
const { rappels } = await import("../drizzle/schema");
|
||||
const result = await db.insert(rappels).values(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mettre à jour un rappel
|
||||
*/
|
||||
export async function updateRappel(id: number, data: Partial<InsertRappel>) {
|
||||
const db = await getDb();
|
||||
if (!db) return;
|
||||
|
||||
const { rappels } = await import("../drizzle/schema");
|
||||
await db.update(rappels).set({
|
||||
...data,
|
||||
updatedAt: new Date(),
|
||||
}).where(eq(rappels.id, id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprimer un rappel
|
||||
*/
|
||||
export async function deleteRappel(id: number) {
|
||||
const db = await getDb();
|
||||
if (!db) return;
|
||||
|
||||
const { rappels } = await import("../drizzle/schema");
|
||||
await db.delete(rappels).where(eq(rappels.id, id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistrer l'envoi d'un rappel
|
||||
*/
|
||||
export async function enregistrerRappelEnvoye(rappelId: number, inscriptionId: number) {
|
||||
const db = await getDb();
|
||||
if (!db) return;
|
||||
|
||||
const { rappelsEnvoyes } = await import("../drizzle/schema");
|
||||
await db.insert(rappelsEnvoyes).values({
|
||||
rappelId,
|
||||
inscriptionId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si un rappel a déjà été envoyé
|
||||
*/
|
||||
export async function rappelDejaEnvoye(rappelId: number, inscriptionId: number) {
|
||||
const db = await getDb();
|
||||
if (!db) return false;
|
||||
|
||||
const { rappelsEnvoyes } = await import("../drizzle/schema");
|
||||
const result = await db.select().from(rappelsEnvoyes)
|
||||
.where(and(
|
||||
eq(rappelsEnvoyes.rappelId, rappelId),
|
||||
eq(rappelsEnvoyes.inscriptionId, inscriptionId)
|
||||
))
|
||||
.limit(1);
|
||||
|
||||
return result.length > 0;
|
||||
}
|
||||
|
||||
@@ -690,6 +690,40 @@ export const appRouter = router({
|
||||
}),
|
||||
}),
|
||||
|
||||
// ===== RAPPELS =====
|
||||
rappels: router({
|
||||
list: adminProcedure.query(async () => {
|
||||
return db.getRappels();
|
||||
}),
|
||||
|
||||
create: adminProcedure.input(z.object({
|
||||
nom: z.string().min(1),
|
||||
joursAvant: z.number().min(1),
|
||||
emailTemplate: z.string().min(1),
|
||||
actif: z.boolean().default(true),
|
||||
})).mutation(async ({ input }) => {
|
||||
await db.createRappel(input);
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
update: adminProcedure.input(z.object({
|
||||
id: z.number(),
|
||||
nom: z.string().min(1).optional(),
|
||||
joursAvant: z.number().min(1).optional(),
|
||||
emailTemplate: z.string().min(1).optional(),
|
||||
actif: z.boolean().optional(),
|
||||
})).mutation(async ({ input }) => {
|
||||
const { id, ...data } = input;
|
||||
await db.updateRappel(id, data);
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
delete: adminProcedure.input(z.object({ id: z.number() })).mutation(async ({ input }) => {
|
||||
await db.deleteRappel(input.id);
|
||||
return { success: true };
|
||||
}),
|
||||
}),
|
||||
|
||||
emailConfig: router({
|
||||
get: adminProcedure.query(async () => {
|
||||
return db.getActiveEmailConfig();
|
||||
|
||||
Reference in New Issue
Block a user