import jsPDF from 'jspdf'; import { WeeklySummary } from './weekly-summary'; export class PDFExportService { /** * Génère un PDF du résumé hebdomadaire */ static async exportWeeklySummary(summary: WeeklySummary): Promise { const pdf = new jsPDF(); const pageWidth = pdf.internal.pageSize.getWidth(); const margin = 20; let yPosition = margin + 10; // Header avec logo/titre pdf.setFontSize(24); pdf.setFont('helvetica', 'bold'); pdf.text('📊 RÉSUMÉ HEBDOMADAIRE', pageWidth / 2, yPosition, { align: 'center' }); yPosition += 15; pdf.setFontSize(12); pdf.setFont('helvetica', 'normal'); const dateRange = `Du ${this.formatDate(summary.period.start)} au ${this.formatDate(summary.period.end)}`; pdf.text(dateRange, pageWidth / 2, yPosition, { align: 'center' }); yPosition += 20; // Section métriques principales pdf.setFontSize(16); pdf.setFont('helvetica', 'bold'); pdf.text('🎯 MÉTRIQUES PRINCIPALES', margin, yPosition); yPosition += 15; pdf.setFontSize(11); pdf.setFont('helvetica', 'normal'); // Statistiques en deux colonnes const statsLeft = [ `Tâches complétées: ${summary.stats.completedTasks}/${summary.stats.totalTasks}`, `Taux de réussite tâches: ${summary.stats.taskCompletionRate.toFixed(1)}%`, `Daily items complétés: ${summary.stats.completedCheckboxes}/${summary.stats.totalCheckboxes}`, `Taux de réussite daily: ${summary.stats.checkboxCompletionRate.toFixed(1)}%` ]; const statsRight = [ `Vélocité actuelle: ${summary.velocity.currentWeekTasks} tâches`, `Semaine précédente: ${summary.velocity.previousWeekTasks} tâches`, `Moyenne 4 semaines: ${summary.velocity.fourWeekAverage.toFixed(1)}`, `Tendance: ${this.formatTrend(summary.velocity.weeklyTrend)}` ]; // Colonne gauche statsLeft.forEach((stat, index) => { pdf.text(`• ${stat}`, margin, yPosition + (index * 8)); }); // Colonne droite statsRight.forEach((stat, index) => { pdf.text(`• ${stat}`, pageWidth / 2 + 10, yPosition + (index * 8)); }); yPosition += 40; // Section jour le plus productif pdf.setFontSize(14); pdf.setFont('helvetica', 'bold'); pdf.text('⭐ INSIGHTS', margin, yPosition); yPosition += 12; pdf.setFontSize(11); pdf.setFont('helvetica', 'normal'); pdf.text(`• Jour le plus productif: ${summary.stats.mostProductiveDay}`, margin + 5, yPosition); yPosition += 8; // Tendance insight let trendInsight = ''; if (summary.velocity.weeklyTrend > 10) { trendInsight = `• Excellente progression ! Amélioration de ${summary.velocity.weeklyTrend.toFixed(1)}% cette semaine.`; } else if (summary.velocity.weeklyTrend < -10) { trendInsight = `• Baisse d'activité de ${Math.abs(summary.velocity.weeklyTrend).toFixed(1)}%. Temps de revoir le planning ?`; } else { trendInsight = '• Rythme stable maintenu cette semaine.'; } pdf.text(trendInsight, margin + 5, yPosition); yPosition += 12; // Section breakdown par jour pdf.setFontSize(14); pdf.setFont('helvetica', 'bold'); pdf.text('📅 RÉPARTITION PAR JOUR', margin, yPosition); yPosition += 12; pdf.setFontSize(10); pdf.setFont('helvetica', 'normal'); // Headers du tableau const colWidth = (pageWidth - 2 * margin) / 4; pdf.text('Jour', margin, yPosition); pdf.text('Tâches', margin + colWidth, yPosition); pdf.text('Daily Items', margin + 2 * colWidth, yPosition); pdf.text('Total', margin + 3 * colWidth, yPosition); yPosition += 8; // Ligne de séparation pdf.line(margin, yPosition - 2, pageWidth - margin, yPosition - 2); // Données du tableau summary.stats.dailyBreakdown.forEach((day) => { pdf.text(day.dayName, margin, yPosition); pdf.text(`${day.completedTasks}/${day.tasks}`, margin + colWidth, yPosition); pdf.text(`${day.completedCheckboxes}/${day.checkboxes}`, margin + 2 * colWidth, yPosition); pdf.text(`${day.completedTasks + day.completedCheckboxes}`, margin + 3 * colWidth, yPosition); yPosition += 6; }); yPosition += 10; // Section principales réalisations pdf.setFontSize(14); pdf.setFont('helvetica', 'bold'); pdf.text('✅ PRINCIPALES RÉALISATIONS', margin, yPosition); yPosition += 12; pdf.setFontSize(10); pdf.setFont('helvetica', 'normal'); const completedActivities = summary.activities .filter(a => a.completed) .slice(0, 8); // Top 8 réalisations if (completedActivities.length === 0) { pdf.text('Aucune réalisation complétée cette semaine.', margin + 5, yPosition); yPosition += 8; } else { completedActivities.forEach((activity) => { const truncatedTitle = activity.title.length > 60 ? activity.title.substring(0, 57) + '...' : activity.title; const icon = activity.type === 'task' ? '🎯' : '✅'; pdf.text(`${icon} ${truncatedTitle}`, margin + 5, yPosition); yPosition += 6; // Nouvelle page si nécessaire if (yPosition > pdf.internal.pageSize.getHeight() - 30) { pdf.addPage(); yPosition = margin + 10; } }); } // Footer const totalPages = pdf.internal.getNumberOfPages(); for (let i = 1; i <= totalPages; i++) { pdf.setPage(i); pdf.setFontSize(8); pdf.setFont('helvetica', 'normal'); const footerText = `TowerControl - Généré le ${new Date().toLocaleDateString('fr-FR')} - Page ${i}/${totalPages}`; pdf.text(footerText, pageWidth / 2, pdf.internal.pageSize.getHeight() - 10, { align: 'center' }); } // Télécharger le PDF const fileName = `weekly-summary-${this.formatDateForFilename(summary.period.start)}_${this.formatDateForFilename(summary.period.end)}.pdf`; pdf.save(fileName); } /** * Formate une date pour l'affichage */ private static formatDate(date: Date): string { return new Date(date).toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); } /** * Formate une date pour le nom de fichier */ private static formatDateForFilename(date: Date): string { return new Date(date).toISOString().split('T')[0]; } /** * Formate le pourcentage de tendance */ private static formatTrend(trend: number): string { const sign = trend > 0 ? '+' : ''; return `${sign}${trend.toFixed(1)}%`; } }