feat: add date range filter to task filters

This commit is contained in:
kolaente 2022-01-09 17:27:28 +01:00
parent ac0182cbc2
commit d4f4ebb66b
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
6 changed files with 58 additions and 91 deletions

View File

@ -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()"

View File

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

View File

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

View 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
}

View File

@ -65,7 +65,7 @@ h6 {
} }
.has-overflow { .has-overflow {
overflow: visible; overflow: visible !important;
} }
.has-horizontal-overflow { .has-horizontal-overflow {

View File

@ -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
}
export default { export default {
name: 'ShowTasks', name: 'ShowTasks',
components: { components: {
@ -93,10 +82,10 @@ export default {
}, },
computed: { computed: {
dateFrom() { dateFrom() {
return parseDate(this.$route.query.from, this.startDate) return parseDateOrString(this.$route.query.from, this.startDate)
}, },
dateTo() { dateTo() {
return parseDate(this.$route.query.to, this.endDate) return parseDateOrString(this.$route.query.to, this.endDate)
}, },
showNulls() { showNulls() {
return this.$route.query.showNulls === 'true' return this.$route.query.showNulls === 'true'