Feature: Affichage tags et icône priorité dans les cartes notes

Ajout de deux nouvelles fonctionnalités pour les notes:

1. Affichage des tags dans les cartes repliées:
- Les tags sont maintenant visibles dans les cartes de notes sans avoir à les ouvrir
- Badge avec icône TagIcon et nom du tag
- Chargement automatique des tags depuis la base de données

2. Icône de priorité cliquable:
- Bouton "!" en haut à droite de chaque carte de note
- Rouge (!) quand la note est prioritaire (toujours visible)
- Gris clair (!) quand non prioritaire (visible au hover)
- Toggle au clic pour marquer/démarquer comme prioritaire
- Les notes prioritaires sont triées en premier

Backend:
- Route PATCH /api/notes/:id/priority pour toggle la priorité
- Modification GET /api/notes pour charger tags et priority
- Tri par priority DESC, updated_at DESC
- Migration automatique pour ajouter colonne priority
- Index pour performance des requêtes de tri

Frontend:
- Interface Note étendue avec tags et priority
- Méthode togglePriority dans NotesService
- Affichage conditionnel de l'icône (toujours visible si prioritaire)
- Empêche le clic sur l'icône d'ouvrir la note (stopPropagation)

Migration:
- Ajout automatique de la colonne priority (INTEGER DEFAULT 0)
- Création d'index idx_notes_priority pour performance
- Fichier de migration 002_add_priority.sql créé pour référence
This commit is contained in:
Claude committed 2025-11-12 21:41:34 +00:00
1 parent b4e2c254ed
commit b36525919f
5 files changed
+119 -7

No files matched your search

+33 -2
View File
@@ -944,11 +944,34 @@ const Index = () => {
paginatedNotes.map(note => (
<Card
key={note.id}
className="cursor-pointer hover:shadow-md transition-all group"
className="cursor-pointer hover:shadow-md transition-all group relative"
onClick={() => handleOpenNote(note)}
>
<CardContent className="p-4">
<h3 className="font-semibold text-base mb-2 line-clamp-1">
{/* Priority icon */}
<Button
variant="ghost"
size="icon"
className={`absolute top-2 right-2 h-6 w-6 transition-opacity ${
note.priority ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
}`}
onClick={(e) => {
e.stopPropagation();
const newPriority = !note.priority;
if (note.id) {
NotesService.togglePriority(note.id, newPriority);
setNotes(notes.map(n => n.id === note.id ? { ...n, priority: newPriority } : n));
}
}}
>
{note.priority ? (
<span className="text-red-500 text-lg font-bold">!</span>
) : (
<span className="text-muted-foreground text-lg">!</span>
)}
</Button>
<h3 className="font-semibold text-base mb-2 line-clamp-1 pr-8">
{note.title || "Sans titre"}
</h3>
<p className="text-sm text-muted-foreground line-clamp-2 mb-3">
@@ -973,6 +996,14 @@ const Index = () => {
{note.files.length}
</Badge>
)}
{note.tags && note.tags.length > 0 && (
note.tags.map((tag) => (
<Badge key={tag.id} variant="outline" className="text-xs">
<TagIcon className="h-3 w-3 mr-1" />
{tag.name}
</Badge>
))
)}
</div>
{note.updated_at && (
<p className="text-xs text-muted-foreground">
+23 -2
View File
@@ -31,6 +31,8 @@ export interface Note {
todos: Todo[];
images: Image[];
files?: NoteFile[];
tags?: Array<{ id: number; name: string }>;
priority?: boolean;
}
class NotesService {
@@ -158,11 +160,30 @@ class NotesService {
method: "DELETE",
headers: AuthService.getHeaders()
});
if (!response.ok) {
throw new Error("Erreur lors de la suppression de l'image");
}
return true;
} catch (error) {
showError(error instanceof Error ? error.message : "Erreur serveur");
return false;
}
}
async togglePriority(noteId: number, priority: boolean): Promise<boolean> {
try {
const response = await fetch(`/api/notes/${noteId}/priority`, {
method: "PATCH",
headers: AuthService.getHeaders(),
body: JSON.stringify({ priority })
});
if (!response.ok) {
throw new Error("Erreur lors de la mise à jour de la priorité");
}
return true;
} catch (error) {
showError(error instanceof Error ? error.message : "Erreur serveur");