feat: add "Move to Today" functionality for pending tasks
- Implemented a new button in the `PendingTasksSection` to move unchecked tasks to today's date. - Created `moveCheckboxToToday` action in `daily.ts` to handle the logic for moving tasks. - Updated `DailyPageClient` and `PendingTasksSection` to integrate the new functionality and refresh the daily view after moving tasks. - Marked the feature as completed in `TODO.md`.
This commit is contained in:
1
TODO.md
1
TODO.md
@@ -53,6 +53,7 @@
|
||||
- [x] Filtrage par date (7/14/30 jours), catégorie (tâches/réunions), ancienneté
|
||||
- [x] Action "Archiver" pour les tâches ni résolues ni à faire
|
||||
- [x] Section repliable dans la page Daily (sous les sections Hier/Aujourd'hui)
|
||||
- [x] **Bouton "Déplacer à aujourd'hui"** pour les tâches non résolues <!-- Implémenté le 22/09/2025 avec server action -->
|
||||
- [x] Indicateurs visuels d'ancienneté (couleurs vert→rouge)
|
||||
- [x] Actions par tâche : Cocher, Archiver, Supprimer
|
||||
- [x] **Statut "Archivé" basique** <!-- Implémenté le 21/09/2025 -->
|
||||
|
||||
@@ -256,3 +256,25 @@ export async function reorderCheckboxes(dailyId: string, checkboxIds: string[]):
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Déplace une checkbox non cochée à aujourd'hui
|
||||
*/
|
||||
export async function moveCheckboxToToday(checkboxId: string): Promise<{
|
||||
success: boolean;
|
||||
data?: DailyCheckbox;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const updatedCheckbox = await dailyService.moveCheckboxToToday(checkboxId);
|
||||
|
||||
revalidatePath('/daily');
|
||||
return { success: true, data: updatedCheckbox };
|
||||
} catch (error) {
|
||||
console.error('Erreur moveCheckboxToToday:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Erreur inconnue'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ export function DailyPageClient({
|
||||
goToPreviousDay,
|
||||
goToNextDay,
|
||||
goToToday,
|
||||
setDate
|
||||
setDate,
|
||||
refreshDailySilent
|
||||
} = useDaily(initialDate, initialDailyView);
|
||||
|
||||
const [dailyDates, setDailyDates] = useState<string[]>(initialDailyDates);
|
||||
@@ -294,6 +295,7 @@ export function DailyPageClient({
|
||||
<PendingTasksSection
|
||||
onToggleCheckbox={handleToggleCheckbox}
|
||||
onDeleteCheckbox={handleDeleteCheckbox}
|
||||
onRefreshDaily={refreshDailySilent}
|
||||
refreshTrigger={refreshTrigger}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useTransition } from 'react';
|
||||
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { DailyCheckbox, DailyCheckboxType } from '@/lib/types';
|
||||
import { dailyClient } from '@/clients/daily-client';
|
||||
import { formatDateShort, getDaysAgo } from '@/lib/date-utils';
|
||||
import { moveCheckboxToToday } from '@/actions/daily';
|
||||
|
||||
interface PendingTasksSectionProps {
|
||||
onToggleCheckbox: (checkboxId: string) => Promise<void>;
|
||||
onDeleteCheckbox: (checkboxId: string) => Promise<void>;
|
||||
onRefreshDaily?: () => Promise<void>; // Pour rafraîchir la vue daily principale
|
||||
refreshTrigger?: number; // Pour forcer le refresh depuis le parent
|
||||
}
|
||||
|
||||
export function PendingTasksSection({
|
||||
onToggleCheckbox,
|
||||
onDeleteCheckbox,
|
||||
onRefreshDaily,
|
||||
refreshTrigger
|
||||
}: PendingTasksSectionProps) {
|
||||
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||
const [pendingTasks, setPendingTasks] = useState<DailyCheckbox[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [filters, setFilters] = useState({
|
||||
maxDays: 7,
|
||||
type: 'all' as 'all' | DailyCheckboxType,
|
||||
@@ -74,6 +78,22 @@ export function PendingTasksSection({
|
||||
await loadPendingTasks(); // Recharger la liste
|
||||
};
|
||||
|
||||
// Gérer le déplacement d'une tâche à aujourd'hui
|
||||
const handleMoveToToday = (checkboxId: string) => {
|
||||
startTransition(async () => {
|
||||
const result = await moveCheckboxToToday(checkboxId);
|
||||
|
||||
if (result.success) {
|
||||
await loadPendingTasks(); // Recharger la liste des tâches en attente
|
||||
if (onRefreshDaily) {
|
||||
await onRefreshDaily(); // Rafraîchir la vue daily principale
|
||||
}
|
||||
} else {
|
||||
console.error('Erreur lors du déplacement vers aujourd\'hui:', result.error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Obtenir la couleur selon l'ancienneté
|
||||
const getAgeColor = (date: Date) => {
|
||||
const days = getDaysAgo(date);
|
||||
@@ -207,15 +227,27 @@ export function PendingTasksSection({
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-1">
|
||||
{!isArchived && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleArchiveTask(task.id)}
|
||||
title="Archiver cette tâche"
|
||||
className="text-xs px-2 py-1"
|
||||
>
|
||||
📦
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleMoveToToday(task.id)}
|
||||
disabled={isPending}
|
||||
title="Déplacer à aujourd'hui"
|
||||
className="text-xs px-2 py-1 text-[var(--primary)] hover:text-[var(--primary)] disabled:opacity-50"
|
||||
>
|
||||
📅
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleArchiveTask(task.id)}
|
||||
title="Archiver cette tâche"
|
||||
className="text-xs px-2 py-1"
|
||||
>
|
||||
📦
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -360,6 +360,7 @@ export interface UpdateDailyCheckboxData {
|
||||
type?: DailyCheckboxType;
|
||||
taskId?: string;
|
||||
order?: number;
|
||||
date?: Date;
|
||||
}
|
||||
|
||||
// Interface pour récupérer les checkboxes d'une journée
|
||||
|
||||
@@ -94,6 +94,7 @@ export class DailyService {
|
||||
}
|
||||
}
|
||||
if (data.order !== undefined) updateData.order = data.order;
|
||||
if (data.date !== undefined) updateData.date = normalizeDate(data.date);
|
||||
|
||||
const checkbox = await prisma.dailyCheckbox.update({
|
||||
where: { id: checkboxId },
|
||||
@@ -327,6 +328,50 @@ export class DailyService {
|
||||
|
||||
return this.mapPrismaCheckbox(checkbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* Déplace une checkbox non cochée à aujourd'hui
|
||||
*/
|
||||
async moveCheckboxToToday(checkboxId: string): Promise<DailyCheckbox> {
|
||||
const checkbox = await prisma.dailyCheckbox.findUnique({
|
||||
where: { id: checkboxId }
|
||||
});
|
||||
|
||||
if (!checkbox) {
|
||||
throw new BusinessError('Checkbox non trouvée');
|
||||
}
|
||||
|
||||
if (checkbox.isChecked) {
|
||||
throw new BusinessError('Impossible de déplacer une tâche déjà cochée');
|
||||
}
|
||||
|
||||
const today = normalizeDate(getToday());
|
||||
|
||||
// Vérifier si la checkbox est déjà pour aujourd'hui
|
||||
if (normalizeDate(checkbox.date).getTime() === today.getTime()) {
|
||||
throw new BusinessError('La tâche est déjà programmée pour aujourd\'hui');
|
||||
}
|
||||
|
||||
// Calculer l'ordre suivant pour aujourd'hui
|
||||
const maxOrder = await prisma.dailyCheckbox.aggregate({
|
||||
where: { date: today },
|
||||
_max: { order: true }
|
||||
});
|
||||
|
||||
const newOrder = (maxOrder._max.order ?? -1) + 1;
|
||||
|
||||
const updatedCheckbox = await prisma.dailyCheckbox.update({
|
||||
where: { id: checkboxId },
|
||||
data: {
|
||||
date: today,
|
||||
order: newOrder,
|
||||
updatedAt: new Date()
|
||||
},
|
||||
include: { task: true }
|
||||
});
|
||||
|
||||
return this.mapPrismaCheckbox(updatedCheckbox);
|
||||
}
|
||||
}
|
||||
|
||||
// Instance singleton du service
|
||||
|
||||
Reference in New Issue
Block a user