feat(projects): allow setting a saved filter for tasks shown on the overview page
continuous-integration/drone/push Build is passing Details

Resolves vikunja/api#1545
Resolves https://community.vikunja.io/t/customizable-overview-page/685
This commit is contained in:
kolaente 2023-06-28 15:20:43 +02:00
parent ac6c4cf2bc
commit 2a14325f62
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
7 changed files with 56 additions and 4 deletions

View File

@ -37,6 +37,10 @@ const props = defineProps({
type: Object as PropType<IProject>,
required: false,
},
savedFiltersOnly: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:modelValue'])
@ -57,6 +61,12 @@ function findProjects(query: string) {
if (query === '') {
select(null)
}
if (props.savedFiltersOnly) {
foundProjects.value = projectStore.searchSavedFilter(query)
return
}
foundProjects.value = projectStore.searchProject(query)
}

View File

@ -86,7 +86,8 @@
"language": "Language",
"defaultProject": "Default Project",
"timezone": "Time Zone",
"overdueTasksRemindersTime": "Overdue tasks reminder email time"
"overdueTasksRemindersTime": "Overdue tasks reminder email time",
"filterUsedOnOverview": "Saved filter used on the overview page"
},
"totp": {
"title": "Two Factor Authentication",

View File

@ -9,6 +9,7 @@ export interface IFrontendSettings {
playSoundWhenDone: boolean
quickAddMagicMode: PrefixMode
colorSchema: BasicColorSchema
filterIdUsedOnOverview: IProject['id'] | null
}
export interface IUserSettings extends IAbstract {

View File

@ -17,6 +17,7 @@ import type {MaybeRef} from '@vueuse/core'
import ProjectModel from '@/models/project'
import {success} from '@/message'
import {useBaseStore} from '@/stores/base'
import {getSavedFilterIdFromProjectId} from '@/services/savedFilter'
const {remove, search, update} = createNewIndexer('projects', ['title', 'description'])
@ -62,6 +63,16 @@ export const useProjectStore = defineStore('project', () => {
|| []
}
})
const searchSavedFilter = computed(() => {
return (query: string, includeArchived = false) => {
return search(query)
?.filter(value => getSavedFilterIdFromProjectId(value) > 0)
.map(id => projects.value[id])
.filter(project => project.isArchived === includeArchived)
|| []
}
})
function setIsLoading(newIsLoading: boolean) {
isLoading.value = newIsLoading
@ -191,6 +202,7 @@ export const useProjectStore = defineStore('project', () => {
getChildProjects,
findProjectByExactname,
searchProject,
searchSavedFilter,
setProject,
setProjects,

View File

@ -29,6 +29,7 @@ import {useKanbanStore} from '@/stores/kanban'
import {useBaseStore} from '@/stores/base'
import ProjectUserService from '@/services/projectUsers'
import {useAuthStore} from '@/stores/auth'
import TaskCollectionService from '@/services/taskCollection'
interface MatchedAssignee extends IUser {
match: string,
@ -123,12 +124,17 @@ export const useTaskStore = defineStore('task', () => {
})
}
async function loadTasks(params) {
const taskService = new TaskService()
async function loadTasks(params, projectId: IProject['id'] | null = null) {
const cancel = setModuleLoading(setIsLoading)
try {
tasks.value = await taskService.getAll({}, params)
if (projectId === null) {
const taskService = new TaskService()
tasks.value = await taskService.getAll({}, params)
} else {
const taskCollectionService = new TaskCollectionService()
tasks.value = await taskCollectionService.getAll({projectId}, params)
}
baseStore.setHasTasks(tasks.value.length > 0)
return tasks.value
} finally {

View File

@ -59,6 +59,7 @@ import LlamaCool from '@/assets/llama-cool.svg?component'
import type {ITask} from '@/modelTypes/ITask'
import {useAuthStore} from '@/stores/auth'
import {useTaskStore} from '@/stores/tasks'
import {useProjectStore} from '@/stores/projects'
const authStore = useAuthStore()
const taskStore = useTaskStore()
@ -69,6 +70,8 @@ const {t} = useI18n({useScope: 'global'})
const tasks = ref<ITask[]>([])
const showNothingToDo = ref<boolean>(false)
const projectStore = useProjectStore()
setTimeout(() => showNothingToDo.value = true, 100)
// Linting disabled because we explicitely enabled destructuring in vite's config, this will work.
@ -178,6 +181,11 @@ async function loadPendingTasks(from: string, to: string) {
params.filterComparator.push('greater')
}
}
if (authStore.settings.frontendSettings.filterIdUsedOnOverview && typeof projectStore.projects[authStore.settings.frontendSettings.filterIdUsedOnOverview] !== 'undefined') {
tasks.value = await taskStore.loadTasks(params, authStore.settings.frontendSettings.filterIdUsedOnOverview)
return
}
tasks.value = await taskStore.loadTasks(params)
}

View File

@ -18,6 +18,12 @@
</label>
<project-search v-model="defaultProject"/>
</div>
<div class="field" v-if="hasFilters">
<label class="label">
{{ $t('user.settings.general.filterUsedOnOverview') }}
</label>
<project-search v-model="filterUsedInOverview" :saved-filters-only="true"/>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox" v-model="settings.overdueTasksRemindersEnabled"/>
@ -167,6 +173,7 @@ import {useTitle} from '@/composables/useTitle'
import {useProjectStore} from '@/stores/projects'
import {useAuthStore} from '@/stores/auth'
import type {IUserSettings} from '@/modelTypes/IUserSettings'
import {isSavedFilter} from '@/services/savedFilter'
const {t} = useI18n({useScope: 'global'})
useTitle(() => `${t('user.settings.general.title')} - ${t('user.settings.title')}`)
@ -233,6 +240,13 @@ const defaultProject = computed({
settings.value.defaultProjectId = l ? l.id : DEFAULT_PROJECT_ID
},
})
const filterUsedInOverview = computed({
get: () => projectStore.projects[settings.value.frontendSettings.filterIdUsedOnOverview],
set(l) {
settings.value.frontendSettings.filterIdUsedOnOverview = l ? l.id : null
},
})
const hasFilters = computed(() => typeof projectStore.projectsArray.find(p => isSavedFilter(p)) !== 'undefined')
const loading = computed(() => authStore.isLoadingGeneralSettings)
async function updateSettings() {