import { prisma } from './database'; import { Task, TaskStatus, TaskPriority, TaskSource, BusinessError } from '@/lib/types'; import { Prisma } from '@prisma/client'; /** * Service pour la gestion des tâches (version standalone) */ export class TasksService { /** * Récupère toutes les tâches avec filtres optionnels */ async getTasks(filters?: { status?: TaskStatus[]; search?: string; limit?: number; offset?: number; }): Promise { const where: Prisma.TaskWhereInput = {}; if (filters?.status) { where.status = { in: filters.status }; } if (filters?.search) { where.OR = [ { title: { contains: filters.search } }, { description: { contains: filters.search } } ]; } const tasks = await prisma.task.findMany({ where, take: filters?.limit || 100, skip: filters?.offset || 0, orderBy: [ { completedAt: 'desc' }, { dueDate: 'asc' }, { createdAt: 'desc' } ] }); return tasks.map(this.mapPrismaTaskToTask); } /** * Crée une nouvelle tâche */ async createTask(taskData: { title: string; description?: string; status?: TaskStatus; priority?: TaskPriority; tags?: string[]; dueDate?: Date; }): Promise { const task = await prisma.task.create({ data: { title: taskData.title, description: taskData.description, status: taskData.status || 'todo', priority: taskData.priority || 'medium', tagsJson: JSON.stringify(taskData.tags || []), dueDate: taskData.dueDate, source: 'manual', // Source manuelle sourceId: `manual-${Date.now()}` // ID unique } }); // Gérer les tags if (taskData.tags && taskData.tags.length > 0) { await this.processTags(taskData.tags); } return this.mapPrismaTaskToTask(task); } /** * Met à jour une tâche */ async updateTask(taskId: string, updates: { title?: string; description?: string; status?: TaskStatus; priority?: TaskPriority; tags?: string[]; dueDate?: Date; }): Promise { const task = await prisma.task.findUnique({ where: { id: taskId } }); if (!task) { throw new BusinessError(`Tâche ${taskId} introuvable`); } // Logique métier : si on marque comme terminé, on ajoute la date const updateData: Prisma.TaskUpdateInput = { title: updates.title, description: updates.description, status: updates.status, priority: updates.priority, dueDate: updates.dueDate, updatedAt: new Date() }; if (updates.tags) { updateData.tagsJson = JSON.stringify(updates.tags); } if (updates.status === 'done' && !task.completedAt) { updateData.completedAt = new Date(); } else if (updates.status && updates.status !== 'done' && task.completedAt) { updateData.completedAt = null; } const updatedTask = await prisma.task.update({ where: { id: taskId }, data: updateData }); // Gérer les tags if (updates.tags && updates.tags.length > 0) { await this.processTags(updates.tags); } return this.mapPrismaTaskToTask(updatedTask); } /** * Supprime une tâche */ async deleteTask(taskId: string): Promise { const task = await prisma.task.findUnique({ where: { id: taskId } }); if (!task) { throw new BusinessError(`Tâche ${taskId} introuvable`); } await prisma.task.delete({ where: { id: taskId } }); } /** * Met à jour le statut d'une tâche */ async updateTaskStatus(taskId: string, newStatus: TaskStatus): Promise { return this.updateTask(taskId, { status: newStatus }); } /** * Récupère les statistiques des tâches */ async getTaskStats() { const [total, completed, inProgress, todo, cancelled] = await Promise.all([ prisma.task.count(), prisma.task.count({ where: { status: 'done' } }), prisma.task.count({ where: { status: 'in_progress' } }), prisma.task.count({ where: { status: 'todo' } }), prisma.task.count({ where: { status: 'cancelled' } }) ]); return { total, completed, inProgress, todo, cancelled, completionRate: total > 0 ? Math.round((completed / total) * 100) : 0 }; } /** * Traite et crée les tags s'ils n'existent pas */ private async processTags(tagNames: string[]): Promise { for (const tagName of tagNames) { try { await prisma.tag.upsert({ where: { name: tagName }, update: {}, // Pas de mise à jour nécessaire create: { name: tagName, color: this.generateTagColor(tagName) } }); } catch (error) { console.error(`Erreur lors de la création du tag ${tagName}:`, error); } } } /** * Génère une couleur pour un tag basée sur son nom */ private generateTagColor(tagName: string): string { const colors = [ '#ef4444', '#f97316', '#f59e0b', '#eab308', '#84cc16', '#22c55e', '#10b981', '#14b8a6', '#06b6d4', '#0ea5e9', '#3b82f6', '#6366f1', '#8b5cf6', '#a855f7', '#d946ef', '#ec4899' ]; // Hash simple du nom pour choisir une couleur let hash = 0; for (let i = 0; i < tagName.length; i++) { hash = tagName.charCodeAt(i) + ((hash << 5) - hash); } return colors[Math.abs(hash) % colors.length]; } /** * Convertit une tâche Prisma en objet Task */ private mapPrismaTaskToTask(prismaTask: Prisma.TaskGetPayload): Task { return { id: prismaTask.id, title: prismaTask.title, description: prismaTask.description ?? undefined, status: prismaTask.status as TaskStatus, priority: prismaTask.priority as TaskPriority, source: prismaTask.source as TaskSource, sourceId: prismaTask.sourceId?? undefined, tags: JSON.parse(prismaTask.tagsJson || '[]'), dueDate: prismaTask.dueDate ?? undefined, completedAt: prismaTask.completedAt ?? undefined, createdAt: prismaTask.createdAt, updatedAt: prismaTask.updatedAt, jiraProject: prismaTask.jiraProject ?? undefined, jiraKey: prismaTask.jiraKey ?? undefined, assignee: prismaTask.assignee ?? undefined }; } } // Instance singleton export const tasksService = new TasksService();