feat(DailyPage, DailyService, Calendar): enhance task deadline management and UI integration
- Implemented user authentication in the daily dates API route to ensure secure access. - Added functionality to retrieve task deadlines and associated tasks, improving task management capabilities. - Updated DailyPageClient to display tasks with deadlines in the calendar view, enhancing user experience. - Enhanced Calendar component to visually indicate deadline dates, providing clearer task management context.
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { dailyService } from '@/services/task-management/daily';
|
import { dailyService } from '@/services/task-management/daily';
|
||||||
|
import { getServerSession } from 'next-auth/next';
|
||||||
|
import { authOptions } from '@/lib/auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API route pour récupérer toutes les dates avec des dailies
|
* API route pour récupérer toutes les dates avec des dailies
|
||||||
@@ -7,7 +9,12 @@ import { dailyService } from '@/services/task-management/daily';
|
|||||||
*/
|
*/
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
const dates = await dailyService.getDailyDates();
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const dates = await dailyService.getDailyDates(session.user.id);
|
||||||
return NextResponse.json({ dates });
|
return NextResponse.json({ dates });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur lors de la récupération des dates:', error);
|
console.error('Erreur lors de la récupération des dates:', error);
|
||||||
|
|||||||
42
src/app/api/daily/deadline-tasks/route.ts
Normal file
42
src/app/api/daily/deadline-tasks/route.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { dailyService } from '@/services/task-management/daily';
|
||||||
|
import { getServerSession } from 'next-auth/next';
|
||||||
|
import { authOptions } from '@/lib/auth';
|
||||||
|
import { parseDate, isValidAPIDate } from '@/lib/date-utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API route pour récupérer les tâches avec deadline pour une date donnée
|
||||||
|
* GET /api/daily/deadline-tasks?date=YYYY-MM-DD
|
||||||
|
*/
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
try {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const dateStr = searchParams.get('date');
|
||||||
|
|
||||||
|
if (!dateStr || !isValidAPIDate(dateStr)) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Date invalide. Format attendu: YYYY-MM-DD' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = parseDate(dateStr);
|
||||||
|
const tasks = await dailyService.getTasksByDeadlineDate(
|
||||||
|
session.user.id,
|
||||||
|
date
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json({ tasks });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la récupération des tâches:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Erreur interne du serveur' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/app/api/daily/deadlines/route.ts
Normal file
30
src/app/api/daily/deadlines/route.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { dailyService } from '@/services/task-management/daily';
|
||||||
|
import { getServerSession } from 'next-auth/next';
|
||||||
|
import { authOptions } from '@/lib/auth';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API route pour récupérer toutes les dates de fin des tâches avec leurs noms
|
||||||
|
* GET /api/daily/deadlines
|
||||||
|
* Retourne un objet { dates: Record<string, string[]> } où chaque clé est une date (YYYY-MM-DD)
|
||||||
|
* et la valeur est un tableau de noms de tâches
|
||||||
|
*/
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return NextResponse.json({ error: 'Non authentifié' }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const deadlineDates = await dailyService.getTaskDeadlineDates(
|
||||||
|
session.user.id
|
||||||
|
);
|
||||||
|
return NextResponse.json({ dates: deadlineDates });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la récupération des dates de fin:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Erreur interne du serveur' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,12 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useDaily } from '@/hooks/useDaily';
|
import { useDaily } from '@/hooks/useDaily';
|
||||||
import { DailyView, DailyCheckboxType, DailyCheckbox } from '@/lib/types';
|
import { DailyView, DailyCheckboxType, DailyCheckbox, Task } from '@/lib/types';
|
||||||
import { DeadlineMetrics } from '@/services/analytics/deadline-analytics';
|
import { DeadlineMetrics } from '@/services/analytics/deadline-analytics';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||||
|
import { TaskCard } from '@/components/ui/TaskCard';
|
||||||
|
import { useTags } from '@/hooks/useTags';
|
||||||
import { Calendar } from '@/components/ui/Calendar';
|
import { Calendar } from '@/components/ui/Calendar';
|
||||||
import { AlertBanner, AlertItem } from '@/components/ui/AlertBanner';
|
import { AlertBanner, AlertItem } from '@/components/ui/AlertBanner';
|
||||||
import { DailySection } from '@/components/daily/DailySection';
|
import { DailySection } from '@/components/daily/DailySection';
|
||||||
@@ -18,6 +20,7 @@ import {
|
|||||||
formatDateLong,
|
formatDateLong,
|
||||||
isToday,
|
isToday,
|
||||||
generateDateTitle,
|
generateDateTitle,
|
||||||
|
formatDateForAPI,
|
||||||
} from '@/lib/date-utils';
|
} from '@/lib/date-utils';
|
||||||
import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts';
|
import { useGlobalKeyboardShortcuts } from '@/hooks/useGlobalKeyboardShortcuts';
|
||||||
import { Emoji } from '@/components/ui/Emoji';
|
import { Emoji } from '@/components/ui/Emoji';
|
||||||
@@ -25,6 +28,7 @@ import { Emoji } from '@/components/ui/Emoji';
|
|||||||
interface DailyPageClientProps {
|
interface DailyPageClientProps {
|
||||||
initialDailyView?: DailyView;
|
initialDailyView?: DailyView;
|
||||||
initialDailyDates?: string[];
|
initialDailyDates?: string[];
|
||||||
|
initialDeadlineDates?: Record<string, string[]>; // Date -> Array de noms de tâches
|
||||||
initialDate?: Date;
|
initialDate?: Date;
|
||||||
initialDeadlineMetrics?: DeadlineMetrics | null;
|
initialDeadlineMetrics?: DeadlineMetrics | null;
|
||||||
initialPendingTasks?: DailyCheckbox[];
|
initialPendingTasks?: DailyCheckbox[];
|
||||||
@@ -33,10 +37,12 @@ interface DailyPageClientProps {
|
|||||||
export function DailyPageClient({
|
export function DailyPageClient({
|
||||||
initialDailyView,
|
initialDailyView,
|
||||||
initialDailyDates = [],
|
initialDailyDates = [],
|
||||||
|
initialDeadlineDates = {},
|
||||||
initialDate,
|
initialDate,
|
||||||
initialDeadlineMetrics,
|
initialDeadlineMetrics,
|
||||||
initialPendingTasks = [],
|
initialPendingTasks = [],
|
||||||
}: DailyPageClientProps = {}) {
|
}: DailyPageClientProps = {}) {
|
||||||
|
const { tags: availableTags } = useTags();
|
||||||
const {
|
const {
|
||||||
dailyView,
|
dailyView,
|
||||||
loading,
|
loading,
|
||||||
@@ -60,6 +66,10 @@ export function DailyPageClient({
|
|||||||
} = useDaily(initialDate, initialDailyView);
|
} = useDaily(initialDate, initialDailyView);
|
||||||
|
|
||||||
const [dailyDates, setDailyDates] = useState<string[]>(initialDailyDates);
|
const [dailyDates, setDailyDates] = useState<string[]>(initialDailyDates);
|
||||||
|
const [deadlineDates, setDeadlineDates] =
|
||||||
|
useState<Record<string, string[]>>(initialDeadlineDates);
|
||||||
|
const [deadlineTasks, setDeadlineTasks] = useState<Task[]>([]);
|
||||||
|
const [loadingDeadlineTasks, setLoadingDeadlineTasks] = useState(false);
|
||||||
const [pendingRefreshTrigger, setPendingRefreshTrigger] = useState(0);
|
const [pendingRefreshTrigger, setPendingRefreshTrigger] = useState(0);
|
||||||
|
|
||||||
// Fonction pour rafraîchir la liste des dates avec des dailies
|
// Fonction pour rafraîchir la liste des dates avec des dailies
|
||||||
@@ -84,6 +94,18 @@ export function DailyPageClient({
|
|||||||
}
|
}
|
||||||
}, [initialDailyDates.length]);
|
}, [initialDailyDates.length]);
|
||||||
|
|
||||||
|
// Charger les dates de fin pour le calendrier (seulement si pas de données SSR)
|
||||||
|
useEffect(() => {
|
||||||
|
if (Object.keys(initialDeadlineDates).length === 0) {
|
||||||
|
import('@/clients/daily-client')
|
||||||
|
.then(({ dailyClient }) => {
|
||||||
|
return dailyClient.getDeadlineDates();
|
||||||
|
})
|
||||||
|
.then(setDeadlineDates)
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
}, [initialDeadlineDates]);
|
||||||
|
|
||||||
const handleAddTodayCheckbox = async (
|
const handleAddTodayCheckbox = async (
|
||||||
text: string,
|
text: string,
|
||||||
type: DailyCheckboxType
|
type: DailyCheckboxType
|
||||||
@@ -163,6 +185,29 @@ export function DailyPageClient({
|
|||||||
return formatDateLong(currentDate);
|
return formatDateLong(currentDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Charger les tâches complètes pour la date sélectionnée
|
||||||
|
useEffect(() => {
|
||||||
|
const loadDeadlineTasks = async () => {
|
||||||
|
const dateKey = formatDateForAPI(currentDate);
|
||||||
|
if (deadlineDates[dateKey] && deadlineDates[dateKey].length > 0) {
|
||||||
|
setLoadingDeadlineTasks(true);
|
||||||
|
try {
|
||||||
|
const tasks = await dailyClient.getDeadlineTasksForDate(currentDate);
|
||||||
|
setDeadlineTasks(tasks);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement des tâches:', error);
|
||||||
|
setDeadlineTasks([]);
|
||||||
|
} finally {
|
||||||
|
setLoadingDeadlineTasks(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setDeadlineTasks([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadDeadlineTasks();
|
||||||
|
}, [currentDate, deadlineDates]);
|
||||||
|
|
||||||
const isTodayDate = () => {
|
const isTodayDate = () => {
|
||||||
return isToday(currentDate);
|
return isToday(currentDate);
|
||||||
};
|
};
|
||||||
@@ -351,9 +396,55 @@ export function DailyPageClient({
|
|||||||
currentDate={currentDate}
|
currentDate={currentDate}
|
||||||
onDateSelect={handleDateSelect}
|
onDateSelect={handleDateSelect}
|
||||||
markedDates={dailyDates}
|
markedDates={dailyDates}
|
||||||
|
deadlineDates={deadlineDates}
|
||||||
showTodayButton={true}
|
showTodayButton={true}
|
||||||
showLegend={true}
|
showLegend={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Section des tâches avec deadline pour la date sélectionnée - Mobile */}
|
||||||
|
{deadlineTasks.length > 0 && (
|
||||||
|
<Card variant="glass">
|
||||||
|
<CardHeader padding="sm" separator={false}>
|
||||||
|
<CardTitle size="sm" className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-[var(--destructive)]"></div>
|
||||||
|
Tâches à terminer
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent padding="sm">
|
||||||
|
{loadingDeadlineTasks ? (
|
||||||
|
<div className="text-sm text-[var(--muted-foreground)] text-center py-4">
|
||||||
|
Chargement...
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{deadlineTasks.map((task) => (
|
||||||
|
<TaskCard
|
||||||
|
key={task.id}
|
||||||
|
variant="compact"
|
||||||
|
title={task.title}
|
||||||
|
description={task.description}
|
||||||
|
tags={task.tags}
|
||||||
|
primaryTagId={task.primaryTagId}
|
||||||
|
priority={task.priority}
|
||||||
|
status={task.status}
|
||||||
|
dueDate={task.dueDate}
|
||||||
|
source={task.source}
|
||||||
|
jiraKey={task.jiraKey}
|
||||||
|
jiraProject={task.jiraProject}
|
||||||
|
jiraType={task.jiraType}
|
||||||
|
todosCount={task.todosCount}
|
||||||
|
availableTags={availableTags}
|
||||||
|
fontSize="small"
|
||||||
|
onClick={() => {
|
||||||
|
window.location.href = `/kanban?taskId=${task.id}`;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -362,14 +453,60 @@ export function DailyPageClient({
|
|||||||
<div className="hidden sm:block">
|
<div className="hidden sm:block">
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
||||||
{/* Calendrier - Desktop */}
|
{/* Calendrier - Desktop */}
|
||||||
<div className="xl:col-span-1">
|
<div className="xl:col-span-1 space-y-6">
|
||||||
<Calendar
|
<Calendar
|
||||||
currentDate={currentDate}
|
currentDate={currentDate}
|
||||||
onDateSelect={handleDateSelect}
|
onDateSelect={handleDateSelect}
|
||||||
markedDates={dailyDates}
|
markedDates={dailyDates}
|
||||||
|
deadlineDates={deadlineDates}
|
||||||
showTodayButton={true}
|
showTodayButton={true}
|
||||||
showLegend={true}
|
showLegend={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Section des tâches avec deadline pour la date sélectionnée */}
|
||||||
|
{deadlineTasks.length > 0 && (
|
||||||
|
<Card variant="glass">
|
||||||
|
<CardHeader padding="sm" separator={false}>
|
||||||
|
<CardTitle size="sm" className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-[var(--destructive)]"></div>
|
||||||
|
Tâches à terminer
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent padding="sm">
|
||||||
|
{loadingDeadlineTasks ? (
|
||||||
|
<div className="text-sm text-[var(--muted-foreground)] text-center py-4">
|
||||||
|
Chargement...
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{deadlineTasks.map((task) => (
|
||||||
|
<TaskCard
|
||||||
|
key={task.id}
|
||||||
|
variant="compact"
|
||||||
|
title={task.title}
|
||||||
|
description={task.description}
|
||||||
|
tags={task.tags}
|
||||||
|
primaryTagId={task.primaryTagId}
|
||||||
|
priority={task.priority}
|
||||||
|
status={task.status}
|
||||||
|
dueDate={task.dueDate}
|
||||||
|
source={task.source}
|
||||||
|
jiraKey={task.jiraKey}
|
||||||
|
jiraProject={task.jiraProject}
|
||||||
|
jiraType={task.jiraType}
|
||||||
|
todosCount={task.todosCount}
|
||||||
|
availableTags={availableTags}
|
||||||
|
fontSize="small"
|
||||||
|
onClick={() => {
|
||||||
|
window.location.href = `/kanban?taskId=${task.id}`;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sections daily - Desktop */}
|
{/* Sections daily - Desktop */}
|
||||||
|
|||||||
@@ -37,27 +37,34 @@ export default async function DailyPage() {
|
|||||||
const today = getToday();
|
const today = getToday();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [dailyView, dailyDates, deadlineMetrics, pendingTasks] =
|
const [
|
||||||
await Promise.all([
|
dailyView,
|
||||||
dailyService.getDailyView(today, session.user.id),
|
dailyDates,
|
||||||
dailyService.getDailyDates(session.user.id),
|
deadlineDatesMap,
|
||||||
DeadlineAnalyticsService.getDeadlineMetrics(session.user.id).catch(
|
deadlineMetrics,
|
||||||
() => null
|
pendingTasks,
|
||||||
), // Graceful fallback
|
] = await Promise.all([
|
||||||
dailyService
|
dailyService.getDailyView(today, session.user.id),
|
||||||
.getPendingCheckboxes({
|
dailyService.getDailyDates(session.user.id),
|
||||||
maxDays: 7,
|
dailyService.getTaskDeadlineDates(session.user.id).catch(() => ({})), // Graceful fallback
|
||||||
excludeToday: true,
|
DeadlineAnalyticsService.getDeadlineMetrics(session.user.id).catch(
|
||||||
limit: 50,
|
() => null
|
||||||
userId: session.user.id,
|
), // Graceful fallback
|
||||||
})
|
dailyService
|
||||||
.catch(() => []), // Graceful fallback
|
.getPendingCheckboxes({
|
||||||
]);
|
maxDays: 7,
|
||||||
|
excludeToday: true,
|
||||||
|
limit: 50,
|
||||||
|
userId: session.user.id,
|
||||||
|
})
|
||||||
|
.catch(() => []), // Graceful fallback
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DailyPageClient
|
<DailyPageClient
|
||||||
initialDailyView={dailyView}
|
initialDailyView={dailyView}
|
||||||
initialDailyDates={dailyDates}
|
initialDailyDates={dailyDates}
|
||||||
|
initialDeadlineDates={deadlineDatesMap}
|
||||||
initialDate={today}
|
initialDate={today}
|
||||||
initialDeadlineMetrics={deadlineMetrics}
|
initialDeadlineMetrics={deadlineMetrics}
|
||||||
initialPendingTasks={pendingTasks}
|
initialPendingTasks={pendingTasks}
|
||||||
|
|||||||
@@ -174,6 +174,29 @@ export class DailyClient {
|
|||||||
return response.dates;
|
return response.dates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère toutes les dates de fin des tâches avec leurs noms
|
||||||
|
* Retourne un objet Record<string, string[]> où chaque clé est une date (YYYY-MM-DD)
|
||||||
|
* et la valeur est un tableau de noms de tâches
|
||||||
|
*/
|
||||||
|
async getDeadlineDates(): Promise<Record<string, string[]>> {
|
||||||
|
const response = await httpClient.get<{ dates: Record<string, string[]> }>(
|
||||||
|
'/daily/deadlines'
|
||||||
|
);
|
||||||
|
return response.dates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les tâches avec deadline pour une date donnée
|
||||||
|
*/
|
||||||
|
async getDeadlineTasksForDate(date: Date): Promise<Task[]> {
|
||||||
|
const dateStr = this.formatDateForAPI(date);
|
||||||
|
const response = await httpClient.get<{
|
||||||
|
tasks: Task[];
|
||||||
|
}>(`/daily/deadline-tasks?date=${dateStr}`);
|
||||||
|
return response.tasks;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère les checkboxes en attente (non cochées)
|
* Récupère les checkboxes en attente (non cochées)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ interface CalendarProps {
|
|||||||
currentDate: Date;
|
currentDate: Date;
|
||||||
onDateSelect: (date: Date) => void;
|
onDateSelect: (date: Date) => void;
|
||||||
markedDates?: string[]; // Liste des dates marquées (format YYYY-MM-DD)
|
markedDates?: string[]; // Liste des dates marquées (format YYYY-MM-DD)
|
||||||
|
deadlineDates?: Record<string, string[]>; // Date -> Array de noms de tâches
|
||||||
showTodayButton?: boolean;
|
showTodayButton?: boolean;
|
||||||
showLegend?: boolean;
|
showLegend?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -20,6 +21,7 @@ export function Calendar({
|
|||||||
currentDate,
|
currentDate,
|
||||||
onDateSelect,
|
onDateSelect,
|
||||||
markedDates = [],
|
markedDates = [],
|
||||||
|
deadlineDates = {},
|
||||||
showTodayButton = true,
|
showTodayButton = true,
|
||||||
showLegend = true,
|
showLegend = true,
|
||||||
className = '',
|
className = '',
|
||||||
@@ -100,6 +102,11 @@ export function Calendar({
|
|||||||
return markedDates.includes(formatDateKey(date));
|
return markedDates.includes(formatDateKey(date));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasDeadlineDate = (date: Date) => {
|
||||||
|
const dateKey = formatDateKey(date);
|
||||||
|
return deadlineDates[dateKey] && deadlineDates[dateKey].length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
const isSelected = (date: Date) => {
|
const isSelected = (date: Date) => {
|
||||||
return formatDateKey(date) === currentDateKey;
|
return formatDateKey(date) === currentDateKey;
|
||||||
};
|
};
|
||||||
@@ -164,6 +171,7 @@ export function Calendar({
|
|||||||
const isCurrentMonthDay = isCurrentMonth(date);
|
const isCurrentMonthDay = isCurrentMonth(date);
|
||||||
const isTodayDay = isTodayDate(date);
|
const isTodayDay = isTodayDate(date);
|
||||||
const hasMarked = hasMarkedDate(date);
|
const hasMarked = hasMarkedDate(date);
|
||||||
|
const hasDeadline = hasDeadlineDate(date);
|
||||||
const isSelectedDay = isSelected(date);
|
const isSelectedDay = isSelected(date);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -188,15 +196,27 @@ export function Calendar({
|
|||||||
>
|
>
|
||||||
{date.getDate()}
|
{date.getDate()}
|
||||||
|
|
||||||
{/* Indicateur de date marquée */}
|
{/* Indicateurs de dates */}
|
||||||
{hasMarked && (
|
<div className="absolute bottom-1 right-1 flex gap-0.5">
|
||||||
<div
|
{/* Indicateur de date marquée (point bleu) */}
|
||||||
className={`
|
{hasMarked && (
|
||||||
absolute bottom-1 right-1 w-2 h-2 rounded-full
|
<div
|
||||||
${isSelectedDay ? 'bg-white' : 'bg-[var(--primary)]'}
|
className={`
|
||||||
`}
|
w-2 h-2 rounded-full
|
||||||
/>
|
${isSelectedDay ? 'bg-white' : 'bg-[var(--primary)]'}
|
||||||
)}
|
`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* Indicateur de date de fin (point rouge) */}
|
||||||
|
{hasDeadline && (
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
w-2 h-2 rounded-full
|
||||||
|
${isSelectedDay ? 'bg-white' : 'bg-[var(--destructive)]'}
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -213,6 +233,14 @@ export function Calendar({
|
|||||||
Jour avec des éléments
|
Jour avec des éléments
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-8 flex justify-center">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-[var(--destructive)]"></div>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-left flex-1">
|
||||||
|
Date de fin de tâche
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-8 flex justify-center">
|
<div className="w-8 flex justify-center">
|
||||||
<div className="w-3 h-3 rounded border border-[var(--primary)] bg-[var(--primary)]/20"></div>
|
<div className="w-3 h-3 rounded border border-[var(--primary)] bg-[var(--primary)]/20"></div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
TaskStatus,
|
TaskStatus,
|
||||||
TaskPriority,
|
TaskPriority,
|
||||||
TaskSource,
|
TaskSource,
|
||||||
|
Task,
|
||||||
} from '@/lib/types';
|
} from '@/lib/types';
|
||||||
import {
|
import {
|
||||||
getPreviousWorkday,
|
getPreviousWorkday,
|
||||||
@@ -466,6 +467,137 @@ export class DailyService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère toutes les dates de fin (dueDate) des tâches non terminées (pour le calendrier)
|
||||||
|
* Retourne un objet avec les dates comme clés et les tâches associées
|
||||||
|
*/
|
||||||
|
async getTaskDeadlineDates(
|
||||||
|
userId: string
|
||||||
|
): Promise<Record<string, string[]>> {
|
||||||
|
const tasks = await prisma.task.findMany({
|
||||||
|
where: {
|
||||||
|
ownerId: userId,
|
||||||
|
dueDate: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
notIn: ['done', 'cancelled', 'archived'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
dueDate: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
dueDate: 'asc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const datesMap: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
tasks.forEach((task) => {
|
||||||
|
if (!task.dueDate) return;
|
||||||
|
// Normaliser la date pour éviter les problèmes de timezone
|
||||||
|
const normalizedDate = normalizeDate(task.dueDate);
|
||||||
|
const dateKey = formatDateForAPI(normalizedDate);
|
||||||
|
|
||||||
|
if (!datesMap[dateKey]) {
|
||||||
|
datesMap[dateKey] = [];
|
||||||
|
}
|
||||||
|
datesMap[dateKey].push(task.title);
|
||||||
|
});
|
||||||
|
|
||||||
|
return datesMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les tâches avec deadline pour une date donnée
|
||||||
|
* Retourne un tableau de tâches complètes
|
||||||
|
*/
|
||||||
|
async getTasksByDeadlineDate(userId: string, date: Date): Promise<Task[]> {
|
||||||
|
const normalizedDate = normalizeDate(date);
|
||||||
|
const dateKey = formatDateForAPI(normalizedDate);
|
||||||
|
|
||||||
|
const tasks = await prisma.task.findMany({
|
||||||
|
where: {
|
||||||
|
ownerId: userId,
|
||||||
|
dueDate: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
notIn: ['done', 'cancelled', 'archived'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
taskTags: {
|
||||||
|
include: {
|
||||||
|
tag: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
primaryTag: true,
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
dailyCheckboxes: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
dueDate: 'asc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filtrer les tâches dont la date de fin correspond à la date demandée
|
||||||
|
const filteredTasks = tasks
|
||||||
|
.filter((task) => {
|
||||||
|
if (!task.dueDate) return false;
|
||||||
|
const taskDateKey = formatDateForAPI(normalizeDate(task.dueDate));
|
||||||
|
return taskDateKey === dateKey;
|
||||||
|
})
|
||||||
|
.map((task) => ({
|
||||||
|
id: task.id,
|
||||||
|
title: task.title,
|
||||||
|
description: task.description ?? undefined,
|
||||||
|
status: task.status as TaskStatus,
|
||||||
|
priority: task.priority as TaskPriority,
|
||||||
|
source: task.source as TaskSource,
|
||||||
|
sourceId: task.sourceId ?? undefined,
|
||||||
|
tags: task.taskTags.map((tt) => tt.tag.name),
|
||||||
|
tagDetails: task.taskTags.map((tt) => ({
|
||||||
|
id: tt.tag.id,
|
||||||
|
name: tt.tag.name,
|
||||||
|
color: tt.tag.color,
|
||||||
|
isPinned: tt.tag.isPinned,
|
||||||
|
})),
|
||||||
|
primaryTagId: task.primaryTagId ?? undefined,
|
||||||
|
primaryTag: task.primaryTag
|
||||||
|
? {
|
||||||
|
id: task.primaryTag.id,
|
||||||
|
name: task.primaryTag.name,
|
||||||
|
color: task.primaryTag.color,
|
||||||
|
isPinned: task.primaryTag.isPinned,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
dueDate: task.dueDate ?? undefined,
|
||||||
|
completedAt: task.completedAt ?? undefined,
|
||||||
|
createdAt: task.createdAt,
|
||||||
|
updatedAt: task.updatedAt,
|
||||||
|
jiraProject: task.jiraProject ?? undefined,
|
||||||
|
jiraKey: task.jiraKey ?? undefined,
|
||||||
|
jiraType: task.jiraType ?? undefined,
|
||||||
|
tfsProject: task.tfsProject ?? undefined,
|
||||||
|
tfsPullRequestId: task.tfsPullRequestId ?? undefined,
|
||||||
|
tfsRepository: task.tfsRepository ?? undefined,
|
||||||
|
tfsSourceBranch: task.tfsSourceBranch ?? undefined,
|
||||||
|
tfsTargetBranch: task.tfsTargetBranch ?? undefined,
|
||||||
|
assignee: task.assignee ?? undefined,
|
||||||
|
ownerId: (task as unknown as { ownerId: string }).ownerId,
|
||||||
|
todosCount: task._count.dailyCheckboxes,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return filteredTasks;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Récupère toutes les checkboxes non cochées (tâches en attente)
|
* Récupère toutes les checkboxes non cochées (tâches en attente)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user