Feature: Amélioration de l'interface Todos et RSS avec onglets

Cette mise à jour améliore l'interface utilisateur des tâches et des flux RSS
avec une meilleure organisation et de nouvelles fonctionnalités.

Changements principaux:

1. **Layout Todos et RSS côte à côte**
   - Les deux boxes sont maintenant affichées côte à côte
   - Meilleure utilisation de l'espace disponible
   - Layout en grid responsive

2. **Système d'onglets pour les Todos**
   - Onglet "Actives" : affiche uniquement les tâches non complétées
   - Onglet "Complétées" : affiche les tâches terminées avec style barré
   - Séparation claire entre tâches en cours et terminées
   - Possibilité de re-activer une tâche complétée en cliquant dessus

3. **Endpoint PATCH /api/todos/:id/toggle ajouté**
   - Nouvelle route backend pour basculer l'état d'une tâche
   - Résout l'erreur 404 précédente
   - Logging des actions pour suivi

4. **Optimisations visuelles**
   - Tailles de texte réduites (text-xs) pour plus de contenu visible
   - Hauteurs de boutons optimisées
   - Espacements améliorés
   - Animations et transitions fluides

Fichiers modifiés:
- routes/todos.routes.js : ajout de la route toggle
- src/pages/Index.tsx : refonte de la sidebar droite avec onglets

Cette version améliore significativement l'expérience utilisateur pour
la gestion des tâches avec une séparation claire entre tâches actives
et complétées.
This commit is contained in:
Claude committed 2025-11-12 08:17:40 +00:00
1 parent 6dd3270f57
commit f0c68b9db1
2 files changed
+159 -95

No files matched your search

+24
View File
@@ -114,6 +114,30 @@ router.put('/:id',
}
);
/**
* PATCH /api/todos/:id/toggle
* Basculer l'état completed d'un todo
*/
router.patch('/:id/toggle', async (req, res) => {
try {
// Vérifier que le todo appartient à l'utilisateur
const todo = await getOne('SELECT id, completed FROM global_todos WHERE id = ? AND user_id = ?', [req.params.id, req.user.id]);
if (!todo) {
return res.status(404).json({ error: 'Todo non trouvé' });
}
const newCompleted = todo.completed ? 0 : 1;
await runQuery('UPDATE global_todos SET completed = ? WHERE id = ?', [newCompleted, req.params.id]);
logger.info(`Todo global ${newCompleted ? 'complété' : 'réouvert'} (ID: ${req.params.id}) par ${req.user.username}`);
res.json({ message: 'Todo modifié avec succès', completed: newCompleted === 1 });
} catch (error) {
logger.error('Erreur lors du toggle du todo:', error);
res.status(500).json({ error: 'Erreur serveur' });
}
});
/**
* DELETE /api/todos/:id
* Supprimer un todo global
+135 -95
View File
@@ -755,105 +755,145 @@ const Index = () => {
{/* Right Sidebar - Todos & RSS */}
<div className="space-y-6">
{/* Todos Box */}
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-lg flex items-center gap-2">
<CheckSquare className="h-5 w-5" />
Tâches
</CardTitle>
<Button size="sm" variant="ghost" onClick={handleAddTodo}>
<Plus className="h-4 w-4" />
</Button>
</div>
</CardHeader>
<CardContent>
<div className="space-y-2 max-h-[300px] overflow-y-auto">
{todos.length > 0 ? (
todos.map(todo => (
<div key={todo.id} className="flex items-center gap-2 p-2 border rounded-md hover:bg-accent/50 transition-colors group">
<Checkbox
checked={todo.completed}
onCheckedChange={() => todo.id && handleToggleTodo(todo.id)}
/>
<span className={`text-sm flex-1 ${todo.completed ? 'line-through text-muted-foreground' : ''}`}>
{todo.text}
</span>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={() => todo.id && handleDeleteTodo(todo.id)}
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
))
) : (
<p className="text-sm text-center text-muted-foreground py-4">Aucune tâche</p>
)}
</div>
</CardContent>
</Card>
{/* RSS Box */}
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-lg flex items-center gap-2">
<Rss className="h-5 w-5" />
Flux RSS
</CardTitle>
<div className="flex gap-1">
<Button size="sm" variant="ghost" onClick={handleRefreshRss}>
<RefreshCw className="h-4 w-4" />
</Button>
<Button size="sm" variant="ghost" onClick={handleAddRssFeed}>
<Plus className="h-4 w-4" />
{/* Boxes côte à côte */}
<div className="grid grid-cols-2 gap-4">
{/* Todos Box */}
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base flex items-center gap-2">
<CheckSquare className="h-4 w-4" />
Tâches
</CardTitle>
<Button size="sm" variant="ghost" onClick={handleAddTodo} className="h-7 w-7 p-0">
<Plus className="h-3 w-3" />
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<div className="space-y-3 max-h-[400px] overflow-y-auto">
{rssArticles.length > 0 ? (
rssArticles.map(article => (
<div
key={article.id}
className="p-2 border rounded-md hover:bg-accent/50 transition-colors cursor-pointer"
onClick={() => article.link && window.open(article.link, '_blank')}
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<h4 className="text-sm font-medium line-clamp-2 mb-1">{article.title}</h4>
{article.description && (
<p className="text-xs text-muted-foreground line-clamp-2">
{article.description.replace(/<[^>]*>/g, '')}
</p>
)}
</div>
<ExternalLink className="h-3 w-3 text-muted-foreground flex-shrink-0 mt-1" />
</div>
</CardHeader>
<CardContent className="pb-3">
<Tabs defaultValue="active" className="w-full">
<TabsList className="grid grid-cols-2 w-full h-8">
<TabsTrigger value="active" className="text-xs">Actives</TabsTrigger>
<TabsTrigger value="completed" className="text-xs">Complétées</TabsTrigger>
</TabsList>
<TabsContent value="active" className="mt-2">
<div className="space-y-2 max-h-[250px] overflow-y-auto">
{todos.filter(t => !t.completed).length > 0 ? (
todos.filter(t => !t.completed).map(todo => (
<div key={todo.id} className="flex items-center gap-2 p-2 border rounded-md hover:bg-accent/50 transition-colors group">
<Checkbox
checked={false}
onCheckedChange={() => todo.id && handleToggleTodo(todo.id)}
/>
<span className="text-xs flex-1">
{todo.text}
</span>
<Button
variant="ghost"
size="icon"
className="h-5 w-5 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={() => todo.id && handleDeleteTodo(todo.id)}
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
))
) : (
<p className="text-xs text-center text-muted-foreground py-4">Aucune tâche active</p>
)}
</div>
))
) : (
<p className="text-sm text-center text-muted-foreground py-4">
Aucun article
<br />
<Button
variant="link"
size="sm"
className="mt-2"
onClick={handleAddRssFeed}
>
Ajouter un flux RSS
</TabsContent>
<TabsContent value="completed" className="mt-2">
<div className="space-y-2 max-h-[250px] overflow-y-auto">
{todos.filter(t => t.completed).length > 0 ? (
todos.filter(t => t.completed).map(todo => (
<div key={todo.id} className="flex items-center gap-2 p-2 border rounded-md hover:bg-accent/50 transition-colors group">
<Checkbox
checked={true}
onCheckedChange={() => todo.id && handleToggleTodo(todo.id)}
/>
<span className="text-xs flex-1 line-through text-muted-foreground">
{todo.text}
</span>
<Button
variant="ghost"
size="icon"
className="h-5 w-5 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={() => todo.id && handleDeleteTodo(todo.id)}
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
))
) : (
<p className="text-xs text-center text-muted-foreground py-4">Aucune tâche complétée</p>
)}
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
{/* RSS Box */}
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-base flex items-center gap-2">
<Rss className="h-4 w-4" />
Flux RSS
</CardTitle>
<div className="flex gap-1">
<Button size="sm" variant="ghost" onClick={handleRefreshRss} className="h-7 w-7 p-0">
<RefreshCw className="h-3 w-3" />
</Button>
</p>
)}
</div>
</CardContent>
</Card>
<Button size="sm" variant="ghost" onClick={handleAddRssFeed} className="h-7 w-7 p-0">
<Plus className="h-3 w-3" />
</Button>
</div>
</div>
</CardHeader>
<CardContent className="pb-3">
<div className="space-y-2 max-h-[330px] overflow-y-auto">
{rssArticles.length > 0 ? (
rssArticles.map(article => (
<div
key={article.id}
className="p-2 border rounded-md hover:bg-accent/50 transition-colors cursor-pointer"
onClick={() => article.link && window.open(article.link, '_blank')}
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<h4 className="text-xs font-medium line-clamp-2 mb-1">{article.title}</h4>
{article.description && (
<p className="text-xs text-muted-foreground line-clamp-2">
{article.description.replace(/<[^>]*>/g, '')}
</p>
)}
</div>
<ExternalLink className="h-3 w-3 text-muted-foreground flex-shrink-0 mt-1" />
</div>
</div>
))
) : (
<p className="text-xs text-center text-muted-foreground py-4">
Aucun article
<br />
<Button
variant="link"
size="sm"
className="mt-2 text-xs"
onClick={handleAddRssFeed}
>
Ajouter un flux RSS
</Button>
</p>
)}
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</div>