feat(tasks): due dates with optional time; alarms at the right moment
Due dates can carry a time (YYYY-MM-DDTHH:MM). Alarms fire at that time,
or 09:00 for all-day tasks, minus the user's lead time, in TZ (default
Europe/Berlin, tzdata embedded). A new due date or reopening a task
re-arms the alarm. Recurrence keeps the time; Markdown export/import
carry it. The UI shows relative labels ("Heute, 14:30"), marks overdue
tasks to the minute and groups by local day instead of the UTC date.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
300fd6a7be
commit
344f5e4ab2
21 files changed
+391
-50
No files matched your search
@@ -28,6 +28,7 @@ import {
|
||||
Repeat,
|
||||
} from 'lucide-react';
|
||||
import { TaskList, Thought, SubTask, Team, User, TaskListShare, TaskListMember, KanbanColumn, PriorityLevel, Recurrence, RECURRENCE_LABELS } from '../../lib/types';
|
||||
import { formatDue, isDueToday, isOverdue, localDay, splitDue } from '../../lib/due';
|
||||
import { api, triggerHaptic } from '../../lib/api';
|
||||
import { UnifiedShareModal } from '../../components/UnifiedShareModal';
|
||||
import { TaskDetailModal } from './TaskDetailModal';
|
||||
@@ -416,10 +417,11 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
if (high.length > 0) groups.push({ key: 'p:high', title: '⚠️ Wichtig', count: high.length, tasks: high });
|
||||
if (normal.length > 0) groups.push({ key: 'p:normal', title: 'Normal', count: normal.length, tasks: normal });
|
||||
} else if (groupBy === 'due') {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const overdue = filteredTasks.filter((t) => t.dueDate && t.dueDate < today);
|
||||
const dueToday = filteredTasks.filter((t) => t.dueDate === today);
|
||||
const upcoming = filteredTasks.filter((t) => t.dueDate && t.dueDate > today);
|
||||
const now = new Date();
|
||||
const today = localDay(now);
|
||||
const overdue = filteredTasks.filter((t) => isOverdue(t.dueDate, now));
|
||||
const dueToday = filteredTasks.filter((t) => isDueToday(t.dueDate, now) && !isOverdue(t.dueDate, now));
|
||||
const upcoming = filteredTasks.filter((t) => t.dueDate && splitDue(t.dueDate).date > today);
|
||||
const noDue = filteredTasks.filter((t) => !t.dueDate);
|
||||
if (overdue.length > 0) groups.push({ key: 'due:overdue', title: '⚠️ Überfällig', count: overdue.length, tasks: overdue });
|
||||
if (dueToday.length > 0) groups.push({ key: 'due:today', title: '📅 Heute fällig', count: dueToday.length, tasks: dueToday });
|
||||
@@ -1311,9 +1313,12 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
|
||||
{/* Due Date */}
|
||||
{task.dueDate && (
|
||||
<span className="inline-flex items-center gap-1 text-[10px] text-muted">
|
||||
<span
|
||||
className={`inline-flex items-center gap-1 text-[10px] ${!task.isCompleted && isOverdue(task.dueDate) ? 'text-status-err font-semibold' : 'text-muted'}`}
|
||||
title={`Fällig: ${task.dueDate.replace('T', ' ')}`}
|
||||
>
|
||||
<Calendar className="w-2.5 h-2.5 text-accent" />
|
||||
<span>{task.dueDate}</span>
|
||||
<span>{formatDue(task.dueDate)}</span>
|
||||
</span>
|
||||
)}
|
||||
{task.recurrence && (
|
||||
@@ -2335,9 +2340,12 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
||||
</span>
|
||||
)}
|
||||
{task.dueDate && (
|
||||
<span className="inline-flex items-center gap-0.5 text-[9px] text-muted">
|
||||
<span
|
||||
className={`inline-flex items-center gap-0.5 text-[9px] ${!task.isCompleted && isOverdue(task.dueDate) ? 'text-status-err font-semibold' : 'text-muted'}`}
|
||||
title={`Fällig: ${task.dueDate.replace('T', ' ')}`}
|
||||
>
|
||||
<Calendar className="w-2.5 h-2.5" />
|
||||
<span>{task.dueDate}</span>
|
||||
<span>{formatDue(task.dueDate)}</span>
|
||||
</span>
|
||||
)}
|
||||
{task.recurrence && (
|
||||
|
||||
Reference in new issue
Block a user