feat: provide listId prop via router
This commit is contained in:
parent
6d62ca1ada
commit
5916a44724
10 changed files with 263 additions and 233 deletions
|
@ -44,7 +44,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {watch, computed, shallowRef, watchEffect} from 'vue'
|
||||
import {watch, computed, shallowRef, watchEffect, h} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import {useEventListener} from '@vueuse/core'
|
||||
|
@ -66,19 +66,36 @@ function useRouteWithModal() {
|
|||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const currentModal = shallowRef(null)
|
||||
watchEffect(() => {
|
||||
currentModal.value = historyState.value.backdropView
|
||||
? route.matched[0]?.components.default
|
||||
const hasBackdropView = historyState.value.backdropView
|
||||
|
||||
if (!hasBackdropView) {
|
||||
currentModal.value = null
|
||||
return
|
||||
}
|
||||
|
||||
// logic from vue-router
|
||||
// https://github.com/vuejs/vue-router-next/blob/798cab0d1e21f9b4d45a2bd12b840d2c7415f38a/src/RouterView.ts#L125
|
||||
const routePropsOption = route.matched[0]?.props.default
|
||||
const routeProps = routePropsOption
|
||||
? routePropsOption === true
|
||||
? route.params
|
||||
: typeof routePropsOption === 'function'
|
||||
? routePropsOption(route)
|
||||
: routePropsOption
|
||||
: null
|
||||
|
||||
currentModal.value = h(
|
||||
route.matched[0]?.components.default,
|
||||
routeProps,
|
||||
)
|
||||
})
|
||||
|
||||
return { routeWithModal, currentModal }
|
||||
}
|
||||
|
||||
useRouteWithModal()
|
||||
const { routeWithModal, currentModal } = useRouteWithModal()
|
||||
|
||||
const store = useStore()
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ref, watch, computed } from 'vue'
|
||||
import { ref, shallowReactive, watch, computed } from 'vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
|
||||
import TaskCollectionService from '@/services/taskCollection'
|
||||
|
@ -13,102 +13,104 @@ export const getDefaultParams = () => ({
|
|||
filter_concat: 'and',
|
||||
})
|
||||
|
||||
|
||||
const filters = {
|
||||
done: {
|
||||
value: false,
|
||||
comparator: 'equals',
|
||||
concat: 'and',
|
||||
},
|
||||
}
|
||||
|
||||
const SORT_BY_DEFAULT = {
|
||||
id: 'desc',
|
||||
}
|
||||
|
||||
/**
|
||||
* This mixin provides a base set of methods and properties to get tasks on a list.
|
||||
*/
|
||||
export function useTaskList(initTasks) {
|
||||
const taskCollectionService = ref(new TaskCollectionService())
|
||||
const loading = computed(() => taskCollectionService.value.loading)
|
||||
const totalPages = computed(() => taskCollectionService.value.totalPages)
|
||||
|
||||
const tasks = ref([])
|
||||
const currentPage = ref(0)
|
||||
const loadedList = ref(null)
|
||||
const searchTerm = ref('')
|
||||
const showTaskFilter = ref(false)
|
||||
export function useTaskList(listId) {
|
||||
const params = ref({...getDefaultParams()})
|
||||
|
||||
const route = useRoute()
|
||||
const search = ref('')
|
||||
const page = ref(1)
|
||||
|
||||
async function loadTasks(
|
||||
page = 1,
|
||||
search = '',
|
||||
loadParams = { ...params.value },
|
||||
forceLoading = false,
|
||||
) {
|
||||
const sortBy = ref({ ...SORT_BY_DEFAULT })
|
||||
|
||||
// Because this function is triggered every time on topNavigation, we're putting a condition here to only load it when we actually want to show tasks
|
||||
// FIXME: This is a bit hacky -> Cleanup.
|
||||
if (
|
||||
route.name !== 'list.list' &&
|
||||
route.name !== 'list.table' &&
|
||||
!forceLoading
|
||||
) {
|
||||
return
|
||||
|
||||
// This makes sure an id sort order is always sorted last.
|
||||
// When tasks would be sorted first by id and then by whatever else was specified, the id sort takes
|
||||
// precedence over everything else, making any other sort columns pretty useless.
|
||||
function formatSortOrder(params) {
|
||||
let hasIdFilter = false
|
||||
const sortKeys = Object.keys(sortBy.value)
|
||||
for (const s of sortKeys) {
|
||||
if (s === 'id') {
|
||||
sortKeys.splice(s, 1)
|
||||
hasIdFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (hasIdFilter) {
|
||||
sortKeys.push('id')
|
||||
}
|
||||
params.sort_by = sortKeys
|
||||
params.order_by = sortKeys.map(s => sortBy.value[s])
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
if (search !== '') {
|
||||
loadParams.s = search
|
||||
const getAllTasksParams = computed(() => {
|
||||
let loadParams = {...params.value}
|
||||
|
||||
if (search.value !== '') {
|
||||
loadParams.s = search.value
|
||||
}
|
||||
|
||||
const list = {listId: parseInt(route.params.listId)}
|
||||
loadParams = formatSortOrder(loadParams)
|
||||
|
||||
const currentList = {
|
||||
id: list.listId,
|
||||
params: loadParams,
|
||||
search,
|
||||
page,
|
||||
}
|
||||
if (
|
||||
JSON.stringify(currentList) === JSON.stringify(loadedList.value) &&
|
||||
!forceLoading
|
||||
) {
|
||||
return
|
||||
}
|
||||
return [
|
||||
{listId: listId.value},
|
||||
loadParams,
|
||||
page.value || 1,
|
||||
]
|
||||
})
|
||||
|
||||
tasks.value = []
|
||||
tasks.value = await taskCollectionService.value.getAll(list, loadParams, page)
|
||||
currentPage.value = page
|
||||
loadedList.value = JSON.parse(JSON.stringify(currentList))
|
||||
const taskCollectionService = shallowReactive(new TaskCollectionService())
|
||||
const loading = computed(() => taskCollectionService.loading)
|
||||
const totalPages = computed(() => taskCollectionService.totalPages)
|
||||
|
||||
const tasks = ref([])
|
||||
async function loadTasks() {
|
||||
tasks.value = await taskCollectionService.getAll(...getAllTasksParams.value)
|
||||
return tasks.value
|
||||
}
|
||||
|
||||
async function loadTasksForPage(query) {
|
||||
const { page, search } = query
|
||||
initTasks(params)
|
||||
return await loadTasks(
|
||||
// The page parameter can be undefined, in the case where the user loads a new list from the side bar menu
|
||||
typeof page === 'undefined' ? 1 : Number(page),
|
||||
search,
|
||||
params.value,
|
||||
)
|
||||
}
|
||||
const route = useRoute()
|
||||
watch(() => route.query, (query) => {
|
||||
const { page: pageQuery, search: searchQuery } = query
|
||||
search.value = searchQuery
|
||||
page.value = pageQuery
|
||||
|
||||
}, { immediate: true })
|
||||
|
||||
async function loadTasksOnSavedFilter() {
|
||||
if (
|
||||
typeof route.params.listId !== 'undefined' &&
|
||||
parseInt(route.params.listId) < 0
|
||||
) {
|
||||
await loadTasks(1, '', null, true)
|
||||
}
|
||||
}
|
||||
|
||||
function initTaskList() {
|
||||
// Only listen for query path changes
|
||||
watch(() => route.query, loadTasksForPage, { immediate: true })
|
||||
watch(() => route.path, loadTasksOnSavedFilter)
|
||||
watch(() => JSON.stringify(getAllTasksParams.value), (newParams, oldParams) => {
|
||||
if (oldParams === newParams) {
|
||||
return
|
||||
}
|
||||
|
||||
loadTasks()
|
||||
}, { immediate: true })
|
||||
|
||||
return {
|
||||
tasks,
|
||||
initTaskList,
|
||||
loading,
|
||||
totalPages,
|
||||
currentPage,
|
||||
showTaskFilter,
|
||||
currentPage: page,
|
||||
loadTasks,
|
||||
searchTerm,
|
||||
searchTerm: search,
|
||||
params,
|
||||
}
|
||||
}
|
|
@ -244,6 +244,9 @@ const router = createRouter({
|
|||
path: '/tasks/:id',
|
||||
name: 'task.detail',
|
||||
component: TaskDetailViewModal,
|
||||
props: route => ({
|
||||
taskId: parseInt(route.params.id),
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: '/tasks/by/upcoming',
|
||||
|
@ -341,21 +344,33 @@ const router = createRouter({
|
|||
path: '/lists/:listId/list',
|
||||
name: 'list.list',
|
||||
component: ListList,
|
||||
props: route => ({
|
||||
listId: parseInt(route.params.listId),
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/gantt',
|
||||
name: 'list.gantt',
|
||||
component: ListGantt,
|
||||
props: route => ({
|
||||
listId: parseInt(route.params.listId),
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/table',
|
||||
name: 'list.table',
|
||||
component: ListTable,
|
||||
props: route => ({
|
||||
listId: parseInt(route.params.listId),
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: '/lists/:listId/kanban',
|
||||
name: 'list.kanban',
|
||||
component: ListKanban,
|
||||
props: route => ({
|
||||
listId: parseInt(route.params.listId),
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: '/teams',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<ListWrapper class="list-gantt">
|
||||
<ListWrapper class="list-gantt" :list-id="props.listId" viewName="gantt">
|
||||
<template #header>
|
||||
<div class="gantt-options p-4">
|
||||
<fancycheckbox class="is-block" v-model="showTaskswithoutDates">
|
||||
|
@ -54,7 +54,7 @@
|
|||
:date-from="dateFrom"
|
||||
:date-to="dateTo"
|
||||
:day-width="dayWidth"
|
||||
:list-id="Number($route.params.listId)"
|
||||
:list-id="props.listId"
|
||||
:show-taskswithout-dates="showTaskswithoutDates"
|
||||
/>
|
||||
|
||||
|
@ -64,16 +64,23 @@
|
|||
</ListWrapper>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from 'vuex'
|
||||
|
||||
import ListWrapper from './ListWrapper'
|
||||
import GanttChart from '@/components/tasks/gantt-component'
|
||||
import Fancycheckbox from '@/components/input/fancycheckbox'
|
||||
import ListWrapper from './ListWrapper.vue'
|
||||
import GanttChart from '@/components/tasks/gantt-component.vue'
|
||||
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
||||
|
||||
const props = defineProps({
|
||||
listId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const DEFAULT_DAY_COUNT = 35
|
||||
|
||||
|
@ -85,7 +92,7 @@ const dateFrom = ref(new Date((new Date()).setDate(now.value.getDate() - 15)))
|
|||
const dateTo = ref(new Date((new Date()).setDate(now.value.getDate() + 30)))
|
||||
|
||||
const {t} = useI18n()
|
||||
const {store} = useStore()
|
||||
const store = useStore()
|
||||
const flatPickerConfig = computed(() => ({
|
||||
altFormat: t('date.altFormatShort'),
|
||||
altInput: true,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<ListWrapper class="list-kanban">
|
||||
<ListWrapper class="list-kanban" :list-id="listId" viewName="kanban">
|
||||
<template #header>
|
||||
<div class="filter-container" v-if="isSavedFilter">
|
||||
<div class="items">
|
||||
|
@ -263,6 +263,14 @@ export default {
|
|||
FilterPopup,
|
||||
draggable,
|
||||
},
|
||||
|
||||
props: {
|
||||
listId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
taskContainerRefs: {},
|
||||
|
@ -310,7 +318,7 @@ export default {
|
|||
},
|
||||
loadBucketParameter() {
|
||||
return {
|
||||
listId: this.$route.params.listId,
|
||||
listId: this.listId,
|
||||
params: this.params,
|
||||
}
|
||||
},
|
||||
|
@ -350,16 +358,11 @@ export default {
|
|||
|
||||
methods: {
|
||||
loadBuckets() {
|
||||
// Prevent trying to load buckets if the task popup view is active
|
||||
if (this.$route.name !== 'list.kanban') {
|
||||
return
|
||||
}
|
||||
|
||||
const {listId, params} = this.loadBucketParameter
|
||||
|
||||
this.collapsedBuckets = getCollapsedBucketState(listId)
|
||||
|
||||
console.debug(`Loading buckets, loadedListId = ${this.loadedListId}, $route.params =`, this.$route.params)
|
||||
console.debug(`Loading buckets, loadedListId = ${this.loadedListId}, $attrs = ${this.$attrs} $route.params =`, this.$route.params)
|
||||
|
||||
this.$store.dispatch('kanban/loadBucketsForList', {listId, params})
|
||||
},
|
||||
|
@ -434,7 +437,7 @@ export default {
|
|||
const task = await this.$store.dispatch('tasks/createNewTask', {
|
||||
title: this.newTaskText,
|
||||
bucketId,
|
||||
listId: this.$route.params.listId,
|
||||
listId: this.listId,
|
||||
})
|
||||
this.newTaskText = ''
|
||||
this.$store.commit('kanban/addTaskToBucket', task)
|
||||
|
@ -456,7 +459,7 @@ export default {
|
|||
|
||||
const newBucket = new BucketModel({
|
||||
title: this.newBucketTitle,
|
||||
listId: parseInt(this.$route.params.listId),
|
||||
listId: this.listId,
|
||||
})
|
||||
|
||||
await this.$store.dispatch('kanban/createBucket', newBucket)
|
||||
|
@ -476,7 +479,7 @@ export default {
|
|||
async deleteBucket() {
|
||||
const bucket = new BucketModel({
|
||||
id: this.bucketToDelete,
|
||||
listId: parseInt(this.$route.params.listId),
|
||||
listId: this.listId,
|
||||
})
|
||||
|
||||
try {
|
||||
|
@ -564,7 +567,7 @@ export default {
|
|||
|
||||
collapseBucket(bucket) {
|
||||
this.collapsedBuckets[bucket.id] = true
|
||||
saveCollapsedBucketState(this.$route.params.listId, this.collapsedBuckets)
|
||||
saveCollapsedBucketState(this.listId, this.collapsedBuckets)
|
||||
},
|
||||
unCollapseBucket(bucket) {
|
||||
if (!this.collapsedBuckets[bucket.id]) {
|
||||
|
@ -572,7 +575,7 @@ export default {
|
|||
}
|
||||
|
||||
this.collapsedBuckets[bucket.id] = false
|
||||
saveCollapsedBucketState(this.$route.params.listId, this.collapsedBuckets)
|
||||
saveCollapsedBucketState(this.listId, this.collapsedBuckets)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<ListWrapper class="list-list">
|
||||
<ListWrapper class="list-list" :list-id="listId" viewName="list">
|
||||
<template #header>
|
||||
<div
|
||||
class="filter-container"
|
||||
|
@ -132,9 +132,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { ref, toRef, defineComponent } from 'vue'
|
||||
|
||||
import ListWrapper from './ListWrapper'
|
||||
import ListWrapper from './ListWrapper.vue'
|
||||
import EditTask from '@/components/tasks/edit-task'
|
||||
import AddTask from '@/components/tasks/add-task'
|
||||
import SingleTaskInList from '@/components/tasks/partials/singleTaskInList'
|
||||
|
@ -167,8 +167,16 @@ function sortTasks(tasks) {
|
|||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: 'List',
|
||||
|
||||
props: {
|
||||
listId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
ctaVisible: false,
|
||||
|
@ -192,19 +200,17 @@ export default {
|
|||
Pagination,
|
||||
},
|
||||
|
||||
setup() {
|
||||
setup(props) {
|
||||
const taskEditTask = ref(null)
|
||||
const isTaskEdit = ref(false)
|
||||
|
||||
// This function initializes the tasks page and loads the first page of tasks
|
||||
function beforeLoad() {
|
||||
taskEditTask.value = null
|
||||
isTaskEdit.value = false
|
||||
}
|
||||
// function beforeLoad() {
|
||||
// taskEditTask.value = null
|
||||
// isTaskEdit.value = false
|
||||
// }
|
||||
|
||||
const taskList = useTaskList(beforeLoad)
|
||||
|
||||
taskList.initTaskList()
|
||||
const taskList = useTaskList(toRef(props, 'listId'))
|
||||
|
||||
return {
|
||||
taskEditTask,
|
||||
|
@ -312,7 +318,7 @@ export default {
|
|||
this.tasks[e.newIndex] = updatedTask
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<ListWrapper class="list-table">
|
||||
<ListWrapper class="list-table" :list-id="listId" viewName="table">
|
||||
<template #header>
|
||||
<div class="filter-container">
|
||||
<div class="items">
|
||||
|
@ -15,50 +15,47 @@
|
|||
</template>
|
||||
<template #content="{isOpen}">
|
||||
<card class="columns-filter" :class="{'is-open': isOpen}">
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.id">#</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.done">
|
||||
<fancycheckbox v-model="activeColumns.id">#</fancycheckbox>
|
||||
<fancycheckbox v-model="activeColumns.done">
|
||||
{{ $t('task.attributes.done') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.title">
|
||||
<fancycheckbox v-model="activeColumns.title">
|
||||
{{ $t('task.attributes.title') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.priority">
|
||||
<fancycheckbox v-model="activeColumns.priority">
|
||||
{{ $t('task.attributes.priority') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.labels">
|
||||
<fancycheckbox v-model="activeColumns.labels">
|
||||
{{ $t('task.attributes.labels') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.assignees">
|
||||
<fancycheckbox v-model="activeColumns.assignees">
|
||||
{{ $t('task.attributes.assignees') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.dueDate">
|
||||
<fancycheckbox v-model="activeColumns.dueDate">
|
||||
{{ $t('task.attributes.dueDate') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.startDate">
|
||||
<fancycheckbox v-model="activeColumns.startDate">
|
||||
{{ $t('task.attributes.startDate') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.endDate">
|
||||
<fancycheckbox v-model="activeColumns.endDate">
|
||||
{{ $t('task.attributes.endDate') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.percentDone">
|
||||
<fancycheckbox v-model="activeColumns.percentDone">
|
||||
{{ $t('task.attributes.percentDone') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.created">
|
||||
<fancycheckbox v-model="activeColumns.created">
|
||||
{{ $t('task.attributes.created') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.updated">
|
||||
<fancycheckbox v-model="activeColumns.updated">
|
||||
{{ $t('task.attributes.updated') }}
|
||||
</fancycheckbox>
|
||||
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.createdBy">
|
||||
<fancycheckbox v-model="activeColumns.createdBy">
|
||||
{{ $t('task.attributes.createdBy') }}
|
||||
</fancycheckbox>
|
||||
</card>
|
||||
</template>
|
||||
</popup>
|
||||
<filter-popup
|
||||
v-model="params"
|
||||
@update:modelValue="loadTasks()"
|
||||
/>
|
||||
<filter-popup v-model="params" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -182,21 +179,23 @@
|
|||
</ListWrapper>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, toRaw } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRef, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import ListWrapper from './ListWrapper'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
import ListWrapper from './ListWrapper.vue'
|
||||
import Done from '@/components/misc/Done.vue'
|
||||
import User from '@/components/misc/user'
|
||||
import PriorityLabel from '@/components/tasks/partials/priorityLabel'
|
||||
import Labels from '@/components/tasks/partials/labels'
|
||||
import DateTableCell from '@/components/tasks/partials/date-table-cell'
|
||||
import Fancycheckbox from '@/components/input/fancycheckbox'
|
||||
import Sort from '@/components/tasks/partials/sort'
|
||||
import User from '@/components/misc/user.vue'
|
||||
import PriorityLabel from '@/components/tasks/partials/priorityLabel.vue'
|
||||
import Labels from '@/components/tasks/partials/labels.vue'
|
||||
import DateTableCell from '@/components/tasks/partials/date-table-cell.vue'
|
||||
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
||||
import Sort from '@/components/tasks/partials/sort.vue'
|
||||
import FilterPopup from '@/components/list/partials/filter-popup.vue'
|
||||
import Pagination from '@/components/misc/pagination.vue'
|
||||
import Popup from '@/components/misc/popup'
|
||||
import Popup from '@/components/misc/popup.vue'
|
||||
|
||||
import { useTaskList } from '@/composables/taskList'
|
||||
|
||||
|
@ -216,57 +215,27 @@ const ACTIVE_COLUMNS_DEFAULT = {
|
|||
createdBy: false,
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
listId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const SORT_BY_DEFAULT = {
|
||||
id: 'desc',
|
||||
}
|
||||
|
||||
function useSavedView(activeColumns, sortBy) {
|
||||
const savedShowColumns = localStorage.getItem('tableViewColumns')
|
||||
if (savedShowColumns !== null) {
|
||||
Object.assign(activeColumns, JSON.parse(savedShowColumns))
|
||||
}
|
||||
|
||||
const savedSortBy = localStorage.getItem('tableViewSortBy')
|
||||
if (savedSortBy !== null) {
|
||||
sortBy.value = JSON.parse(savedSortBy)
|
||||
}
|
||||
}
|
||||
|
||||
const activeColumns = reactive({ ...ACTIVE_COLUMNS_DEFAULT })
|
||||
const sortBy = ref({ ...SORT_BY_DEFAULT })
|
||||
|
||||
useSavedView(activeColumns, sortBy)
|
||||
|
||||
function beforeLoad(params) {
|
||||
// This makes sure an id sort order is always sorted last.
|
||||
// When tasks would be sorted first by id and then by whatever else was specified, the id sort takes
|
||||
// precedence over everything else, making any other sort columns pretty useless.
|
||||
let hasIdFilter = false
|
||||
const sortKeys = Object.keys(sortBy.value)
|
||||
for (const s of sortKeys) {
|
||||
if (s === 'id') {
|
||||
sortKeys.splice(s, 1)
|
||||
hasIdFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (hasIdFilter) {
|
||||
sortKeys.push('id')
|
||||
}
|
||||
params.value.sort_by = sortKeys
|
||||
params.value.order_by = sortKeys.map(s => sortBy.value[s])
|
||||
}
|
||||
const activeColumns = useStorage('tableViewColumns', { ...ACTIVE_COLUMNS_DEFAULT })
|
||||
const sortBy = useStorage('tableViewSortBy', { ...SORT_BY_DEFAULT })
|
||||
|
||||
const {
|
||||
tasks,
|
||||
loading,
|
||||
params,
|
||||
loadTasks,
|
||||
totalPages,
|
||||
currentPage,
|
||||
searchTerm,
|
||||
initTaskList,
|
||||
} = useTaskList(beforeLoad)
|
||||
} = useTaskList(toRef(props, 'listId'))
|
||||
|
||||
Object.assign(params.value, {
|
||||
filter_by: [],
|
||||
|
@ -274,8 +243,19 @@ Object.assign(params.value, {
|
|||
filter_comparator: [],
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
// FIXME: by doing this we can have multiple sort orders
|
||||
function sort(property) {
|
||||
const order = sortBy.value[property]
|
||||
if (typeof order === 'undefined' || order === 'none') {
|
||||
sortBy.value[property] = 'desc'
|
||||
} else if (order === 'desc') {
|
||||
sortBy.value[property] = 'asc'
|
||||
} else {
|
||||
delete sortBy.value[property]
|
||||
}
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const taskDetailRoutes = computed(() => Object.fromEntries(
|
||||
tasks.value.map(({id}) => ([
|
||||
id,
|
||||
|
@ -286,26 +266,6 @@ const taskDetailRoutes = computed(() => Object.fromEntries(
|
|||
},
|
||||
])),
|
||||
))
|
||||
|
||||
function sort(property) {
|
||||
const order = sortBy.value[property]
|
||||
if (typeof order === 'undefined' || order === 'none') {
|
||||
sortBy.value[property] = 'desc'
|
||||
} else if (order === 'desc') {
|
||||
sortBy.value[property] = 'asc'
|
||||
} else {
|
||||
delete sortBy.value[property]
|
||||
}
|
||||
beforeLoad(currentPage.value, searchTerm.value)
|
||||
// Save the order to be able to retrieve them later
|
||||
localStorage.setItem('tableViewSortBy', JSON.stringify(sortBy.value))
|
||||
}
|
||||
|
||||
function saveTaskColumns() {
|
||||
localStorage.setItem('tableViewColumns', JSON.stringify(toRaw(activeColumns)))
|
||||
}
|
||||
|
||||
initTaskList()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -8,28 +8,28 @@
|
|||
<router-link
|
||||
v-shortcut="'g l'"
|
||||
:title="$t('keyboardShortcuts.list.switchToListView')"
|
||||
:class="{'is-active': $route.name === 'list.list'}"
|
||||
:class="{'is-active': viewName === 'list'}"
|
||||
:to="{ name: 'list.list', params: { listId } }">
|
||||
{{ $t('list.list.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g g'"
|
||||
:title="$t('keyboardShortcuts.list.switchToGanttView')"
|
||||
:class="{'is-active': $route.name === 'list.gantt'}"
|
||||
:class="{'is-active': viewName === 'gantt'}"
|
||||
:to="{ name: 'list.gantt', params: { listId } }">
|
||||
{{ $t('list.gantt.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g t'"
|
||||
:title="$t('keyboardShortcuts.list.switchToTableView')"
|
||||
:class="{'is-active': $route.name === 'list.table'}"
|
||||
:class="{'is-active': viewName === 'table'}"
|
||||
:to="{ name: 'list.table', params: { listId } }">
|
||||
{{ $t('list.table.title') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-shortcut="'g k'"
|
||||
:title="$t('keyboardShortcuts.list.switchToKanbanView')"
|
||||
:class="{'is-active': $route.name === 'list.kanban'}"
|
||||
:class="{'is-active': viewName === 'kanban'}"
|
||||
:to="{ name: 'list.kanban', params: { listId } }">
|
||||
{{ $t('list.kanban.title') }}
|
||||
</router-link>
|
||||
|
@ -46,11 +46,11 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import {ref, shallowRef, computed, watchEffect} from 'vue'
|
||||
import {useRoute} from 'vue-router'
|
||||
|
||||
import Message from '@/components/misc/message'
|
||||
import Message from '@/components/misc/message.vue'
|
||||
|
||||
import ListModel from '@/models/list'
|
||||
import ListService from '@/services/list'
|
||||
|
@ -63,11 +63,22 @@ import {saveListView} from '@/helpers/saveListView'
|
|||
import {saveListToHistory} from '@/modules/listHistory'
|
||||
import { useTitle } from '@/composables/useTitle'
|
||||
|
||||
const props = defineProps({
|
||||
listId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
viewName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// Save the current list view to local storage
|
||||
// We use local storage and not vuex here to make it persistent across reloads.
|
||||
saveListView(route.params.listId, route.name)
|
||||
saveListView(props.listId, props.viewName)
|
||||
|
||||
const listService = shallowRef(new ListService())
|
||||
const loadedListId = ref(0)
|
||||
|
@ -80,14 +91,12 @@ const currentList = computed(() => {
|
|||
} : store.state.currentList
|
||||
})
|
||||
|
||||
// Computed property to let "listId" always have a value
|
||||
const listId = computed(() => typeof route.params.listId === 'undefined' ? 0 : parseInt(route.params.listId))
|
||||
// call again the method if the listId changes
|
||||
watchEffect(() => loadList(listId.value))
|
||||
watchEffect(() => loadList(props.listId))
|
||||
|
||||
useTitle(() => currentList.value.id ? getListTitle(currentList.value) : '')
|
||||
|
||||
async function loadList(listIdToLoad) {
|
||||
async function loadList(listIdToLoad: number) {
|
||||
const listData = {id: listIdToLoad}
|
||||
saveListToHistory(listData)
|
||||
|
||||
|
@ -97,8 +106,8 @@ async function loadList(listIdToLoad) {
|
|||
// We don't do this for the table view because that does not change tasks.
|
||||
// FIXME: remove this
|
||||
if (
|
||||
route.name === 'list.list' ||
|
||||
route.name === 'list.gantt'
|
||||
props.viewName === 'list.list' ||
|
||||
props.viewName === 'list.gantt'
|
||||
) {
|
||||
store.commit('kanban/setListId', 0)
|
||||
}
|
||||
|
@ -116,7 +125,7 @@ async function loadList(listIdToLoad) {
|
|||
return
|
||||
}
|
||||
|
||||
console.debug(`Loading list, $route.name = ${route.name}, $route.params =`, route.params, `, loadedListId = ${loadedListId.value}, currentList = `, currentList.value)
|
||||
console.debug(`Loading list, props.viewName = ${props.viewName}, $route.params =`, route.params, `, loadedListId = ${loadedListId.value}, currentList = `, currentList.value)
|
||||
|
||||
// We create an extra list object instead of creating it in list.value because that would trigger a ui update which would result in bad ux.
|
||||
const list = new ListModel(listData)
|
||||
|
@ -124,7 +133,7 @@ async function loadList(listIdToLoad) {
|
|||
const loadedList = await listService.value.get(list)
|
||||
await store.dispatch(CURRENT_LIST, loadedList)
|
||||
} finally {
|
||||
loadedListId.value = listId.value
|
||||
loadedListId.value = props.listId
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -475,6 +475,14 @@ export default {
|
|||
description,
|
||||
heading,
|
||||
},
|
||||
|
||||
props: {
|
||||
taskId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
taskService: new TaskService(),
|
||||
|
@ -525,10 +533,6 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
taskId() {
|
||||
const {id} = this.$route.params
|
||||
return id === undefined ? id : Number(id)
|
||||
},
|
||||
currentList() {
|
||||
return this.$store.state[CURRENT_LIST]
|
||||
},
|
||||
|
|
|
@ -7,15 +7,22 @@
|
|||
<a @click="close()" class="close">
|
||||
<icon icon="times"/>
|
||||
</a>
|
||||
<task-detail-view/>
|
||||
<task-detail-view :task-id="props.taskId"/>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import {computed} from 'vue'
|
||||
import {useRouter, useRoute} from 'vue-router'
|
||||
|
||||
import TaskDetailView from './TaskDetailView'
|
||||
import TaskDetailView from './TaskDetailView.vue'
|
||||
|
||||
const props = defineProps({
|
||||
taskId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const historyState = computed(() => route.fullPath && window.history.state)
|
||||
|
|
Loading…
Reference in a new issue