mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
Merge pull request #118 from R0m1k3/antigravity
feat: add comprehensive dashboard page with todo management, RSS feed…
This commit is contained in:
5 files changed
+129
-30
No files matched your search
Generated
+20
-24
@@ -138,6 +138,7 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -3681,6 +3682,7 @@
|
||||
"integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -3691,6 +3693,7 @@
|
||||
"integrity": "sha512-k5dJVszUiNr1DSe8Cs+knKR6IrqhqdhpUwzqhkS8ecQTSf3THNtbfIp/umqHMpX2bv+9dkx3fwDv/86LcSfvSg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -3701,6 +3704,7 @@
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -3757,6 +3761,7 @@
|
||||
"integrity": "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.46.4",
|
||||
"@typescript-eslint/types": "8.46.4",
|
||||
@@ -4042,6 +4047,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4476,6 +4482,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.25",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
@@ -5328,7 +5335,8 @@
|
||||
"version": "8.6.0",
|
||||
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
||||
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/embla-carousel-react": {
|
||||
"version": "8.6.0",
|
||||
@@ -5373,29 +5381,6 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
@@ -5552,6 +5537,7 @@
|
||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -7006,6 +6992,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
@@ -8239,6 +8226,7 @@
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"pg-connection-string": "^2.9.1",
|
||||
"pg-pool": "^3.10.1",
|
||||
@@ -8378,6 +8366,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -8768,6 +8757,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -8808,6 +8798,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -9860,6 +9851,7 @@
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
|
||||
"integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
@@ -10064,6 +10056,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -10192,6 +10185,7 @@
|
||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -10439,6 +10433,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -10530,6 +10525,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
+48
-5
@@ -19,7 +19,7 @@ router.get('/', async (req, res) => {
|
||||
// Essayer d'abord avec le champ priority (nouvelle version)
|
||||
try {
|
||||
const todos = await getAll(`
|
||||
SELECT id, text, completed, priority, created_at
|
||||
SELECT id, text, completed, priority, in_progress, created_at
|
||||
FROM global_todos
|
||||
WHERE user_id = $1
|
||||
ORDER BY priority DESC, created_at DESC
|
||||
@@ -37,8 +37,8 @@ router.get('/', async (req, res) => {
|
||||
ORDER BY created_at DESC
|
||||
`, [req.user.id]);
|
||||
|
||||
// Ajouter priority=false par défaut pour compatibilité frontend
|
||||
const todosWithPriority = todos.map(t => ({ ...t, priority: false }));
|
||||
// Ajouter priority=false et in_progress=false par défaut pour compatibilité frontend
|
||||
const todosWithPriority = todos.map(t => ({ ...t, priority: false, in_progress: false }));
|
||||
res.json(todosWithPriority);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -77,6 +77,7 @@ router.post('/',
|
||||
text,
|
||||
completed: false,
|
||||
priority: false,
|
||||
in_progress: false,
|
||||
created_at: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -94,7 +95,8 @@ router.put('/:id',
|
||||
[
|
||||
body('text').optional().trim().notEmpty(),
|
||||
body('completed').optional().isBoolean(),
|
||||
body('priority').optional().isBoolean()
|
||||
body('priority').optional().isBoolean(),
|
||||
body('in_progress').optional().isBoolean()
|
||||
],
|
||||
async (req, res) => {
|
||||
try {
|
||||
@@ -103,7 +105,7 @@ router.put('/:id',
|
||||
return res.status(400).json({ error: 'Données invalides', details: errors.array() });
|
||||
}
|
||||
|
||||
const { text, completed, priority } = req.body;
|
||||
const { text, completed, priority, in_progress } = req.body;
|
||||
|
||||
// Vérifier que le todo appartient à l'utilisateur
|
||||
const todo = await getOne('SELECT id FROM global_todos WHERE id = $1 AND user_id = $2', [req.params.id, req.user.id]);
|
||||
@@ -130,6 +132,11 @@ router.put('/:id',
|
||||
updates.push(`priority = $${paramCount}`);
|
||||
params.push(priority ? 1 : 0);
|
||||
}
|
||||
if (in_progress !== undefined) {
|
||||
paramCount++;
|
||||
updates.push(`in_progress = $${paramCount}`);
|
||||
params.push(in_progress ? 1 : 0);
|
||||
}
|
||||
|
||||
if (updates.length > 0) {
|
||||
paramCount++;
|
||||
@@ -205,6 +212,42 @@ router.patch('/:id/priority', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PATCH /api/todos/:id/in-progress
|
||||
* Basculer l'état in_progress d'un todo
|
||||
*/
|
||||
router.patch('/:id/in-progress', async (req, res) => {
|
||||
try {
|
||||
// Vérifier que le todo appartient à l'utilisateur
|
||||
try {
|
||||
const todo = await getOne('SELECT id, in_progress FROM global_todos WHERE id = $1 AND user_id = $2', [req.params.id, req.user.id]);
|
||||
if (!todo) {
|
||||
return res.status(404).json({ error: 'Todo non trouvé' });
|
||||
}
|
||||
|
||||
const newInProgress = todo.in_progress ? 0 : 1;
|
||||
await runQuery('UPDATE global_todos SET in_progress = $1 WHERE id = $2', [newInProgress, req.params.id]);
|
||||
|
||||
logger.info(`Todo global ${newInProgress ? 'marqué en cours' : 'démarqué en cours'} (ID: ${req.params.id}) par ${req.user.username}`);
|
||||
|
||||
res.json({ message: 'Statut en cours modifié avec succès', in_progress: newInProgress === 1 });
|
||||
} catch (inProgressError) {
|
||||
// Si le champ in_progress n'existe pas encore
|
||||
if (inProgressError.message && inProgressError.message.includes('in_progress')) {
|
||||
logger.warn('Tentative d\'utilisation de la fonctionnalité in_progress sans migration');
|
||||
return res.status(400).json({
|
||||
error: 'Fonctionnalité in_progress non disponible',
|
||||
message: 'Veuillez exécuter la migration: node scripts/migrate_003.js'
|
||||
});
|
||||
}
|
||||
throw inProgressError;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Erreur lors du toggle du statut en cours:', error);
|
||||
res.status(500).json({ error: 'Erreur serveur' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/todos/:id
|
||||
* Supprimer un todo global
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
const { pool } = require('../config/database');
|
||||
const logger = require('../config/logger');
|
||||
|
||||
async function migrate() {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
logger.info('Starting migration: Add in_progress column to global_todos');
|
||||
|
||||
// Check if column exists
|
||||
const checkRes = await client.query(`
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name='global_todos' AND column_name='in_progress'
|
||||
`);
|
||||
|
||||
if (checkRes.rows.length === 0) {
|
||||
await client.query('ALTER TABLE global_todos ADD COLUMN in_progress INTEGER DEFAULT 0');
|
||||
logger.info('✓ Column in_progress added successfully');
|
||||
} else {
|
||||
logger.info('⚠ Column in_progress already exists');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Migration failed:', error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
client.release();
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
migrate();
|
||||
+18
-1
@@ -572,6 +572,15 @@ const Index = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleTodoInProgress = async (todoId: number) => {
|
||||
try {
|
||||
await TodosService.toggleInProgress(todoId);
|
||||
setTodos(todos.map(t => t.id === todoId ? { ...t, in_progress: !t.in_progress } : t));
|
||||
} catch (error) {
|
||||
showError("Erreur lors de la mise à jour du statut");
|
||||
}
|
||||
};
|
||||
|
||||
const confirmAddRssFeed = async (url: string) => {
|
||||
try {
|
||||
const newFeed = await RssService.addFeed(url);
|
||||
@@ -1617,7 +1626,15 @@ const Index = () => {
|
||||
<span className="text-sm flex-1 leading-snug">
|
||||
{todo.text}
|
||||
</span>
|
||||
<Activity className="h-3.5 w-3.5 text-muted-foreground mr-1" title="En cours" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={`h-7 w-7 ${todo.in_progress ? 'opacity-100 text-orange-500' : 'opacity-0 group-hover:opacity-100 text-muted-foreground'} transition-all`}
|
||||
onClick={() => todo.id && handleToggleTodoInProgress(todo.id)}
|
||||
title={todo.in_progress ? "Marquer comme non commencé" : "Marquer comme en cours"}
|
||||
>
|
||||
<Activity className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface Todo {
|
||||
text: string;
|
||||
completed: boolean;
|
||||
priority?: boolean;
|
||||
in_progress?: boolean;
|
||||
due_date?: string;
|
||||
created_at?: string;
|
||||
}
|
||||
@@ -81,6 +82,16 @@ class TodosService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async toggleInProgress(todoId: number): Promise<boolean> {
|
||||
try {
|
||||
await axios.patch(`${this.baseURL}/${todoId}/in-progress`, {}, this.getAuthHeader());
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error toggling in progress status:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new TodosService();
|
||||
Reference in new issue
Block a user