Task 5.25: Dashboard-System — GET /api/v1/dashboard/widgets, DashboardWidgetLoader, DashboardGrid with drag-and-drop, 3 example widgets (RecentContacts, TasksSummary, CalendarUpcoming), updated Dashboard.tsx, i18n, 6 tests
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* TasksSummaryWidget — shows open tasks count (Task 5.25).
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTasks } from '@/api/tasks';
|
||||
import { CheckSquare, AlertCircle, Clock } from 'lucide-react';
|
||||
|
||||
export function TasksSummaryWidget() {
|
||||
const { t } = useTranslation();
|
||||
const { data, isLoading, isError } = useTasks(1, 100);
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="animate-pulse h-8 bg-secondary-100 rounded" data-testid="tasks-summary-loading" />;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <p className="text-sm text-secondary-500" data-testid="tasks-summary-error">{t('dashboard.widgetError')}</p>;
|
||||
}
|
||||
|
||||
const tasks = data?.items ?? [];
|
||||
const openTasks = tasks.filter((task) => task.status !== 'done');
|
||||
const overdueTasks = tasks.filter((task) => {
|
||||
if (!task.due_date || task.status === 'done') return false;
|
||||
return new Date(task.due_date) < new Date();
|
||||
});
|
||||
const highPriority = openTasks.filter((task) => task.priority === 'high' || task.priority === 'urgent');
|
||||
|
||||
return (
|
||||
<div className="space-y-3" data-testid="tasks-summary-widget">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckSquare className="w-5 h-5 text-primary-600" />
|
||||
<span className="text-2xl font-bold text-secondary-900">{openTasks.length}</span>
|
||||
<span className="text-sm text-secondary-500">{t('dashboard.openTasks')}</span>
|
||||
</div>
|
||||
{overdueTasks.length > 0 && (
|
||||
<div className="flex items-center gap-2 text-sm text-danger-600">
|
||||
<AlertCircle className="w-4 h-4" />
|
||||
<span>{overdueTasks.length} {t('dashboard.overdueTasks')}</span>
|
||||
</div>
|
||||
)}
|
||||
{highPriority.length > 0 && (
|
||||
<div className="flex items-center gap-2 text-sm text-warning-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>{highPriority.length} {t('dashboard.highPriorityTasks')}</span>
|
||||
</div>
|
||||
)}
|
||||
{openTasks.length === 0 && (
|
||||
<p className="text-sm text-secondary-500">{t('dashboard.noOpenTasks')}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user