feat: add weekly summary link to Header component
- Introduced a new navigation link for the weekly summary in the Header component, enhancing user access to summary insights.
This commit is contained in:
260
services/weekly-summary.ts
Normal file
260
services/weekly-summary.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
import { prisma } from './database';
|
||||
import { Task, TaskStatus, TaskPriority, TaskSource } from '@/lib/types';
|
||||
|
||||
export interface DailyItem {
|
||||
id: string;
|
||||
text: string;
|
||||
isChecked: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
date: Date;
|
||||
}
|
||||
|
||||
export interface WeeklyStats {
|
||||
totalCheckboxes: number;
|
||||
completedCheckboxes: number;
|
||||
totalTasks: number;
|
||||
completedTasks: number;
|
||||
checkboxCompletionRate: number;
|
||||
taskCompletionRate: number;
|
||||
mostProductiveDay: string;
|
||||
dailyBreakdown: Array<{
|
||||
date: string;
|
||||
dayName: string;
|
||||
checkboxes: number;
|
||||
completedCheckboxes: number;
|
||||
tasks: number;
|
||||
completedTasks: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface WeeklyActivity {
|
||||
id: string;
|
||||
type: 'checkbox' | 'task';
|
||||
title: string;
|
||||
completed: boolean;
|
||||
completedAt?: Date;
|
||||
createdAt: Date;
|
||||
date: string;
|
||||
dayName: string;
|
||||
}
|
||||
|
||||
export interface WeeklySummary {
|
||||
stats: WeeklyStats;
|
||||
activities: WeeklyActivity[];
|
||||
period: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
};
|
||||
}
|
||||
|
||||
export class WeeklySummaryService {
|
||||
/**
|
||||
* Récupère le résumé complet de la semaine écoulée
|
||||
*/
|
||||
static async getWeeklySummary(): Promise<WeeklySummary> {
|
||||
const now = new Date();
|
||||
const startOfWeek = new Date(now);
|
||||
startOfWeek.setDate(now.getDate() - 7);
|
||||
startOfWeek.setHours(0, 0, 0, 0);
|
||||
|
||||
const endOfWeek = new Date(now);
|
||||
endOfWeek.setHours(23, 59, 59, 999);
|
||||
|
||||
console.log(`📊 Génération du résumé hebdomadaire du ${startOfWeek.toLocaleDateString()} au ${endOfWeek.toLocaleDateString()}`);
|
||||
|
||||
const [checkboxes, tasks] = await Promise.all([
|
||||
this.getWeeklyCheckboxes(startOfWeek, endOfWeek),
|
||||
this.getWeeklyTasks(startOfWeek, endOfWeek)
|
||||
]);
|
||||
|
||||
const stats = this.calculateStats(checkboxes, tasks, startOfWeek, endOfWeek);
|
||||
const activities = this.mergeActivities(checkboxes, tasks);
|
||||
|
||||
return {
|
||||
stats,
|
||||
activities,
|
||||
period: {
|
||||
start: startOfWeek,
|
||||
end: endOfWeek
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les checkboxes des 7 derniers jours
|
||||
*/
|
||||
private static async getWeeklyCheckboxes(startDate: Date, endDate: Date): Promise<DailyItem[]> {
|
||||
const items = await prisma.dailyCheckbox.findMany({
|
||||
where: {
|
||||
date: {
|
||||
gte: startDate,
|
||||
lte: endDate
|
||||
}
|
||||
},
|
||||
orderBy: [
|
||||
{ date: 'desc' },
|
||||
{ createdAt: 'desc' }
|
||||
]
|
||||
});
|
||||
|
||||
return items.map(item => ({
|
||||
id: item.id,
|
||||
text: item.text,
|
||||
isChecked: item.isChecked,
|
||||
createdAt: item.createdAt,
|
||||
updatedAt: item.updatedAt,
|
||||
date: item.date
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les tâches des 7 derniers jours (créées ou modifiées)
|
||||
*/
|
||||
private static async getWeeklyTasks(startDate: Date, endDate: Date): Promise<Task[]> {
|
||||
const tasks = await prisma.task.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
createdAt: {
|
||||
gte: startDate,
|
||||
lte: endDate
|
||||
}
|
||||
},
|
||||
{
|
||||
updatedAt: {
|
||||
gte: startDate,
|
||||
lte: endDate
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
orderBy: {
|
||||
updatedAt: 'desc'
|
||||
}
|
||||
});
|
||||
|
||||
return tasks.map(task => ({
|
||||
id: task.id,
|
||||
title: task.title,
|
||||
description: task.description || '',
|
||||
status: task.status as TaskStatus,
|
||||
priority: task.priority as TaskPriority,
|
||||
source: task.source as TaskSource,
|
||||
sourceId: task.sourceId || undefined,
|
||||
createdAt: task.createdAt,
|
||||
updatedAt: task.updatedAt,
|
||||
dueDate: task.dueDate || undefined,
|
||||
completedAt: task.completedAt || undefined,
|
||||
jiraProject: task.jiraProject || undefined,
|
||||
jiraKey: task.jiraKey || undefined,
|
||||
jiraType: task.jiraType || undefined,
|
||||
assignee: task.assignee || undefined,
|
||||
tags: [] // Les tags sont dans une relation séparée, on les laisse vides pour l'instant
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule les statistiques de la semaine
|
||||
*/
|
||||
private static calculateStats(
|
||||
checkboxes: DailyItem[],
|
||||
tasks: Task[],
|
||||
startDate: Date,
|
||||
endDate: Date
|
||||
): WeeklyStats {
|
||||
const completedCheckboxes = checkboxes.filter(c => c.isChecked);
|
||||
const completedTasks = tasks.filter(t => t.status === 'done');
|
||||
|
||||
// Créer un breakdown par jour
|
||||
const dailyBreakdown = [];
|
||||
const current = new Date(startDate);
|
||||
|
||||
while (current <= endDate) {
|
||||
const dayCheckboxes = checkboxes.filter(c =>
|
||||
c.date.toISOString().split('T')[0] === current.toISOString().split('T')[0]
|
||||
);
|
||||
const dayCompletedCheckboxes = dayCheckboxes.filter(c => c.isChecked);
|
||||
|
||||
// Pour les tâches, on compte celles modifiées ce jour-là
|
||||
const dayTasks = tasks.filter(t =>
|
||||
t.updatedAt.toISOString().split('T')[0] === current.toISOString().split('T')[0] ||
|
||||
t.createdAt.toISOString().split('T')[0] === current.toISOString().split('T')[0]
|
||||
);
|
||||
const dayCompletedTasks = dayTasks.filter(t => t.status === 'done');
|
||||
|
||||
dailyBreakdown.push({
|
||||
date: current.toISOString().split('T')[0],
|
||||
dayName: current.toLocaleDateString('fr-FR', { weekday: 'long' }),
|
||||
checkboxes: dayCheckboxes.length,
|
||||
completedCheckboxes: dayCompletedCheckboxes.length,
|
||||
tasks: dayTasks.length,
|
||||
completedTasks: dayCompletedTasks.length
|
||||
});
|
||||
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
|
||||
// Trouver le jour le plus productif
|
||||
const mostProductiveDay = dailyBreakdown.reduce((max, day) => {
|
||||
const dayScore = day.completedCheckboxes + day.completedTasks;
|
||||
const maxScore = max.completedCheckboxes + max.completedTasks;
|
||||
return dayScore > maxScore ? day : max;
|
||||
}, dailyBreakdown[0]);
|
||||
|
||||
return {
|
||||
totalCheckboxes: checkboxes.length,
|
||||
completedCheckboxes: completedCheckboxes.length,
|
||||
totalTasks: tasks.length,
|
||||
completedTasks: completedTasks.length,
|
||||
checkboxCompletionRate: checkboxes.length > 0 ? (completedCheckboxes.length / checkboxes.length) * 100 : 0,
|
||||
taskCompletionRate: tasks.length > 0 ? (completedTasks.length / tasks.length) * 100 : 0,
|
||||
mostProductiveDay: mostProductiveDay.dayName,
|
||||
dailyBreakdown
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fusionne les activités (checkboxes + tâches) en une timeline
|
||||
*/
|
||||
private static mergeActivities(checkboxes: DailyItem[], tasks: Task[]): WeeklyActivity[] {
|
||||
const activities: WeeklyActivity[] = [];
|
||||
|
||||
// Ajouter les checkboxes
|
||||
checkboxes.forEach(checkbox => {
|
||||
activities.push({
|
||||
id: `checkbox-${checkbox.id}`,
|
||||
type: 'checkbox',
|
||||
title: checkbox.text,
|
||||
completed: checkbox.isChecked,
|
||||
completedAt: checkbox.isChecked ? checkbox.updatedAt : undefined,
|
||||
createdAt: checkbox.createdAt,
|
||||
date: checkbox.date.toISOString().split('T')[0],
|
||||
dayName: checkbox.date.toLocaleDateString('fr-FR', { weekday: 'long' })
|
||||
});
|
||||
});
|
||||
|
||||
// Ajouter les tâches
|
||||
tasks.forEach(task => {
|
||||
const date = task.updatedAt.toISOString().split('T')[0];
|
||||
const dateObj = new Date(date + 'T00:00:00');
|
||||
activities.push({
|
||||
id: `task-${task.id}`,
|
||||
type: 'task',
|
||||
title: task.title,
|
||||
completed: task.status === 'done',
|
||||
completedAt: task.status === 'done' ? task.updatedAt : undefined,
|
||||
createdAt: task.createdAt,
|
||||
date: date,
|
||||
dayName: dateObj.toLocaleDateString('fr-FR', { weekday: 'long' })
|
||||
});
|
||||
});
|
||||
|
||||
// Trier par date (plus récent en premier)
|
||||
return activities.sort((a, b) => {
|
||||
const dateA = a.completedAt || a.createdAt;
|
||||
const dateB = b.completedAt || b.createdAt;
|
||||
return dateB.getTime() - dateA.getTime();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user