feat: add date range filter to task filters
This commit is contained in:
parent
3a12be505d
commit
7aa2cfc8d4
6 changed files with 58 additions and 91 deletions
|
@ -6,7 +6,7 @@
|
||||||
>
|
>
|
||||||
{{ $t('filters.clear') }}
|
{{ $t('filters.clear') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
<popup>
|
<popup :has-overflow="true">
|
||||||
<template #trigger="{toggle}">
|
<template #trigger="{toggle}">
|
||||||
<x-button
|
<x-button
|
||||||
@click.prevent.stop="toggle()"
|
@click.prevent.stop="toggle()"
|
||||||
|
|
|
@ -67,49 +67,25 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ $t('task.attributes.dueDate') }}</label>
|
<label class="label">{{ $t('task.attributes.dueDate') }}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<flat-pickr
|
<datepicker-with-range :show-selected-on-button="true" @dateChanged="setDueDateFilter"/>
|
||||||
:config="flatPickerConfig"
|
|
||||||
@on-close="setDueDateFilter"
|
|
||||||
class="input"
|
|
||||||
:placeholder="$t('filters.attributes.dueDateRange')"
|
|
||||||
v-model="filters.dueDate"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ $t('task.attributes.startDate') }}</label>
|
<label class="label">{{ $t('task.attributes.startDate') }}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<flat-pickr
|
<datepicker-with-range :show-selected-on-button="true" @dateChanged="setStartDateFilter"/>
|
||||||
:config="flatPickerConfig"
|
|
||||||
@on-close="setStartDateFilter"
|
|
||||||
class="input"
|
|
||||||
:placeholder="$t('filters.attributes.startDateRange')"
|
|
||||||
v-model="filters.startDate"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ $t('task.attributes.endDate') }}</label>
|
<label class="label">{{ $t('task.attributes.endDate') }}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<flat-pickr
|
<datepicker-with-range :show-selected-on-button="true" @dateChanged="setEndDateFilter"/>
|
||||||
:config="flatPickerConfig"
|
|
||||||
@on-close="setEndDateFilter"
|
|
||||||
class="input"
|
|
||||||
:placeholder="$t('filters.attributes.endDateRange')"
|
|
||||||
v-model="filters.endDate"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ $t('task.attributes.reminders') }}</label>
|
<label class="label">{{ $t('task.attributes.reminders') }}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<flat-pickr
|
<datepicker-with-range :show-selected-on-button="true" @dateChanged="setReminderFilter"/>
|
||||||
:config="flatPickerConfig"
|
|
||||||
@on-close="setReminderFilter"
|
|
||||||
class="input"
|
|
||||||
:placeholder="$t('filters.attributes.reminderRange')"
|
|
||||||
v-model="filters.reminders"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -175,15 +151,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import DatepickerWithRange from '@/components/date/datepickerWithRange'
|
||||||
import Fancycheckbox from '../../input/fancycheckbox'
|
import Fancycheckbox from '../../input/fancycheckbox'
|
||||||
import flatPickr from 'vue-flatpickr-component'
|
|
||||||
import 'flatpickr/dist/flatpickr.css'
|
|
||||||
|
|
||||||
import {includesById} from '@/helpers/utils'
|
import {includesById} from '@/helpers/utils'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
import PrioritySelect from '@/components/tasks/partials/prioritySelect.vue'
|
import PrioritySelect from '@/components/tasks/partials/prioritySelect.vue'
|
||||||
import PercentDoneSelect from '@/components/tasks/partials/percentDoneSelect.vue'
|
import PercentDoneSelect from '@/components/tasks/partials/percentDoneSelect.vue'
|
||||||
import Multiselect from '@/components/input/multiselect.vue'
|
import Multiselect from '@/components/input/multiselect.vue'
|
||||||
|
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
|
||||||
|
|
||||||
import UserService from '@/services/user'
|
import UserService from '@/services/user'
|
||||||
import ListService from '@/services/list'
|
import ListService from '@/services/list'
|
||||||
|
@ -222,15 +197,15 @@ const DEFAULT_FILTERS = {
|
||||||
namespace: '',
|
namespace: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ALPHABETICAL_SORT = 'title'
|
export const ALPHABETICAL_SORT = 'title'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'filters',
|
name: 'filters',
|
||||||
components: {
|
components: {
|
||||||
|
DatepickerWithRange,
|
||||||
EditLabels,
|
EditLabels,
|
||||||
PrioritySelect,
|
PrioritySelect,
|
||||||
Fancycheckbox,
|
Fancycheckbox,
|
||||||
flatPickr,
|
|
||||||
PercentDoneSelect,
|
PercentDoneSelect,
|
||||||
Multiselect,
|
Multiselect,
|
||||||
},
|
},
|
||||||
|
@ -281,7 +256,7 @@ export default {
|
||||||
return this.params?.sort_by?.find(sortBy => sortBy === ALPHABETICAL_SORT) !== undefined
|
return this.params?.sort_by?.find(sortBy => sortBy === ALPHABETICAL_SORT) !== undefined
|
||||||
},
|
},
|
||||||
set(sortAlphabetically) {
|
set(sortAlphabetically) {
|
||||||
this.params.sort_by = sortAlphabetically
|
this.params.sort_by = sortAlphabetically
|
||||||
? [ALPHABETICAL_SORT]
|
? [ALPHABETICAL_SORT]
|
||||||
: getDefaultParams().sort_by
|
: getDefaultParams().sort_by
|
||||||
|
|
||||||
|
@ -291,19 +266,6 @@ export default {
|
||||||
foundLabels() {
|
foundLabels() {
|
||||||
return this.$store.getters['labels/filterLabelsByQuery'](this.labels, this.query)
|
return this.$store.getters['labels/filterLabelsByQuery'](this.labels, this.query)
|
||||||
},
|
},
|
||||||
flatPickerConfig() {
|
|
||||||
return {
|
|
||||||
altFormat: this.$t('date.altFormatLong'),
|
|
||||||
altInput: true,
|
|
||||||
dateFormat: 'Y-m-d H:i',
|
|
||||||
enableTime: true,
|
|
||||||
time_24hr: true,
|
|
||||||
mode: 'range',
|
|
||||||
locale: {
|
|
||||||
firstDayOfWeek: this.$store.state.auth.settings.weekStart,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
change() {
|
change() {
|
||||||
|
@ -343,19 +305,12 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setDateFilter(filterName, variableName = null) {
|
setDateFilter(filterName, {dateFrom, dateTo}) {
|
||||||
if (variableName === null) {
|
dateFrom = parseDateOrString(dateFrom, null)
|
||||||
variableName = filterName
|
dateTo = parseDateOrString(dateTo, null)
|
||||||
}
|
|
||||||
|
|
||||||
// Only filter if we have a start and end due date
|
// Only filter if we have a date
|
||||||
if (this.filters[variableName] !== '') {
|
if (dateFrom !== null && dateTo !== null) {
|
||||||
|
|
||||||
const parts = this.filters[variableName].split(' to ')
|
|
||||||
|
|
||||||
if (parts.length < 2) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we already have values in params and only update them if we do
|
// Check if we already have values in params and only update them if we do
|
||||||
let foundStart = false
|
let foundStart = false
|
||||||
|
@ -363,23 +318,23 @@ export default {
|
||||||
this.params.filter_by.forEach((f, i) => {
|
this.params.filter_by.forEach((f, i) => {
|
||||||
if (f === filterName && this.params.filter_comparator[i] === 'greater_equals') {
|
if (f === filterName && this.params.filter_comparator[i] === 'greater_equals') {
|
||||||
foundStart = true
|
foundStart = true
|
||||||
this.params.filter_value[i] = formatISO(new Date(parts[0]))
|
this.params.filter_value[i] = dateFrom
|
||||||
}
|
}
|
||||||
if (f === filterName && this.params.filter_comparator[i] === 'less_equals') {
|
if (f === filterName && this.params.filter_comparator[i] === 'less_equals') {
|
||||||
foundEnd = true
|
foundEnd = true
|
||||||
this.params.filter_value[i] = formatISO(new Date(parts[1]))
|
this.params.filter_value[i] = dateTo
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!foundStart) {
|
if (!foundStart) {
|
||||||
this.params.filter_by.push(filterName)
|
this.params.filter_by.push(filterName)
|
||||||
this.params.filter_comparator.push('greater_equals')
|
this.params.filter_comparator.push('greater_equals')
|
||||||
this.params.filter_value.push(formatISO(new Date(parts[0])))
|
this.params.filter_value.push(dateFrom)
|
||||||
}
|
}
|
||||||
if (!foundEnd) {
|
if (!foundEnd) {
|
||||||
this.params.filter_by.push(filterName)
|
this.params.filter_by.push(filterName)
|
||||||
this.params.filter_comparator.push('less_equals')
|
this.params.filter_comparator.push('less_equals')
|
||||||
this.params.filter_value.push(formatISO(new Date(parts[1])))
|
this.params.filter_value.push(dateTo)
|
||||||
}
|
}
|
||||||
this.change()
|
this.change()
|
||||||
return
|
return
|
||||||
|
@ -513,23 +468,23 @@ export default {
|
||||||
this.params.filter_concat = 'or'
|
this.params.filter_concat = 'or'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setDueDateFilter() {
|
|
||||||
this.setDateFilter('due_date', 'dueDate')
|
|
||||||
},
|
|
||||||
setPriority() {
|
setPriority() {
|
||||||
this.setSingleValueFilter('priority', 'priority', 'usePriority')
|
this.setSingleValueFilter('priority', 'priority', 'usePriority')
|
||||||
},
|
},
|
||||||
setStartDateFilter() {
|
|
||||||
this.setDateFilter('start_date', 'startDate')
|
|
||||||
},
|
|
||||||
setEndDateFilter() {
|
|
||||||
this.setDateFilter('end_date', 'endDate')
|
|
||||||
},
|
|
||||||
setPercentDoneFilter() {
|
setPercentDoneFilter() {
|
||||||
this.setSingleValueFilter('percent_done', 'percentDone', 'usePercentDone')
|
this.setSingleValueFilter('percent_done', 'percentDone', 'usePercentDone')
|
||||||
},
|
},
|
||||||
setReminderFilter() {
|
setDueDateFilter(values) {
|
||||||
this.setDateFilter('reminders')
|
this.setDateFilter('due_date', values)
|
||||||
|
},
|
||||||
|
setStartDateFilter(values) {
|
||||||
|
this.setDateFilter('start_date', values)
|
||||||
|
},
|
||||||
|
setEndDateFilter(values) {
|
||||||
|
this.setDateFilter('end_date', values)
|
||||||
|
},
|
||||||
|
setReminderFilter(values) {
|
||||||
|
this.setDateFilter('reminders', values)
|
||||||
},
|
},
|
||||||
clear(kind) {
|
clear(kind) {
|
||||||
this[`found${kind}`] = []
|
this[`found${kind}`] = []
|
||||||
|
@ -618,4 +573,8 @@ export default {
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.datepicker-with-range-container .popup {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<slot name="trigger" :isOpen="open" :toggle="toggle"></slot>
|
<slot name="trigger" :isOpen="open" :toggle="toggle"></slot>
|
||||||
<div class="popup" :class="{'is-open': open}" ref="popup">
|
<div class="popup" :class="{'is-open': open, 'has-overflow': props.hasOverflow}" ref="popup">
|
||||||
<slot name="content" :isOpen="open"/>
|
<slot name="content" :isOpen="open"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -16,6 +16,13 @@ const toggle = () => {
|
||||||
open.value = !open.value
|
open.value = !open.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
hasOverflow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function hidePopup(e) {
|
function hidePopup(e) {
|
||||||
if (!open.value) {
|
if (!open.value) {
|
||||||
return
|
return
|
||||||
|
|
12
src/helpers/time/parseDateOrString.ts
Normal file
12
src/helpers/time/parseDateOrString.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
export function parseDateOrString(rawValue: string, fallback: any) {
|
||||||
|
if (typeof rawValue === 'undefined') {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = new Date(rawValue)
|
||||||
|
|
||||||
|
// @ts-ignore if rawValue is an invalid date, isNan will return false.
|
||||||
|
return !isNaN(d)
|
||||||
|
? d
|
||||||
|
: rawValue
|
||||||
|
}
|
|
@ -65,7 +65,7 @@ h6 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.has-overflow {
|
.has-overflow {
|
||||||
overflow: visible;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.has-horizontal-overflow {
|
.has-horizontal-overflow {
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
<script>
|
<script>
|
||||||
import {dateRanges} from '@/components/date/dateRanges'
|
import {dateRanges} from '@/components/date/dateRanges'
|
||||||
import SingleTaskInList from '@/components/tasks/partials/singleTaskInList'
|
import SingleTaskInList from '@/components/tasks/partials/singleTaskInList'
|
||||||
|
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
|
||||||
import {mapState} from 'vuex'
|
import {mapState} from 'vuex'
|
||||||
|
|
||||||
import Fancycheckbox from '@/components/input/fancycheckbox'
|
import Fancycheckbox from '@/components/input/fancycheckbox'
|
||||||
|
@ -48,18 +49,6 @@ import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
|
||||||
import LlamaCool from '@/assets/llama-cool.svg?component'
|
import LlamaCool from '@/assets/llama-cool.svg?component'
|
||||||
import DatepickerWithRange from '@/components/date/datepickerWithRange'
|
import DatepickerWithRange from '@/components/date/datepickerWithRange'
|
||||||
|
|
||||||
function parseDate(query, fallback) {
|
|
||||||
if (typeof query === 'undefined') {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
const d = new Date(query)
|
|
||||||
|
|
||||||
return !isNaN(d)
|
|
||||||
? d
|
|
||||||
: query
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNextWeekDate() {
|
function getNextWeekDate() {
|
||||||
return new Date((new Date()).getTime() + 7 * 24 * 60 * 60 * 1000)
|
return new Date((new Date()).getTime() + 7 * 24 * 60 * 60 * 1000)
|
||||||
}
|
}
|
||||||
|
@ -95,10 +84,10 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
dateFrom() {
|
dateFrom() {
|
||||||
return parseDate(this.$route.query.from, new Date())
|
return parseDateOrString(this.$route.query.from, new Date())
|
||||||
},
|
},
|
||||||
dateTo() {
|
dateTo() {
|
||||||
return parseDate(this.$route.query.to, getNextWeekDate())
|
return parseDateOrString(this.$route.query.to, getNextWeekDate())
|
||||||
},
|
},
|
||||||
showNulls() {
|
showNulls() {
|
||||||
return this.$route.query.showNulls === 'true'
|
return this.$route.query.showNulls === 'true'
|
||||||
|
|
Loading…
Reference in a new issue