Merge pull request #113 from R0m1k3/claude/add-database-cleanup-017GyBxnCE3LSomAPinGgDT5

Feat: Ajout de l'icône de priorité pour les tâches
This commit is contained in:
LogiFlow authored and GitHub committed 2025-11-21 14:52:21 +01:00
commit 5bde1a3530
6 files changed
+160 -8

No files matched your search

+2
View File
@@ -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
)
`);
+1
View File
@@ -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"
},
+35 -4
View File
@@ -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
+87
View File
@@ -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();
+22 -1
View File
@@ -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 = () => {
<span className="text-sm flex-1 leading-snug">
{todo.text}
</span>
<Button
variant="ghost"
size="icon"
className={`h-7 w-7 ${todo.priority ? '' : 'opacity-0 group-hover:opacity-100'} transition-opacity`}
onClick={() => todo.id && handleToggleTodoPriority(todo.id)}
title={todo.priority ? "Retirer la priorité" : "Marquer comme prioritaire"}
>
<Star className={`h-3.5 w-3.5 ${todo.priority ? 'fill-yellow-400 text-yellow-400' : ''}`} />
</Button>
<Button
variant="ghost"
size="icon"
@@ -1631,6 +1649,9 @@ const Index = () => {
<span className="text-sm flex-1 line-through text-muted-foreground leading-snug">
{todo.text}
</span>
{todo.priority && (
<Star className="h-3.5 w-3.5 fill-yellow-400 text-yellow-400 opacity-50" />
)}
<Button
variant="ghost"
size="icon"
+13 -3
View File
@@ -4,7 +4,7 @@ export interface Todo {
id?: number;
text: string;
completed: boolean;
priority?: 'low' | 'medium' | 'high';
priority?: boolean;
due_date?: string;
created_at?: string;
}
@@ -32,9 +32,9 @@ class TodosService {
}
}
async createTodo(text: string, priority?: 'low' | 'medium' | 'high'): Promise<Todo> {
async createTodo(text: string, priority?: boolean): Promise<Todo> {
try {
const response = await axios.post(this.baseURL, { text, priority, completed: false }, this.getAuthHeader());
const response = await axios.post(this.baseURL, { text, priority: priority || false, completed: false }, this.getAuthHeader());
return response.data;
} catch (error) {
console.error('Error creating todo:', error);
@@ -71,6 +71,16 @@ class TodosService {
throw error;
}
}
async togglePriority(todoId: number): Promise<boolean> {
try {
await axios.patch(`${this.baseURL}/${todoId}/priority`, {}, this.getAuthHeader());
return true;
} catch (error) {
console.error('Error toggling priority:', error);
throw error;
}
}
}
export default new TodosService();