feat: complete tag management and UI integration
- Marked multiple tasks as completed in TODO.md related to tag management features. - Replaced manual tag input with `TagInput` component in `CreateTaskForm`, `EditTaskForm`, and `QuickAddTask` for better UX. - Updated `TaskCard` to display tags using `TagDisplay` with color support. - Enhanced `TasksService` to manage task-tag relationships with CRUD operations. - Integrated tag management into the global context for better accessibility across components.
This commit is contained in:
223
hooks/useTags.ts
Normal file
223
hooks/useTags.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { tagsClient, TagFilters, CreateTagData, UpdateTagData, TagsClient } from '@/clients/tags-client';
|
||||
import { Tag } from '@/lib/types';
|
||||
|
||||
interface UseTagsState {
|
||||
tags: Tag[];
|
||||
popularTags: Array<Tag & { usage: number }>;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface UseTagsActions {
|
||||
refreshTags: () => Promise<void>;
|
||||
searchTags: (query: string, limit?: number) => Promise<Tag[]>;
|
||||
createTag: (data: CreateTagData) => Promise<Tag | null>;
|
||||
updateTag: (data: UpdateTagData) => Promise<Tag | null>;
|
||||
deleteTag: (tagId: string) => Promise<void>;
|
||||
getPopularTags: (limit?: number) => Promise<void>;
|
||||
setFilters: (filters: TagFilters) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook pour la gestion des tags
|
||||
*/
|
||||
export function useTags(
|
||||
initialFilters?: TagFilters
|
||||
): UseTagsState & UseTagsActions {
|
||||
const [state, setState] = useState<UseTagsState>({
|
||||
tags: [],
|
||||
popularTags: [],
|
||||
loading: false,
|
||||
error: null
|
||||
});
|
||||
|
||||
const [filters, setFilters] = useState<TagFilters>(initialFilters || {});
|
||||
|
||||
/**
|
||||
* Récupère les tags depuis l'API
|
||||
*/
|
||||
const refreshTags = useCallback(async () => {
|
||||
setState(prev => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await tagsClient.getTags(filters);
|
||||
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
tags: response.data,
|
||||
loading: false
|
||||
}));
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Erreur inconnue'
|
||||
}));
|
||||
}
|
||||
}, [filters]);
|
||||
|
||||
/**
|
||||
* Recherche des tags par nom (pour autocomplete)
|
||||
*/
|
||||
const searchTags = useCallback(async (query: string, limit: number = 10): Promise<Tag[]> => {
|
||||
try {
|
||||
const response = await tagsClient.searchTags(query, limit);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la recherche de tags:', error);
|
||||
return [];
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Récupère les tags populaires
|
||||
*/
|
||||
const getPopularTags = useCallback(async (limit: number = 10) => {
|
||||
setState(prev => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await tagsClient.getPopularTags(limit);
|
||||
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
popularTags: response.data,
|
||||
loading: false
|
||||
}));
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Erreur inconnue'
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Crée un nouveau tag
|
||||
*/
|
||||
const createTag = useCallback(async (data: CreateTagData): Promise<Tag | null> => {
|
||||
setState(prev => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
// Validation côté client
|
||||
const errors = TagsClient.validateTagData(data);
|
||||
if (errors.length > 0) {
|
||||
throw new Error(errors[0]);
|
||||
}
|
||||
|
||||
const response = await tagsClient.createTag(data);
|
||||
|
||||
// Rafraîchir la liste après création
|
||||
await refreshTags();
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Erreur lors de la création'
|
||||
}));
|
||||
return null;
|
||||
}
|
||||
}, [refreshTags]);
|
||||
|
||||
/**
|
||||
* Met à jour un tag
|
||||
*/
|
||||
const updateTag = useCallback(async (data: UpdateTagData): Promise<Tag | null> => {
|
||||
setState(prev => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await tagsClient.updateTag(data);
|
||||
|
||||
// Rafraîchir la liste après mise à jour
|
||||
await refreshTags();
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Erreur lors de la mise à jour'
|
||||
}));
|
||||
return null;
|
||||
}
|
||||
}, [refreshTags]);
|
||||
|
||||
/**
|
||||
* Supprime un tag
|
||||
*/
|
||||
const deleteTag = useCallback(async (tagId: string): Promise<void> => {
|
||||
setState(prev => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
await tagsClient.deleteTag(tagId);
|
||||
|
||||
// Rafraîchir la liste après suppression
|
||||
await refreshTags();
|
||||
} 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
|
||||
}
|
||||
}, [refreshTags]);
|
||||
|
||||
// Charger les tags au montage et quand les filtres changent
|
||||
useEffect(() => {
|
||||
refreshTags();
|
||||
}, [refreshTags]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
refreshTags,
|
||||
searchTags,
|
||||
createTag,
|
||||
updateTag,
|
||||
deleteTag,
|
||||
getPopularTags,
|
||||
setFilters
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook simplifié pour l'autocomplete des tags
|
||||
*/
|
||||
export function useTagsAutocomplete() {
|
||||
const [suggestions, setSuggestions] = useState<Tag[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const searchTags = useCallback(async (query: string) => {
|
||||
if (!query.trim()) {
|
||||
setSuggestions([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await tagsClient.searchTags(query, 5);
|
||||
setSuggestions(response.data);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la recherche de tags:', error);
|
||||
setSuggestions([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const clearSuggestions = useCallback(() => {
|
||||
setSuggestions([]);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
suggestions,
|
||||
loading,
|
||||
searchTags,
|
||||
clearSuggestions
|
||||
};
|
||||
}
|
||||
@@ -162,9 +162,6 @@ export function useTasks(
|
||||
|
||||
// 3. Appel API en arrière-plan
|
||||
try {
|
||||
// Délai artificiel pour voir l'indicateur de sync (à supprimer en prod)
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const response = await tasksClient.updateTask(data);
|
||||
|
||||
// Si l'API retourne des données différentes, on met à jour
|
||||
|
||||
Reference in New Issue
Block a user