diff --git a/config/database-postgres.js b/config/database-postgres.js index 2708c25..584f60c 100644 --- a/config/database-postgres.js +++ b/config/database-postgres.js @@ -74,6 +74,7 @@ async function initDatabase() { text TEXT NOT NULL, completed BOOLEAN DEFAULT FALSE, completed_at TIMESTAMP, + priority BOOLEAN DEFAULT FALSE, position INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) @@ -87,6 +88,7 @@ async function initDatabase() { text TEXT NOT NULL, completed BOOLEAN DEFAULT FALSE, completed_at TIMESTAMP, + priority BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); diff --git a/package.json b/package.json index bd2bc94..636d780 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "docker:logs": "docker-compose logs -f", "docker:restart": "docker-compose restart", "db:migrate": "node scripts/add-cleanup-tracking-fields.js", + "db:migrate:priority": "node scripts/add-priority-field.js", "db:cleanup": "node scripts/database-cleanup.js", "db:cleanup:dry-run": "DRY_RUN=true node scripts/database-cleanup.js" }, diff --git a/routes/todos.routes.js b/routes/todos.routes.js index 82a77ac..c89d892 100644 --- a/routes/todos.routes.js +++ b/routes/todos.routes.js @@ -17,10 +17,10 @@ router.use(authenticateToken); router.get('/', async (req, res) => { try { const todos = await getAll(` - SELECT id, text, completed, created_at + SELECT id, text, completed, priority, created_at FROM global_todos WHERE user_id = $1 - ORDER BY created_at DESC + ORDER BY priority DESC, created_at DESC `, [req.user.id]); res.json(todos); @@ -59,6 +59,7 @@ router.post('/', id: result.id, text, completed: false, + priority: false, created_at: new Date().toISOString() }); } catch (error) { @@ -75,7 +76,8 @@ router.post('/', router.put('/:id', [ body('text').optional().trim().notEmpty(), - body('completed').optional().isBoolean() + body('completed').optional().isBoolean(), + body('priority').optional().isBoolean() ], async (req, res) => { try { @@ -84,7 +86,7 @@ router.put('/:id', return res.status(400).json({ error: 'Données invalides', details: errors.array() }); } - const { text, completed } = req.body; + const { text, completed, priority } = 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]); @@ -106,6 +108,11 @@ router.put('/:id', updates.push(`completed = $${paramCount}`); params.push(completed ? 1 : 0); } + if (priority !== undefined) { + paramCount++; + updates.push(`priority = $${paramCount}`); + params.push(priority ? 1 : 0); + } if (updates.length > 0) { paramCount++; @@ -145,6 +152,30 @@ router.patch('/:id/toggle', async (req, res) => { } }); +/** + * PATCH /api/todos/:id/priority + * Basculer l'état priority d'un todo + */ +router.patch('/:id/priority', async (req, res) => { + try { + // Vérifier que le todo appartient à l'utilisateur + const todo = await getOne('SELECT id, priority 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 newPriority = todo.priority ? 0 : 1; + await runQuery('UPDATE global_todos SET priority = $1 WHERE id = $2', [newPriority, req.params.id]); + + logger.info(`Todo global ${newPriority ? 'marqué prioritaire' : 'démarqué prioritaire'} (ID: ${req.params.id}) par ${req.user.username}`); + + res.json({ message: 'Priorité modifiée avec succès', priority: newPriority === 1 }); + } catch (error) { + logger.error('Erreur lors du toggle de la priorité:', error); + res.status(500).json({ error: 'Erreur serveur' }); + } +}); + /** * DELETE /api/todos/:id * Supprimer un todo global diff --git a/scripts/add-priority-field.js b/scripts/add-priority-field.js new file mode 100644 index 0000000..37f2888 --- /dev/null +++ b/scripts/add-priority-field.js @@ -0,0 +1,87 @@ +#!/usr/bin/env node + +/** + * Script de migration pour ajouter le champ priority aux tâches + * - priority (BOOLEAN) pour global_todos + * - priority (BOOLEAN) pour note_todos + */ + +const { Pool } = require('pg'); + +const DATABASE_URL = process.env.DATABASE_URL || + `postgresql://${process.env.PGUSER || 'noteflow'}:${process.env.PGPASSWORD || 'noteflow_secure_password_change_me'}@${process.env.PGHOST || 'postgres'}:${process.env.PGPORT || '5499'}/${process.env.PGDATABASE || 'noteflow'}`; + +async function migrate() { + const pool = new Pool({ connectionString: DATABASE_URL }); + + try { + console.log('\n╔════════════════════════════════════════════════════════════╗'); + console.log('║ MIGRATION: Ajout du champ priority aux tâches ║'); + console.log('╚════════════════════════════════════════════════════════════╝\n'); + + const client = await pool.connect(); + + try { + await client.query('BEGIN'); + + // 1. Ajouter priority à la table global_todos + console.log('📝 Ajout du champ priority à la table global_todos...'); + await client.query(` + ALTER TABLE global_todos + ADD COLUMN IF NOT EXISTS priority BOOLEAN DEFAULT FALSE + `); + console.log('✅ Champ priority ajouté à global_todos\n'); + + // 2. Ajouter priority à la table note_todos + console.log('📝 Ajout du champ priority à la table note_todos...'); + await client.query(` + ALTER TABLE note_todos + ADD COLUMN IF NOT EXISTS priority BOOLEAN DEFAULT FALSE + `); + console.log('✅ Champ priority ajouté à note_todos\n'); + + // 3. Créer un index pour améliorer les performances de tri par priorité + console.log('🔧 Création des index pour les tâches prioritaires...'); + await client.query(` + CREATE INDEX IF NOT EXISTS idx_global_todos_priority + ON global_todos(priority DESC, created_at DESC) + `); + await client.query(` + CREATE INDEX IF NOT EXISTS idx_note_todos_priority + ON note_todos(priority DESC, position) + `); + console.log('✅ Index créés avec succès\n'); + + await client.query('COMMIT'); + + console.log('╔════════════════════════════════════════════════════════════╗'); + console.log('║ ✅ MIGRATION TERMINÉE AVEC SUCCÈS ║'); + console.log('╚════════════════════════════════════════════════════════════╝\n'); + + console.log('📝 RÉSUMÉ DES CHANGEMENTS:\n'); + console.log('• Champ priority (BOOLEAN) ajouté à global_todos'); + console.log('• Champ priority (BOOLEAN) ajouté à note_todos'); + console.log('• Index créés pour optimiser le tri par priorité'); + console.log('• Valeur par défaut: FALSE (non prioritaire)\n'); + + console.log('💡 UTILISATION:\n'); + console.log('Les tâches peuvent maintenant être marquées comme prioritaires'); + console.log('avec une étoile dans l\'interface.\n'); + + } catch (error) { + await client.query('ROLLBACK'); + throw error; + } finally { + client.release(); + } + + await pool.end(); + + } catch (error) { + console.error('\n❌ Erreur:', error.message); + console.error(error.stack); + process.exit(1); + } +} + +migrate(); diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index aacbecd..a23c1b2 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -15,7 +15,7 @@ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, Command import { RichTextEditor } from "@/components/RichTextEditor"; import { PlusCircle, Search, User, LogOut, Settings, ChevronDown, Plus, Archive, Trash2, - Image as ImageIcon, CheckSquare, FileText, Rss, ExternalLink, RefreshCw, Key, Zap, Paperclip, X, Edit, Calendar as CalendarIcon, Tag as TagIcon, MessageSquare, Send, Check, ChevronsUpDown + Image as ImageIcon, CheckSquare, FileText, Rss, ExternalLink, RefreshCw, Key, Zap, Paperclip, X, Edit, Calendar as CalendarIcon, Tag as TagIcon, MessageSquare, Send, Check, ChevronsUpDown, Star } from "lucide-react"; import AuthService from "@/services/AuthService"; import AdminService from "@/services/AdminService"; @@ -553,6 +553,15 @@ const Index = () => { } }; + const handleToggleTodoPriority = async (todoId: number) => { + try { + await TodosService.togglePriority(todoId); + setTodos(todos.map(t => t.id === todoId ? { ...t, priority: !t.priority } : t)); + } catch (error) { + showError("Erreur lors de la mise à jour de la priorité"); + } + }; + const handleDeleteTodo = async (todoId: number) => { try { await TodosService.deleteTodo(todoId); @@ -1579,6 +1588,15 @@ const Index = () => { {todo.text} +