Checkpoint: Système de questionnaires de satisfaction et d'évaluation - Phase 1 & 2 complétées

 Schéma de base de données créé :
- Table questionnaires (satisfaction, evaluation_pre, evaluation_post)
- Table questions (choix_multiple, echelle, texte_libre, oui_non)
- Table reponsesQuestionnaires
- Table reponsesQuestions
- Table envoisQuestionnaires

 Backend complet implémenté :
- Fichier questionnaireDb.ts avec toutes les fonctions CRUD
- Procédures tRPC pour questionnaires (list, getById, create, update, delete, getStats, getReponsesDetaillees)
- Procédures tRPC pour questions (list, create, update, delete)
- Procédures tRPC publiques pour réponses (getByToken, submit)
- Fonctions de statistiques (taux de réponse, réponses détaillées)

 Corrections :
- Erreurs TypeScript dans exportService.ts corrigées

🚧 À faire dans la prochaine session :
- Interfaces d'administration (AdminQuestionnaires, AdminQuestionnaireEdit)
- Page publique de réponse aux questionnaires
- Système d'envoi automatique post-formation
- Tableaux de bord d'analyse avec graphiques
- Exports Excel/PDF des feedbacks
This commit is contained in:
Manus Sandbox
2025-12-05 06:55:06 -05:00
parent 228c0e0056
commit 3521c55297
11 changed files with 2164 additions and 979 deletions

View File

@@ -288,3 +288,132 @@ export const rappels = mysqlTable("rappels", {
export type Rappel = typeof rappels.$inferSelect;
export type InsertRappel = typeof rappels.$inferInsert;
/**
* Table des questionnaires
* Stocke les questionnaires de satisfaction et d'évaluation des acquis
*/
export const questionnaires = mysqlTable("questionnaires", {
id: int("id").autoincrement().primaryKey(),
/** Nom du questionnaire */
nom: varchar("nom", { length: 255 }).notNull(),
/** Type de questionnaire (satisfaction, evaluation_pre, evaluation_post) */
type: mysqlEnum("type", ["satisfaction", "evaluation_pre", "evaluation_post"]).notNull(),
/** Description du questionnaire */
description: text("description"),
/** ID de la formation liée (null = tous les formations) */
formationId: int("formationId"),
/** Actif ou non */
actif: boolean("actif").default(true).notNull(),
/** Envoi automatique après la formation */
envoiAutomatique: boolean("envoiAutomatique").default(false).notNull(),
/** Délai en jours après la dernière date de la séquence (pour envoi automatique) */
delaiEnvoiJours: int("delaiEnvoiJours").default(1).notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
export type Questionnaire = typeof questionnaires.$inferSelect;
export type InsertQuestionnaire = typeof questionnaires.$inferInsert;
/**
* Table des questions
* Stocke les questions associées à chaque questionnaire
*/
export const questions = mysqlTable("questions", {
id: int("id").autoincrement().primaryKey(),
/** ID du questionnaire */
questionnaireId: int("questionnaireId").notNull(),
/** Ordre d'affichage */
ordre: int("ordre").notNull(),
/** Texte de la question */
texte: text("texte").notNull(),
/** Type de question (choix_multiple, echelle, texte_libre, oui_non) */
typeQuestion: mysqlEnum("typeQuestion", ["choix_multiple", "echelle", "texte_libre", "oui_non"]).notNull(),
/** Options pour choix multiple (JSON array) */
options: text("options"),
/** Valeur minimale pour échelle */
echelleMin: int("echelleMin"),
/** Valeur maximale pour échelle */
echelleMax: int("echelleMax"),
/** Label pour valeur minimale (ex: "Pas du tout satisfait") */
echelleLabelMin: varchar("echelleLabelMin", { length: 100 }),
/** Label pour valeur maximale (ex: "Très satisfait") */
echelleLabelMax: varchar("echelleLabelMax", { length: 100 }),
/** Question obligatoire */
obligatoire: boolean("obligatoire").default(false).notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
export type Question = typeof questions.$inferSelect;
export type InsertQuestion = typeof questions.$inferInsert;
/**
* Table des réponses aux questionnaires
* Stocke les réponses complètes d'un apprenant à un questionnaire
*/
export const reponsesQuestionnaires = mysqlTable("reponsesQuestionnaires", {
id: int("id").autoincrement().primaryKey(),
/** ID du questionnaire */
questionnaireId: int("questionnaireId").notNull(),
/** ID de l'apprenant */
apprenantId: int("apprenantId").notNull(),
/** ID de la séquence */
sequenceId: int("sequenceId").notNull(),
/** Date de réponse */
dateReponse: timestamp("dateReponse").defaultNow().notNull(),
/** Questionnaire complété */
complete: boolean("complete").default(false).notNull(),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
export type ReponseQuestionnaire = typeof reponsesQuestionnaires.$inferSelect;
export type InsertReponseQuestionnaire = typeof reponsesQuestionnaires.$inferInsert;
/**
* Table des réponses aux questions
* Stocke les réponses individuelles à chaque question
*/
export const reponsesQuestions = mysqlTable("reponsesQuestions", {
id: int("id").autoincrement().primaryKey(),
/** ID de la réponse au questionnaire */
reponseQuestionnaireId: int("reponseQuestionnaireId").notNull(),
/** ID de la question */
questionId: int("questionId").notNull(),
/** Réponse numérique (pour échelle, oui_non) */
reponseNumerique: int("reponseNumerique"),
/** Réponse texte (pour texte_libre, choix_multiple) */
reponseTexte: text("reponseTexte"),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
export type ReponseQuestion = typeof reponsesQuestions.$inferSelect;
export type InsertReponseQuestion = typeof reponsesQuestions.$inferInsert;
/**
* Table des envois de questionnaires
* Stocke l'historique des envois de questionnaires aux apprenants
*/
export const envoisQuestionnaires = mysqlTable("envoisQuestionnaires", {
id: int("id").autoincrement().primaryKey(),
/** ID du questionnaire */
questionnaireId: int("questionnaireId").notNull(),
/** ID de l'apprenant */
apprenantId: int("apprenantId").notNull(),
/** ID de la séquence */
sequenceId: int("sequenceId").notNull(),
/** Date d'envoi */
dateEnvoi: timestamp("dateEnvoi").defaultNow().notNull(),
/** Token unique pour accéder au questionnaire */
token: varchar("token", { length: 100 }).notNull().unique(),
/** Date de réponse (null si pas encore répondu) */
dateReponse: timestamp("dateReponse"),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
});
export type EnvoiQuestionnaire = typeof envoisQuestionnaires.$inferSelect;
export type InsertEnvoiQuestionnaire = typeof envoisQuestionnaires.$inferInsert;