feat(views): fetch tasks via view context when accessing them through views

This commit is contained in:
kolaente 2024-03-15 22:18:44 +01:00
parent ee6ea03506
commit cf15cc6f12
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
16 changed files with 210 additions and 108 deletions

View File

@ -6,47 +6,19 @@
<h1 class="project-title-print"> <h1 class="project-title-print">
{{ getProjectTitle(currentProject) }} {{ getProjectTitle(currentProject) }}
</h1> </h1>
<div class="switch-view-container d-print-none"> <div class="switch-view-container d-print-none">
<div class="switch-view"> <div class="switch-view">
<BaseButton <BaseButton
v-shortcut="'g l'" v-for="v in views"
:title="$t('keyboardShortcuts.project.switchToListView')"
class="switch-view-button" class="switch-view-button"
:class="{'is-active': viewName === 'project'}" :class="{'is-active': v.id === view.id}"
:to="{ name: 'project.list', params: { projectId } }" :to="{ name: 'project.view', params: { projectId, viewId: v.id } }"
> >
{{ $t('project.list.title') }} {{ getViewTitle(v) }}
</BaseButton>
<BaseButton
v-shortcut="'g g'"
:title="$t('keyboardShortcuts.project.switchToGanttView')"
class="switch-view-button"
:class="{'is-active': viewName === 'gantt'}"
:to="{ name: 'project.gantt', params: { projectId } }"
>
{{ $t('project.gantt.title') }}
</BaseButton>
<BaseButton
v-shortcut="'g t'"
:title="$t('keyboardShortcuts.project.switchToTableView')"
class="switch-view-button"
:class="{'is-active': viewName === 'table'}"
:to="{ name: 'project.table', params: { projectId } }"
>
{{ $t('project.table.title') }}
</BaseButton>
<BaseButton
v-shortcut="'g k'"
:title="$t('keyboardShortcuts.project.switchToKanbanView')"
class="switch-view-button"
:class="{'is-active': viewName === 'kanban'}"
:to="{ name: 'project.kanban', params: { projectId } }"
>
{{ $t('project.kanban.title') }}
</BaseButton> </BaseButton>
</div> </div>
<slot name="header" /> <slot name="header"/>
</div> </div>
<CustomTransition name="fade"> <CustomTransition name="fade">
<Message <Message
@ -58,12 +30,12 @@
</Message> </Message>
</CustomTransition> </CustomTransition>
<slot v-if="loadedProjectId" /> <slot v-if="loadedProjectId"/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {ref, computed, watch} from 'vue' import {computed, ref, watch} from 'vue'
import {useRoute} from 'vue-router' import {useRoute} from 'vue-router'
import BaseButton from '@/components/base/BaseButton.vue' import BaseButton from '@/components/base/BaseButton.vue'
@ -79,26 +51,27 @@ import {useTitle} from '@/composables/useTitle'
import {useBaseStore} from '@/stores/base' import {useBaseStore} from '@/stores/base'
import {useProjectStore} from '@/stores/projects' import {useProjectStore} from '@/stores/projects'
import type {IProject} from '@/modelTypes/IProject'
import type {IProjectView} from '@/modelTypes/IProjectView'
import {useI18n} from 'vue-i18n'
const props = defineProps({ const {
projectId: { projectId,
type: Number, view,
required: true, } = defineProps<{
}, projectId: number,
viewName: { view: IProjectView,
type: String, }>()
required: true,
},
})
const route = useRoute() const route = useRoute()
const {t} = useI18n()
const baseStore = useBaseStore() const baseStore = useBaseStore()
const projectStore = useProjectStore() const projectStore = useProjectStore()
const projectService = ref(new ProjectService()) const projectService = ref(new ProjectService())
const loadedProjectId = ref(0) const loadedProjectId = ref(0)
const currentProject = computed(() => { const currentProject = computed<IProject>(() => {
return typeof baseStore.currentProject === 'undefined' ? { return typeof baseStore.currentProject === 'undefined' ? {
id: 0, id: 0,
title: '', title: '',
@ -108,13 +81,15 @@ const currentProject = computed(() => {
}) })
useTitle(() => currentProject.value?.id ? getProjectTitle(currentProject.value) : '') useTitle(() => currentProject.value?.id ? getProjectTitle(currentProject.value) : '')
const views = computed(() => currentProject.value?.views)
// watchEffect would be called every time the prop would get a value assigned, even if that value was the same as before. // watchEffect would be called every time the prop would get a value assigned, even if that value was the same as before.
// This resulted in loading and setting the project multiple times, even when navigating away from it. // This resulted in loading and setting the project multiple times, even when navigating away from it.
// This caused wired bugs where the project background would be set on the home page but only right after setting a new // This caused wired bugs where the project background would be set on the home page but only right after setting a new
// project background and then navigating to home. It also highlighted the project in the menu and didn't allow changing any // project background and then navigating to home. It also highlighted the project in the menu and didn't allow changing any
// of it, most likely due to the rights not being properly populated. // of it, most likely due to the rights not being properly populated.
watch( watch(
() => props.projectId, () => projectId,
// loadProject // loadProject
async (projectIdToLoad: number) => { async (projectIdToLoad: number) => {
const projectData = {id: projectIdToLoad} const projectData = {id: projectIdToLoad}
@ -130,11 +105,11 @@ watch(
) )
&& typeof currentProject.value !== 'undefined' && currentProject.value.maxRight !== null && typeof currentProject.value !== 'undefined' && currentProject.value.maxRight !== null
) { ) {
loadedProjectId.value = props.projectId loadedProjectId.value = projectId
return return
} }
console.debug(`Loading project, props.viewName = ${props.viewName}, $route.params =`, route.params, `, loadedProjectId = ${loadedProjectId.value}, currentProject = `, currentProject.value) console.debug(`Loading project, props.view = ${view}, $route.params =`, route.params, `, loadedProjectId = ${loadedProjectId.value}, currentProject = `, currentProject.value)
// Set the current project to the one we're about to load so that the title is already shown at the top // Set the current project to the one we're about to load so that the title is already shown at the top
loadedProjectId.value = 0 loadedProjectId.value = 0
@ -149,31 +124,46 @@ watch(
const loadedProject = await projectService.value.get(project) const loadedProject = await projectService.value.get(project)
baseStore.handleSetCurrentProject({project: loadedProject}) baseStore.handleSetCurrentProject({project: loadedProject})
} finally { } finally {
loadedProjectId.value = props.projectId loadedProjectId.value = projectId
} }
}, },
{immediate: true}, {immediate: true},
) )
function getViewTitle(view: IProjectView) {
switch (view.title) {
case 'List':
return t('project.list.title')
case 'Gantt':
return t('project.gantt.title')
case 'Table':
return t('project.table.title')
case 'Kanban':
return t('project.kanban.title')
}
return view.title
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.switch-view-container { .switch-view-container {
@media screen and (max-width: $tablet) { @media screen and (max-width: $tablet) {
display: flex; display: flex;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
} }
} }
.switch-view { .switch-view {
background: var(--white); background: var(--white);
display: inline-flex; display: inline-flex;
border-radius: $radius; border-radius: $radius;
font-size: .75rem; font-size: .75rem;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
height: $switch-view-height; height: $switch-view-height;
margin: 0 auto 1rem; margin: 0 auto 1rem;
padding: .5rem; padding: .5rem;
} }
.switch-view-button { .switch-view-button {
@ -201,7 +191,7 @@ watch(
// FIXME: this should be in notification and set via a prop // FIXME: this should be in notification and set via a prop
.is-archived .notification.is-warning { .is-archived .notification.is-warning {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.project-title-print { .project-title-print {
@ -209,7 +199,7 @@ watch(
font-size: 1.75rem; font-size: 1.75rem;
text-align: center; text-align: center;
margin-bottom: .5rem; margin-bottom: .5rem;
@media print { @media print {
display: block; display: block;
} }

View File

@ -7,6 +7,7 @@ import type {ITask} from '@/modelTypes/ITask'
import {error} from '@/message' import {error} from '@/message'
import type {IProject} from '@/modelTypes/IProject' import type {IProject} from '@/modelTypes/IProject'
import {useAuthStore} from '@/stores/auth' import {useAuthStore} from '@/stores/auth'
import type {IProjectView} from '@/modelTypes/IProjectView'
export type Order = 'asc' | 'desc' | 'none' export type Order = 'asc' | 'desc' | 'none'
@ -54,9 +55,14 @@ const SORT_BY_DEFAULT: SortBy = {
/** /**
* This mixin provides a base set of methods and properties to get tasks. * This mixin provides a base set of methods and properties to get tasks.
*/ */
export function useTaskList(projectIdGetter: ComputedGetter<IProject['id']>, sortByDefault: SortBy = SORT_BY_DEFAULT) { export function useTaskList(
projectIdGetter: ComputedGetter<IProject['id']>,
projectViewIdGetter: ComputedGetter<IProjectView['id']>,
sortByDefault: SortBy = SORT_BY_DEFAULT
) {
const projectId = computed(() => projectIdGetter()) const projectId = computed(() => projectIdGetter())
const projectViewId = computed(() => projectViewIdGetter())
const params = ref<TaskFilterParams>({...getDefaultTaskFilterParams()}) const params = ref<TaskFilterParams>({...getDefaultTaskFilterParams()})
@ -87,7 +93,10 @@ export function useTaskList(projectIdGetter: ComputedGetter<IProject['id']>, sor
const getAllTasksParams = computed(() => { const getAllTasksParams = computed(() => {
return [ return [
{projectId: projectId.value}, {
projectId: projectId.value,
viewId: projectViewId.value,
},
{ {
...allParams.value, ...allParams.value,
filter_timezone: authStore.settings.timezone, filter_timezone: authStore.settings.timezone,

View File

@ -3,11 +3,7 @@ import router from '@/router'
import type {IProject} from '@/modelTypes/IProject' import type {IProject} from '@/modelTypes/IProject'
export type ProjectRouteName = Extract<RouteRecordName, string> export type ProjectViewSettings = Record<IProject['id'], number>
export type ProjectViewSettings = Record<
IProject['id'],
Extract<RouteRecordName, ProjectRouteName>
>
const SETTINGS_KEY_PROJECT_VIEW = 'projectView' const SETTINGS_KEY_PROJECT_VIEW = 'projectView'
@ -50,12 +46,8 @@ function migrateStoredProjectRouteSettings() {
/** /**
* Save the current project view to local storage * Save the current project view to local storage
*/ */
export function saveProjectView(projectId: IProject['id'], routeName: string) { export function saveProjectView(projectId: IProject['id'], viewId: number) {
if (routeName.includes('settings.')) { if (!projectId || !viewId) {
return
}
if (!projectId) {
return return
} }
@ -71,7 +63,7 @@ export function saveProjectView(projectId: IProject['id'], routeName: string) {
projectViewSettings = savedProjectViewSettings projectViewSettings = savedProjectViewSettings
} }
projectViewSettings[projectId] = routeName projectViewSettings[projectId] = viewId
localStorage.setItem(SETTINGS_KEY_PROJECT_VIEW, JSON.stringify(projectViewSettings)) localStorage.setItem(SETTINGS_KEY_PROJECT_VIEW, JSON.stringify(projectViewSettings))
} }

View File

@ -2,6 +2,7 @@ import type {IAbstract} from './IAbstract'
import type {ITask} from './ITask' import type {ITask} from './ITask'
import type {IUser} from './IUser' import type {IUser} from './IUser'
import type {ISubscription} from './ISubscription' import type {ISubscription} from './ISubscription'
import type {IProjectView} from '@/modelTypes/IProjectView'
export interface IProject extends IAbstract { export interface IProject extends IAbstract {
@ -21,6 +22,7 @@ export interface IProject extends IAbstract {
parentProjectId: number parentProjectId: number
doneBucketId: number doneBucketId: number
defaultBucketId: number defaultBucketId: number
views: IProjectView[]
created: Date created: Date
updated: Date updated: Date

View File

@ -0,0 +1,24 @@
import type {IAbstract} from './IAbstract'
import type {ITask} from './ITask'
import type {IUser} from './IUser'
import type {ISubscription} from './ISubscription'
import type {IProject} from '@/modelTypes/IProject'
export interface IProjectView extends IAbstract {
id: number
title: string
projectId: IProject['id']
viewKind: 'list' | 'gantt' | 'table' | 'kanban'
fitler: string
position: number
bucketConfigurationMode: 'none' | 'manual' | 'filter'
bucketConfiguration: object
defaultBucketId: number
doneBucketId: number
created: Date
updated: Date
}

View File

@ -37,6 +37,7 @@ const MigrationHandlerComponent = () => import('@/views/migrate/MigrationHandler
const ProjectList = () => import('@/views/project/ProjectList.vue') const ProjectList = () => import('@/views/project/ProjectList.vue')
const ProjectGantt = () => import('@/views/project/ProjectGantt.vue') const ProjectGantt = () => import('@/views/project/ProjectGantt.vue')
const ProjectTable = () => import('@/views/project/ProjectTable.vue') const ProjectTable = () => import('@/views/project/ProjectTable.vue')
const ProjectView = () => import('@/views/project/ProjectView.vue')
// If we load the component async, using it as a backdrop view will not work. Instead, everything explodes // If we load the component async, using it as a backdrop view will not work. Instead, everything explodes
// with an error from the core saying "Cannot read properties of undefined (reading 'parentNode')" // with an error from the core saying "Cannot read properties of undefined (reading 'parentNode')"
// Of course, with no clear indicator of where the problem comes from. // Of course, with no clear indicator of where the problem comes from.
@ -359,6 +360,16 @@ const router = createRouter({
} }
}, },
}, },
{
path: '/projects/:projectId/:viewId',
name: 'project.view',
component: ProjectView,
beforeEnter: (to) => saveProjectView(parseInt(to.params.projectId as string), parseInt(to.params.viewId as string)),
props: route => ({
projectId: Number(route.params.projectId as string),
viewId: Number(route.params.viewId as string),
}),
},
{ {
path: '/projects/:projectId/list', path: '/projects/:projectId/list',
name: 'project.list', name: 'project.list',

View File

@ -27,7 +27,7 @@ export function getDefaultTaskFilterParams(): TaskFilterParams {
export default class TaskCollectionService extends AbstractService<ITask> { export default class TaskCollectionService extends AbstractService<ITask> {
constructor() { constructor() {
super({ super({
getAll: '/projects/{projectId}/tasks', getAll: '/projects/{projectId}/views/{viewId}/tasks',
}) })
} }

View File

@ -15,6 +15,7 @@ import type {ITask} from '@/modelTypes/ITask'
import type {IProject} from '@/modelTypes/IProject' import type {IProject} from '@/modelTypes/IProject'
import type {IBucket} from '@/modelTypes/IBucket' import type {IBucket} from '@/modelTypes/IBucket'
import {useAuthStore} from '@/stores/auth' import {useAuthStore} from '@/stores/auth'
import type {IProjectView} from '@/modelTypes/IProjectView'
const TASKS_PER_BUCKET = 25 const TASKS_PER_BUCKET = 25
@ -247,6 +248,7 @@ export const useKanbanStore = defineStore('kanban', () => {
async function loadNextTasksForBucket( async function loadNextTasksForBucket(
projectId: IProject['id'], projectId: IProject['id'],
viewId: IProjectView['id'],
ps: TaskFilterParams, ps: TaskFilterParams,
bucketId: IBucket['id'], bucketId: IBucket['id'],
) { ) {
@ -275,7 +277,7 @@ export const useKanbanStore = defineStore('kanban', () => {
const taskService = new TaskCollectionService() const taskService = new TaskCollectionService()
try { try {
const tasks = await taskService.getAll({projectId}, params, page) const tasks = await taskService.getAll({projectId, viewId}, params, page)
addTasksToBucket({tasks, bucketId: bucketId}) addTasksToBucket({tasks, bucketId: bucketId})
setTasksLoadedForBucketPage({bucketId, page}) setTasksLoadedForBucketPage({bucketId, page})
if (taskService.totalPages <= page) { if (taskService.totalPages <= page) {

View File

@ -30,6 +30,7 @@ import ProjectUserService from '@/services/projectUsers'
import {useAuthStore} from '@/stores/auth' import {useAuthStore} from '@/stores/auth'
import TaskCollectionService, {type TaskFilterParams} from '@/services/taskCollection' import TaskCollectionService, {type TaskFilterParams} from '@/services/taskCollection'
import {getRandomColorHex} from '@/helpers/color/randomColor' import {getRandomColorHex} from '@/helpers/color/randomColor'
import type {IProjectView} from '@/modelTypes/IProjectView'
interface MatchedAssignee extends IUser { interface MatchedAssignee extends IUser {
match: string, match: string,
@ -124,21 +125,23 @@ export const useTaskStore = defineStore('task', () => {
}) })
} }
async function loadTasks(params: TaskFilterParams, projectId: IProject['id'] | null = null) { async function loadTasks(
params: TaskFilterParams,
projectId: IProject['id'] | null = null,
) {
if (!params.filter_timezone || params.filter_timezone === '') { if (!params.filter_timezone || params.filter_timezone === '') {
params.filter_timezone = authStore.settings.timezone params.filter_timezone = authStore.settings.timezone
} }
if (projectId !== null) {
params.filter = 'project = '+projectId+' && (' + params.filter +')'
}
const cancel = setModuleLoading(setIsLoading) const cancel = setModuleLoading(setIsLoading)
try { try {
if (projectId === null) { const taskService = new TaskService()
const taskService = new TaskService() tasks.value = await taskService.getAll({}, params)
tasks.value = await taskService.getAll({}, params)
} else {
const taskCollectionService = new TaskCollectionService()
tasks.value = await taskCollectionService.getAll({projectId}, params)
}
baseStore.setHasTasks(tasks.value.length > 0) baseStore.setHasTasks(tasks.value.length > 0)
return tasks.value return tasks.value
} finally { } finally {

View File

@ -2,7 +2,7 @@
<ProjectWrapper <ProjectWrapper
class="project-gantt" class="project-gantt"
:project-id="filters.projectId" :project-id="filters.projectId"
view-name="gantt" :view
> >
<template #header> <template #header>
<card :has-content="false"> <card :has-content="false">
@ -92,10 +92,14 @@ import {RIGHTS} from '@/constants/rights'
import type {DateISO} from '@/types/DateISO' import type {DateISO} from '@/types/DateISO'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import type {IProjectView} from '@/modelTypes/IProjectView'
type Options = Flatpickr.Options.Options type Options = Flatpickr.Options.Options
const props = defineProps<{route: RouteLocationNormalized}>() const props = defineProps<{
route: RouteLocationNormalized
view: IProjectView
}>()
const GanttChart = createAsyncComponent(() => import('@/components/tasks/GanttChart.vue')) const GanttChart = createAsyncComponent(() => import('@/components/tasks/GanttChart.vue'))
@ -111,7 +115,7 @@ const {
isLoading, isLoading,
addTask, addTask,
updateTask, updateTask,
} = useGanttFilters(route) } = useGanttFilters(route, props.view)
const DEFAULT_DATE_RANGE_DAYS = 7 const DEFAULT_DATE_RANGE_DAYS = 7

View File

@ -2,7 +2,7 @@
<ProjectWrapper <ProjectWrapper
class="project-kanban" class="project-kanban"
:project-id="projectId" :project-id="projectId"
view-name="kanban" :view
> >
<template #header> <template #header>
<div class="filter-container"> <div class="filter-container">
@ -301,11 +301,14 @@ import {isSavedFilter} from '@/services/savedFilter'
import {success} from '@/message' import {success} from '@/message'
import {useProjectStore} from '@/stores/projects' import {useProjectStore} from '@/stores/projects'
import type {TaskFilterParams} from '@/services/taskCollection' import type {TaskFilterParams} from '@/services/taskCollection'
import type {IProjectView} from '@/modelTypes/IProjectView'
const { const {
projectId = undefined, projectId = undefined,
view,
} = defineProps<{ } = defineProps<{
projectId: number, projectId: number,
view: IProjectView,
}>() }>()
const DRAG_OPTIONS = { const DRAG_OPTIONS = {
@ -424,6 +427,7 @@ function handleTaskContainerScroll(id: IBucket['id'], projectId: IProject['id'],
kanbanStore.loadNextTasksForBucket( kanbanStore.loadNextTasksForBucket(
projectId, projectId,
view.id,
params.value, params.value,
id, id,
) )

View File

@ -2,7 +2,7 @@
<ProjectWrapper <ProjectWrapper
class="project-list" class="project-list"
:project-id="projectId" :project-id="projectId"
view-name="project" :view
> >
<template #header> <template #header>
<div class="filter-container"> <div class="filter-container">
@ -117,11 +117,14 @@ import {useBaseStore} from '@/stores/base'
import {useTaskStore} from '@/stores/tasks' import {useTaskStore} from '@/stores/tasks'
import type {IProject} from '@/modelTypes/IProject' import type {IProject} from '@/modelTypes/IProject'
import type {IProjectView} from '@/modelTypes/IProjectView'
const { const {
projectId, projectId,
view,
} = defineProps<{ } = defineProps<{
projectId: IProject['id'], projectId: IProject['id'],
view: IProjectView,
}>() }>()
const ctaVisible = ref(false) const ctaVisible = ref(false)
@ -140,7 +143,7 @@ const {
loadTasks, loadTasks,
params, params,
sortByParam, sortByParam,
} = useTaskList(() => projectId, {position: 'asc'}) } = useTaskList(() => projectId, () => view.id, {position: 'asc'})
const tasks = ref<ITask[]>([]) const tasks = ref<ITask[]>([])
watch( watch(

View File

@ -2,7 +2,7 @@
<ProjectWrapper <ProjectWrapper
class="project-table" class="project-table"
:project-id="projectId" :project-id="projectId"
view-name="table" :view
> >
<template #header> <template #header>
<div class="filter-container"> <div class="filter-container">
@ -289,11 +289,14 @@ import {useTaskList} from '@/composables/useTaskList'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import type {IProject} from '@/modelTypes/IProject' import type {IProject} from '@/modelTypes/IProject'
import AssigneeList from '@/components/tasks/partials/assigneeList.vue' import AssigneeList from '@/components/tasks/partials/assigneeList.vue'
import type {IProjectView} from '@/modelTypes/IProjectView'
const { const {
projectId, projectId,
view,
} = defineProps<{ } = defineProps<{
projectId: IProject['id'], projectId: IProject['id'],
view: IProjectView,
}>() }>()
const ACTIVE_COLUMNS_DEFAULT = { const ACTIVE_COLUMNS_DEFAULT = {

View File

@ -0,0 +1,55 @@
<script setup lang="ts">
import {computed} from 'vue'
import {useProjectStore} from '@/stores/projects'
import ProjectList from '@/views/project/ProjectList.vue'
import ProjectGantt from '@/views/project/ProjectGantt.vue'
import ProjectTable from '@/views/project/ProjectTable.vue'
import ProjectKanban from '@/views/project/ProjectKanban.vue'
import {useRoute} from 'vue-router'
const {
projectId,
viewId,
} = defineProps<{
projectId: number,
viewId: number,
}>()
const projectStore = useProjectStore()
const currentView = computed(() => {
const project = projectStore.projects[projectId]
return project?.views.find(v => v.id === viewId)
})
const route = useRoute()
</script>
<template>
<ProjectList
v-if="currentView?.viewKind === 'list'"
:project-id="projectId"
:view="currentView"
/>
<ProjectGantt
v-if="currentView?.viewKind === 'gantt'"
:route
:view="currentView"
/>
<ProjectTable
v-if="currentView?.viewKind === 'table'"
:project-id="projectId"
:view="currentView"
/>
<ProjectKanban
v-if="currentView?.viewKind === 'kanban'"
:project-id="projectId"
:view="currentView"
/>
</template>
<style scoped lang="scss">
</style>

View File

@ -12,6 +12,7 @@ import type {TaskFilterParams} from '@/services/taskCollection'
import type {DateISO} from '@/types/DateISO' import type {DateISO} from '@/types/DateISO'
import type {DateKebab} from '@/types/DateKebab' import type {DateKebab} from '@/types/DateKebab'
import type {IProjectView} from '@/modelTypes/IProjectView'
// convenient internal filter object // convenient internal filter object
export interface GanttFilters { export interface GanttFilters {
@ -88,7 +89,7 @@ export type UseGanttFiltersReturn =
ReturnType<typeof useRouteFilters<GanttFilters>> & ReturnType<typeof useRouteFilters<GanttFilters>> &
ReturnType<typeof useGanttTaskList<GanttFilters>> ReturnType<typeof useGanttTaskList<GanttFilters>>
export function useGanttFilters(route: Ref<RouteLocationNormalized>): UseGanttFiltersReturn { export function useGanttFilters(route: Ref<RouteLocationNormalized>, view: IProjectView): UseGanttFiltersReturn {
const { const {
filters, filters,
hasDefaultFilters, hasDefaultFilters,
@ -108,7 +109,7 @@ export function useGanttFilters(route: Ref<RouteLocationNormalized>): UseGanttFi
isLoading, isLoading,
addTask, addTask,
updateTask, updateTask,
} = useGanttTaskList<GanttFilters>(filters, ganttFiltersToApiParams) } = useGanttTaskList<GanttFilters>(filters, ganttFiltersToApiParams, view)
return { return {
filters, filters,

View File

@ -1,4 +1,4 @@
import {computed, ref, shallowReactive, watch, type Ref} from 'vue' import {computed, ref, type Ref, shallowReactive, watch} from 'vue'
import {klona} from 'klona/lite' import {klona} from 'klona/lite'
import type {Filters} from '@/composables/useRouteFilters' import type {Filters} from '@/composables/useRouteFilters'
@ -10,16 +10,15 @@ import TaskService from '@/services/task'
import TaskModel from '@/models/task' import TaskModel from '@/models/task'
import {error, success} from '@/message' import {error, success} from '@/message'
import {useAuthStore} from '@/stores/auth' import {useAuthStore} from '@/stores/auth'
import type {IProjectView} from '@/modelTypes/IProjectView'
// FIXME: unify with general `useTaskList` // FIXME: unify with general `useTaskList`
export function useGanttTaskList<F extends Filters>( export function useGanttTaskList<F extends Filters>(
filters: Ref<F>, filters: Ref<F>,
filterToApiParams: (filters: F) => TaskFilterParams, filterToApiParams: (filters: F) => TaskFilterParams,
options: { view: IProjectView,
loadAll?: boolean, loadAll: boolean = true,
} = { ) {
loadAll: true,
}) {
const taskCollectionService = shallowReactive(new TaskCollectionService()) const taskCollectionService = shallowReactive(new TaskCollectionService())
const taskService = shallowReactive(new TaskService()) const taskService = shallowReactive(new TaskService())
const authStore = useAuthStore() const authStore = useAuthStore()
@ -29,13 +28,13 @@ export function useGanttTaskList<F extends Filters>(
const tasks = ref<Map<ITask['id'], ITask>>(new Map()) const tasks = ref<Map<ITask['id'], ITask>>(new Map())
async function fetchTasks(params: TaskFilterParams, page = 1): Promise<ITask[]> { async function fetchTasks(params: TaskFilterParams, page = 1): Promise<ITask[]> {
if(params.filter_timezone === '') { if (params.filter_timezone === '') {
params.filter_timezone = authStore.settings.timezone params.filter_timezone = authStore.settings.timezone
} }
const tasks = await taskCollectionService.getAll({projectId: filters.value.projectId}, params, page) as ITask[] const tasks = await taskCollectionService.getAll({projectId: filters.value.projectId}, params, page) as ITask[]
if (options.loadAll && page < taskCollectionService.totalPages) { if (loadAll && page < taskCollectionService.totalPages) {
const nextTasks = await fetchTasks(params, page + 1) const nextTasks = await fetchTasks(params, page + 1)
return tasks.concat(nextTasks) return tasks.concat(nextTasks)
} }