26948fdb51
- DashboardBuilder: @dnd-kit 12-Spalten-Flow-Grid (seed-konsistent), View/Edit-Schalter, Resize, Tab-Verwaltung, Dashboard-CRUD + Set-Default, Dirty-Save - MiniAppHost ersetzt DashboardWidgetLoader (lazy Registry + settings-Props); Palette nur renderbare Apps (component-Filter) - WidgetSettingsForm generisch aus settings_schema; Bestands-Widgets settings-fähig (RecentContacts: limit) - api/miniapps.ts + api/dashboards.ts (TanStack-Query-Hooks, documents.ts-Muster) - Dashboard.tsx = Builder-Host (StatCards/SystemMetrics bleiben bis M4); Legacy-Grid/Loader gelöscht, Geister-Test ersetzt - Tests: Builder 13/13, Page 11/11, i18n de/en, tsc clean, build OK
56 lines
2.1 KiB
TypeScript
56 lines
2.1 KiB
TypeScript
/**
|
|
* TasksSummaryWidget — shows open tasks count (Task 5.25).
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import type { WidgetComponentProps } from '@/components/dashboard/MiniAppHost';
|
|
import { useTasks } from '@/api/tasks';
|
|
import { CheckSquare, AlertCircle, Clock } from 'lucide-react';
|
|
|
|
export function TasksSummaryWidget({ settings }: WidgetComponentProps) {
|
|
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>
|
|
);
|
|
}
|