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] 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] 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] 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] Indicateurs visuels d'ancienneté (couleurs vert→rouge)
|
||||||
- [x] Actions par tâche : Cocher, Archiver, Supprimer
|
- [x] Actions par tâche : Cocher, Archiver, Supprimer
|
||||||
- [x] **Statut "Archivé" basique** <!-- Implémenté le 21/09/2025 -->
|
- [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,
|
goToPreviousDay,
|
||||||
goToNextDay,
|
goToNextDay,
|
||||||
goToToday,
|
goToToday,
|
||||||
setDate
|
setDate,
|
||||||
|
refreshDailySilent
|
||||||
} = useDaily(initialDate, initialDailyView);
|
} = useDaily(initialDate, initialDailyView);
|
||||||
|
|
||||||
const [dailyDates, setDailyDates] = useState<string[]>(initialDailyDates);
|
const [dailyDates, setDailyDates] = useState<string[]>(initialDailyDates);
|
||||||
@@ -294,6 +295,7 @@ export function DailyPageClient({
|
|||||||
<PendingTasksSection
|
<PendingTasksSection
|
||||||
onToggleCheckbox={handleToggleCheckbox}
|
onToggleCheckbox={handleToggleCheckbox}
|
||||||
onDeleteCheckbox={handleDeleteCheckbox}
|
onDeleteCheckbox={handleDeleteCheckbox}
|
||||||
|
onRefreshDaily={refreshDailySilent}
|
||||||
refreshTrigger={refreshTrigger}
|
refreshTrigger={refreshTrigger}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback, useTransition } from 'react';
|
||||||
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { DailyCheckbox, DailyCheckboxType } from '@/lib/types';
|
import { DailyCheckbox, DailyCheckboxType } from '@/lib/types';
|
||||||
import { dailyClient } from '@/clients/daily-client';
|
import { dailyClient } from '@/clients/daily-client';
|
||||||
import { formatDateShort, getDaysAgo } from '@/lib/date-utils';
|
import { formatDateShort, getDaysAgo } from '@/lib/date-utils';
|
||||||
|
import { moveCheckboxToToday } from '@/actions/daily';
|
||||||
|
|
||||||
interface PendingTasksSectionProps {
|
interface PendingTasksSectionProps {
|
||||||
onToggleCheckbox: (checkboxId: string) => Promise<void>;
|
onToggleCheckbox: (checkboxId: string) => Promise<void>;
|
||||||
onDeleteCheckbox: (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
|
refreshTrigger?: number; // Pour forcer le refresh depuis le parent
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PendingTasksSection({
|
export function PendingTasksSection({
|
||||||
onToggleCheckbox,
|
onToggleCheckbox,
|
||||||
onDeleteCheckbox,
|
onDeleteCheckbox,
|
||||||
|
onRefreshDaily,
|
||||||
refreshTrigger
|
refreshTrigger
|
||||||
}: PendingTasksSectionProps) {
|
}: PendingTasksSectionProps) {
|
||||||
const [isCollapsed, setIsCollapsed] = useState(true);
|
const [isCollapsed, setIsCollapsed] = useState(true);
|
||||||
const [pendingTasks, setPendingTasks] = useState<DailyCheckbox[]>([]);
|
const [pendingTasks, setPendingTasks] = useState<DailyCheckbox[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
const [filters, setFilters] = useState({
|
const [filters, setFilters] = useState({
|
||||||
maxDays: 7,
|
maxDays: 7,
|
||||||
type: 'all' as 'all' | DailyCheckboxType,
|
type: 'all' as 'all' | DailyCheckboxType,
|
||||||
@@ -74,6 +78,22 @@ export function PendingTasksSection({
|
|||||||
await loadPendingTasks(); // Recharger la liste
|
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é
|
// Obtenir la couleur selon l'ancienneté
|
||||||
const getAgeColor = (date: Date) => {
|
const getAgeColor = (date: Date) => {
|
||||||
const days = getDaysAgo(date);
|
const days = getDaysAgo(date);
|
||||||
@@ -207,6 +227,17 @@ export function PendingTasksSection({
|
|||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{!isArchived && (
|
{!isArchived && (
|
||||||
|
<>
|
||||||
|
<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
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -216,6 +247,7 @@ export function PendingTasksSection({
|
|||||||
>
|
>
|
||||||
📦
|
📦
|
||||||
</Button>
|
</Button>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
@@ -360,6 +360,7 @@ export interface UpdateDailyCheckboxData {
|
|||||||
type?: DailyCheckboxType;
|
type?: DailyCheckboxType;
|
||||||
taskId?: string;
|
taskId?: string;
|
||||||
order?: number;
|
order?: number;
|
||||||
|
date?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface pour récupérer les checkboxes d'une journée
|
// 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.order !== undefined) updateData.order = data.order;
|
||||||
|
if (data.date !== undefined) updateData.date = normalizeDate(data.date);
|
||||||
|
|
||||||
const checkbox = await prisma.dailyCheckbox.update({
|
const checkbox = await prisma.dailyCheckbox.update({
|
||||||
where: { id: checkboxId },
|
where: { id: checkboxId },
|
||||||
@@ -327,6 +328,50 @@ export class DailyService {
|
|||||||
|
|
||||||
return this.mapPrismaCheckbox(checkbox);
|
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
|
// Instance singleton du service
|
||||||
|
|||||||
Reference in New Issue
Block a user