before store

This commit is contained in:
2025-06-26 11:06:48 +03:00
parent 1c732e16dd
commit 34baeb40e3
59 changed files with 3180 additions and 2149 deletions

View File

@@ -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>