- Introduced `onEditTask` and `onUpdateTitle` props in `KanbanBoard`, `KanbanColumn`, and `TaskCard` components for enhanced task management. - Implemented editing functionality in `TaskCard` to allow users to update task titles directly. - Added `EditTaskForm` in `KanbanBoardContainer` to handle task editing state and submission. - Updated `TasksService` to ensure all task properties can be modified during updates.
242 lines
6.3 KiB
TypeScript
242 lines
6.3 KiB
TypeScript
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<Task[]> {
|
|
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<Task> {
|
|
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<Task> {
|
|
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<void> {
|
|
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<Task> {
|
|
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<void> {
|
|
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<object>): 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();
|