feat: show overdue tasks on today view
ci / docker (push) Successful in 4m16s

This commit is contained in:
2026-09-08 09:05:19 +08:00
parent 0dd67f2824
commit 5e0c168da1
3 changed files with 36 additions and 2 deletions
+23
View File
@@ -28,6 +28,7 @@ const folders = ref<FolderItem[]>([])
const lists = ref<TaskList[]>([])
const archivedLists = ref<TaskList[]>([])
const tasks = ref<Task[]>([])
const overdueTasks = ref<Task[]>([])
const trash = ref<Task[]>([])
const activeList = ref('')
const activeView = ref<View>(defaultView())
@@ -227,6 +228,7 @@ const visibleTasks = computed(() => {
const selectedTaskSubtasks = computed(() => selectedTask.value?.subtasks ?? [])
const filteredTaskTree = computed(() => groupTaskTree(visibleTasks.value))
const taskTree = computed(() => filteredTaskTree.value)
const overdueTaskTree = computed(() => groupTaskTree(overdueTasks.value))
let searchTimer: number | undefined
watch(query, () => {
if (searchTimer) window.clearTimeout(searchTimer)
@@ -321,6 +323,12 @@ function isoAtLocalDayOffset(offset: number) {
day.setHours(0, 0, 0, 0)
return day.toISOString()
}
async function loadOverdueTasks() {
const params = new URLSearchParams()
params.set('due_to', isoAtLocalDayOffset(0))
params.set('completed', 'false')
overdueTasks.value = await loadTaskPages(`/tasks?${params}`)
}
async function loadTasksPage() {
const params = new URLSearchParams({ page: String(page.value), page_size: String(pageSize) })
if (query.value) params.set('q', query.value)
@@ -356,6 +364,8 @@ async function loadAll() {
try {
if (!navigationLoaded.value) await loadNavigation()
await loadTasksPage()
if (activeView.value === 'today') await loadOverdueTasks()
else overdueTasks.value = []
if (page.value > totalPages.value) { page.value = totalPages.value; await loadTasksPage() }
} catch (reason) { fail(reason) } finally { loading.value = false }
}
@@ -389,6 +399,11 @@ async function patchTask(task: Task, patch: Partial<Task>) {
const updated = await api(`/tasks/${task.id}`, { method: 'PATCH', body: JSON.stringify({ ...patch, version: task.version }) })
const index = tasks.value.findIndex((item) => item.id === task.id)
if (index >= 0) tasks.value[index] = { ...tasks.value[index], ...updated }
const overdueIndex = overdueTasks.value.findIndex((item) => item.id === task.id)
if (overdueIndex >= 0) {
if (updated.completed) overdueTasks.value.splice(overdueIndex, 1)
else overdueTasks.value[overdueIndex] = { ...overdueTasks.value[overdueIndex], ...updated }
}
if (selectedTask.value?.id === task.id) selectedTask.value = { ...selectedTask.value, ...updated }
return updated as Task
}
@@ -682,6 +697,14 @@ onMounted(bootstrap)
<div class="today-stat muted"><strong>{{ totalTasks }}</strong><span>今日任务</span></div>
</section>
<template v-if="activeView==='today'">
<section v-if="overdueTaskTree.length" class="overdue-section">
<h3 class="section-heading overdue-heading"><CalendarDays/>已过期 <span>{{overdueTaskTree.length}}</span></h3>
<div class="task-list overdue-list">
<template v-for="node in overdueTaskTree" :key="`overdue-${node.task.id}`">
<article class="task-row overdue-task"><button class="task-check" :aria-label="`完成${node.task.title}`" :aria-pressed="false" @click.stop="toggle(node.task)"><span class="task-check-mark" :class="`p${node.task.priority}`"></span></button><div class="task-main" role="button" tabindex="0" @click="selectTask(node.task)" @keydown.enter="selectTask(node.task)"><strong>{{node.task.title}}</strong><span class="meta"><span><CalendarDays/>{{formatDue(node.task.due_at)}}</span></span></div><span class="overdue-badge">已过期</span></article>
</template>
</div>
</section>
<h3 class="section-heading"><ListTodo/>任务</h3>
</template>
<div class="list-toolbar"><label v-if="activeView!=='trash'"><input v-model="showCompleted" type="checkbox"> 显示已完成</label><span v-if="activeView==='trash' || totalPages > 1 || totalTasks > 0">{{ totalPages > 1 ? `第 ${page} / ${totalPages} 页 · ` : '' }}共 {{totalTasks}} 项</span><button v-if="query" class="link" @click="query=''">清除搜索</button></div>