feat: overhaul TODO.md and enhance Kanban components

- Updated TODO.md to reflect the new project structure and phases, marking several tasks as completed.
- Enhanced Kanban components with a tech-inspired design, including new styles for columns and task cards.
- Removed the obsolete reminders service and task processor, streamlining the codebase for better maintainability.
- Introduced a modern API for task management, including CRUD operations and improved error handling.
- Updated global styles for a cohesive dark theme and added custom scrollbar styles.
This commit is contained in:
Julien Froidefond
2025-09-14 08:15:22 +02:00
parent d645fffd87
commit 124e8baee8
18 changed files with 857 additions and 1154 deletions

View File

@@ -1,100 +0,0 @@
import { NextResponse } from 'next/server';
import { getConfig, getTargetRemindersList, getEnabledRemindersLists } from '@/lib/config';
import { remindersService } from '@/services/reminders';
/**
* API route pour récupérer la configuration actuelle
*/
export async function GET() {
try {
const config = getConfig();
const availableLists = await remindersService.getReminderLists();
return NextResponse.json({
success: true,
config,
availableLists,
currentTarget: getTargetRemindersList(),
enabledLists: getEnabledRemindersLists()
});
} catch (error) {
console.error('❌ Erreur lors de la récupération de la config:', error);
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : 'Erreur inconnue'
}, { status: 500 });
}
}
/**
* API route pour tester l'accès à une liste spécifique
*/
export async function POST(request: Request) {
try {
const body = await request.json();
const { listName, action } = body;
if (!listName) {
return NextResponse.json({
success: false,
error: 'listName est requis'
}, { status: 400 });
}
let result: any = {};
switch (action) {
case 'test':
// Tester l'accès à une liste spécifique
const reminders = await remindersService.getRemindersByList(listName);
result = {
listName,
accessible: true,
reminderCount: reminders.length,
sample: reminders.slice(0, 3).map(r => ({
title: r.title,
completed: r.completed,
priority: r.priority
}))
};
break;
case 'preview':
// Prévisualiser les rappels d'une liste
const previewReminders = await remindersService.getRemindersByList(listName);
result = {
listName,
reminders: previewReminders.map(r => ({
id: r.id,
title: r.title,
completed: r.completed,
priority: r.priority,
tags: r.tags || []
}))
};
break;
default:
return NextResponse.json({
success: false,
error: 'Action non supportée. Utilisez "test" ou "preview"'
}, { status: 400 });
}
return NextResponse.json({
success: true,
action,
result
});
} catch (error) {
console.error('❌ Erreur lors du test de liste:', error);
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : 'Erreur inconnue'
}, { status: 500 });
}
}

View File

@@ -1,60 +0,0 @@
import { NextResponse } from 'next/server';
import { taskProcessorService } from '@/services/task-processor';
/**
* API route pour synchroniser les rappels macOS avec la base de données
*/
export async function POST() {
try {
console.log('🔄 Début de la synchronisation des rappels...');
const syncResult = await taskProcessorService.syncRemindersToDatabase();
return NextResponse.json({
success: true,
message: 'Synchronisation des rappels terminée',
syncLog: syncResult
});
} catch (error) {
console.error('❌ Erreur lors de la synchronisation:', error);
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : 'Erreur inconnue lors de la synchronisation'
}, { status: 500 });
}
}
/**
* API route pour obtenir le statut de la dernière synchronisation
*/
export async function GET() {
try {
// Récupérer les derniers logs de sync
const { prisma } = await import('@/services/database');
const lastSyncLogs = await prisma.syncLog.findMany({
where: { source: 'reminders' },
orderBy: { createdAt: 'desc' },
take: 5
});
const taskStats = await taskProcessorService.getTaskStats();
return NextResponse.json({
success: true,
lastSyncLogs,
taskStats,
message: 'Statut de synchronisation récupéré'
});
} catch (error) {
console.error('❌ Erreur lors de la récupération du statut:', error);
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : 'Erreur inconnue'
}, { status: 500 });
}
}

View File

@@ -1,6 +1,6 @@
import { NextResponse } from 'next/server';
import { taskProcessorService } from '@/services/task-processor';
import { TaskStatus } from '@/lib/types';
import { tasksService } from '@/services/tasks';
import { TaskStatus, TaskPriority } from '@/lib/types';
/**
* API route pour récupérer les tâches avec filtres optionnels
@@ -10,7 +10,13 @@ export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
// Extraire les paramètres de filtre
const filters: any = {};
const filters: {
status?: TaskStatus[];
source?: string[];
search?: string;
limit?: number;
offset?: number;
} = {};
const status = searchParams.get('status');
if (status) {
@@ -38,8 +44,8 @@ export async function GET(request: Request) {
}
// Récupérer les tâches
const tasks = await taskProcessorService.getTasks(filters);
const stats = await taskProcessorService.getTaskStats();
const tasks = await tasksService.getTasks(filters);
const stats = await tasksService.getTaskStats();
return NextResponse.json({
success: true,
@@ -60,26 +66,71 @@ export async function GET(request: Request) {
}
/**
* API route pour mettre à jour le statut d'une tâche
* API route pour créer une nouvelle tâche
*/
export async function POST(request: Request) {
try {
const body = await request.json();
const { title, description, status, priority, tags, dueDate } = body;
if (!title) {
return NextResponse.json({
success: false,
error: 'Le titre est requis'
}, { status: 400 });
}
const task = await tasksService.createTask({
title,
description,
status: status as TaskStatus,
priority: priority as TaskPriority,
tags,
dueDate: dueDate ? new Date(dueDate) : undefined
});
return NextResponse.json({
success: true,
data: task,
message: 'Tâche créée avec succès'
});
} catch (error) {
console.error('❌ Erreur lors de la création de la tâche:', error);
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : 'Erreur inconnue'
}, { status: 500 });
}
}
/**
* API route pour mettre à jour une tâche
*/
export async function PATCH(request: Request) {
try {
const body = await request.json();
const { taskId, status } = body;
const { taskId, ...updates } = body;
if (!taskId || !status) {
if (!taskId) {
return NextResponse.json({
success: false,
error: 'taskId et status sont requis'
error: 'taskId est requis'
}, { status: 400 });
}
const updatedTask = await taskProcessorService.updateTaskStatus(taskId, status);
// Convertir dueDate si présent
if (updates.dueDate) {
updates.dueDate = new Date(updates.dueDate);
}
const updatedTask = await tasksService.updateTask(taskId, updates);
return NextResponse.json({
success: true,
data: updatedTask,
message: `Tâche ${taskId} mise à jour avec le statut ${status}`
message: 'Tâche mise à jour avec succès'
});
} catch (error) {
@@ -91,3 +142,35 @@ export async function PATCH(request: Request) {
}, { status: 500 });
}
}
/**
* API route pour supprimer une tâche
*/
export async function DELETE(request: Request) {
try {
const { searchParams } = new URL(request.url);
const taskId = searchParams.get('taskId');
if (!taskId) {
return NextResponse.json({
success: false,
error: 'taskId est requis'
}, { status: 400 });
}
await tasksService.deleteTask(taskId);
return NextResponse.json({
success: true,
message: 'Tâche supprimée avec succès'
});
} catch (error) {
console.error('❌ Erreur lors de la suppression de la tâche:', error);
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : 'Erreur inconnue'
}, { status: 500 });
}
}

View File

@@ -1,59 +0,0 @@
import { NextResponse } from 'next/server';
import { testDatabaseConnection } from '@/services/database';
import { remindersService } from '@/services/reminders';
import { taskProcessorService } from '@/services/task-processor';
/**
* API route de test pour vérifier que tous les services fonctionnent
*/
export async function GET() {
try {
const results = {
timestamp: new Date().toISOString(),
database: false,
reminders: false,
taskProcessor: false,
reminderLists: [] as string[],
taskStats: null as any
};
// Test de la base de données
try {
results.database = await testDatabaseConnection();
} catch (error) {
console.error('Test DB failed:', error);
}
// Test de l'accès aux rappels
try {
results.reminders = await remindersService.testRemindersAccess();
if (results.reminders) {
results.reminderLists = await remindersService.getReminderLists();
}
} catch (error) {
console.error('Test Reminders failed:', error);
}
// Test du service de traitement des tâches
try {
results.taskStats = await taskProcessorService.getTaskStats();
results.taskProcessor = true;
} catch (error) {
console.error('Test TaskProcessor failed:', error);
}
return NextResponse.json({
success: true,
message: 'Tests des services terminés',
results
});
} catch (error) {
console.error('Erreur dans l\'API de test:', error);
return NextResponse.json({
success: false,
error: error instanceof Error ? error.message : 'Erreur inconnue'
}, { status: 500 });
}
}

View File

@@ -1,8 +1,8 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
--background: #020617; /* slate-950 */
--foreground: #f1f5f9; /* slate-100 */
}
@theme inline {
@@ -12,15 +12,38 @@
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
font-family: var(--font-geist-mono), 'Courier New', monospace;
overflow-x: hidden;
}
/* Scrollbar tech style */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #1e293b; /* slate-800 */
}
::-webkit-scrollbar-thumb {
background: #475569; /* slate-600 */
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #06b6d4; /* cyan-500 */
}
/* Animations tech */
@keyframes glow {
0%, 100% { box-shadow: 0 0 5px rgba(6, 182, 212, 0.3); }
50% { box-shadow: 0 0 20px rgba(6, 182, 212, 0.6); }
}
.animate-glow {
animation: glow 2s ease-in-out infinite;
}

View File

@@ -1,26 +1,23 @@
import { taskProcessorService } from '@/services/task-processor';
import { getTargetRemindersList } from '@/lib/config';
import { tasksService } from '@/services/tasks';
import { KanbanBoard } from '../../components/kanban/Board';
import { Header } from '../../components/ui/Header';
export default async function HomePage() {
// SSR - Récupération des données côté serveur
// SSR - Récupération des données côté serveur (focus sur les tâches récentes)
const [tasks, stats] = await Promise.all([
taskProcessorService.getTasks({ limit: 100 }),
taskProcessorService.getTaskStats()
tasksService.getTasks({ limit: 20 }), // Réduire pour voir les nouvelles tâches
tasksService.getTaskStats()
]);
const targetList = getTargetRemindersList();
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<div className="min-h-screen bg-slate-950">
<Header
title="TowerControl"
subtitle={`Tâches synchronisées depuis "${targetList}"`}
subtitle="Gestionnaire de tâches moderne"
stats={stats}
/>
<main className="container mx-auto px-4 py-6">
<main className="h-[calc(100vh-120px)]">
<KanbanBoard tasks={tasks} />
</main>
</div>