v1
This commit is contained in:
269
src/pages/main/TasksPage.vue
Normal file
269
src/pages/main/TasksPage.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user