mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
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:
6 files changed
+160
-8
No files matched your search
@@ -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
|
||||
)
|
||||
`);
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
Reference in new issue
Block a user