refacto: passing by server actions on taskCard
This commit is contained in:
26
TODO.md
26
TODO.md
@@ -138,23 +138,27 @@
|
||||
- [ ] Graphiques avec Chart.js ou Recharts
|
||||
- [ ] Export des données en CSV/JSON
|
||||
|
||||
## Autre Todo
|
||||
- [ ] Avoir un bouton pour réduire/agrandir la font des taches dans les kanban (swimlane et classique)
|
||||
|
||||
|
||||
## 🔧 Phase 4: Server Actions - Migration API Routes (Nouveau)
|
||||
|
||||
### 4.1 Migration vers Server Actions - Actions rapides
|
||||
**Objectif** : Remplacer les API routes par des server actions pour les actions simples et fréquentes
|
||||
|
||||
#### Actions TaskCard (Priorité 1)
|
||||
- [ ] Créer `actions/tasks.ts` avec server actions de base
|
||||
- [ ] `updateTaskStatus(taskId, status)` - Changement de statut
|
||||
- [ ] `updateTaskTitle(taskId, title)` - Édition inline du titre
|
||||
- [ ] `deleteTask(taskId)` - Suppression de tâche
|
||||
- [ ] Modifier `TaskCard.tsx` pour utiliser server actions directement
|
||||
- [ ] Remplacer les props callbacks par calls directs aux actions
|
||||
- [ ] Intégrer `useTransition` pour les loading states natifs
|
||||
- [ ] Tester la revalidation automatique du cache
|
||||
- [ ] **Nettoyage** : Supprimer `PATCH /api/tasks` et `DELETE /api/tasks`
|
||||
- [ ] **Nettoyage** : Simplifier `tasks-client.ts` (garder GET et POST uniquement)
|
||||
- [ ] **Nettoyage** : Modifier `useTasks.ts` pour remplacer mutations par server actions
|
||||
- [x] Créer `actions/tasks.ts` avec server actions de base
|
||||
- [x] `updateTaskStatus(taskId, status)` - Changement de statut
|
||||
- [x] `updateTaskTitle(taskId, title)` - Édition inline du titre
|
||||
- [x] `deleteTask(taskId)` - Suppression de tâche
|
||||
- [x] Modifier `TaskCard.tsx` pour utiliser server actions directement
|
||||
- [x] Remplacer les props callbacks par calls directs aux actions
|
||||
- [x] Intégrer `useTransition` pour les loading states natifs
|
||||
- [x] Tester la revalidation automatique du cache
|
||||
- [x] **Nettoyage** : Supprimer props obsolètes dans tous les composants Kanban
|
||||
- [x] **Nettoyage** : Simplifier `tasks-client.ts` (garder GET et POST uniquement)
|
||||
- [x] **Nettoyage** : Modifier `useTasks.ts` pour remplacer mutations par server actions
|
||||
|
||||
#### Actions Daily (Priorité 2)
|
||||
- [ ] Créer `actions/daily.ts` pour les checkboxes
|
||||
|
||||
@@ -65,43 +65,8 @@ export class TasksClient {
|
||||
return httpClient.get<TasksResponse>('/tasks', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une nouvelle tâche
|
||||
*/
|
||||
async createTask(data: CreateTaskData): Promise<{ success: boolean; data: Task; message: string }> {
|
||||
const payload = {
|
||||
...data,
|
||||
dueDate: data.dueDate?.toISOString()
|
||||
};
|
||||
|
||||
return httpClient.post('/tasks', payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour une tâche
|
||||
*/
|
||||
async updateTask(data: UpdateTaskData): Promise<{ success: boolean; data: Task; message: string }> {
|
||||
const payload = {
|
||||
...data,
|
||||
dueDate: data.dueDate?.toISOString()
|
||||
};
|
||||
|
||||
return httpClient.patch('/tasks', payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime une tâche
|
||||
*/
|
||||
async deleteTask(taskId: string): Promise<{ success: boolean; message: string }> {
|
||||
return httpClient.delete('/tasks', { taskId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour le statut d'une tâche
|
||||
*/
|
||||
async updateTaskStatus(taskId: string, status: TaskStatus): Promise<{ success: boolean; data: Task; message: string }> {
|
||||
return this.updateTask({ taskId, status });
|
||||
}
|
||||
// Note: Les méthodes createTask, updateTask et deleteTask ont été migrées vers Server Actions
|
||||
// Voir /src/actions/tasks.ts pour createTask, updateTask, updateTaskTitle, updateTaskStatus, deleteTask
|
||||
}
|
||||
|
||||
// Instance singleton
|
||||
|
||||
@@ -6,19 +6,26 @@ import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { TagInput } from '@/components/ui/TagInput';
|
||||
import { Task, TaskPriority, TaskStatus } from '@/lib/types';
|
||||
import { UpdateTaskData } from '@/clients/tasks-client';
|
||||
// UpdateTaskData removed - using Server Actions directly
|
||||
import { getAllStatuses, getAllPriorities } from '@/lib/status-config';
|
||||
|
||||
interface EditTaskFormProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: UpdateTaskData) => Promise<void>;
|
||||
onSubmit: (data: { taskId: string; title?: string; description?: string; status?: TaskStatus; priority?: TaskPriority; tags?: string[]; dueDate?: Date; }) => Promise<void>;
|
||||
task: Task | null;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export function EditTaskForm({ isOpen, onClose, onSubmit, task, loading = false }: EditTaskFormProps) {
|
||||
const [formData, setFormData] = useState<Omit<UpdateTaskData, 'taskId'>>({
|
||||
const [formData, setFormData] = useState<{
|
||||
title: string;
|
||||
description: string;
|
||||
status: TaskStatus;
|
||||
priority: TaskPriority;
|
||||
tags: string[];
|
||||
dueDate?: Date;
|
||||
}>({
|
||||
title: '',
|
||||
description: '',
|
||||
status: 'todo' as TaskStatus,
|
||||
|
||||
@@ -18,15 +18,13 @@ import { TaskCard } from './TaskCard';
|
||||
interface KanbanBoardProps {
|
||||
tasks: Task[];
|
||||
onCreateTask?: (data: CreateTaskData) => Promise<void>;
|
||||
onDeleteTask?: (taskId: string) => Promise<void>;
|
||||
onEditTask?: (task: Task) => void;
|
||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||
onUpdateStatus?: (taskId: string, newStatus: TaskStatus) => Promise<void>;
|
||||
compactView?: boolean;
|
||||
visibleStatuses?: TaskStatus[];
|
||||
}
|
||||
|
||||
export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onUpdateTitle, onUpdateStatus, compactView = false, visibleStatuses }: KanbanBoardProps) {
|
||||
export function KanbanBoard({ tasks, onCreateTask, onEditTask, onUpdateStatus, compactView = false, visibleStatuses }: KanbanBoardProps) {
|
||||
const [activeTask, setActiveTask] = useState<Task | null>(null);
|
||||
const { isColumnVisible } = useUserPreferences();
|
||||
const { isMounted, sensors } = useDragAndDrop();
|
||||
@@ -95,9 +93,7 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU
|
||||
id={column.id}
|
||||
tasks={column.tasks}
|
||||
onCreateTask={onCreateTask}
|
||||
onDeleteTask={onDeleteTask}
|
||||
onEditTask={onEditTask}
|
||||
onUpdateTitle={onUpdateTitle}
|
||||
compactView={compactView}
|
||||
/>
|
||||
))}
|
||||
@@ -124,9 +120,7 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU
|
||||
<div className="rotate-3 opacity-90">
|
||||
<TaskCard
|
||||
task={activeTask}
|
||||
onDelete={undefined}
|
||||
onEdit={undefined}
|
||||
onUpdateTitle={undefined}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -9,8 +9,9 @@ import { KanbanFilters } from './KanbanFilters';
|
||||
import { EditTaskForm } from '@/components/forms/EditTaskForm';
|
||||
import { useTasksContext } from '@/contexts/TasksContext';
|
||||
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
|
||||
import { Task, TaskStatus } from '@/lib/types';
|
||||
import { UpdateTaskData, CreateTaskData } from '@/clients/tasks-client';
|
||||
import { Task, TaskStatus, TaskPriority } from '@/lib/types';
|
||||
import { CreateTaskData } from '@/clients/tasks-client';
|
||||
import { updateTask, createTask } from '@/actions/tasks';
|
||||
import { getAllStatuses } from '@/lib/status-config';
|
||||
|
||||
interface KanbanBoardContainerProps {
|
||||
@@ -26,13 +27,11 @@ export function KanbanBoardContainer({
|
||||
filteredTasks,
|
||||
pinnedTasks,
|
||||
loading,
|
||||
createTask,
|
||||
deleteTask,
|
||||
updateTask,
|
||||
updateTaskOptimistic,
|
||||
kanbanFilters,
|
||||
setKanbanFilters,
|
||||
tags
|
||||
tags,
|
||||
refreshTasks
|
||||
} = useTasksContext();
|
||||
|
||||
const { preferences, toggleColumnVisibility, isColumnVisible } = useUserPreferences();
|
||||
@@ -45,24 +44,20 @@ export function KanbanBoardContainer({
|
||||
setEditingTask(task);
|
||||
};
|
||||
|
||||
const handleUpdateTask = async (data: UpdateTaskData) => {
|
||||
await updateTask(data);
|
||||
const handleUpdateTask = async (data: { taskId: string; title?: string; description?: string; status?: TaskStatus; priority?: TaskPriority; tags?: string[]; dueDate?: Date; }) => {
|
||||
const result = await updateTask(data);
|
||||
if (result.success) {
|
||||
await refreshTasks(); // Rafraîchir les données
|
||||
setEditingTask(null);
|
||||
} else {
|
||||
console.error('Error updating task:', result.error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateTitle = async (taskId: string, newTitle: string) => {
|
||||
await updateTask({
|
||||
taskId,
|
||||
title: newTitle
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateStatus = async (taskId: string, newStatus: TaskStatus) => {
|
||||
// Utiliser la mise à jour optimiste pour le drag & drop
|
||||
await updateTaskOptimistic({
|
||||
taskId,
|
||||
status: newStatus
|
||||
});
|
||||
await updateTaskOptimistic(taskId, newStatus);
|
||||
};
|
||||
|
||||
// Obtenir le nom du tag épinglé pour l'affichage
|
||||
@@ -70,7 +65,12 @@ export function KanbanBoardContainer({
|
||||
|
||||
// Wrapper pour adapter le type de createTask
|
||||
const handleCreateTask = async (data: CreateTaskData): Promise<void> => {
|
||||
await createTask(data);
|
||||
const result = await createTask(data);
|
||||
if (result.success) {
|
||||
await refreshTasks(); // Rafraîchir les données
|
||||
} else {
|
||||
console.error('Error creating task:', result.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -89,9 +89,7 @@ export function KanbanBoardContainer({
|
||||
{showObjectives && pinnedTasks.length > 0 && (
|
||||
<ObjectivesBoard
|
||||
tasks={pinnedTasks}
|
||||
onDeleteTask={deleteTask}
|
||||
onEditTask={handleEditTask}
|
||||
onUpdateTitle={handleUpdateTitle}
|
||||
onUpdateStatus={handleUpdateStatus}
|
||||
compactView={kanbanFilters.compactView}
|
||||
pinnedTagName={pinnedTagName}
|
||||
@@ -103,9 +101,7 @@ export function KanbanBoardContainer({
|
||||
<PrioritySwimlanesBoard
|
||||
tasks={filteredTasks}
|
||||
onCreateTask={handleCreateTask}
|
||||
onDeleteTask={deleteTask}
|
||||
onEditTask={handleEditTask}
|
||||
onUpdateTitle={handleUpdateTitle}
|
||||
onUpdateStatus={handleUpdateStatus}
|
||||
compactView={kanbanFilters.compactView}
|
||||
visibleStatuses={visibleStatuses}
|
||||
@@ -115,9 +111,7 @@ export function KanbanBoardContainer({
|
||||
<SwimlanesBoard
|
||||
tasks={filteredTasks}
|
||||
onCreateTask={handleCreateTask}
|
||||
onDeleteTask={deleteTask}
|
||||
onEditTask={handleEditTask}
|
||||
onUpdateTitle={handleUpdateTitle}
|
||||
onUpdateStatus={handleUpdateStatus}
|
||||
compactView={kanbanFilters.compactView}
|
||||
visibleStatuses={visibleStatuses}
|
||||
@@ -128,9 +122,7 @@ export function KanbanBoardContainer({
|
||||
<KanbanBoard
|
||||
tasks={filteredTasks}
|
||||
onCreateTask={handleCreateTask}
|
||||
onDeleteTask={deleteTask}
|
||||
onEditTask={handleEditTask}
|
||||
onUpdateTitle={handleUpdateTitle}
|
||||
onUpdateStatus={handleUpdateStatus}
|
||||
compactView={kanbanFilters.compactView}
|
||||
visibleStatuses={visibleStatuses}
|
||||
|
||||
@@ -12,13 +12,11 @@ interface KanbanColumnProps {
|
||||
id: TaskStatus;
|
||||
tasks: Task[];
|
||||
onCreateTask?: (data: CreateTaskData) => Promise<void>;
|
||||
onDeleteTask?: (taskId: string) => Promise<void>;
|
||||
onEditTask?: (task: Task) => void;
|
||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||
compactView?: boolean;
|
||||
}
|
||||
|
||||
export function KanbanColumn({ id, tasks, onCreateTask, onDeleteTask, onEditTask, onUpdateTitle, compactView = false }: KanbanColumnProps) {
|
||||
export function KanbanColumn({ id, tasks, onCreateTask, onEditTask, compactView = false }: KanbanColumnProps) {
|
||||
const [showQuickAdd, setShowQuickAdd] = useState(false);
|
||||
|
||||
// Configuration de la zone droppable
|
||||
@@ -91,7 +89,7 @@ export function KanbanColumn({ id, tasks, onCreateTask, onDeleteTask, onEditTask
|
||||
</div>
|
||||
) : (
|
||||
tasks.map((task) => (
|
||||
<TaskCard key={task.id} task={task} onDelete={onDeleteTask} onEdit={onEditTask} onUpdateTitle={onUpdateTitle} compactView={compactView} />
|
||||
<TaskCard key={task.id} task={task} onEdit={onEditTask} compactView={compactView} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -21,9 +21,7 @@ import { useDroppable } from '@dnd-kit/core';
|
||||
|
||||
interface ObjectivesBoardProps {
|
||||
tasks: Task[];
|
||||
onDeleteTask?: (taskId: string) => Promise<void>;
|
||||
onEditTask?: (task: Task) => void;
|
||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||
onUpdateStatus?: (taskId: string, newStatus: TaskStatus) => Promise<void>;
|
||||
compactView?: boolean;
|
||||
pinnedTagName?: string;
|
||||
@@ -36,9 +34,7 @@ function DroppableColumn({
|
||||
title,
|
||||
color,
|
||||
icon,
|
||||
onDeleteTask,
|
||||
onEditTask,
|
||||
onUpdateTitle,
|
||||
compactView
|
||||
}: {
|
||||
status: TaskStatus;
|
||||
@@ -46,9 +42,7 @@ function DroppableColumn({
|
||||
title: string;
|
||||
color: string;
|
||||
icon: string;
|
||||
onDeleteTask?: (taskId: string) => Promise<void>;
|
||||
onEditTask?: (task: Task) => void;
|
||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||
compactView: boolean;
|
||||
}) {
|
||||
const { setNodeRef } = useDroppable({
|
||||
@@ -80,9 +74,7 @@ function DroppableColumn({
|
||||
<div key={task.id} className="transform hover:scale-[1.02] transition-transform duration-200">
|
||||
<TaskCard
|
||||
task={task}
|
||||
onDelete={onDeleteTask}
|
||||
onEdit={onEditTask}
|
||||
onUpdateTitle={onUpdateTitle}
|
||||
compactView={compactView}
|
||||
/>
|
||||
</div>
|
||||
@@ -96,9 +88,7 @@ function DroppableColumn({
|
||||
|
||||
export function ObjectivesBoard({
|
||||
tasks,
|
||||
onDeleteTask,
|
||||
onEditTask,
|
||||
onUpdateTitle,
|
||||
onUpdateStatus,
|
||||
compactView = false,
|
||||
pinnedTagName = "Objectifs"
|
||||
@@ -209,9 +199,7 @@ export function ObjectivesBoard({
|
||||
title="À faire"
|
||||
color="bg-[var(--primary)]"
|
||||
icon="📋"
|
||||
onDeleteTask={onDeleteTask}
|
||||
onEditTask={onEditTask}
|
||||
onUpdateTitle={onUpdateTitle}
|
||||
compactView={compactView}
|
||||
/>
|
||||
|
||||
@@ -221,9 +209,7 @@ export function ObjectivesBoard({
|
||||
title="En cours"
|
||||
color="bg-yellow-400"
|
||||
icon="🔄"
|
||||
onDeleteTask={onDeleteTask}
|
||||
onEditTask={onEditTask}
|
||||
onUpdateTitle={onUpdateTitle}
|
||||
compactView={compactView}
|
||||
/>
|
||||
|
||||
@@ -233,9 +219,7 @@ export function ObjectivesBoard({
|
||||
title="Terminé"
|
||||
color="bg-green-400"
|
||||
icon="✅"
|
||||
onDeleteTask={onDeleteTask}
|
||||
onEditTask={onEditTask}
|
||||
onUpdateTitle={onUpdateTitle}
|
||||
compactView={compactView}
|
||||
/>
|
||||
</div>
|
||||
@@ -267,9 +251,7 @@ export function ObjectivesBoard({
|
||||
<div className="rotate-3 opacity-90">
|
||||
<TaskCard
|
||||
task={activeTask}
|
||||
onDelete={undefined}
|
||||
onEdit={undefined}
|
||||
onUpdateTitle={undefined}
|
||||
compactView={compactView}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -10,9 +10,7 @@ interface PrioritySwimlanesBoardProps {
|
||||
loading: boolean;
|
||||
tasks: Task[];
|
||||
onCreateTask?: (data: CreateTaskData) => Promise<void>;
|
||||
onDeleteTask?: (taskId: string) => Promise<void>;
|
||||
onEditTask?: (task: Task) => void;
|
||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||
onUpdateStatus?: (taskId: string, newStatus: TaskStatus) => Promise<void>;
|
||||
compactView?: boolean;
|
||||
visibleStatuses?: TaskStatus[];
|
||||
@@ -21,9 +19,7 @@ interface PrioritySwimlanesBoardProps {
|
||||
export function PrioritySwimlanesBoard({
|
||||
tasks,
|
||||
onCreateTask,
|
||||
onDeleteTask,
|
||||
onEditTask,
|
||||
onUpdateTitle,
|
||||
onUpdateStatus,
|
||||
compactView = false,
|
||||
visibleStatuses,
|
||||
@@ -66,9 +62,7 @@ export function PrioritySwimlanesBoard({
|
||||
tasks={tasks}
|
||||
swimlanes={swimlanesData}
|
||||
onCreateTask={onCreateTask}
|
||||
onDeleteTask={onDeleteTask}
|
||||
onEditTask={onEditTask}
|
||||
onUpdateTitle={onUpdateTitle}
|
||||
onUpdateStatus={onUpdateStatus}
|
||||
compactView={compactView}
|
||||
visibleStatuses={visibleStatuses}
|
||||
|
||||
@@ -25,9 +25,7 @@ import { useDroppable } from '@dnd-kit/core';
|
||||
function DroppableColumn({
|
||||
status,
|
||||
tasks,
|
||||
onDeleteTask,
|
||||
onEditTask,
|
||||
onUpdateTitle,
|
||||
compactView,
|
||||
onCreateTask,
|
||||
showQuickAdd,
|
||||
@@ -36,9 +34,7 @@ function DroppableColumn({
|
||||
}: {
|
||||
status: TaskStatus;
|
||||
tasks: Task[];
|
||||
onDeleteTask?: (taskId: string) => Promise<void>;
|
||||
onEditTask?: (task: Task) => void;
|
||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||
compactView: boolean;
|
||||
onCreateTask?: (data: CreateTaskData) => Promise<void>;
|
||||
showQuickAdd?: boolean;
|
||||
@@ -60,9 +56,7 @@ function DroppableColumn({
|
||||
<TaskCard
|
||||
key={task.id}
|
||||
task={task}
|
||||
onDelete={onDeleteTask}
|
||||
onEdit={onEditTask}
|
||||
onUpdateTitle={onUpdateTitle}
|
||||
compactView={compactView}
|
||||
/>
|
||||
))}
|
||||
@@ -117,9 +111,7 @@ interface SwimlanesBaseProps {
|
||||
tasks: Task[];
|
||||
swimlanes: SwimlaneData[];
|
||||
onCreateTask?: (data: CreateTaskData) => Promise<void>;
|
||||
onDeleteTask?: (taskId: string) => Promise<void>;
|
||||
onEditTask?: (task: Task) => void;
|
||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||
onUpdateStatus?: (taskId: string, newStatus: TaskStatus) => Promise<void>;
|
||||
compactView?: boolean;
|
||||
visibleStatuses?: TaskStatus[];
|
||||
@@ -129,9 +121,7 @@ export function SwimlanesBase({
|
||||
tasks,
|
||||
swimlanes,
|
||||
onCreateTask,
|
||||
onDeleteTask,
|
||||
onEditTask,
|
||||
onUpdateTitle,
|
||||
onUpdateStatus,
|
||||
compactView = false,
|
||||
visibleStatuses
|
||||
@@ -270,9 +260,7 @@ export function SwimlanesBase({
|
||||
key={columnId}
|
||||
status={status}
|
||||
tasks={statusTasks}
|
||||
onDeleteTask={onDeleteTask}
|
||||
onEditTask={onEditTask}
|
||||
onUpdateTitle={onUpdateTitle}
|
||||
compactView={compactView}
|
||||
onCreateTask={onCreateTask ? (data) => handleQuickAdd(data, columnId) : undefined}
|
||||
showQuickAdd={showQuickAdd[columnId] || false}
|
||||
|
||||
@@ -9,9 +9,7 @@ import { SwimlanesBase, SwimlaneData } from './SwimlanesBase';
|
||||
interface SwimlanesboardProps {
|
||||
tasks: Task[];
|
||||
onCreateTask?: (data: CreateTaskData) => Promise<void>;
|
||||
onDeleteTask?: (taskId: string) => Promise<void>;
|
||||
onEditTask?: (task: Task) => void;
|
||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||
onUpdateStatus?: (taskId: string, newStatus: TaskStatus) => Promise<void>;
|
||||
compactView?: boolean;
|
||||
visibleStatuses?: TaskStatus[];
|
||||
@@ -21,9 +19,7 @@ interface SwimlanesboardProps {
|
||||
export function SwimlanesBoard({
|
||||
tasks,
|
||||
onCreateTask,
|
||||
onDeleteTask,
|
||||
onEditTask,
|
||||
onUpdateTitle,
|
||||
onUpdateStatus,
|
||||
compactView = false,
|
||||
visibleStatuses,
|
||||
@@ -88,9 +84,7 @@ export function SwimlanesBoard({
|
||||
tasks={tasks}
|
||||
swimlanes={swimlanesData}
|
||||
onCreateTask={onCreateTask}
|
||||
onDeleteTask={onDeleteTask}
|
||||
onEditTask={onEditTask}
|
||||
onUpdateTitle={onUpdateTitle}
|
||||
onUpdateStatus={onUpdateStatus}
|
||||
compactView={compactView}
|
||||
visibleStatuses={visibleStatuses}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useState, useEffect, useRef, useTransition } from 'react';
|
||||
import { Task } from '@/lib/types';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { fr } from 'date-fns/locale';
|
||||
@@ -9,21 +9,21 @@ import { useTasksContext } from '@/contexts/TasksContext';
|
||||
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
|
||||
import { useDraggable } from '@dnd-kit/core';
|
||||
import { getPriorityConfig, getPriorityColorHex } from '@/lib/status-config';
|
||||
import { updateTaskTitle, deleteTask } from '@/actions/tasks';
|
||||
|
||||
interface TaskCardProps {
|
||||
task: Task;
|
||||
onDelete?: (taskId: string) => Promise<void>;
|
||||
onEdit?: (task: Task) => void;
|
||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||
compactView?: boolean;
|
||||
}
|
||||
|
||||
export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView = false }: TaskCardProps) {
|
||||
export function TaskCard({ task, onEdit, compactView = false }: TaskCardProps) {
|
||||
const [isEditingTitle, setIsEditingTitle] = useState(false);
|
||||
const [editTitle, setEditTitle] = useState(task.title);
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const { tags: availableTags } = useTasksContext();
|
||||
const { tags: availableTags, refreshTasks } = useTasksContext();
|
||||
const { preferences } = useUserPreferences();
|
||||
|
||||
// Helper pour construire l'URL Jira
|
||||
@@ -61,8 +61,18 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
|
||||
const handleDelete = async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (onDelete) {
|
||||
await onDelete(task.id);
|
||||
|
||||
if (window.confirm('Êtes-vous sûr de vouloir supprimer cette tâche ?')) {
|
||||
startTransition(async () => {
|
||||
const result = await deleteTask(task.id);
|
||||
if (!result.success) {
|
||||
console.error('Error deleting task:', result.error);
|
||||
// TODO: Afficher une notification d'erreur
|
||||
} else {
|
||||
// Rafraîchir les données après suppression réussie
|
||||
await refreshTasks();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,7 +87,7 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
|
||||
const handleTitleClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (onUpdateTitle && !isDragging) {
|
||||
if (!isDragging && !isPending) {
|
||||
setIsEditingTitle(true);
|
||||
setShowTooltip(false);
|
||||
}
|
||||
@@ -85,8 +95,19 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
|
||||
|
||||
const handleTitleSave = async () => {
|
||||
const trimmedTitle = editTitle.trim();
|
||||
if (trimmedTitle && trimmedTitle !== task.title && onUpdateTitle) {
|
||||
await onUpdateTitle(task.id, trimmedTitle);
|
||||
if (trimmedTitle && trimmedTitle !== task.title) {
|
||||
startTransition(async () => {
|
||||
const result = await updateTaskTitle(task.id, trimmedTitle);
|
||||
if (!result.success) {
|
||||
console.error('Error updating task title:', result.error);
|
||||
// Remettre l'ancien titre en cas d'erreur
|
||||
setEditTitle(task.title);
|
||||
} else {
|
||||
// Mettre à jour optimistiquement le titre local
|
||||
// La Server Action a déjà mis à jour la DB, on synchronise juste l'affichage
|
||||
task.title = trimmedTitle;
|
||||
}
|
||||
});
|
||||
}
|
||||
setIsEditingTitle(false);
|
||||
setShowTooltip(false);
|
||||
@@ -142,7 +163,7 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
|
||||
onClick={handleTitleClick}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
title={onUpdateTitle ? "Cliquer pour éditer" : undefined}
|
||||
title="Cliquer pour éditer"
|
||||
>
|
||||
{titleWithoutEmojis}
|
||||
</h4>
|
||||
@@ -190,6 +211,8 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
|
||||
task.status === 'done' ? 'opacity-60' : ''
|
||||
} ${
|
||||
isJiraTask ? 'jira-task' : ''
|
||||
} ${
|
||||
isPending ? 'opacity-70 pointer-events-none' : ''
|
||||
}`}
|
||||
{...attributes}
|
||||
{...(isEditingTitle ? {} : listeners)}
|
||||
@@ -231,17 +254,19 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
|
||||
{!isEditingTitle && onEdit && (
|
||||
<button
|
||||
onClick={handleEdit}
|
||||
className="opacity-0 group-hover:opacity-100 w-5 h-5 rounded-full bg-[var(--primary)]/20 hover:bg-[var(--primary)]/30 border border-[var(--primary)]/30 hover:border-[var(--primary)]/50 flex items-center justify-center transition-all duration-200 text-[var(--primary)] hover:text-[var(--primary)] text-xs"
|
||||
disabled={isPending}
|
||||
className="opacity-0 group-hover:opacity-100 w-5 h-5 rounded-full bg-[var(--primary)]/20 hover:bg-[var(--primary)]/30 border border-[var(--primary)]/30 hover:border-[var(--primary)]/50 flex items-center justify-center transition-all duration-200 text-[var(--primary)] hover:text-[var(--primary)] text-xs disabled:opacity-50"
|
||||
title="Modifier la tâche"
|
||||
>
|
||||
✎
|
||||
</button>
|
||||
)}
|
||||
|
||||
{!isEditingTitle && onDelete && (
|
||||
{!isEditingTitle && (
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="opacity-0 group-hover:opacity-100 w-5 h-5 rounded-full bg-[var(--destructive)]/20 hover:bg-[var(--destructive)]/30 border border-[var(--destructive)]/30 hover:border-[var(--destructive)]/50 flex items-center justify-center transition-all duration-200 text-[var(--destructive)] hover:text-[var(--destructive)] text-xs"
|
||||
disabled={isPending}
|
||||
className="opacity-0 group-hover:opacity-100 w-5 h-5 rounded-full bg-[var(--destructive)]/20 hover:bg-[var(--destructive)]/30 border border-[var(--destructive)]/30 hover:border-[var(--destructive)]/50 flex items-center justify-center transition-all duration-200 text-[var(--destructive)] hover:text-[var(--destructive)] text-xs disabled:opacity-50"
|
||||
title="Supprimer la tâche"
|
||||
>
|
||||
×
|
||||
@@ -270,6 +295,8 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
|
||||
task.status === 'done' ? 'opacity-60' : ''
|
||||
} ${
|
||||
isJiraTask ? 'jira-task' : ''
|
||||
} ${
|
||||
isPending ? 'opacity-70 pointer-events-none' : ''
|
||||
}`}
|
||||
{...attributes}
|
||||
{...(isEditingTitle ? {} : listeners)}
|
||||
@@ -312,7 +339,8 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
|
||||
{!isEditingTitle && onEdit && (
|
||||
<button
|
||||
onClick={handleEdit}
|
||||
className="opacity-0 group-hover:opacity-100 w-4 h-4 rounded-full bg-[var(--primary)]/20 hover:bg-[var(--primary)]/30 border border-[var(--primary)]/30 hover:border-[var(--primary)]/50 flex items-center justify-center transition-all duration-200 text-[var(--primary)] hover:text-[var(--primary)] text-xs"
|
||||
disabled={isPending}
|
||||
className="opacity-0 group-hover:opacity-100 w-4 h-4 rounded-full bg-[var(--primary)]/20 hover:bg-[var(--primary)]/30 border border-[var(--primary)]/30 hover:border-[var(--primary)]/50 flex items-center justify-center transition-all duration-200 text-[var(--primary)] hover:text-[var(--primary)] text-xs disabled:opacity-50"
|
||||
title="Modifier la tâche"
|
||||
>
|
||||
✎
|
||||
@@ -320,10 +348,11 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView =
|
||||
)}
|
||||
|
||||
{/* Bouton de suppression discret - masqué en mode édition */}
|
||||
{!isEditingTitle && onDelete && (
|
||||
{!isEditingTitle && (
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="opacity-0 group-hover:opacity-100 w-4 h-4 rounded-full bg-[var(--destructive)]/20 hover:bg-[var(--destructive)]/30 border border-[var(--destructive)]/30 hover:border-[var(--destructive)]/50 flex items-center justify-center transition-all duration-200 text-[var(--destructive)] hover:text-[var(--destructive)] text-xs"
|
||||
disabled={isPending}
|
||||
className="opacity-0 group-hover:opacity-100 w-4 h-4 rounded-full bg-[var(--destructive)]/20 hover:bg-[var(--destructive)]/30 border border-[var(--destructive)]/30 hover:border-[var(--destructive)]/50 flex items-center justify-center transition-all duration-200 text-[var(--destructive)] hover:text-[var(--destructive)] text-xs disabled:opacity-50"
|
||||
title="Supprimer la tâche"
|
||||
>
|
||||
×
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { tasksClient, TaskFilters, CreateTaskData, UpdateTaskData } from '@/clients/tasks-client';
|
||||
import { Task, TaskStats } from '@/lib/types';
|
||||
import { tasksClient, TaskFilters, CreateTaskData } from '@/clients/tasks-client';
|
||||
import { updateTaskStatus } from '@/actions/tasks';
|
||||
import { Task, TaskStats, TaskStatus } from '@/lib/types';
|
||||
|
||||
interface UseTasksState {
|
||||
tasks: Task[];
|
||||
@@ -15,9 +16,7 @@ interface UseTasksState {
|
||||
interface UseTasksActions {
|
||||
refreshTasks: () => Promise<void>;
|
||||
createTask: (data: CreateTaskData) => Promise<Task | null>;
|
||||
updateTask: (data: UpdateTaskData) => Promise<Task | null>;
|
||||
updateTaskOptimistic: (data: UpdateTaskData) => Promise<Task | null>;
|
||||
deleteTask: (taskId: string) => Promise<void>;
|
||||
updateTaskOptimistic: (taskId: string, status: TaskStatus) => Promise<Task | null>;
|
||||
setFilters: (filters: TaskFilters) => void;
|
||||
}
|
||||
|
||||
@@ -95,35 +94,13 @@ export function useTasks(
|
||||
}
|
||||
}, [refreshTasks]);
|
||||
|
||||
/**
|
||||
* Met à jour une tâche
|
||||
*/
|
||||
const updateTask = useCallback(async (data: UpdateTaskData): Promise<Task | null> => {
|
||||
setState(prev => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await tasksClient.updateTask(data);
|
||||
|
||||
// Rafraîchir la liste après mise à jour
|
||||
await refreshTasks();
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Erreur lors de la mise à jour'
|
||||
}));
|
||||
return null;
|
||||
}
|
||||
}, [refreshTasks]);
|
||||
// Note: updateTask et deleteTask ont été migrés vers Server Actions
|
||||
// Voir /src/actions/tasks.ts pour updateTaskTitle, updateTaskStatus, deleteTask
|
||||
|
||||
/**
|
||||
* Met à jour une tâche de manière optimiste (pour drag & drop)
|
||||
* Met à jour le statut d'une tâche de manière optimiste (pour drag & drop)
|
||||
*/
|
||||
const updateTaskOptimistic = useCallback(async (data: UpdateTaskData): Promise<Task | null> => {
|
||||
const { taskId, ...updates } = data;
|
||||
|
||||
const updateTaskOptimistic = useCallback(async (taskId: string, status: TaskStatus): Promise<Task | null> => {
|
||||
// 1. Sauvegarder l'état actuel pour rollback
|
||||
const currentTasks = state.tasks;
|
||||
const taskToUpdate = currentTasks.find(t => t.id === taskId);
|
||||
@@ -134,7 +111,7 @@ export function useTasks(
|
||||
}
|
||||
|
||||
// 2. Mise à jour optimiste immédiate de l'état local
|
||||
const updatedTask = { ...taskToUpdate, ...updates };
|
||||
const updatedTask = { ...taskToUpdate, status };
|
||||
const updatedTasks = currentTasks.map(task =>
|
||||
task.id === taskId ? updatedTask : task
|
||||
);
|
||||
@@ -162,24 +139,17 @@ export function useTasks(
|
||||
syncing: true // Indiquer qu'une synchronisation est en cours
|
||||
}));
|
||||
|
||||
// 3. Appel API en arrière-plan
|
||||
// 3. Appel Server Action en arrière-plan
|
||||
try {
|
||||
const response = await tasksClient.updateTask(data);
|
||||
const result = await updateTaskStatus(taskId, status);
|
||||
|
||||
// Si l'API retourne des données différentes, on met à jour
|
||||
if (response.data) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
tasks: prev.tasks.map(task =>
|
||||
task.id === taskId ? response.data : task
|
||||
),
|
||||
syncing: false // Synchronisation terminée
|
||||
}));
|
||||
} else {
|
||||
// Si l'action réussit, la revalidation automatique se charge du reste
|
||||
if (result.success) {
|
||||
setState(prev => ({ ...prev, syncing: false }));
|
||||
return result.data as Task;
|
||||
} else {
|
||||
throw new Error(result.error || 'Erreur lors de la mise à jour');
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
// 4. Rollback en cas d'erreur
|
||||
setState(prev => ({
|
||||
@@ -207,26 +177,8 @@ export function useTasks(
|
||||
}
|
||||
}, [state.tasks]);
|
||||
|
||||
/**
|
||||
* Supprime une tâche
|
||||
*/
|
||||
const deleteTask = useCallback(async (taskId: string): Promise<void> => {
|
||||
setState(prev => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
await tasksClient.deleteTask(taskId);
|
||||
|
||||
// Rafraîchir la liste après suppression
|
||||
await refreshTasks();
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Erreur lors de la suppression'
|
||||
}));
|
||||
throw error; // Re-throw pour que l'UI puisse gérer l'erreur
|
||||
}
|
||||
}, [refreshTasks]);
|
||||
// Note: deleteTask a été migré vers Server Actions
|
||||
// Utilisez directement deleteTask depuis /src/actions/tasks.ts dans les composants
|
||||
|
||||
// Charger les tâches au montage seulement si pas de données initiales
|
||||
useEffect(() => {
|
||||
@@ -239,9 +191,7 @@ export function useTasks(
|
||||
...state,
|
||||
refreshTasks,
|
||||
createTask,
|
||||
updateTask,
|
||||
updateTaskOptimistic,
|
||||
deleteTask,
|
||||
setFilters
|
||||
};
|
||||
}
|
||||
|
||||
165
src/actions/tasks.ts
Normal file
165
src/actions/tasks.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
'use server'
|
||||
|
||||
import { tasksService } from '@/services/tasks';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { TaskStatus, TaskPriority } from '@/lib/types';
|
||||
|
||||
export type ActionResult<T = unknown> = {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Server Action pour mettre à jour le statut d'une tâche
|
||||
*/
|
||||
export async function updateTaskStatus(
|
||||
taskId: string,
|
||||
status: TaskStatus
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
const task = await tasksService.updateTask(taskId, { status });
|
||||
|
||||
// Revalidation automatique du cache
|
||||
revalidatePath('/');
|
||||
revalidatePath('/tasks');
|
||||
|
||||
return { success: true, data: task };
|
||||
} catch (error) {
|
||||
console.error('Error updating task status:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update task status'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Server Action pour mettre à jour le titre d'une tâche
|
||||
*/
|
||||
export async function updateTaskTitle(
|
||||
taskId: string,
|
||||
title: string
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
if (!title.trim()) {
|
||||
return { success: false, error: 'Title cannot be empty' };
|
||||
}
|
||||
|
||||
const task = await tasksService.updateTask(taskId, { title: title.trim() });
|
||||
|
||||
// Revalidation automatique du cache
|
||||
revalidatePath('/');
|
||||
revalidatePath('/tasks');
|
||||
|
||||
return { success: true, data: task };
|
||||
} catch (error) {
|
||||
console.error('Error updating task title:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update task title'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Server Action pour supprimer une tâche
|
||||
*/
|
||||
export async function deleteTask(taskId: string): Promise<ActionResult> {
|
||||
try {
|
||||
await tasksService.deleteTask(taskId);
|
||||
|
||||
// Revalidation automatique du cache
|
||||
revalidatePath('/');
|
||||
revalidatePath('/tasks');
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error deleting task:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to delete task'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Server Action pour mettre à jour une tâche complète (formulaire d'édition)
|
||||
*/
|
||||
export async function updateTask(data: {
|
||||
taskId: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
status?: TaskStatus;
|
||||
priority?: TaskPriority;
|
||||
tags?: string[];
|
||||
dueDate?: Date;
|
||||
}): Promise<ActionResult> {
|
||||
try {
|
||||
const updateData: Record<string, unknown> = {};
|
||||
|
||||
if (data.title !== undefined) {
|
||||
if (!data.title.trim()) {
|
||||
return { success: false, error: 'Title cannot be empty' };
|
||||
}
|
||||
updateData.title = data.title.trim();
|
||||
}
|
||||
|
||||
if (data.description !== undefined) updateData.description = data.description.trim();
|
||||
if (data.status !== undefined) updateData.status = data.status;
|
||||
if (data.priority !== undefined) updateData.priority = data.priority;
|
||||
if (data.tags !== undefined) updateData.tags = data.tags;
|
||||
if (data.dueDate !== undefined) updateData.dueDate = data.dueDate;
|
||||
|
||||
const task = await tasksService.updateTask(data.taskId, updateData);
|
||||
|
||||
// Revalidation automatique du cache
|
||||
revalidatePath('/');
|
||||
revalidatePath('/tasks');
|
||||
|
||||
return { success: true, data: task };
|
||||
} catch (error) {
|
||||
console.error('Error updating task:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update task'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Server Action pour créer une nouvelle tâche
|
||||
*/
|
||||
export async function createTask(data: {
|
||||
title: string;
|
||||
description?: string;
|
||||
status?: TaskStatus;
|
||||
priority?: TaskPriority;
|
||||
tags?: string[];
|
||||
}): Promise<ActionResult> {
|
||||
try {
|
||||
if (!data.title.trim()) {
|
||||
return { success: false, error: 'Title is required' };
|
||||
}
|
||||
|
||||
const task = await tasksService.createTask({
|
||||
title: data.title.trim(),
|
||||
description: data.description?.trim() || '',
|
||||
status: data.status || 'todo',
|
||||
priority: data.priority || 'medium',
|
||||
tags: data.tags || []
|
||||
});
|
||||
|
||||
// Revalidation automatique du cache
|
||||
revalidatePath('/');
|
||||
revalidatePath('/tasks');
|
||||
|
||||
return { success: true, data: task };
|
||||
} catch (error) {
|
||||
console.error('Error creating task:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to create task'
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -65,112 +65,8 @@ export async function GET(request: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API route pour créer une nouvelle tâche
|
||||
*/
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { title, description, status, priority, tags, dueDate } = body;
|
||||
|
||||
if (!title) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Le titre est requis'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const task = await tasksService.createTask({
|
||||
title,
|
||||
description,
|
||||
status: status as TaskStatus,
|
||||
priority: priority as TaskPriority,
|
||||
tags,
|
||||
dueDate: dueDate ? new Date(dueDate) : undefined
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: task,
|
||||
message: 'Tâche créée avec succès'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la création de la tâche:', error);
|
||||
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Erreur inconnue'
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API route pour mettre à jour une tâche
|
||||
*/
|
||||
export async function PATCH(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { taskId, ...updates } = body;
|
||||
|
||||
if (!taskId) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'taskId est requis'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
// Convertir dueDate si présent
|
||||
if (updates.dueDate) {
|
||||
updates.dueDate = new Date(updates.dueDate);
|
||||
}
|
||||
|
||||
const updatedTask = await tasksService.updateTask(taskId, updates);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: updatedTask,
|
||||
message: 'Tâche mise à jour avec succès'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la mise à jour de la tâche:', error);
|
||||
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Erreur inconnue'
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API route pour supprimer une tâche
|
||||
*/
|
||||
export async function DELETE(request: Request) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const taskId = searchParams.get('taskId');
|
||||
|
||||
if (!taskId) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'taskId est requis'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
await tasksService.deleteTask(taskId);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Tâche supprimée avec succès'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erreur lors de la suppression de la tâche:', error);
|
||||
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Erreur inconnue'
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
// POST, PATCH, DELETE methods have been migrated to Server Actions
|
||||
// See /src/actions/tasks.ts for:
|
||||
// - createTask (replaces POST)
|
||||
// - updateTask, updateTaskStatus, updateTaskTitle (replaces PATCH)
|
||||
// - deleteTask (replaces DELETE)
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTasks } from '@/hooks/useTasks';
|
||||
import { useTags } from '@/hooks/useTags';
|
||||
import { useUserPreferences } from './UserPreferencesContext';
|
||||
import { Task, Tag, TaskStats } from '@/lib/types';
|
||||
import { CreateTaskData, UpdateTaskData, TaskFilters } from '@/clients/tasks-client';
|
||||
import { CreateTaskData, TaskFilters } from '@/clients/tasks-client';
|
||||
import { KanbanFilters } from '@/components/kanban/KanbanFilters';
|
||||
import { sortTasks, getSortOption, DEFAULT_SORT, createSortKey } from '@/lib/sort-config';
|
||||
|
||||
@@ -17,9 +17,7 @@ interface TasksContextType {
|
||||
syncing: boolean;
|
||||
error: string | null;
|
||||
createTask: (data: CreateTaskData) => Promise<Task | null>;
|
||||
updateTask: (data: UpdateTaskData) => Promise<Task | null>;
|
||||
updateTaskOptimistic: (data: UpdateTaskData) => Promise<Task | null>;
|
||||
deleteTask: (taskId: string) => Promise<void>;
|
||||
updateTaskOptimistic: (taskId: string, status: TaskStatus) => Promise<Task | null>;
|
||||
refreshTasks: () => Promise<void>;
|
||||
setFilters: (filters: TaskFilters) => void;
|
||||
// Kanban filters
|
||||
|
||||
Reference in New Issue
Block a user