2019-04-29 21:41:39 +00:00
|
|
|
<template>
|
2021-12-10 14:29:28 +00:00
|
|
|
<ListWrapper class="list-list" :list-id="listId" viewName="list">
|
2021-11-14 20:33:53 +00:00
|
|
|
<template #header>
|
2021-07-17 21:21:46 +00:00
|
|
|
<div
|
|
|
|
class="filter-container"
|
2022-09-28 16:08:23 +00:00
|
|
|
v-if="!isSavedFilter(list)"
|
2021-07-17 21:21:46 +00:00
|
|
|
>
|
2020-06-11 15:34:13 +00:00
|
|
|
<div class="items">
|
|
|
|
<div class="search">
|
2021-07-17 21:21:46 +00:00
|
|
|
<div :class="{ hidden: !showTaskSearch }" class="field has-addons">
|
2020-06-11 15:34:13 +00:00
|
|
|
<div class="control has-icons-left has-icons-right">
|
|
|
|
<input
|
2020-09-05 20:35:52 +00:00
|
|
|
@blur="hideSearchBar()"
|
|
|
|
@keyup.enter="searchTasks"
|
|
|
|
class="input"
|
2021-06-23 23:24:57 +00:00
|
|
|
:placeholder="$t('misc.search')"
|
2020-09-05 20:35:52 +00:00
|
|
|
type="text"
|
|
|
|
v-focus
|
2021-07-17 21:21:46 +00:00
|
|
|
v-model="searchTerm"
|
|
|
|
/>
|
2020-06-11 15:34:13 +00:00
|
|
|
<span class="icon is-left">
|
2021-07-28 19:56:29 +00:00
|
|
|
<icon icon="search"/>
|
2020-06-11 15:34:13 +00:00
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<div class="control">
|
2021-01-17 17:57:57 +00:00
|
|
|
<x-button
|
2021-10-25 20:17:23 +00:00
|
|
|
:loading="loading"
|
2020-09-05 20:35:52 +00:00
|
|
|
@click="searchTasks"
|
2021-01-17 17:57:57 +00:00
|
|
|
:shadow="false"
|
|
|
|
>
|
2021-06-23 23:24:57 +00:00
|
|
|
{{ $t('misc.search') }}
|
2021-01-17 17:57:57 +00:00
|
|
|
</x-button>
|
2020-06-11 15:34:13 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2021-01-17 17:57:57 +00:00
|
|
|
<x-button
|
|
|
|
@click="showTaskSearch = !showTaskSearch"
|
|
|
|
icon="search"
|
2022-01-04 18:58:06 +00:00
|
|
|
variant="secondary"
|
2021-01-17 17:57:57 +00:00
|
|
|
v-if="!showTaskSearch"
|
|
|
|
/>
|
2020-01-31 10:05:53 +00:00
|
|
|
</div>
|
2021-11-13 19:48:06 +00:00
|
|
|
<filter-popup
|
|
|
|
v-model="params"
|
|
|
|
@update:modelValue="loadTasks()"
|
|
|
|
/>
|
2020-01-31 10:05:53 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2021-11-14 20:33:53 +00:00
|
|
|
</template>
|
2020-03-04 19:27:27 +00:00
|
|
|
|
2021-11-14 20:33:53 +00:00
|
|
|
<template #default>
|
|
|
|
<div
|
|
|
|
:class="{ 'is-loading': loading }"
|
|
|
|
class="loader-container is-max-width-desktop list-view"
|
|
|
|
>
|
2021-01-24 14:37:19 +00:00
|
|
|
<card :padding="false" :has-content="false" class="has-overflow">
|
2021-07-17 21:21:46 +00:00
|
|
|
<template
|
2022-07-12 09:59:39 +00:00
|
|
|
v-if="!list.isArchived && canWrite"
|
2021-07-17 21:21:46 +00:00
|
|
|
>
|
|
|
|
<add-task
|
|
|
|
@taskAdded="updateTaskList"
|
2022-09-28 16:08:23 +00:00
|
|
|
ref="addTaskRef"
|
2021-07-28 19:56:29 +00:00
|
|
|
:default-position="firstNewPosition"
|
2021-07-17 21:21:46 +00:00
|
|
|
/>
|
|
|
|
</template>
|
2019-04-29 21:41:39 +00:00
|
|
|
|
2021-10-25 20:17:23 +00:00
|
|
|
<nothing v-if="ctaVisible && tasks.length === 0 && !loading">
|
2021-06-23 23:24:57 +00:00
|
|
|
{{ $t('list.list.empty') }}
|
2022-06-22 19:53:20 +00:00
|
|
|
<ButtonLink @click="focusNewTaskInput()">
|
2021-06-23 23:24:57 +00:00
|
|
|
{{ $t('list.list.newTaskCta') }}
|
2022-06-22 19:53:20 +00:00
|
|
|
</ButtonLink>
|
2021-01-30 16:17:04 +00:00
|
|
|
</nothing>
|
2020-07-04 17:12:15 +00:00
|
|
|
|
2021-10-17 11:36:21 +00:00
|
|
|
<div class="tasks-container" :class="{ 'has-task-edit-open': isTaskEdit }">
|
2021-07-17 21:21:46 +00:00
|
|
|
<div
|
|
|
|
class="tasks mt-0"
|
|
|
|
v-if="tasks && tasks.length > 0"
|
|
|
|
>
|
2021-07-28 19:56:29 +00:00
|
|
|
<draggable
|
2022-09-28 16:08:23 +00:00
|
|
|
v-bind="DRAG_OPTIONS"
|
2021-07-28 19:56:29 +00:00
|
|
|
v-model="tasks"
|
|
|
|
group="tasks"
|
|
|
|
@start="() => drag = true"
|
|
|
|
@end="saveTaskPosition"
|
|
|
|
handle=".handle"
|
2021-07-29 11:05:33 +00:00
|
|
|
:disabled="!canWrite"
|
2021-08-20 13:46:41 +00:00
|
|
|
item-key="id"
|
2022-07-19 14:32:12 +00:00
|
|
|
tag="ul"
|
2021-10-16 11:54:24 +00:00
|
|
|
:component-data="{
|
2021-12-21 16:29:49 +00:00
|
|
|
class: { 'dragging-disabled': !canWrite || isAlphabeticalSorting },
|
2022-07-19 14:32:12 +00:00
|
|
|
type: 'transition-group'
|
2021-10-16 11:54:24 +00:00
|
|
|
}"
|
2020-09-05 20:16:17 +00:00
|
|
|
>
|
2021-08-20 13:46:41 +00:00
|
|
|
<template #item="{element: t}">
|
|
|
|
<single-task-in-list
|
|
|
|
:show-list-color="false"
|
|
|
|
:disabled="!canWrite"
|
2022-09-28 16:08:23 +00:00
|
|
|
:can-mark-as-done="canWrite || isSavedFilter(list)"
|
2021-08-20 13:46:41 +00:00
|
|
|
:the-task="t"
|
|
|
|
@taskUpdated="updateTasks"
|
2021-07-28 19:56:29 +00:00
|
|
|
>
|
2021-10-07 10:20:52 +00:00
|
|
|
<template v-if="canWrite">
|
|
|
|
<span class="icon handle">
|
|
|
|
<icon icon="grip-lines"/>
|
|
|
|
</span>
|
2022-05-10 23:14:38 +00:00
|
|
|
<BaseButton
|
2021-10-07 10:20:52 +00:00
|
|
|
@click="editTask(t.id)"
|
|
|
|
class="icon settings"
|
|
|
|
v-if="!list.isArchived"
|
|
|
|
>
|
|
|
|
<icon icon="pencil-alt"/>
|
2022-05-10 23:14:38 +00:00
|
|
|
</BaseButton>
|
2021-10-07 10:20:52 +00:00
|
|
|
</template>
|
2021-08-20 13:46:41 +00:00
|
|
|
</single-task-in-list>
|
|
|
|
</template>
|
2021-07-28 19:56:29 +00:00
|
|
|
</draggable>
|
2019-04-29 21:41:39 +00:00
|
|
|
</div>
|
2022-09-28 16:08:23 +00:00
|
|
|
<EditTask
|
2021-01-24 13:00:21 +00:00
|
|
|
v-if="isTaskEdit"
|
2021-11-13 14:13:56 +00:00
|
|
|
class="taskedit mt-0"
|
|
|
|
:title="$t('list.list.editTask')"
|
2022-09-30 19:13:13 +00:00
|
|
|
@close="closeTaskEditPane()"
|
2021-11-13 14:13:56 +00:00
|
|
|
:shadow="false"
|
|
|
|
:task="taskEditTask"
|
|
|
|
/>
|
2019-04-29 21:41:39 +00:00
|
|
|
</div>
|
2019-12-03 18:09:12 +00:00
|
|
|
|
2021-10-25 20:17:23 +00:00
|
|
|
<Pagination
|
|
|
|
:total-pages="totalPages"
|
2021-09-21 19:03:38 +00:00
|
|
|
:current-page="currentPage"
|
|
|
|
/>
|
2021-01-24 13:00:21 +00:00
|
|
|
</card>
|
2021-11-14 20:33:53 +00:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</ListWrapper>
|
2019-04-29 21:41:39 +00:00
|
|
|
</template>
|
|
|
|
|
2022-02-15 12:07:34 +00:00
|
|
|
<script lang="ts">
|
2022-09-28 16:08:23 +00:00
|
|
|
export default { name: 'List' }
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2022-09-30 19:13:13 +00:00
|
|
|
import {ref, computed, toRef, nextTick, onMounted, type PropType, watch} from 'vue'
|
2022-09-28 16:08:23 +00:00
|
|
|
import draggable from 'zhyswan-vuedraggable'
|
|
|
|
import {useRoute, useRouter} from 'vue-router'
|
2021-10-25 20:17:23 +00:00
|
|
|
|
2022-09-28 16:08:23 +00:00
|
|
|
import ListWrapper from './ListWrapper.vue'
|
2022-05-10 23:14:38 +00:00
|
|
|
import BaseButton from '@/components/base/BaseButton.vue'
|
2022-06-22 19:53:20 +00:00
|
|
|
import ButtonLink from '@/components/misc/ButtonLink.vue'
|
2022-07-12 09:59:39 +00:00
|
|
|
import EditTask from '@/components/tasks/edit-task.vue'
|
|
|
|
import AddTask from '@/components/tasks/add-task.vue'
|
|
|
|
import SingleTaskInList from '@/components/tasks/partials/singleTaskInList.vue'
|
2021-07-25 13:27:15 +00:00
|
|
|
import FilterPopup from '@/components/list/partials/filter-popup.vue'
|
|
|
|
import Nothing from '@/components/misc/nothing.vue'
|
2021-09-21 19:03:38 +00:00
|
|
|
import Pagination from '@/components/misc/pagination.vue'
|
2021-10-03 13:54:24 +00:00
|
|
|
import {ALPHABETICAL_SORT} from '@/components/list/partials/filters.vue'
|
2019-04-29 21:41:39 +00:00
|
|
|
|
2022-10-27 13:47:48 +00:00
|
|
|
import {useTaskList} from '@/composables/useTaskList'
|
2022-09-28 16:08:23 +00:00
|
|
|
import {RIGHTS as Rights} from '@/constants/rights'
|
|
|
|
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
2022-09-06 09:36:01 +00:00
|
|
|
import type {ITask} from '@/modelTypes/ITask'
|
2022-10-27 19:56:14 +00:00
|
|
|
import {isSavedFilter} from '@/services/savedFilter'
|
2022-09-24 13:20:40 +00:00
|
|
|
|
|
|
|
import {useBaseStore} from '@/stores/base'
|
2022-09-23 10:55:53 +00:00
|
|
|
import {useTaskStore} from '@/stores/tasks'
|
|
|
|
|
|
|
|
import type {IList} from '@/modelTypes/IList'
|
2021-07-28 19:56:29 +00:00
|
|
|
|
2022-07-20 22:42:36 +00:00
|
|
|
function sortTasks(tasks: ITask[]) {
|
2022-08-04 18:57:43 +00:00
|
|
|
if (tasks === null || Array.isArray(tasks) && tasks.length === 0) {
|
2021-09-07 15:04:46 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
return tasks.sort((a, b) => {
|
|
|
|
if (a.done < b.done)
|
|
|
|
return -1
|
|
|
|
if (a.done > b.done)
|
|
|
|
return 1
|
|
|
|
|
|
|
|
if (a.position < b.position)
|
|
|
|
return -1
|
|
|
|
if (a.position > b.position)
|
|
|
|
return 1
|
|
|
|
return 0
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-09-28 16:08:23 +00:00
|
|
|
const props = defineProps({
|
|
|
|
listId: {
|
2022-09-23 10:55:53 +00:00
|
|
|
type: Number as PropType<IList['id']>,
|
2022-09-28 16:08:23 +00:00
|
|
|
required: true,
|
2021-12-10 14:29:28 +00:00
|
|
|
},
|
2022-09-28 16:08:23 +00:00
|
|
|
})
|
2021-12-10 14:29:28 +00:00
|
|
|
|
2022-09-28 16:08:23 +00:00
|
|
|
const ctaVisible = ref(false)
|
|
|
|
const showTaskSearch = ref(false)
|
2021-07-28 19:56:29 +00:00
|
|
|
|
2022-09-28 16:08:23 +00:00
|
|
|
const drag = ref(false)
|
|
|
|
const DRAG_OPTIONS = {
|
|
|
|
animation: 100,
|
|
|
|
ghostClass: 'ghost',
|
|
|
|
} as const
|
2021-10-03 13:54:24 +00:00
|
|
|
|
2021-10-25 20:17:23 +00:00
|
|
|
|
2022-09-29 09:44:58 +00:00
|
|
|
const taskEditTask = ref<ITask | null>(null)
|
2022-09-28 16:08:23 +00:00
|
|
|
const isTaskEdit = ref(false)
|
2021-10-25 20:17:23 +00:00
|
|
|
|
2022-09-30 19:13:13 +00:00
|
|
|
function closeTaskEditPane() {
|
|
|
|
isTaskEdit.value = false
|
|
|
|
taskEditTask.value = null
|
|
|
|
}
|
|
|
|
|
|
|
|
watch(
|
|
|
|
() => props.listId,
|
|
|
|
closeTaskEditPane,
|
|
|
|
)
|
|
|
|
|
2022-09-28 16:08:23 +00:00
|
|
|
const {
|
|
|
|
tasks,
|
|
|
|
loading,
|
|
|
|
totalPages,
|
|
|
|
currentPage,
|
|
|
|
loadTasks,
|
|
|
|
searchTerm,
|
|
|
|
params,
|
|
|
|
// sortByParam,
|
|
|
|
} = useTaskList(toRef(props, 'listId'), {position: 'asc' })
|
2021-11-01 17:19:59 +00:00
|
|
|
|
2021-07-28 19:56:29 +00:00
|
|
|
|
2022-09-28 16:08:23 +00:00
|
|
|
const isAlphabeticalSorting = computed(() => {
|
|
|
|
return params.value.sort_by.find(sortBy => sortBy === ALPHABETICAL_SORT) !== undefined
|
|
|
|
})
|
2021-12-21 16:29:49 +00:00
|
|
|
|
2022-09-28 16:08:23 +00:00
|
|
|
const firstNewPosition = computed(() => {
|
|
|
|
if (tasks.value.length === 0) {
|
|
|
|
return 0
|
|
|
|
}
|
2021-10-11 17:37:20 +00:00
|
|
|
|
2022-09-28 16:08:23 +00:00
|
|
|
return calculateItemPosition(null, tasks.value[0].position)
|
|
|
|
})
|
2021-11-13 19:48:06 +00:00
|
|
|
|
2022-09-23 10:55:53 +00:00
|
|
|
const taskStore = useTaskStore()
|
2022-09-24 13:20:40 +00:00
|
|
|
const baseStore = useBaseStore()
|
|
|
|
const list = computed(() => baseStore.currentList)
|
2021-11-13 19:48:06 +00:00
|
|
|
|
2022-09-28 16:08:23 +00:00
|
|
|
const canWrite = computed(() => {
|
|
|
|
return list.value.maxRight > Rights.READ && list.value.id > 0
|
|
|
|
})
|
2021-09-07 15:04:46 +00:00
|
|
|
|
2022-09-28 16:08:23 +00:00
|
|
|
onMounted(async () => {
|
|
|
|
await nextTick()
|
|
|
|
ctaVisible.value = true
|
2021-12-10 14:29:28 +00:00
|
|
|
})
|
2022-09-28 16:08:23 +00:00
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
const router = useRouter()
|
2022-09-23 10:55:53 +00:00
|
|
|
|
2022-09-28 16:08:23 +00:00
|
|
|
function searchTasks() {
|
|
|
|
// Only search if the search term changed
|
|
|
|
if (route.query as unknown as string === searchTerm.value) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
router.push({
|
|
|
|
name: 'list.list',
|
|
|
|
query: {search: searchTerm.value},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function hideSearchBar() {
|
|
|
|
// This is a workaround.
|
|
|
|
// When clicking on the search button, @blur from the input is fired. If we
|
|
|
|
// would then directly hide the whole search bar directly, no click event
|
|
|
|
// from the button gets fired. To prevent this, we wait 200ms until we hide
|
|
|
|
// everything so the button has a chance of firing the search event.
|
|
|
|
setTimeout(() => {
|
|
|
|
showTaskSearch.value = false
|
|
|
|
}, 200)
|
|
|
|
}
|
|
|
|
|
|
|
|
const addTaskRef = ref<typeof AddTask | null>(null)
|
|
|
|
function focusNewTaskInput() {
|
|
|
|
addTaskRef.value?.focusTaskInput()
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateTaskList(task: ITask) {
|
|
|
|
if (isAlphabeticalSorting.value ) {
|
|
|
|
// reload tasks with current filter and sorting
|
|
|
|
loadTasks(1, undefined, undefined, true)
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
tasks.value = [
|
|
|
|
task,
|
|
|
|
...tasks.value,
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2022-09-24 13:20:40 +00:00
|
|
|
baseStore.setHasTasks(true)
|
2022-09-28 16:08:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function editTask(id: ITask['id']) {
|
|
|
|
taskEditTask.value = {...tasks.value.find(t => t.id === Number(id))}
|
|
|
|
isTaskEdit.value = true
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateTasks(updatedTask: ITask) {
|
|
|
|
for (const t in tasks.value) {
|
|
|
|
if (tasks.value[t].id === updatedTask.id) {
|
|
|
|
tasks.value[t] = updatedTask
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// FIXME: Use computed
|
|
|
|
sortTasks(tasks.value)
|
|
|
|
}
|
|
|
|
|
|
|
|
async function saveTaskPosition(e) {
|
|
|
|
drag.value = false
|
|
|
|
|
|
|
|
const task = tasks.value[e.newIndex]
|
|
|
|
const taskBefore = tasks.value[e.newIndex - 1] ?? null
|
|
|
|
const taskAfter = tasks.value[e.newIndex + 1] ?? null
|
|
|
|
|
|
|
|
const newTask = {
|
|
|
|
...task,
|
|
|
|
position: calculateItemPosition(taskBefore !== null ? taskBefore.position : null, taskAfter !== null ? taskAfter.position : null),
|
|
|
|
}
|
|
|
|
|
2022-09-23 10:55:53 +00:00
|
|
|
const updatedTask = await taskStore.update(newTask)
|
2022-09-29 09:44:58 +00:00
|
|
|
tasks.value[e.newIndex] = updatedTask
|
2022-09-28 16:08:23 +00:00
|
|
|
}
|
2021-07-17 21:21:46 +00:00
|
|
|
</script>
|
2021-10-18 12:21:02 +00:00
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
2021-10-18 12:22:47 +00:00
|
|
|
.tasks-container {
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
&.has-task-edit-open {
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
@media screen and (min-width: $tablet) {
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
|
|
.tasks {
|
|
|
|
width: 66%;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.tasks {
|
|
|
|
width: 100%;
|
2021-11-16 21:44:07 +00:00
|
|
|
padding: .5rem;
|
2021-10-18 12:22:47 +00:00
|
|
|
|
|
|
|
.ghost {
|
|
|
|
border-radius: $radius;
|
2021-11-22 21:12:54 +00:00
|
|
|
background: var(--grey-100);
|
|
|
|
border: 2px dashed var(--grey-300);
|
2021-10-18 12:22:47 +00:00
|
|
|
|
|
|
|
* {
|
|
|
|
opacity: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.taskedit {
|
|
|
|
width: 33%;
|
|
|
|
margin-right: 1rem;
|
|
|
|
margin-left: .5rem;
|
2021-11-13 14:13:56 +00:00
|
|
|
min-height: calc(100% - 1rem);
|
2021-10-18 12:22:47 +00:00
|
|
|
|
|
|
|
@media screen and (max-width: $tablet) {
|
|
|
|
width: 100%;
|
|
|
|
border-radius: 0;
|
|
|
|
margin: 0;
|
|
|
|
border-left: 0;
|
|
|
|
border-right: 0;
|
|
|
|
border-bottom: 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-18 12:21:02 +00:00
|
|
|
.list-view .task-add {
|
|
|
|
padding: 1rem 1rem 0;
|
|
|
|
}
|
2022-05-08 09:13:19 +00:00
|
|
|
|
|
|
|
.link-share-view .card {
|
|
|
|
border: none;
|
|
|
|
box-shadow: none;
|
|
|
|
}
|
2021-10-18 12:22:47 +00:00
|
|
|
</style>
|