feat: complete Phase 4 of service refactoring

- Marked tasks in `TODO.md` as completed for moving task-related files to the `task-management` directory and correcting imports across the codebase.
- Updated imports in `seed-data.ts`, `seed-tags.ts`, API routes, and various components to reflect the new structure.
- Removed obsolete `daily.ts`, `tags.ts`, and `tasks.ts` files to streamline the codebase.
- Added new tasks in `TODO.md` for future cleaning and organization of service imports.
This commit is contained in:
Julien Froidefond
2025-09-23 10:25:41 +02:00
parent b8e0307f03
commit f5417040fd
23 changed files with 38 additions and 35 deletions

View File

@@ -0,0 +1,378 @@
import { prisma } from '../core/database';
import { Prisma } from '@prisma/client';
import { DailyCheckbox, DailyView, CreateDailyCheckboxData, UpdateDailyCheckboxData, BusinessError, DailyCheckboxType, TaskStatus, TaskPriority, TaskSource } from '@/lib/types';
import { getPreviousWorkday, normalizeDate, formatDateForAPI, getToday, getYesterday } from '@/lib/date-utils';
/**
* Service pour la gestion des checkboxes daily
*/
export class DailyService {
/**
* Récupère la vue daily pour une date donnée (checkboxes d'hier et d'aujourd'hui)
*/
async getDailyView(date: Date): Promise<DailyView> {
// Normaliser la date (début de journée)
const today = normalizeDate(date);
// Utiliser la logique de jour de travail précédent au lieu de jour-1
const yesterday = getPreviousWorkday(today);
// Récupérer les checkboxes des deux jours
const [yesterdayCheckboxes, todayCheckboxes] = await Promise.all([
this.getCheckboxesByDate(yesterday),
this.getCheckboxesByDate(today)
]);
return {
date: today,
yesterday: yesterdayCheckboxes,
today: todayCheckboxes
};
}
/**
* Récupère toutes les checkboxes d'une date donnée
*/
async getCheckboxesByDate(date: Date): Promise<DailyCheckbox[]> {
// Normaliser la date (début de journée)
const normalizedDate = normalizeDate(date);
const checkboxes = await prisma.dailyCheckbox.findMany({
where: { date: normalizedDate },
include: { task: true },
orderBy: { order: 'asc' }
});
return checkboxes.map(this.mapPrismaCheckbox);
}
/**
* Ajoute une checkbox à une date donnée
*/
async addCheckbox(data: CreateDailyCheckboxData): Promise<DailyCheckbox> {
// Normaliser la date
const normalizedDate = normalizeDate(data.date);
// Calculer l'ordre suivant pour cette date
const maxOrder = await prisma.dailyCheckbox.aggregate({
where: { date: normalizedDate },
_max: { order: true }
});
const order = data.order ?? ((maxOrder._max.order ?? -1) + 1);
const checkbox = await prisma.dailyCheckbox.create({
data: {
date: normalizedDate,
text: data.text.trim(),
type: data.type ?? 'task',
taskId: data.taskId,
order,
isChecked: data.isChecked ?? false
},
include: { task: true }
});
return this.mapPrismaCheckbox(checkbox);
}
/**
* Met à jour une checkbox
*/
async updateCheckbox(checkboxId: string, data: UpdateDailyCheckboxData): Promise<DailyCheckbox> {
const updateData: Prisma.DailyCheckboxUpdateInput = {};
if (data.text !== undefined) updateData.text = data.text.trim();
if (data.isChecked !== undefined) updateData.isChecked = data.isChecked;
if (data.type !== undefined) updateData.type = data.type;
if (data.taskId !== undefined) {
if (data.taskId === null) {
updateData.task = { disconnect: true };
} else {
updateData.task = { connect: { id: data.taskId } };
}
}
if (data.order !== undefined) updateData.order = data.order;
if (data.date !== undefined) updateData.date = normalizeDate(data.date);
const checkbox = await prisma.dailyCheckbox.update({
where: { id: checkboxId },
data: updateData,
include: { task: true }
});
return this.mapPrismaCheckbox(checkbox);
}
/**
* Supprime une checkbox
*/
async deleteCheckbox(checkboxId: string): Promise<void> {
const checkbox = await prisma.dailyCheckbox.findUnique({
where: { id: checkboxId }
});
if (!checkbox) {
throw new BusinessError('Checkbox non trouvée');
}
await prisma.dailyCheckbox.delete({
where: { id: checkboxId }
});
}
/**
* Réordonne les checkboxes d'une date donnée
*/
async reorderCheckboxes(date: Date, checkboxIds: string[]): Promise<void> {
await prisma.$transaction(async (prisma) => {
for (let i = 0; i < checkboxIds.length; i++) {
await prisma.dailyCheckbox.update({
where: { id: checkboxIds[i] },
data: { order: i }
});
}
});
}
/**
* Recherche dans les checkboxes
*/
async searchCheckboxes(query: string, limit: number = 20): Promise<DailyCheckbox[]> {
const checkboxes = await prisma.dailyCheckbox.findMany({
where: {
text: {
contains: query
}
},
include: { task: true },
orderBy: { date: 'desc' },
take: limit
});
return checkboxes.map(this.mapPrismaCheckbox);
}
/**
* Récupère l'historique des checkboxes (groupées par date)
*/
async getCheckboxHistory(limit: number = 30): Promise<{ date: Date; checkboxes: DailyCheckbox[] }[]> {
// Récupérer les dates distinctes des dernières checkboxes
const distinctDates = await prisma.dailyCheckbox.findMany({
select: { date: true },
distinct: ['date'],
orderBy: { date: 'desc' },
take: limit
});
const history = [];
for (const { date } of distinctDates) {
const checkboxes = await this.getCheckboxesByDate(date);
if (checkboxes.length > 0) {
history.push({ date, checkboxes });
}
}
return history;
}
/**
* Récupère la vue daily d'aujourd'hui
*/
async getTodaysDailyView(): Promise<DailyView> {
return this.getDailyView(getToday());
}
/**
* Ajoute une checkbox pour aujourd'hui
*/
async addTodayCheckbox(text: string, taskId?: string): Promise<DailyCheckbox> {
return this.addCheckbox({
date: getToday(),
text,
taskId
});
}
/**
* Ajoute une checkbox pour hier
*/
async addYesterdayCheckbox(text: string, taskId?: string): Promise<DailyCheckbox> {
return this.addCheckbox({
date: getYesterday(),
text,
taskId
});
}
/**
* Mappe une checkbox Prisma vers notre interface
*/
private mapPrismaCheckbox(checkbox: Prisma.DailyCheckboxGetPayload<{ include: { task: true } }>): DailyCheckbox {
return {
id: checkbox.id,
date: checkbox.date,
text: checkbox.text,
isChecked: checkbox.isChecked,
type: checkbox.type as DailyCheckboxType,
order: checkbox.order,
taskId: checkbox.taskId || undefined,
task: checkbox.task ? {
id: checkbox.task.id,
title: checkbox.task.title,
description: checkbox.task.description || undefined,
status: checkbox.task.status as TaskStatus,
priority: checkbox.task.priority as TaskPriority,
source: checkbox.task.source as TaskSource,
sourceId: checkbox.task.sourceId || undefined,
tags: [], // Les tags seront chargés séparément si nécessaire
dueDate: checkbox.task.dueDate || undefined,
completedAt: checkbox.task.completedAt || undefined,
createdAt: checkbox.task.createdAt,
updatedAt: checkbox.task.updatedAt,
jiraProject: checkbox.task.jiraProject || undefined,
jiraKey: checkbox.task.jiraKey || undefined,
assignee: checkbox.task.assignee || undefined
} : undefined,
createdAt: checkbox.createdAt,
updatedAt: checkbox.updatedAt
};
}
/**
* Récupère toutes les dates qui ont des checkboxes (pour le calendrier)
*/
async getDailyDates(): Promise<string[]> {
const checkboxes = await prisma.dailyCheckbox.findMany({
select: {
date: true
},
distinct: ['date'],
orderBy: {
date: 'desc'
}
});
return checkboxes.map(checkbox => {
return formatDateForAPI(checkbox.date);
});
}
/**
* Récupère toutes les checkboxes non cochées (tâches en attente)
*/
async getPendingCheckboxes(options?: {
maxDays?: number;
excludeToday?: boolean;
type?: DailyCheckboxType;
limit?: number;
}): Promise<DailyCheckbox[]> {
const today = normalizeDate(getToday());
const maxDays = options?.maxDays ?? 30;
const excludeToday = options?.excludeToday ?? true;
// Calculer la date limite (maxDays jours en arrière)
const limitDate = new Date(today);
limitDate.setDate(limitDate.getDate() - maxDays);
// Construire les conditions de filtrage
const whereConditions: {
isChecked: boolean;
date: {
gte: Date;
lt?: Date;
lte?: Date;
};
type?: DailyCheckboxType;
} = {
isChecked: false,
date: {
gte: limitDate,
...(excludeToday ? { lt: today } : { lte: today })
}
};
// Filtrer par type si spécifié
if (options?.type) {
whereConditions.type = options.type;
}
const checkboxes = await prisma.dailyCheckbox.findMany({
where: whereConditions,
include: { task: true },
orderBy: [
{ date: 'desc' },
{ order: 'asc' }
],
...(options?.limit ? { take: options.limit } : {})
});
return checkboxes.map(this.mapPrismaCheckbox);
}
/**
* Archive une checkbox (marque comme archivée sans la cocher)
*/
async archiveCheckbox(checkboxId: string): Promise<DailyCheckbox> {
// Pour l'instant, on utilise un champ text pour marquer comme archivé
// Plus tard on pourra ajouter un champ dédié dans la DB
const checkbox = await prisma.dailyCheckbox.update({
where: { id: checkboxId },
data: {
text: (await prisma.dailyCheckbox.findUnique({ where: { id: checkboxId } }))?.text + ' [ARCHIVÉ]',
updatedAt: new Date()
},
include: { task: true }
});
return this.mapPrismaCheckbox(checkbox);
}
/**
* Déplace une checkbox non cochée à aujourd'hui
*/
async moveCheckboxToToday(checkboxId: string): Promise<DailyCheckbox> {
const checkbox = await prisma.dailyCheckbox.findUnique({
where: { id: checkboxId }
});
if (!checkbox) {
throw new BusinessError('Checkbox non trouvée');
}
if (checkbox.isChecked) {
throw new BusinessError('Impossible de déplacer une tâche déjà cochée');
}
const today = normalizeDate(getToday());
// Vérifier si la checkbox est déjà pour aujourd'hui
if (normalizeDate(checkbox.date).getTime() === today.getTime()) {
throw new BusinessError('La tâche est déjà programmée pour aujourd\'hui');
}
// Calculer l'ordre suivant pour aujourd'hui
const maxOrder = await prisma.dailyCheckbox.aggregate({
where: { date: today },
_max: { order: true }
});
const newOrder = (maxOrder._max.order ?? -1) + 1;
const updatedCheckbox = await prisma.dailyCheckbox.update({
where: { id: checkboxId },
data: {
date: today,
order: newOrder,
updatedAt: new Date()
},
include: { task: true }
});
return this.mapPrismaCheckbox(updatedCheckbox);
}
}
// Instance singleton du service
export const dailyService = new DailyService();