feat: show tasks completed today
This commit is contained in:
+23
-3
@@ -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()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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('))
|
||||
|
||||
Reference in New Issue
Block a user