before store
This commit is contained in:
@@ -11,8 +11,18 @@
|
||||
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
|
||||
@@ -27,11 +37,11 @@
|
||||
</q-input>
|
||||
|
||||
<q-btn
|
||||
@click="showFilters = true"
|
||||
@click="showFiltersDialog = true"
|
||||
icon="mdi-filter-outline"
|
||||
dense round flat
|
||||
size="lg"
|
||||
:color="showFilters ? 'primary' : 'grey'"
|
||||
:color="showFiltersDialog ? 'primary' : 'grey'"
|
||||
class="q-mr-xs"
|
||||
>
|
||||
<div>
|
||||
@@ -41,38 +51,69 @@
|
||||
floating
|
||||
transparent
|
||||
style="position: relative; top: -16px; margin-left: -12px"
|
||||
:style="{ opacity: selectedFilters.length !== 0 ? 0.8 : 0 }"
|
||||
: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="date"
|
||||
v-model="datesRange"
|
||||
range
|
||||
flat
|
||||
:events="tasksDates"
|
||||
event-color="brand"
|
||||
today-btn
|
||||
minimal
|
||||
dense
|
||||
/>
|
||||
</div>
|
||||
</q-slide-transition>
|
||||
</template>
|
||||
|
||||
<q-list bordered separator>
|
||||
<q-item
|
||||
v-for="item in displayTasks"
|
||||
:key="item.id"
|
||||
clickable v-ripple
|
||||
<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>
|
||||
<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"
|
||||
>
|
||||
<task-item :item/>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<div class="q-py-lg"/> <!-- fix hide scroll area by tabs -->
|
||||
<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>
|
||||
</pn-scroll-list>
|
||||
|
||||
<q-page-sticky
|
||||
position="bottom-right"
|
||||
:offset="[0, 18]"
|
||||
@@ -93,124 +134,225 @@
|
||||
</q-page-sticky>
|
||||
</div>
|
||||
|
||||
<q-dialog
|
||||
v-model="showFilters"
|
||||
position="bottom"
|
||||
<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"
|
||||
>
|
||||
<div class="w100 filter-panel top-rounded-card bg-white q-pa-md">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-h6">{{ $t('tasks__filters') }}</span>
|
||||
<q-btn
|
||||
@click="showFilters = false"
|
||||
flat dense round
|
||||
size="md"
|
||||
icon="close"
|
||||
/>
|
||||
<template #btnSlot>
|
||||
<div>
|
||||
<q-btn
|
||||
v-if="!checkFiltersSelect"
|
||||
@click="resetFilters"
|
||||
flat
|
||||
no-caps
|
||||
dense
|
||||
color="grey-6"
|
||||
>
|
||||
{{ $t('tasks__filters_reset')}}
|
||||
</q-btn>
|
||||
</div>
|
||||
<q-list class="w100">
|
||||
<template v-for="filter in filters" :key="filter.id">
|
||||
<q-item-label header v-if="filter.header">{{$t(filter.label)}}</q-item-label>
|
||||
<q-item v-else class="q-px-none">
|
||||
<q-item-section side>
|
||||
<q-checkbox
|
||||
v-model="selectedFilters"
|
||||
:val="filter.id"
|
||||
dense
|
||||
class="q-px-sm"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label class="flex items-center">
|
||||
<span>
|
||||
{{ $t(filter.label) }}
|
||||
</span>
|
||||
<pn-task-priority-icon :priority="filter.priority"/>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-list>
|
||||
</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>
|
||||
</q-dialog>
|
||||
|
||||
<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 date = ref(Date.now())
|
||||
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()
|
||||
|
||||
const tasks = computed(() => tasksStore.tasks)
|
||||
|
||||
// filter
|
||||
const showFilters = ref(false)
|
||||
const selectedFilters = ref([])
|
||||
|
||||
const filters = [
|
||||
{ id: 'taskType', label: 'tasks__filters_types', header: true },
|
||||
{ id: 'taskIn', label: 'tasks__filters_in' },
|
||||
{ id: 'taskOut', label: 'tasks__filters_out' },
|
||||
{ id: 'taskWatch', label: 'tasks__filters_watch' },
|
||||
{ id: 'taskPriority', label: 'tasks__filters_priority', header: true },
|
||||
{ id: 'taskPriorityNormal', label: 'tasks__filters_priority_normal' },
|
||||
{ id: 'taskPriorityImportant', label: 'tasks__filters_priority_important', priority: 1 },
|
||||
{ id: 'taskPriorityCritical', label: 'tasks__filters_priority_critical', priority: 2 }
|
||||
]
|
||||
|
||||
const displayTasks = computed(() => {
|
||||
let filteredTasks = [...tasks.value]
|
||||
|
||||
if (selectedFilters.value.length > 0) {
|
||||
|
||||
const filterFunctions = selectedFilters.value.map((filterId: string) => {
|
||||
switch(filterId) {
|
||||
case 'taskPriorityNormal':
|
||||
return (task: Task) => task.priority === 0 || !task.priority
|
||||
|
||||
case 'taskPriorityImportant':
|
||||
return (task: Task) => task.priority === 1
|
||||
|
||||
case 'taskPriorityCritical':
|
||||
return (task: Task) => task.priority === 2
|
||||
|
||||
/* case 'taskIn':
|
||||
return (task: Task) => task.type === 'in'
|
||||
|
||||
case 'taskOut':
|
||||
return (task: Task) => task.type === 'out'
|
||||
|
||||
case 'taskWatch':
|
||||
return (task: Task) => task.type === 'watch' */
|
||||
|
||||
default:
|
||||
return () => true
|
||||
}
|
||||
})
|
||||
|
||||
filteredTasks = filteredTasks.filter(task =>
|
||||
filterFunctions.every(filterFn => filterFn(task))
|
||||
)
|
||||
interface SlideEvent {
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
if (search.value?.trim()) {
|
||||
const searchValue = search.value.trim().toLowerCase()
|
||||
filteredTasks = filteredTasks.filter(task =>
|
||||
task.name.toLowerCase().includes(searchValue) ||
|
||||
(task.description && task.description.toLowerCase().includes(searchValue))
|
||||
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 // показыать закрытые менее недели назад
|
||||
)
|
||||
}
|
||||
|
||||
return filteredTasks
|
||||
})
|
||||
|
||||
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 }})
|
||||
}
|
||||
|
||||
@@ -218,6 +360,57 @@
|
||||
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)
|
||||
@@ -245,8 +438,12 @@
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* fix mini border after slide */
|
||||
:deep(.q-slide-item__right)
|
||||
{
|
||||
:deep(.q-slide-item__right) {
|
||||
align-self: center;
|
||||
height: 98%;
|
||||
}
|
||||
|
||||
:deep(.q-slide-item__left) {
|
||||
align-self: center;
|
||||
height: 98%;
|
||||
}
|
||||
@@ -260,10 +457,4 @@
|
||||
.fix-calendar :deep(.q-date__calendar-days-container) {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.filter-panel {
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user