feat: add date math for filters #1342

Merged
konrad merged 88 commits from feature/date-math into main 2022-03-28 17:30:43 +00:00
6 changed files with 58 additions and 91 deletions
Showing only changes of commit 7aa2cfc8d4 - Show all commits

View File

@ -6,7 +6,7 @@
>
{{ $t('filters.clear') }}
</x-button>
<popup>
<popup :has-overflow="true">
<template #trigger="{toggle}">
<x-button
@click.prevent.stop="toggle()"

View File

@ -67,49 +67,25 @@
<div class="field">
<label class="label">{{ $t('task.attributes.dueDate') }}</label>
<div class="control">
<flat-pickr
:config="flatPickerConfig"
@on-close="setDueDateFilter"
class="input"
:placeholder="$t('filters.attributes.dueDateRange')"
v-model="filters.dueDate"
/>
<datepicker-with-range :show-selected-on-button="true" @dateChanged="setDueDateFilter"/>
</div>
</div>
<div class="field">
<label class="label">{{ $t('task.attributes.startDate') }}</label>
<div class="control">
<flat-pickr
:config="flatPickerConfig"
@on-close="setStartDateFilter"
class="input"
:placeholder="$t('filters.attributes.startDateRange')"
v-model="filters.startDate"
/>
<datepicker-with-range :show-selected-on-button="true" @dateChanged="setStartDateFilter"/>
</div>
</div>
<div class="field">
<label class="label">{{ $t('task.attributes.endDate') }}</label>
<div class="control">
<flat-pickr
:config="flatPickerConfig"
@on-close="setEndDateFilter"
class="input"
:placeholder="$t('filters.attributes.endDateRange')"
v-model="filters.endDate"
/>
<datepicker-with-range :show-selected-on-button="true" @dateChanged="setEndDateFilter"/>
</div>
</div>
<div class="field">
<label class="label">{{ $t('task.attributes.reminders') }}</label>
<div class="control">
<flat-pickr
:config="flatPickerConfig"
@on-close="setReminderFilter"
class="input"
:placeholder="$t('filters.attributes.reminderRange')"
v-model="filters.reminders"
/>
<datepicker-with-range :show-selected-on-button="true" @dateChanged="setReminderFilter"/>
</div>
</div>
@ -175,15 +151,14 @@
</template>
<script>
konrad marked this conversation as resolved Outdated

Use script setup and ts

Use script setup and ts

This component's js is ~450 lines, I'm not sure this PR is a good place to refactor it.

This component's js is ~450 lines, I'm not sure this PR is a good place to refactor it.

If we don't start converting when we touch the component, then when will we?

As a compromise we could try to add lang="ts" on these occasions and add everything new inside a new setup function =)

If we don't start converting when we touch the component, then when will we? As a compromise we could try to add `lang="ts"` on these occasions and add everything new inside a new setup function =)

If we don't start converting when we touch the component, then when will we?

yeah well I can't say anything against that :) I keep saying myself "yeah we'll do that one day" but I think we need to put in the effort.

> If we don't start converting when we touch the component, then when will we? yeah well I can't say anything against that :) I keep saying myself "yeah we'll do that one day" but I think we need to put in the effort.
import DatepickerWithRange from '@/components/date/datepickerWithRange'
import Fancycheckbox from '../../input/fancycheckbox'
import flatPickr from 'vue-flatpickr-component'
import 'flatpickr/dist/flatpickr.css'
import {includesById} from '@/helpers/utils'
import {formatISO} from 'date-fns'
import PrioritySelect from '@/components/tasks/partials/prioritySelect.vue'
import PercentDoneSelect from '@/components/tasks/partials/percentDoneSelect.vue'
import Multiselect from '@/components/input/multiselect.vue'
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
import UserService from '@/services/user'
import ListService from '@/services/list'
@ -222,15 +197,15 @@ const DEFAULT_FILTERS = {
namespace: '',
}
export const ALPHABETICAL_SORT = 'title'
export const ALPHABETICAL_SORT = 'title'
export default {
name: 'filters',
components: {
DatepickerWithRange,
EditLabels,
PrioritySelect,
Fancycheckbox,
flatPickr,
PercentDoneSelect,
Multiselect,
},
@ -281,7 +256,7 @@ export default {
return this.params?.sort_by?.find(sortBy => sortBy === ALPHABETICAL_SORT) !== undefined
},
set(sortAlphabetically) {
this.params.sort_by = sortAlphabetically
this.params.sort_by = sortAlphabetically
? [ALPHABETICAL_SORT]
: getDefaultParams().sort_by
@ -291,19 +266,6 @@ export default {
foundLabels() {
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: {
change() {
@ -343,19 +305,12 @@ export default {
}
}
},
setDateFilter(filterName, variableName = null) {
if (variableName === null) {
variableName = filterName
}
setDateFilter(filterName, {dateFrom, dateTo}) {
dateFrom = parseDateOrString(dateFrom, null)
dateTo = parseDateOrString(dateTo, null)
// Only filter if we have a start and end due date
if (this.filters[variableName] !== '') {
const parts = this.filters[variableName].split(' to ')
if (parts.length < 2) {
return
}
// Only filter if we have a date
if (dateFrom !== null && dateTo !== null) {
// Check if we already have values in params and only update them if we do
let foundStart = false
@ -363,23 +318,23 @@ export default {
this.params.filter_by.forEach((f, i) => {
if (f === filterName && this.params.filter_comparator[i] === 'greater_equals') {
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') {
foundEnd = true
this.params.filter_value[i] = formatISO(new Date(parts[1]))
this.params.filter_value[i] = dateTo
}
})
if (!foundStart) {
this.params.filter_by.push(filterName)
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) {
this.params.filter_by.push(filterName)
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()
return
@ -513,23 +468,23 @@ export default {
this.params.filter_concat = 'or'
}
},
setDueDateFilter() {
this.setDateFilter('due_date', 'dueDate')
},
setPriority() {
this.setSingleValueFilter('priority', 'priority', 'usePriority')
},
setStartDateFilter() {
this.setDateFilter('start_date', 'startDate')
},
setEndDateFilter() {
this.setDateFilter('end_date', 'endDate')
},
setPercentDoneFilter() {
this.setSingleValueFilter('percent_done', 'percentDone', 'usePercentDone')
},
setReminderFilter() {
this.setDateFilter('reminders')
setDueDateFilter(values) {
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) {
this[`found${kind}`] = []
@ -618,4 +573,8 @@ export default {
margin-left: .5rem;
}
}
.datepicker-with-range-container .popup {
right: 0;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<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"/>
</div>
</template>
@ -16,6 +16,13 @@ const toggle = () => {
open.value = !open.value
}
const props = defineProps({
hasOverflow: {
type: Boolean,
default: false,
}
})
function hidePopup(e) {
if (!open.value) {
return

View File

@ -0,0 +1,12 @@
export function parseDateOrString(rawValue: string, fallback: any) {
konrad marked this conversation as resolved Outdated

Maybe we should just move this to the emits section of the date range component? To avoid callees requiring to import it every time?

Maybe we should just move this to the emits section of the date range component? To avoid callees requiring to import it every time?

Yes that makes sense!

Yes that makes sense!

Since we're now using route props where we need this function I think we'll have to leave it as is.

Since we're now using route props where we need this function I think we'll have to leave it as is.
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 {
overflow: visible;
overflow: visible !important;
}
.has-horizontal-overflow {

View File

@ -40,6 +40,7 @@
<script>
import {dateRanges} from '@/components/date/dateRanges'
import SingleTaskInList from '@/components/tasks/partials/singleTaskInList'
import {parseDateOrString} from '@/helpers/time/parseDateOrString'
import {mapState} from 'vuex'
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 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() {
return new Date((new Date()).getTime() + 7 * 24 * 60 * 60 * 1000)
}
@ -95,10 +84,10 @@ export default {
},
computed: {
dateFrom() {
return parseDate(this.$route.query.from, new Date())
return parseDateOrString(this.$route.query.from, new Date())
konrad marked this conversation as resolved Outdated

get value from props to remove dependency on route. Removing dependency from router makes the components easier reusable nested inside another view (which is what we do).

get value from props to remove dependency on route. Removing dependency from router makes the components easier reusable nested inside another view (which is what we do).
},
dateTo() {
return parseDate(this.$route.query.to, getNextWeekDate())
return parseDateOrString(this.$route.query.to, getNextWeekDate())
},
showNulls() {
return this.$route.query.showNulls === 'true'