feat: show tasks completed today
ci / gitleaks (push) Successful in 7s
ci / docker (push) Successful in 5m38s

This commit is contained in:
2026-09-15 12:52:43 +08:00
parent d519843a9f
commit ebabb87780
10 changed files with 245 additions and 9 deletions
+23 -3
View File
@@ -26,7 +26,7 @@ import { readTodaySectionCollapse, writeTodaySectionCollapse, type TodaySectionC
type FolderItem = { id: string; name: string }
type TaskList = { id: string; folder_id: string | null; name: string; is_inbox: boolean }
type Task = { id: string; list_id: string; parent_id: string | null; title: string; description: string; priority: number; completed: boolean; version: number; due_at: string | null; due_has_time: boolean; subtasks?: Task[] }
type Task = { id: string; list_id: string; parent_id: string | null; title: string; description: string; priority: number; completed: boolean; completed_at: string | null; version: number; due_at: string | null; due_has_time: boolean; subtasks?: Task[] }
type RepeatOption = TaskRepeatOption
type Recurrence = { id: string; task_id: string; rrule: string | null; trigger_mode: 'scheduled' | 'after_completion'; after_completion_days: number | null }
type View = 'tasks' | 'today' | 'upcoming' | 'trash' | 'habits' | 'countdowns' | 'memos' | 'settings'
@@ -365,7 +365,11 @@ const visibleTasks = computed(() => {
const end = new Date(now); end.setDate(end.getDate() + 7)
let result = sourceTasks.value
if (['habits','settings','countdowns','memos'].includes(activeView.value)) return []
if (activeView.value === 'today') result = result.filter((task) => task.due_at && new Date(task.due_at).toDateString() === now.toDateString())
if (activeView.value === 'today') result = result.filter((task) => {
const dueToday = task.due_at && new Date(task.due_at).toDateString() === now.toDateString()
const completedToday = task.completed_at && new Date(task.completed_at).toDateString() === now.toDateString()
return Boolean(dueToday || completedToday)
})
if (activeView.value === 'upcoming') result = result.filter((task) => task.due_at && new Date(task.due_at) >= now && new Date(task.due_at) <= end)
return query.value.trim() ? filterTasks(result, query.value) : result
})
@@ -505,6 +509,10 @@ async function loadTodayTaskSummary() {
params.set('due_from', isoAtLocalDayOffset(0))
params.set('due_to', isoAtLocalDayOffset(1))
params.set('completed', String(completed))
if (completed) {
params.set('completed_from', isoAtLocalDayOffset(0))
params.set('completed_to', isoAtLocalDayOffset(1))
}
const data = await api(`/tasks?${params}`)
return Number(data.total ?? data.items?.length ?? 0)
}
@@ -532,7 +540,14 @@ async function loadTasksPage(request = beginLatestRequest('tasks')) {
const params = new URLSearchParams({ page: String(page.value), page_size: String(pageSize) })
if (query.value) params.set('q', query.value)
else if (activeView.value === 'tasks' && activeList.value) params.set('list_id', activeList.value)
if (activeView.value === 'today') { params.set('due_from', isoAtLocalDayOffset(0)); params.set('due_to', isoAtLocalDayOffset(1)) }
if (activeView.value === 'today') {
params.set('due_from', isoAtLocalDayOffset(0))
params.set('due_to', isoAtLocalDayOffset(1))
if (showCompleted.value) {
params.set('completed_from', isoAtLocalDayOffset(0))
params.set('completed_to', isoAtLocalDayOffset(1))
}
}
if (activeView.value === 'upcoming') { params.set('due_from', isoAtLocalDayOffset(0)); params.set('due_to', isoAtLocalDayOffset(8)) }
if (!showCompleted.value && activeView.value !== 'trash') params.set('completed', 'false')
const data = await api(`/tasks?${params}`)
@@ -545,6 +560,10 @@ async function loadTasksPage(request = beginLatestRequest('tasks')) {
completedParams.set('page', '1')
completedParams.set('page_size', '1')
completedParams.set('completed', 'true')
if (activeView.value === 'today') {
completedParams.set('completed_from', isoAtLocalDayOffset(0))
completedParams.set('completed_to', isoAtLocalDayOffset(1))
}
const completedData = await api(`/tasks?${completedParams}`)
if (!isLatestRequest('tasks', request)) return
hiddenCompletedTaskCount.value = Number(completedData.total ?? completedData.items?.length ?? 0)
@@ -697,6 +716,7 @@ async function toggle(task: Task) {
await waitForCompletionExit()
setTaskCompletionExiting(task.id, false)
}
if (activeView.value === 'today' && showCompleted.value) await loadAll()
},
)
}
+9
View File
@@ -774,6 +774,15 @@ describe('task and habit row decoration', () => {
expect(mvpPanel).toContain("!showCompleted && habits.length ? '已完成的习惯已隐藏。'")
})
it('includes tasks actually completed today in Today even when they were overdue', () => {
expect(app).toContain('completed_at: string | null')
expect(app).toContain("const completedToday = task.completed_at && new Date(task.completed_at).toDateString() === now.toDateString()")
expect(app).toContain('return Boolean(dueToday || completedToday)')
expect(app).toContain("params.set('completed_from', isoAtLocalDayOffset(0))")
expect(app).toContain("params.set('completed_to', isoAtLocalDayOffset(1))")
expect(app).toContain("if (activeView.value === 'today' && showCompleted.value) await loadAll()")
})
it('reclassifies Today tasks after due edits without toggling page loading', () => {
const saveBlock = app.slice(app.indexOf('async function saveTask('), app.indexOf('async function removeTask'))
const refreshBlock = app.slice(app.indexOf('async function refreshTodayAfterTaskSave()'), app.indexOf('async function saveTask('))