feat(views): fetch tasks via view context when accessing them through views
This commit is contained in:
parent
ee6ea03506
commit
cf15cc6f12
@ -10,40 +10,12 @@
|
|||||||
<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"/>
|
||||||
@ -63,7 +35,7 @@
|
|||||||
</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,11 +124,26 @@ 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>
|
||||||
|
@ -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,
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
24
frontend/src/modelTypes/IProjectView.ts
Normal file
24
frontend/src/modelTypes/IProjectView.ts
Normal 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
|
||||||
|
}
|
@ -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',
|
||||||
|
@ -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',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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(
|
||||||
|
@ -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 = {
|
||||||
|
55
frontend/src/views/project/ProjectView.vue
Normal file
55
frontend/src/views/project/ProjectView.vue
Normal 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>
|
@ -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,
|
||||||
|
@ -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()
|
||||||
@ -35,7 +34,7 @@ export function useGanttTaskList<F extends Filters>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user