Merge pull request #29 from R0m1k3/claude/fix-npm-ci-build-error-011CV3c2K4ETHdBRQUs7tkJf

Feature: Amélioration de l'interface Todos et RSS avec onglets
This commit is contained in:
LogiFlow authored and GitHub committed 2025-11-12 09:21:22 +01:00
commit f1890b78e6
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 * DELETE /api/todos/:id
* Supprimer un todo global * Supprimer un todo global
+135 -95
View File
@@ -755,105 +755,145 @@ const Index = () => {
{/* Right Sidebar - Todos & RSS */} {/* Right Sidebar - Todos & RSS */}
<div className="space-y-6"> <div className="space-y-6">
{/* Todos Box */} {/* Boxes côte à côte */}
<Card> <div className="grid grid-cols-2 gap-4">
<CardHeader className="pb-3"> {/* Todos Box */}
<div className="flex items-center justify-between"> <Card>
<CardTitle className="text-lg flex items-center gap-2"> <CardHeader className="pb-3">
<CheckSquare className="h-5 w-5" /> <div className="flex items-center justify-between">
Tâches <CardTitle className="text-base flex items-center gap-2">
</CardTitle> <CheckSquare className="h-4 w-4" />
<Button size="sm" variant="ghost" onClick={handleAddTodo}> Tâches
<Plus className="h-4 w-4" /> </CardTitle>
</Button> <Button size="sm" variant="ghost" onClick={handleAddTodo} className="h-7 w-7 p-0">
</div> <Plus className="h-3 w-3" />
</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" />
</Button> </Button>
</div> </div>
</div> </CardHeader>
</CardHeader> <CardContent className="pb-3">
<CardContent> <Tabs defaultValue="active" className="w-full">
<div className="space-y-3 max-h-[400px] overflow-y-auto"> <TabsList className="grid grid-cols-2 w-full h-8">
{rssArticles.length > 0 ? ( <TabsTrigger value="active" className="text-xs">Actives</TabsTrigger>
rssArticles.map(article => ( <TabsTrigger value="completed" className="text-xs">Complétées</TabsTrigger>
<div </TabsList>
key={article.id}
className="p-2 border rounded-md hover:bg-accent/50 transition-colors cursor-pointer" <TabsContent value="active" className="mt-2">
onClick={() => article.link && window.open(article.link, '_blank')} <div className="space-y-2 max-h-[250px] overflow-y-auto">
> {todos.filter(t => !t.completed).length > 0 ? (
<div className="flex items-start justify-between gap-2"> todos.filter(t => !t.completed).map(todo => (
<div className="flex-1 min-w-0"> <div key={todo.id} className="flex items-center gap-2 p-2 border rounded-md hover:bg-accent/50 transition-colors group">
<h4 className="text-sm font-medium line-clamp-2 mb-1">{article.title}</h4> <Checkbox
{article.description && ( checked={false}
<p className="text-xs text-muted-foreground line-clamp-2"> onCheckedChange={() => todo.id && handleToggleTodo(todo.id)}
{article.description.replace(/<[^>]*>/g, '')} />
</p> <span className="text-xs flex-1">
)} {todo.text}
</div> </span>
<ExternalLink className="h-3 w-3 text-muted-foreground flex-shrink-0 mt-1" /> <Button
</div> 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> </div>
)) </TabsContent>
) : (
<p className="text-sm text-center text-muted-foreground py-4"> <TabsContent value="completed" className="mt-2">
Aucun article <div className="space-y-2 max-h-[250px] overflow-y-auto">
<br /> {todos.filter(t => t.completed).length > 0 ? (
<Button todos.filter(t => t.completed).map(todo => (
variant="link" <div key={todo.id} className="flex items-center gap-2 p-2 border rounded-md hover:bg-accent/50 transition-colors group">
size="sm" <Checkbox
className="mt-2" checked={true}
onClick={handleAddRssFeed} onCheckedChange={() => todo.id && handleToggleTodo(todo.id)}
> />
Ajouter un flux RSS <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> </Button>
</p> <Button size="sm" variant="ghost" onClick={handleAddRssFeed} className="h-7 w-7 p-0">
)} <Plus className="h-3 w-3" />
</div> </Button>
</CardContent> </div>
</Card> </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> </div>
</div> </div>