diff --git a/components/kanban/TaskCard.tsx b/components/kanban/TaskCard.tsx index 79e3156..bb13824 100644 --- a/components/kanban/TaskCard.tsx +++ b/components/kanban/TaskCard.tsx @@ -186,32 +186,36 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle }: TaskCardProp )} - {/* Footer tech avec séparateur néon */} -
-
- {task.dueDate ? ( - - - {formatDistanceToNow(new Date(task.dueDate), { - addSuffix: true, - locale: fr - })} - - ) : ( - --:-- - )} - - {task.source !== 'manual' && task.source && ( - - {task.source} - - )} - - {task.completedAt && ( - ✓ DONE - )} + {/* Footer tech avec séparateur néon - seulement si des données à afficher */} + {(task.dueDate || (task.source && task.source !== 'manual') || task.completedAt) && ( +
+
+ {task.dueDate ? ( + + + {formatDistanceToNow(new Date(task.dueDate), { + addSuffix: true, + locale: fr + })} + + ) : ( +
+ )} + +
+ {task.source !== 'manual' && task.source && ( + + {task.source} + + )} + + {task.completedAt && ( + ✓ DONE + )} +
+
-
+ )} ); } diff --git a/components/ui/TagList.tsx b/components/ui/TagList.tsx index 48edffe..f2e2fb5 100644 --- a/components/ui/TagList.tsx +++ b/components/ui/TagList.tsx @@ -1,5 +1,4 @@ import { Tag } from '@/lib/types'; -import { Button } from '@/components/ui/Button'; interface TagListProps { tags: (Tag & { usage?: number })[]; @@ -7,13 +6,15 @@ interface TagListProps { onTagDelete?: (tag: Tag) => void; showActions?: boolean; showUsage?: boolean; + deletingTagId?: string | null; } export function TagList({ tags, onTagEdit, onTagDelete, - showActions = true + showActions = true, + deletingTagId }: TagListProps) { if (tags.length === 0) { return ( @@ -27,10 +28,15 @@ export function TagList({ return (
- {tags.map((tag) => ( + {tags.map((tag) => { + const isDeleting = deletingTagId === tag.id; + + return (
{/* Contenu principal */}
@@ -57,24 +63,21 @@ export function TagList({ {showActions && (onTagEdit || onTagDelete) && (
{onTagEdit && ( - + )} {onTagDelete && ( - + {isDeleting ? '⏳' : '🗑️'} + )}
)} @@ -85,7 +88,8 @@ export function TagList({ style={{ backgroundColor: tag.color }} />
- ))} + ); + })}
); } diff --git a/hooks/useTasks.ts b/hooks/useTasks.ts index cefc23c..671736b 100644 --- a/hooks/useTasks.ts +++ b/hooks/useTasks.ts @@ -222,10 +222,12 @@ export function useTasks( } }, [refreshTasks]); - // Charger les tâches au montage et quand les filtres changent + // Charger les tâches au montage seulement si pas de données initiales useEffect(() => { - refreshTasks(); - }, [refreshTasks]); + if (!initialData?.tasks?.length) { + refreshTasks(); + } + }, [refreshTasks, initialData?.tasks?.length]); return { ...state, diff --git a/services/tags.ts b/services/tags.ts index 658494e..21d3b59 100644 --- a/services/tags.ts +++ b/services/tags.ts @@ -133,7 +133,7 @@ export const tagsService = { }, /** - * Supprime un tag + * Supprime un tag et toutes ses relations */ async deleteTag(id: string): Promise { // Vérifier que le tag existe @@ -142,15 +142,12 @@ export const tagsService = { throw new Error(`Tag avec l'ID "${id}" non trouvé`); } - // Vérifier si le tag est utilisé par des tâches via la relation TaskTag - const taskTagCount = await prisma.taskTag.count({ + // Supprimer d'abord toutes les relations TaskTag + await prisma.taskTag.deleteMany({ where: { tagId: id } }); - if (taskTagCount > 0) { - throw new Error(`Impossible de supprimer le tag "${existing.name}" car il est utilisé par ${taskTagCount} tâche(s)`); - } - + // Puis supprimer le tag lui-même await prisma.tag.delete({ where: { id } }); diff --git a/services/tasks.ts b/services/tasks.ts index e8c975c..4943ddd 100644 --- a/services/tasks.ts +++ b/services/tasks.ts @@ -38,7 +38,7 @@ export class TasksService { } } }, - take: filters?.limit || 100, + take: filters?.limit, // Pas de limite par défaut - récupère toutes les tâches skip: filters?.offset || 0, orderBy: [ { completedAt: 'desc' }, diff --git a/src/app/page.tsx b/src/app/page.tsx index 14ecebd..8f23f1f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -4,7 +4,7 @@ import { HomePageClient } from '@/components/HomePageClient'; export default async function HomePage() { // SSR - Récupération des données côté serveur const [initialTasks, initialStats] = await Promise.all([ - tasksService.getTasks({ limit: 20 }), + tasksService.getTasks(), tasksService.getTaskStats() ]); diff --git a/src/app/tags/TagsPageClient.tsx b/src/app/tags/TagsPageClient.tsx index 86d2e2d..14b4b9f 100644 --- a/src/app/tags/TagsPageClient.tsx +++ b/src/app/tags/TagsPageClient.tsx @@ -1,6 +1,7 @@ 'use client'; import { useState } from 'react'; +import React from 'react'; import { Tag } from '@/lib/types'; import { useTags } from '@/hooks/useTags'; import { CreateTagData, UpdateTagData } from '@/clients/tags-client'; @@ -32,11 +33,20 @@ export function TagsPageClient({ initialTags }: TagsPageClientProps) { const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [editingTag, setEditingTag] = useState(null); const [showPopular, setShowPopular] = useState(false); + const [deletingTagId, setDeletingTagId] = useState(null); + const [localTags, setLocalTags] = useState(initialTags); - // Utiliser les tags initiaux si pas encore chargés - const displayTags = tags.length > 0 ? tags : initialTags; + // Utiliser les tags du hook s'ils sont chargés, sinon les tags locaux + const displayTags = tags.length > 0 ? tags : localTags; const filteredTags = searchQuery ? searchResults : displayTags; + // Synchroniser les tags locaux avec les tags du hook une seule fois + React.useEffect(() => { + if (tags.length > 0 && localTags === initialTags) { + setLocalTags(tags); + } + }, [tags, localTags, initialTags]); + const handleSearch = async (query: string) => { setSearchQuery(query); if (query.trim()) { @@ -67,13 +77,22 @@ export function TagsPageClient({ initialTags }: TagsPageClientProps) { }; const handleDeleteTag = async (tag: Tag) => { - if (confirm(`Êtes-vous sûr de vouloir supprimer le tag "${tag.name}" ?`)) { - try { - await deleteTag(tag.id); - } catch (error) { - // L'erreur est déjà gérée dans le hook - console.error('Erreur lors de la suppression:', error); - } + // Suppression optimiste : retirer immédiatement de l'affichage + setDeletingTagId(tag.id); + + // Mettre à jour les tags locaux pour suppression immédiate + const updatedTags = displayTags.filter(t => t.id !== tag.id); + setLocalTags(updatedTags); + + try { + await deleteTag(tag.id); + // Succès : les tags seront mis à jour par le hook + } catch (error) { + // En cas d'erreur, restaurer le tag dans l'affichage + setLocalTags(prev => [...prev, tag].sort((a, b) => a.name.localeCompare(b.name))); + console.error('Erreur lors de la suppression:', error); + } finally { + setDeletingTagId(null); } }; @@ -122,14 +141,14 @@ export function TagsPageClient({ initialTags }: TagsPageClientProps) { @@ -194,7 +213,7 @@ export function TagsPageClient({ initialTags }: TagsPageClientProps) {
)} - {loading && ( + {loading && displayTags.length === 0 && (
Chargement...
@@ -215,6 +234,7 @@ export function TagsPageClient({ initialTags }: TagsPageClientProps) { onTagEdit={handleEditTag} onTagDelete={handleDeleteTag} showUsage={true} + deletingTagId={deletingTagId} />
@@ -225,7 +245,7 @@ export function TagsPageClient({ initialTags }: TagsPageClientProps) { isOpen={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} onSubmit={handleCreateTag} - loading={loading} + loading={false} /> setEditingTag(null)} onSubmit={handleUpdateTag} tag={editingTag} - loading={loading} + loading={false} /> );