Commit 1a440237 authored by Administrator's avatar Administrator
Browse files

feat(task): add "only incomplete" filter



Adds a checkbox to the TaskView filter panel that, when checked,
attaches completion_percent_lte=99 to the /api/tasks/ query — i.e.
tasks with completion_percent < 100% (not_started + in_progress).

Bug fix: TaskView.fetchTasks built its query inline and bypassed
useTaskFilters.toApiParams(), so any future field added to the
composable's params mapping would silently no-op. Route the call
through toApiParams() to make the composable the single source of
truth.

Coverage: extend useTaskFilters.spec toApiParams suite with both
branches of onlyIncomplete.
Co-Authored-By: default avatarClaude <noreply@anthropic.com>
parent efd558d6
...@@ -200,4 +200,21 @@ describe('toApiParams', () => { ...@@ -200,4 +200,21 @@ describe('toApiParams', () => {
story_id: null, story_id: null,
}) })
}) })
it('attaches completion_percent_lte=99 when onlyIncomplete is set', () => {
const f = useTaskFilters(refs)
f.filters.value.onlyIncomplete = true
expect(f.toApiParams()).toEqual({
student_id: null,
subject_id: null,
term_id: null,
story_id: null,
completion_percent_lte: 99,
})
})
it('omits completion_percent_lte when onlyIncomplete is false', () => {
const f = useTaskFilters(refs)
expect(f.toApiParams()).not.toHaveProperty('completion_percent_lte')
})
}) })
...@@ -27,6 +27,7 @@ const emit = defineEmits([ ...@@ -27,6 +27,7 @@ const emit = defineEmits([
'update:subject-id', 'update:subject-id',
'update:term-id', 'update:term-id',
'update:story-id', 'update:story-id',
'update:only-incomplete',
]) ])
const { t } = useI18n() // available as $t in template; used here for consistency. const { t } = useI18n() // available as $t in template; used here for consistency.
...@@ -114,6 +115,22 @@ const { t } = useI18n() // available as $t in template; used here for consistenc ...@@ -114,6 +115,22 @@ const { t } = useI18n() // available as $t in template; used here for consistenc
/> />
</v-col> </v-col>
<v-col cols="12">
<v-checkbox
:model-value="filters.onlyIncomplete"
:label="t('task.filters.onlyIncomplete')"
density="compact"
hide-details
color="primary"
@update:model-value="
(v) => {
emit('update:only-incomplete', v)
emit('apply')
}
"
/>
</v-col>
<v-col cols="12"> <v-col cols="12">
<v-btn <v-btn
color="primary" color="primary"
......
...@@ -25,6 +25,8 @@ export function useTaskFilters({ students, subjects, terms, stories }) { ...@@ -25,6 +25,8 @@ export function useTaskFilters({ students, subjects, terms, stories }) {
subjectId: null, subjectId: null,
termId: null, termId: null,
storyId: null, storyId: null,
// 只看未完成:completion_percent < 100%(覆盖 not_started + in_progress)
onlyIncomplete: false,
}) })
// 横屏(width > height)时默认收起 filter panel,避免 304px 高的 panel 把 // 横屏(width > height)时默认收起 filter panel,避免 304px 高的 panel 把
...@@ -79,16 +81,22 @@ export function useTaskFilters({ students, subjects, terms, stories }) { ...@@ -79,16 +81,22 @@ export function useTaskFilters({ students, subjects, terms, stories }) {
subjectId: null, subjectId: null,
termId: null, termId: null,
storyId: null, storyId: null,
onlyIncomplete: false,
} }
} }
function toApiParams() { function toApiParams() {
return { const params = {
student_id: filters.value.studentId, student_id: filters.value.studentId,
subject_id: filters.value.subjectId, subject_id: filters.value.subjectId,
term_id: filters.value.termId, term_id: filters.value.termId,
story_id: filters.value.storyId, story_id: filters.value.storyId,
} }
// 只看未完成:completion_percent < 100%(兼容后端 status 枚举推导规则)
if (filters.value.onlyIncomplete) {
params.completion_percent_lte = 99
}
return params
} }
// Cascade: 学生变了 → 重置学期和系列任务。 // Cascade: 学生变了 → 重置学期和系列任务。
......
...@@ -66,6 +66,7 @@ export default { ...@@ -66,6 +66,7 @@ export default {
expand: 'Expand Filter Panel', expand: 'Expand Filter Panel',
collapse: 'Collapse Filter Panel', collapse: 'Collapse Filter Panel',
expandToReselect: 'Click to Expand and Reselect', expandToReselect: 'Click to Expand and Reselect',
onlyIncomplete: 'Only incomplete',
}, },
edit: { edit: {
title: 'Edit Task', title: 'Edit Task',
......
...@@ -66,6 +66,7 @@ export default { ...@@ -66,6 +66,7 @@ export default {
expand: '展开筛选面板', expand: '展开筛选面板',
collapse: '收起筛选面板', collapse: '收起筛选面板',
expandToReselect: '点击展开重新选择', expandToReselect: '点击展开重新选择',
onlyIncomplete: '只看未完成',
}, },
edit: { edit: {
title: '编辑作业', title: '编辑作业',
......
...@@ -90,12 +90,7 @@ async function fetchTasks(filterParams = null) { ...@@ -90,12 +90,7 @@ async function fetchTasks(filterParams = null) {
isLoading.value = true isLoading.value = true
clearError() clearError()
try { try {
const params = filterParams || { const params = filterParams || filterPanel.toApiParams()
student_id: filterPanel.filters.value.studentId,
subject_id: filterPanel.filters.value.subjectId,
term_id: filterPanel.filters.value.termId,
story_id: filterPanel.filters.value.storyId,
}
const response = await getTasks(params) const response = await getTasks(params)
tasks.value = Array.isArray(response) ? response : [] tasks.value = Array.isArray(response) ? response : []
} catch (e) { } catch (e) {
...@@ -134,6 +129,9 @@ function onUpdateTermId(value) { ...@@ -134,6 +129,9 @@ function onUpdateTermId(value) {
function onUpdateStoryId(value) { function onUpdateStoryId(value) {
filterPanel.filters.value.storyId = value filterPanel.filters.value.storyId = value
} }
function onUpdateOnlyIncomplete(value) {
filterPanel.filters.value.onlyIncomplete = value
}
// ─── Edit dialog open / close ─────────────────────────────────────── // ─── Edit dialog open / close ───────────────────────────────────────
function openEditForTask(taskId) { function openEditForTask(taskId) {
...@@ -255,6 +253,7 @@ onUnmounted(() => { ...@@ -255,6 +253,7 @@ onUnmounted(() => {
@update:subject-id="onUpdateSubjectId" @update:subject-id="onUpdateSubjectId"
@update:term-id="onUpdateTermId" @update:term-id="onUpdateTermId"
@update:story-id="onUpdateStoryId" @update:story-id="onUpdateStoryId"
@update:only-incomplete="onUpdateOnlyIncomplete"
/> />
<TaskCalendar <TaskCalendar
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment