Files
tgCrewUser/src/pages/main/TasksPage.vue
2025-06-05 20:00:58 +03:00

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>