diff --git a/clients/tags-client.ts b/clients/tags-client.ts index 9b2972a..4da48e3 100644 --- a/clients/tags-client.ts +++ b/clients/tags-client.ts @@ -21,7 +21,7 @@ export interface TagFilters { // Types pour les réponses export interface TagsResponse { - data: Tag[]; + data: Array; message: string; } diff --git a/hooks/useTags.ts b/hooks/useTags.ts index 7ca0cd1..a1ee690 100644 --- a/hooks/useTags.ts +++ b/hooks/useTags.ts @@ -5,7 +5,7 @@ import { tagsClient, TagFilters, CreateTagData, UpdateTagData, TagsClient } from import { Tag } from '@/lib/types'; interface UseTagsState { - tags: Tag[]; + tags: Array; popularTags: Array; loading: boolean; error: string | null; @@ -29,7 +29,7 @@ export function useTags( initialFilters?: TagFilters ): UseTagsState & UseTagsActions { const [state, setState] = useState({ - tags: initialData?.map(tag => ({ id: tag.id, name: tag.name, color: tag.color, isPinned: tag.isPinned })) || [], + tags: initialData || [], popularTags: initialData || [], loading: !initialData, error: null diff --git a/src/app/tags/TagsPageClient.tsx b/src/app/tags/TagsPageClient.tsx index 14b4b9f..b86da3b 100644 --- a/src/app/tags/TagsPageClient.tsx +++ b/src/app/tags/TagsPageClient.tsx @@ -1,13 +1,12 @@ 'use client'; -import { useState } from 'react'; +import { useState, useMemo } from 'react'; import React from 'react'; import { Tag } from '@/lib/types'; import { useTags } from '@/hooks/useTags'; -import { CreateTagData, UpdateTagData } from '@/clients/tags-client'; +import { CreateTagData } from '@/clients/tags-client'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; -import { TagList } from '@/components/ui/TagList'; import { TagForm } from '@/components/forms/TagForm'; import Link from 'next/link'; @@ -18,44 +17,38 @@ interface TagsPageClientProps { export function TagsPageClient({ initialTags }: TagsPageClientProps) { const { tags, - popularTags, loading, error, createTag, updateTag, - deleteTag, - getPopularTags, - searchTags - } = useTags(); + deleteTag + } = useTags(initialTags as (Tag & { usage: number })[]); const [searchQuery, setSearchQuery] = useState(''); - const [searchResults, setSearchResults] = useState([]); 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 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); + // Filtrer et trier les tags + const filteredAndSortedTags = useMemo(() => { + let filtered = tags; + + // Filtrer par recherche + if (searchQuery.trim()) { + const query = searchQuery.toLowerCase(); + filtered = tags.filter(tag => + tag.name.toLowerCase().includes(query) + ); } - }, [tags, localTags, initialTags]); - - const handleSearch = async (query: string) => { - setSearchQuery(query); - if (query.trim()) { - const results = await searchTags(query); - setSearchResults(results); - } else { - setSearchResults([]); - } - }; + + // Trier par usage puis par nom + return filtered.sort((a, b) => { + const usageA = (a as Tag & { usage?: number }).usage || 0; + const usageB = (b as Tag & { usage?: number }).usage || 0; + if (usageB !== usageA) return usageB - usageA; + return a.name.localeCompare(b.name); + }); + }, [tags, searchQuery]); const handleCreateTag = async (data: CreateTagData) => { await createTag(data); @@ -66,7 +59,7 @@ export function TagsPageClient({ initialTags }: TagsPageClientProps) { setEditingTag(tag); }; - const handleUpdateTag = async (data: { name: string; color: string }) => { + const handleUpdateTag = async (data: { name: string; color: string; isPinned?: boolean }) => { if (editingTag) { await updateTag({ tagId: editingTag.id, @@ -77,38 +70,22 @@ export function TagsPageClient({ initialTags }: TagsPageClientProps) { }; const handleDeleteTag = async (tag: Tag) => { - // 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); } }; - const handleShowPopular = async () => { - if (!showPopular) { - await getPopularTags(10); - } - setShowPopular(!showPopular); - }; - return (
- {/* Header */} + {/* Header simplifié */}
-
-
+
+
{/* Bouton retour */}
-
-

- Gestion des Tags -

-

- Organisez et gérez vos étiquettes -

-
+

+ Tags ({filteredAndSortedTags.length}) +

-
- - -
+
{/* Contenu principal */} -
-
- {/* Barre de recherche */} -
- - handleSearch(e.target.value)} - placeholder="Tapez pour rechercher..." - className="max-w-md" - /> -
- - {/* Statistiques */} -
-
-
- {displayTags.length} -
-
- Tags total -
-
- -
-
- {popularTags.length > 0 ? popularTags[0]?.usage || 0 : 0} -
-
- Plus utilisé -
-
- -
-
- {searchQuery ? searchResults.length : displayTags.length} -
-
- {searchQuery ? 'Résultats' : 'Affichés'} -
-
-
- - {/* Messages d'état */} - {error && ( -
-
- Erreur : {error} -
-
- )} - - {loading && displayTags.length === 0 && ( -
-
Chargement...
-
- )} - - {/* Liste des tags */} -
-

- {showPopular - ? 'Tags populaires' - : searchQuery - ? `Résultats pour "${searchQuery}"` - : 'Tous les tags' - } -

- -
+
+ {/* Barre de recherche */} +
+ setSearchQuery(e.target.value)} + placeholder="Rechercher un tag..." + className="w-full" + />
+ + {/* Messages d'état */} + {error && ( +
+
Erreur : {error}
+
+ )} + + {loading && ( +
+
Chargement...
+
+ )} + + {/* Tags en grille compacte */} + {!loading && ( +
+ {filteredAndSortedTags.length === 0 ? ( +
+
🏷️
+

+ {searchQuery ? 'Aucun tag trouvé' : 'Aucun tag'} +

+

+ {searchQuery ? 'Essayez un autre terme' : 'Créez votre premier tag'} +

+
+ ) : ( +
+ {filteredAndSortedTags.map((tag) => { + const isDeleting = deletingTagId === tag.id; + const usage = (tag as Tag & { usage?: number }).usage || 0; + + return ( +
+ {/* Actions en overlay */} +
+ + +
+ + {/* Contenu principal */} +
+
+
+

+ {tag.name} +

+

+ {usage} tâche{usage > 1 ? 's' : ''} +

+ {tag.isPinned && ( + + 🎯 Objectif + + )} +
+
+
+ ); + })} +
+ )} +
+ )}
{/* Modals */}