feat: add DeadlineReminder component for urgent task notifications

- Introduced `DeadlineReminder` component to display urgent tasks based on deadlines.
- Integrated the component into `DailyPageClient` for desktop view, enhancing user awareness of critical tasks.
- Implemented logic to fetch and sort urgent tasks by urgency level and remaining days.
This commit is contained in:
Julien Froidefond
2025-09-23 21:52:56 +02:00
parent 34b9aff6e7
commit 7ac961f6c7
2 changed files with 127 additions and 1 deletions

View File

@@ -11,6 +11,7 @@ import { DailySection } from '@/components/daily/DailySection';
import { PendingTasksSection } from '@/components/daily/PendingTasksSection';
import { dailyClient } from '@/clients/daily-client';
import { Header } from '@/components/ui/Header';
import { DeadlineReminder } from '@/components/daily/DeadlineReminder';
import { getPreviousWorkday, formatDateLong, isToday, generateDateTitle, formatDateShort, isYesterday } from '@/lib/date-utils';
interface DailyPageClientProps {
@@ -211,8 +212,13 @@ export function DailyPageClient({
</div>
</div>
{/* Rappel des échéances urgentes - Desktop uniquement */}
<div className="hidden sm:block container mx-auto px-4 pt-4 pb-2">
<DeadlineReminder />
</div>
{/* Contenu principal */}
<main className="container mx-auto px-4 py-8">
<main className="container mx-auto px-4 py-6 sm:py-4">
{/* Layout Mobile uniquement - Section Aujourd'hui en premier */}
<div className="block sm:hidden">
{dailyView && (

View File

@@ -0,0 +1,120 @@
'use client';
import { useState, useEffect, useTransition } from 'react';
import { DeadlineTask } from '@/services/analytics/deadline-analytics';
import { getDeadlineMetrics } from '@/actions/deadline-analytics';
import { Card } from '@/components/ui/Card';
export function DeadlineReminder() {
const [urgentTasks, setUrgentTasks] = useState<DeadlineTask[]>([]);
const [error, setError] = useState<string | null>(null);
const [isPending, startTransition] = useTransition();
useEffect(() => {
const loadUrgentTasks = () => {
startTransition(async () => {
try {
setError(null);
const response = await getDeadlineMetrics();
if (response.success && response.data) {
// Combiner toutes les tâches urgentes et trier par urgence
const combinedTasks = [
...response.data.overdue,
...response.data.critical,
...response.data.warning
].sort((a, b) => {
// En retard d'abord, puis critique, puis attention
const urgencyOrder: Record<string, number> = { 'overdue': 0, 'critical': 1, 'warning': 2 };
if (urgencyOrder[a.urgencyLevel] !== urgencyOrder[b.urgencyLevel]) {
return urgencyOrder[a.urgencyLevel] - urgencyOrder[b.urgencyLevel];
}
// Si même urgence, trier par jours restants
return a.daysRemaining - b.daysRemaining;
});
setUrgentTasks(combinedTasks);
} else {
setError(response.error || 'Erreur lors du chargement des échéances');
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Erreur lors du chargement des échéances');
console.error('Erreur échéances:', err);
}
});
};
loadUrgentTasks();
}, []);
const getUrgencyIcon = (task: DeadlineTask) => {
if (task.urgencyLevel === 'overdue') return '🔴';
if (task.urgencyLevel === 'critical') return '🟠';
return '🟡';
};
const getUrgencyText = (task: DeadlineTask) => {
if (task.urgencyLevel === 'overdue') {
return task.daysRemaining === -1 ? 'En retard de 1 jour' : `En retard de ${Math.abs(task.daysRemaining)} jours`;
} else if (task.urgencyLevel === 'critical') {
return task.daysRemaining === 0 ? 'Échéance aujourd\'hui' :
task.daysRemaining === 1 ? 'Échéance demain' :
`Dans ${task.daysRemaining} jours`;
} else {
return `Dans ${task.daysRemaining} jours`;
}
};
const getSourceIcon = (source: string) => {
switch (source) {
case 'jira': return '🔗';
case 'reminder': return '📱';
default: return '📋';
}
};
// Ne rien afficher si pas de tâches urgentes ou si en cours de chargement
if (isPending || error || urgentTasks.length === 0) {
return null;
}
return (
<Card className="mb-2 p-4 bg-gradient-to-r from-amber-50/50 to-orange-50/50 dark:from-amber-950/20 dark:to-orange-950/20 border-amber-200/50 dark:border-amber-800/50">
<div className="flex items-start gap-3">
<div className="text-2xl"></div>
<div className="flex-1 min-w-0">
<h3 className="text-sm font-semibold text-amber-800 dark:text-amber-200 mb-2">
Rappel - Tâches urgentes ({urgentTasks.length})
</h3>
<div className="flex flex-wrap gap-2">
{urgentTasks.map((task, index) => (
<div
key={task.id}
className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs font-medium bg-white/60 dark:bg-gray-800/60 border border-amber-200/60 dark:border-amber-700/60"
>
<span>{getUrgencyIcon(task)}</span>
<span className="truncate max-w-[200px]" title={task.title}>
{task.title}
</span>
<span className="text-[10px] text-amber-700 dark:text-amber-300 opacity-75">
({getUrgencyText(task)})
</span>
<span className="text-[10px] opacity-60">
{getSourceIcon(task.source)}
</span>
{index < urgentTasks.length - 1 && (
<span className="text-amber-400 dark:text-amber-500 opacity-50"></span>
)}
</div>
))}
</div>
<div className="mt-2 text-xs text-amber-700 dark:text-amber-300 opacity-75">
Consultez la page d'accueil pour plus de détails sur les échéances
</div>
</div>
</div>
</Card>
);
}