feat: task management enhancements (cancelled status, recurrence, timestamps)
- Add 'cancelled' status to TaskStatus type, StatusBadge, TaskCard, TaskEditorView, TaskViewerView, TasksListView - Add RecurrenceEditor component (none / interval / calendar rules) - TaskEditorView: wire RecurrenceEditor, show started_at/completed_at timestamps read-only, include recurrence_rule in save payload - TaskViewerView: show recurrence summary, timestamps in meta row - tasks.ts: statusFilter/priorityFilter as arrays, add recurrence_rule to updateTask and createTask payloads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,12 +33,14 @@ const statusCycle: Record<TaskStatus, TaskStatus> = {
|
||||
todo: "in_progress",
|
||||
in_progress: "done",
|
||||
done: "todo",
|
||||
cancelled: "todo",
|
||||
};
|
||||
|
||||
const statusDotClass: Record<TaskStatus, string> = {
|
||||
todo: "dot-todo",
|
||||
in_progress: "dot-in-progress",
|
||||
done: "dot-done",
|
||||
cancelled: "dot-cancelled",
|
||||
};
|
||||
|
||||
function cycleSubTaskStatus(subTask: Note) {
|
||||
@@ -137,8 +139,25 @@ const forwardStatus: Record<TaskStatus, TaskStatus | null> = {
|
||||
todo: "in_progress",
|
||||
in_progress: "done",
|
||||
done: null,
|
||||
cancelled: null,
|
||||
};
|
||||
|
||||
function recurrenceSummary(rule: Record<string, unknown> | null): string | null {
|
||||
if (!rule) return null;
|
||||
if (rule.type === "interval") {
|
||||
return `Every ${rule.every} ${rule.unit}(s)`;
|
||||
}
|
||||
if (rule.type === "calendar") {
|
||||
if (rule.unit === "month") return `Monthly on day ${rule.day_of_month}`;
|
||||
if (rule.unit === "year") {
|
||||
const months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
||||
const m = months[((rule.month as number) ?? 1) - 1];
|
||||
return `Yearly on ${m} ${rule.day_of_month}`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const advanceLabel = computed(() => {
|
||||
const s = store.currentTask?.status as TaskStatus | undefined;
|
||||
if (!s) return null;
|
||||
@@ -318,6 +337,17 @@ const subTaskProgress = computed(() => {
|
||||
Due: {{ store.currentTask.due_date }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="task-meta-row" v-if="store.currentTask.started_at || store.currentTask.completed_at || store.currentTask.recurrence_rule">
|
||||
<span v-if="store.currentTask.started_at" class="task-meta-item">
|
||||
Started: {{ new Date(store.currentTask.started_at).toLocaleString() }}
|
||||
</span>
|
||||
<span v-if="store.currentTask.completed_at" class="task-meta-item">
|
||||
Completed: {{ new Date(store.currentTask.completed_at).toLocaleString() }}
|
||||
</span>
|
||||
<span v-if="recurrenceSummary(store.currentTask.recurrence_rule as Record<string, unknown> | null)" class="task-meta-item task-meta-recurrence">
|
||||
↻ {{ recurrenceSummary(store.currentTask.recurrence_rule as Record<string, unknown> | null) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="tags" v-if="store.currentTask.tags.length">
|
||||
<TagPill
|
||||
v-for="tag in store.currentTask.tags"
|
||||
@@ -551,6 +581,20 @@ const subTaskProgress = computed(() => {
|
||||
color: var(--color-overdue);
|
||||
font-weight: 600;
|
||||
}
|
||||
.task-meta-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.task-meta-item {
|
||||
font-size: 0.78rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.task-meta-recurrence {
|
||||
color: var(--color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
.tags {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
@@ -637,6 +681,9 @@ const subTaskProgress = computed(() => {
|
||||
.dot-done {
|
||||
background: var(--color-status-done, #22c55e);
|
||||
}
|
||||
.dot-cancelled {
|
||||
background: var(--color-text-muted, #6b7280);
|
||||
}
|
||||
.sub-title {
|
||||
flex: 1;
|
||||
font-size: 0.9rem;
|
||||
|
||||
Reference in New Issue
Block a user