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:
@@ -11,6 +11,7 @@ import { DailySection } from '@/components/daily/DailySection';
|
|||||||
import { PendingTasksSection } from '@/components/daily/PendingTasksSection';
|
import { PendingTasksSection } from '@/components/daily/PendingTasksSection';
|
||||||
import { dailyClient } from '@/clients/daily-client';
|
import { dailyClient } from '@/clients/daily-client';
|
||||||
import { Header } from '@/components/ui/Header';
|
import { Header } from '@/components/ui/Header';
|
||||||
|
import { DeadlineReminder } from '@/components/daily/DeadlineReminder';
|
||||||
import { getPreviousWorkday, formatDateLong, isToday, generateDateTitle, formatDateShort, isYesterday } from '@/lib/date-utils';
|
import { getPreviousWorkday, formatDateLong, isToday, generateDateTitle, formatDateShort, isYesterday } from '@/lib/date-utils';
|
||||||
|
|
||||||
interface DailyPageClientProps {
|
interface DailyPageClientProps {
|
||||||
@@ -211,8 +212,13 @@ export function DailyPageClient({
|
|||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* 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 */}
|
{/* Layout Mobile uniquement - Section Aujourd'hui en premier */}
|
||||||
<div className="block sm:hidden">
|
<div className="block sm:hidden">
|
||||||
{dailyView && (
|
{dailyView && (
|
||||||
|
|||||||
120
src/components/daily/DeadlineReminder.tsx
Normal file
120
src/components/daily/DeadlineReminder.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user