refacto: passing by server actions on taskCard

This commit is contained in:
Julien Froidefond
2025-09-18 09:37:46 +02:00
parent 228e1563c6
commit 4a4eb9c8ad
15 changed files with 286 additions and 330 deletions

View File

@@ -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"
>
×