Files
tgCrewUser/src/pages/main/TasksPage.vue

463 lines
13 KiB
Vue

<template>
<div class="q-pa-none flex column col-grow no-scroll">
<pn-scroll-list>
<template #card-body-header>
<div class="flex row q-mb-xs q-mt-md q-mx-sm justify-between">
<q-btn
icon="mdi-calendar-month-outline"
flat dense round
class="q-mr-sm"
size="lg"
:color="showCalendar ? 'primary' : 'grey'"
@click="showCalendar = !showCalendar"
>
<div>
<q-badge
color="red"
rounded
floating
transparent
style="position: relative; top: -16px; margin-left: -12px"
:style="{ opacity: datesRange ? 0.8 : 0 }"
/>
</div>
</q-btn>
<q-input
v-model="search"
clearable
clear-icon="close"
:placeholder="$t('tasks__search')"
dense
class="col-grow"
>
<template #prepend>
<q-icon name="mdi-magnify" />
</template>
</q-input>
<q-btn
@click="showFiltersDialog = true"
icon="mdi-filter-outline"
dense round flat
size="lg"
:color="showFiltersDialog ? 'primary' : 'grey'"
class="q-mr-xs"
>
<div>
<q-badge
color="red"
rounded
floating
transparent
style="position: relative; top: -16px; margin-left: -12px"
:style="{ opacity: !checkFiltersSelect ? 0.8 : 0 }"
/>
</div>
</q-btn>
</div>
<q-slide-transition>
<div v-show="showCalendar">
<q-date
class="w100 fix-calendar q-mb-sm q-mt-xs"
first-day-of-week="1"
v-model="datesRange"
range
flat
:events="tasksDates"
event-color="brand"
today-btn
minimal
dense
/>
</div>
</q-slide-transition>
</template>
<div class="w100 flex justify-end q-px-md">
<q-btn flat dense no-caps @click="showArchiveTasks=!showArchiveTasks">
<span class="text-caption text-grey">
{{
$t(!showArchiveTasks ? 'tasks__show_archive' : 'tasks__hide_archive') +
(hiddenTask !== 0 ? ' (+' + hiddenTask +')' : '')
}}
</span>
</q-btn>
</div>
<q-list separator>
<template v-for="item in displayTasks" :key="item.id">
<q-slide-item
@right="handleSlideRight($event, item.id)"
@left="handleSlideLeft($event, item.id)"
clickable
v-ripple
@click="goTask(item.id)"
right-color="red"
left-color="green"
>
<template #right v-if="item.status !== 6">
<q-icon size="lg" name="mdi-clipboard-remove-outline"/>
</template>
<template #left v-if="item.status === 6">
<q-icon size="lg" name="mdi-clipboard-play-outline"/>
</template>
<q-item
:key="item.id"
:style = "{
backgroundColor:
item.status === 6
? '#999'
: 'inherit',
border: item.status === 6 ? 'solid 1px #999' : 'inherit'
}"
>
<task-item :item/>
</q-item>
</q-slide-item>
</template>
</q-list>
</pn-scroll-list>
<q-page-sticky
position="bottom-right"
:offset="[0, 18]"
class="fix-fab-offset"
>
<transition
appear
enter-active-class="animated zoomIn"
>
<q-btn
v-if="showFab"
fab
icon="add"
color="brand"
@click="createTask()"
/>
</transition>
</q-page-sticky>
</div>
<pn-small-dialog
v-model="showDialogDeleteTask"
icon="mdi-clipboard-remove-outline"
color="negative"
title="tasks__dialog_cancel_title"
mainBtnLabel="tasks__dialog_cancel_ok"
auxBtnLabel="tasks__dialog_cancel_delete"
@clickMainBtn="onConfirmCancel()"
@clickAuxBtn="onConfirmDelete()"
@close="onCancel()"
@before-hide="onDialogBeforeHide()"
/>
<pn-small-dialog
v-model="showDialogRestoreTask"
icon="mdi-clipboard-play-outline"
color="green"
title="tasks__dialog_restore_title"
mainBtnLabel="tasks__dialog_restore_ok"
@clickMainBtn="onConfirmRestore()"
@close="onCancel()"
@before-hide="onDialogBeforeHide()"
/>
<pn-bottom-sheet-dialog
title="tasks__filters"
v-model="showFiltersDialog"
>
<template #btnSlot>
<div>
<q-btn
v-if="!checkFiltersSelect"
@click="resetFilters"
flat
no-caps
dense
color="grey-6"
>
{{ $t('tasks__filters_reset')}}
</q-btn>
</div>
</template>
<template #footer>
<q-btn
rounded
class="w100"
color="primary"
@click="showFiltersDialog = false"
>
{{$t('tasks__filters_continue')}}
</q-btn>
</template>
<div class="q-pl-sm text-bold text-caption">
{{ $t('tasks__filters_by_participant') }}
</div>
<div class="flex column">
<div
v-for="(item,idx) in taskParticipantsOptions"
:key="idx"
>
<q-checkbox
v-model="filters.byParticipants"
:val="item.value"
>
{{ $t(item.label) }}
</q-checkbox>
</div>
</div>
<div class="q-pl-sm q-mt-md text-bold text-caption">
{{ $t('tasks__filters_by_priority') }}
</div>
<div class="flex column">
<div
v-for="(item,idx) in taskPriorityOptions"
:key="idx"
>
<q-checkbox
v-model="filters.byPriority"
:val="item.value"
>
{{ $t(item.label) }}
</q-checkbox>
</div>
</div>
</pn-bottom-sheet-dialog>
</template>
<script setup lang="ts">
import { ref, computed, onActivated, onDeactivated, onBeforeUnmount } from 'vue'
import { useTasksStore } from 'stores/tasks'
import { useUsersStore } from 'stores/users'
import { useRouter } from 'vue-router'
import taskItem from 'components/taskItem.vue'
import type { Task } from 'types/Task'
import { date } from 'quasar'
const search = ref('')
const showCalendar = ref<boolean>(false)
const datesRange = ref<null | { from: string, to: string }>(null)
const deleteTaskId = ref<number | undefined>(undefined)
const restoreTaskId = ref<number | undefined>(undefined)
const showDialogDeleteTask = ref<boolean>(false)
const showDialogRestoreTask = ref<boolean>(false)
const showFiltersDialog = ref(false)
const showArchiveTasks = ref(false)
const currentSlideEvent = ref<SlideEvent | null>(null)
const closedByUserAction = ref(false)
const tasksStore = useTasksStore()
const usersStore = useUsersStore()
const router = useRouter()
interface SlideEvent {
reset: () => void
}
const tasks = tasksStore.getTasks
const tasksDates = computed(() => tasks.map(el => date.formatDate(el.plan_date * 1000, 'YYYY/MM/DD')))
const displayTasks = computed(() => {
return filteredTasks.value.filter(archiveTasks)
function archiveTasks (el: Task) {
if (showArchiveTasks.value) return true
return (
el.close_date < Date.now() - 7 * 24 * 60 * 60 * 1000 // показыать закрытые менее недели назад
)
}
})
const hiddenTask = computed(() => filteredTasks.value.length - displayTasks.value.length)
const filteredTasks = computed(() => {
return tasks
.filter(searchTasks)
.filter(checkDateInterval)
.filter(byParticipants)
.filter(byPriority)
function searchTasks (el: Task) {
if (!search.value || !(search.value && search.value.trim())) return true
const searchValue = search.value.trim().toLowerCase()
return (
el.name.toLowerCase().includes(searchValue) ||
el.description && el.description.toLowerCase().includes(searchValue)
)
}
function checkDateInterval (el: Task) {
if (!datesRange.value) return true
const from = date.extractDate(datesRange.value.from, 'YYYY/MM/DD').getTime()
const to = date.extractDate(datesRange.value.to, 'YYYY/MM/DD').getTime() + 86399999
return (from < el.plan_date * 1000) && ( to >= el.plan_date * 1000)
}
function byParticipants(task: Task): boolean {
const selected = filters.value.byParticipants
if (selected.length === 0) return true
const userId = usersStore.myId.id
return selected.some(opt => {
switch (opt) {
case 1: return task.assigned_to === userId
case 2: return task.created_by === userId
case 3: return task.observers.includes(userId)
case 4: return task.assigned_to !== userId
&& task.created_by !== userId
&& !task.observers.includes(userId)
default: return false
}
})
}
function byPriority (el: Task) {
if (filters.value.byPriority.length === 0) return true
return filters.value.byPriority.includes(el.priority)
}
})
interface Filters {
byParticipants: number[]
byPriority: number[]
}
const defaultFilters = {
byParticipants: [],
byPriority: []
}
const filters = ref<Filters>({ ...defaultFilters })
const checkFiltersSelect = computed(() => (
Object.values(filters.value).every(el => el.length === 0)
))
function resetFilters() {
(Object.keys(filters.value) as (keyof Filters)[]).forEach(key => filters.value[key] = [])
}
const taskParticipantsOptions = [
{ id: 1, value: 1, label: 'tasks__filters_to_me' },
{ id: 2, value: 2, label: 'tasks__filters_from_me' },
{ id: 3, value: 3, label: 'tasks__filters_observers' },
{ id: 4, value: 4, label: 'tasks__filters_not_involved' }
]
const taskPriorityOptions = [
{ id: 0, value: 0, label: 'tasks__filters_priority_normal' },
{ id: 1, value: 1, label: 'tasks__filters_priority_important' },
{ id: 2, value: 2, label: 'tasks__filters_priority_critical' }
]
async function goTask (taskId: number) {
await new Promise(resolve => setTimeout(resolve, 300))
await router.push({ name: 'task_info', params: { taskId }})
}
async function createTask () {
await router.push({ name: 'task_add'})
}
function handleSlideRight (event: SlideEvent, id: number) {
currentSlideEvent.value = event
showDialogDeleteTask.value = true
deleteTaskId.value = id
}
function handleSlideLeft (event: SlideEvent, id: number) {
currentSlideEvent.value = event
showDialogRestoreTask.value = true
restoreTaskId.value = id
}
function onDialogBeforeHide () {
if (!closedByUserAction.value) {
onCancel()
}
closedByUserAction.value = false
}
function onCancel() {
closedByUserAction.value = true
if (currentSlideEvent.value) {
currentSlideEvent.value.reset()
currentSlideEvent.value = null
}
}
async function onConfirmDelete() {
closedByUserAction.value = true
if (deleteTaskId.value) {
await tasksStore.remove(deleteTaskId.value)
}
currentSlideEvent.value = null
}
async function onConfirmCancel() {
closedByUserAction.value = true
if (deleteTaskId.value) {
await tasksStore.setCancelStatus(deleteTaskId.value)
}
currentSlideEvent.value = null
}
async function onConfirmRestore() {
closedByUserAction.value = true
if (restoreTaskId.value) {
await tasksStore.setRestoreStatus(restoreTaskId.value)
}
currentSlideEvent.value = null
}
// fix fab jumping
const showFab = ref(false)
const timerId = ref<ReturnType<typeof setTimeout> | null>(null)
onActivated(() => {
timerId.value = setTimeout(() => {
showFab.value = true
}, 300)
})
onDeactivated(() => {
showFab.value = false
if (timerId.value) {
clearTimeout(timerId.value)
timerId.value = null
}
})
onBeforeUnmount(() => {
if (timerId.value) clearTimeout(timerId.value)
})
</script>
<style scoped lang="scss">
/* fix mini border after slide */
:deep(.q-slide-item__right) {
align-self: center;
height: 98%;
}
:deep(.q-slide-item__left) {
align-self: center;
height: 98%;
}
.fix-calendar :deep(.q-date__view) {
padding-top: 0;
padding-bottom: 0;
min-height: auto;
}
.fix-calendar :deep(.q-date__calendar-days-container) {
min-height: auto;
}
</style>