feat(tasks): repeating tasks (daily, weekly, monthly, yearly)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m28s

- recurrence on tasks, set in the detail dialog; 🔁 on rows and Kanban cards
- Completing a repeating task (detail, list, Kanban drag to done, note
  checkbox) creates the next one: title, tags, priority, notes, list,
  assignees and rule copied, subtasks reset, first open column, due date
  one interval later (month end clamped); pushed live as TASK_CREATED
- Each task spawns at most once (re-open + complete makes no duplicate)
- Export/import keep the rule (Obsidian Tasks "🔁 every week", JSON field)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-29 07:23:09 +01:00
1 parent 0e9fd1b3ad
commit 588c679c6a
16 files changed
+376 -19

No files matched your search

@@ -17,8 +17,9 @@ import {
Webhook,
Lock,
UserPlus,
Repeat,
} from 'lucide-react';
import { Thought, SubTask, TaskList, TaskListMember, KanbanColumn, User } from '../../lib/types';
import { Thought, SubTask, TaskList, TaskListMember, KanbanColumn, User, Recurrence } from '../../lib/types';
import { UserAvatar } from '../../components/UserAvatar';
import { UserAutocomplete } from '../UserAutocomplete';
import { triggerHaptic } from '../../lib/api';
@@ -34,6 +35,7 @@ interface TaskDetailModalProps {
onUpdateStatus: (task: Thought, newStatus: string) => Promise<void>;
onUpdatePriority: (task: Thought, newPriority: string) => Promise<void>;
onUpdateDueDate: (task: Thought, newDueDate: string) => Promise<void>;
onUpdateRecurrence?: (task: Thought, recurrence: Recurrence) => Promise<void>;
onUpdateTaskList: (task: Thought, newListId: string) => Promise<void>;
onUpdateNotes: (task: Thought, newNotes: string) => Promise<void>;
onUpdateTags?: (task: Thought, newTags: string[]) => Promise<void>;
@@ -58,6 +60,7 @@ export const TaskDetailModal: React.FC<TaskDetailModalProps> = ({
onUpdateStatus,
onUpdatePriority,
onUpdateDueDate,
onUpdateRecurrence,
onUpdateTaskList,
onUpdateNotes,
onUpdateTags,
@@ -509,6 +512,32 @@ export const TaskDetailModal: React.FC<TaskDetailModalProps> = ({
+1 Woche
</button>
</div>
{onUpdateRecurrence && (
<div className="pt-1">
<label className="flex items-center gap-1 text-[11px] text-muted mb-1">
<Repeat className="w-3 h-3 text-accent" />
<span>Wiederholung</span>
</label>
<select
value={task.recurrence || ''}
disabled={readOnly}
onChange={(e) => onUpdateRecurrence(task, e.target.value as Recurrence)}
className="w-full h-11 sm:h-8 input-field px-2 text-base sm:text-xs bg-panel"
>
<option value="">Keine Wiederholung</option>
<option value="daily">🔁 Täglich</option>
<option value="weekly">🔁 Wöchentlich</option>
<option value="monthly">🔁 Monatlich</option>
<option value="yearly">🔁 Jährlich</option>
</select>
{task.recurrence && (
<p className="text-[10px] text-faint mt-1">
Beim Erledigen entsteht automatisch die nächste Aufgabe, fällig{' '}
{task.dueDate ? 'ein Intervall nach diesem Datum' : 'ein Intervall nach dem Erledigen'}.
</p>
)}
</div>
)}
</div>
{/* Task List assignment */}
+22 -1
View File
@@ -25,8 +25,9 @@ import {
Folder,
Filter,
Webhook,
Repeat,
} from 'lucide-react';
import { TaskList, Thought, SubTask, Team, User, TaskListShare, TaskListMember, KanbanColumn, PriorityLevel } from '../../lib/types';
import { TaskList, Thought, SubTask, Team, User, TaskListShare, TaskListMember, KanbanColumn, PriorityLevel, Recurrence, RECURRENCE_LABELS } from '../../lib/types';
import { api, triggerHaptic } from '../../lib/api';
import { UnifiedShareModal } from '../../components/UnifiedShareModal';
import { TaskDetailModal } from './TaskDetailModal';
@@ -504,6 +505,15 @@ export const TasksView: React.FC<TasksViewProps> = ({
await api.updateThought(task.id, { notes: newNotes });
};
const handleUpdateTaskRecurrence = async (task: Thought, recurrence: Recurrence) => {
setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, recurrence } : t)));
try {
await api.updateThought(task.id, { recurrence });
} catch {
setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, recurrence: task.recurrence } : t)));
}
};
const handleUpdateTaskAssignees = async (task: Thought, assignees: string[]) => {
triggerHaptic(10);
setTasks((prev) => prev.map((t) => (t.id === task.id ? { ...t, assignees } : t)));
@@ -1306,6 +1316,11 @@ export const TasksView: React.FC<TasksViewProps> = ({
<span>{task.dueDate}</span>
</span>
)}
{task.recurrence && (
<span className="inline-flex items-center text-muted" title={`Wiederholt sich ${RECURRENCE_LABELS[task.recurrence]}`}>
<Repeat className="w-3 h-3" />
</span>
)}
{/* Hashtag badges */}
{task.tags?.map((tag) => (
@@ -2131,6 +2146,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
onUpdateStatus={handleSetKanbanStatus}
onUpdatePriority={handleSetPriority}
onUpdateDueDate={handleUpdateTaskDueDate}
onUpdateRecurrence={handleUpdateTaskRecurrence}
onUpdateTaskList={handleUpdateTaskTaskList}
onUpdateNotes={handleUpdateTaskNotes}
onUpdateTags={handleUpdateTaskTags}
@@ -2324,6 +2340,11 @@ export const TasksView: React.FC<TasksViewProps> = ({
<span>{task.dueDate}</span>
</span>
)}
{task.recurrence && (
<span className="inline-flex items-center text-muted" title={`Wiederholt sich ${RECURRENCE_LABELS[task.recurrence]}`}>
<Repeat className="w-2.5 h-2.5" />
</span>
)}
{task.tags?.map((tag) => (
<button
key={tag}