270 lines
7.1 KiB
Vue
270 lines
7.1 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"
|
|
/>
|
|
|
|
<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="showFilters = true"
|
|
icon="mdi-filter-outline"
|
|
dense round flat
|
|
size="lg"
|
|
:color="showFilters ? 'primary' : 'grey'"
|
|
class="q-mr-xs"
|
|
>
|
|
<div>
|
|
<q-badge
|
|
color="red"
|
|
rounded
|
|
floating
|
|
transparent
|
|
style="position: relative; top: -16px; margin-left: -12px"
|
|
:style="{ opacity: selectedFilters.length !== 0 ? 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"
|
|
minimal
|
|
dense
|
|
/>
|
|
</div>
|
|
</q-slide-transition>
|
|
</template>
|
|
|
|
<q-list bordered separator>
|
|
<q-item
|
|
v-for="item in displayTasks"
|
|
:key="item.id"
|
|
clickable v-ripple
|
|
@click="goTask(item.id)"
|
|
>
|
|
<task-item :item/>
|
|
</q-item>
|
|
</q-list>
|
|
<div class="q-py-lg"/> <!-- fix hide scroll area by tabs -->
|
|
</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>
|
|
|
|
<q-dialog
|
|
v-model="showFilters"
|
|
position="bottom"
|
|
>
|
|
<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"
|
|
/>
|
|
</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>
|
|
</div>
|
|
</q-dialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onActivated, onDeactivated, onBeforeUnmount } from 'vue'
|
|
import { useTasksStore } from 'stores/tasks'
|
|
import { useRouter } from 'vue-router'
|
|
import taskItem from 'components/taskItem.vue'
|
|
import type { Task } from 'types/Task'
|
|
|
|
const search = ref('')
|
|
const showCalendar = ref<boolean>(false)
|
|
const date = ref(Date.now())
|
|
const tasksStore = useTasksStore()
|
|
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))
|
|
)
|
|
}
|
|
|
|
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))
|
|
)
|
|
}
|
|
|
|
return filteredTasks
|
|
})
|
|
|
|
async function goTask (taskId: number) {
|
|
await router.push({ name: 'task_info', params: { taskId }})
|
|
}
|
|
|
|
async function createTask () {
|
|
await router.push({ name: 'task_add'})
|
|
}
|
|
|
|
// 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%;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.filter-panel {
|
|
max-height: 70vh;
|
|
overflow: auto;
|
|
}
|
|
|
|
</style>
|