- Renommage questionnaire.nom → questionnaire.titre dans QuestionnaireReponse.tsx, AdminQuestionnaireEdit.tsx, questionnaireScheduler.ts, questionnaireExport.ts - Correction des requêtes SQL dans questionnaireDb.ts (suppression de la condition complete inexistante) - Ajout des colonnes valeurMin et valeurMax dans la table questions - Renommage de la colonne nom en titre dans la table questionnaires
175 lines
5.3 KiB
TypeScript
175 lines
5.3 KiB
TypeScript
import ExcelJS from 'exceljs';
|
|
import { jsPDF } from 'jspdf';
|
|
import { getQuestionnaireById, getQuestionsByQuestionnaireId, getQuestionnaireStats, getReponsesDetailleesQuestionnaire } from './questionnaireDb';
|
|
import { getDb } from './db';
|
|
import { sql } from 'drizzle-orm';
|
|
|
|
/**
|
|
* Génère un fichier Excel avec les statistiques du questionnaire
|
|
*/
|
|
export async function exportQuestionnaireToExcel(questionnaireId: number): Promise<Buffer> {
|
|
const questionnaire = await getQuestionnaireById(questionnaireId);
|
|
const stats = await getQuestionnaireStats(questionnaireId);
|
|
const questionsData = await getQuestionsByQuestionnaireId(questionnaireId);
|
|
const reponsesData = await getReponsesDetailleesQuestionnaire(questionnaireId);
|
|
|
|
if (!questionnaire || !stats) {
|
|
throw new Error('Questionnaire introuvable');
|
|
}
|
|
|
|
const workbook = new ExcelJS.Workbook();
|
|
|
|
// Feuille 1: Statistiques globales
|
|
const statsSheet = workbook.addWorksheet('Statistiques');
|
|
|
|
statsSheet.addRow(['Questionnaire', questionnaire.titre]);
|
|
statsSheet.addRow(['Type', questionnaire.type]);
|
|
statsSheet.addRow(['']);
|
|
statsSheet.addRow(['Nombre d\'envois', stats.totalEnvois]);
|
|
statsSheet.addRow(['Nombre de réponses', stats.totalReponses]);
|
|
statsSheet.addRow(['Taux de réponse', `${stats.tauxReponse.toFixed(1)}%`]);
|
|
statsSheet.addRow(['']);
|
|
|
|
// Style pour l'en-tête
|
|
statsSheet.getColumn(1).width = 30;
|
|
statsSheet.getColumn(2).width = 40;
|
|
statsSheet.getRow(1).font = { bold: true, size: 14 };
|
|
|
|
// Feuille 2: Statistiques par question
|
|
const questionsSheet = workbook.addWorksheet('Par question');
|
|
|
|
questionsSheet.addRow(['Question', 'Type', 'Ordre']);
|
|
questionsSheet.getRow(1).font = { bold: true };
|
|
questionsSheet.getRow(1).fill = {
|
|
type: 'pattern',
|
|
pattern: 'solid',
|
|
fgColor: { argb: 'FFE0E0E0' }
|
|
};
|
|
|
|
questionsData.forEach((q: any) => {
|
|
questionsSheet.addRow([
|
|
q.texte,
|
|
q.typeQuestion,
|
|
q.ordre
|
|
]);
|
|
});
|
|
|
|
questionsSheet.getColumn(1).width = 60;
|
|
questionsSheet.getColumn(2).width = 20;
|
|
questionsSheet.getColumn(3).width = 10;
|
|
|
|
// Feuille 3: Réponses brutes
|
|
const reponsesSheet = workbook.addWorksheet('Réponses brutes');
|
|
|
|
reponsesSheet.addRow(['Date réponse', 'Apprenant ID', 'Séquence ID', 'Question ID', 'Réponse numérique', 'Réponse texte']);
|
|
reponsesSheet.getRow(1).font = { bold: true };
|
|
reponsesSheet.getRow(1).fill = {
|
|
type: 'pattern',
|
|
pattern: 'solid',
|
|
fgColor: { argb: 'FFE0E0E0' }
|
|
};
|
|
|
|
reponsesData.forEach((r: any) => {
|
|
reponsesSheet.addRow([
|
|
r.dateReponse ? new Date(r.dateReponse).toLocaleDateString('fr-FR') : '',
|
|
r.apprenantId,
|
|
r.sequenceId,
|
|
r.questionId,
|
|
r.reponseNumerique || '',
|
|
r.reponseTexte || ''
|
|
]);
|
|
});
|
|
|
|
reponsesSheet.getColumn(1).width = 15;
|
|
reponsesSheet.getColumn(2).width = 15;
|
|
reponsesSheet.getColumn(3).width = 15;
|
|
reponsesSheet.getColumn(4).width = 15;
|
|
reponsesSheet.getColumn(5).width = 20;
|
|
reponsesSheet.getColumn(6).width = 50;
|
|
|
|
// Générer le buffer
|
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
return Buffer.from(buffer);
|
|
}
|
|
|
|
/**
|
|
* Génère un fichier PDF avec les statistiques du questionnaire
|
|
*/
|
|
export async function exportQuestionnaireToPDF(questionnaireId: number): Promise<Buffer> {
|
|
const questionnaire = await getQuestionnaireById(questionnaireId);
|
|
const stats = await getQuestionnaireStats(questionnaireId);
|
|
const questionsData = await getQuestionsByQuestionnaireId(questionnaireId);
|
|
|
|
if (!questionnaire || !stats) {
|
|
throw new Error('Questionnaire introuvable');
|
|
}
|
|
|
|
const doc = new jsPDF();
|
|
let yPos = 20;
|
|
|
|
// Titre
|
|
doc.setFontSize(18);
|
|
doc.setFont('helvetica', 'bold');
|
|
doc.text(questionnaire.titre, 20, yPos);
|
|
yPos += 10;
|
|
|
|
// Type
|
|
doc.setFontSize(12);
|
|
doc.setFont('helvetica', 'normal');
|
|
doc.text(`Type: ${questionnaire.type}`, 20, yPos);
|
|
yPos += 15;
|
|
|
|
// Statistiques globales
|
|
doc.setFont('helvetica', 'bold');
|
|
doc.text('Statistiques globales', 20, yPos);
|
|
yPos += 8;
|
|
|
|
doc.setFont('helvetica', 'normal');
|
|
doc.text(`Nombre d'envois: ${stats.totalEnvois}`, 20, yPos);
|
|
yPos += 6;
|
|
doc.text(`Nombre de réponses: ${stats.totalReponses}`, 20, yPos);
|
|
yPos += 6;
|
|
doc.text(`Taux de réponse: ${stats.tauxReponse.toFixed(1)}%`, 20, yPos);
|
|
yPos += 15;
|
|
|
|
// Liste des questions
|
|
doc.setFont('helvetica', 'bold');
|
|
doc.text('Questions du questionnaire', 20, yPos);
|
|
yPos += 10;
|
|
|
|
questionsData.forEach((q: any, index: number) => {
|
|
// Vérifier si on doit ajouter une nouvelle page
|
|
if (yPos > 250) {
|
|
doc.addPage();
|
|
yPos = 20;
|
|
}
|
|
|
|
doc.setFont('helvetica', 'bold');
|
|
doc.setFontSize(11);
|
|
const questionText = `${index + 1}. ${q.texte}`;
|
|
const lines = doc.splitTextToSize(questionText, 170);
|
|
doc.text(lines, 20, yPos);
|
|
yPos += lines.length * 6;
|
|
|
|
doc.setFont('helvetica', 'normal');
|
|
doc.setFontSize(10);
|
|
doc.text(`Type: ${q.typeQuestion}`, 25, yPos);
|
|
yPos += 8;
|
|
});
|
|
|
|
// Pied de page
|
|
const pageCount = doc.getNumberOfPages();
|
|
for (let i = 1; i <= pageCount; i++) {
|
|
doc.setPage(i);
|
|
doc.setFontSize(8);
|
|
doc.setFont('helvetica', 'normal');
|
|
doc.text(
|
|
`Page ${i} sur ${pageCount} - Généré le ${new Date().toLocaleDateString('fr-FR')}`,
|
|
20,
|
|
290
|
|
);
|
|
}
|
|
|
|
return Buffer.from(doc.output('arraybuffer'));
|
|
}
|