Skip to content

Instantly share code, notes, and snippets.

@codenamezjames
Created June 29, 2020 16:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save codenamezjames/fea38f8b6bf68d69a7d2484a88e8c086 to your computer and use it in GitHub Desktop.
Save codenamezjames/fea38f8b6bf68d69a7d2484a88e8c086 to your computer and use it in GitHub Desktop.
quasar date range picker
<template>
<div>
<div class="row q-col-gutter-sm q-mb-md q-px-md q-pt-md">
<div class="col flex items-center q-gutter-sm">
<q-input
v-model="zFromUs"
label="From"
outlined
dense
mask="##-##-####"
style="width: 140px;"
>
<template #prepend>
<q-icon
:name="mdiCalendar"
color="primary"
/>
</template>
</q-input>
<q-btn
outline
color="transition-2"
:label="zStart"
>
<q-popup-proxy
transition-show="scale"
transition-hide="scale"
@before-show="zStartPicker = zStart"
>
<q-time
v-model="zStartPicker"
mask="hh:mm A"
>
<div class="row items-center justify-end q-gutter-sm">
<div class="col">
<q-btn
:icon="mdiClockTimeTwelveOutline"
flat
round
@click="zStartPicker = '12:00 AM'"
>
<q-tooltip>Set time to start of day</q-tooltip>
</q-btn>
</div>
<q-btn
v-close-popup
label="Cancel"
color="primary"
flat
/>
<q-btn
v-close-popup
label="OK"
color="primary"
flat
@click="saveTime('zStart')"
/>
</div>
</q-time>
</q-popup-proxy>
</q-btn>
<div><hr style="width: 10px"></div>
<!-- TODO: from and to should only be from past to future -->
<q-input
v-model="zToUs"
label="To"
outlined
dense
mask="##-##-####"
style="width: 140px;"
>
<template #prepend>
<q-icon
:name="mdiCalendar"
color="primary"
/>
</template>
</q-input>
<q-btn
outline
color="transition-2"
:label="zEnd"
>
<q-popup-proxy
transition-show="scale"
transition-hide="scale"
@before-show="zEndPicker = zEnd"
>
<q-time
v-model="zEndPicker"
mask="hh:mm A"
>
<div class="row items-center justify-end q-gutter-sm">
<div class="col">
<q-btn
:icon="mdiClockTimeElevenOutline"
flat
round
@click="zEndPicker = '11:59 PM'"
>
<q-tooltip>Set time to end of day</q-tooltip>
</q-btn>
</div>
<q-btn
v-close-popup
label="Cancel"
color="primary"
flat
/>
<q-btn
v-close-popup
label="OK"
color="primary"
flat
@click="saveTime('zEnd')"
/>
</div>
</q-time>
</q-popup-proxy>
</q-btn>
</div>
</div>
<q-separator />
<div class="row q-col-gutter-lg q-px-md">
<div class="col-auto">
<q-list
dense
class="q-my-md"
>
<q-item
clickable
:active="activeItemSelection === 'today'"
@click="setRange('today')"
>
<q-item-section>Today</q-item-section>
</q-item>
<q-item
clickable
:active="activeItemSelection === 'yesterday'"
@click="setRange('yesterday')"
>
<q-item-section>Yesterday</q-item-section>
</q-item>
<q-item
clickable
:active="activeItemSelection === 'thisWeek'"
@click="setRange('thisWeek')"
>
<q-item-section>This Week</q-item-section>
</q-item>
<q-item
clickable
:active="activeItemSelection === 'lastWeek'"
@click="setRange('lastWeek')"
>
<q-item-section>Last Week</q-item-section>
</q-item>
<q-item
clickable
:active="activeItemSelection === 'thisMonth'"
@click="setRange('thisMonth')"
>
<q-item-section>This Month</q-item-section>
</q-item>
<q-item
clickable
:active="activeItemSelection === 'lastMonth'"
@click="setRange('lastMonth')"
>
<q-item-section>Last Month</q-item-section>
</q-item>
<q-item
clickable
:active="activeItemSelection === 'thisYear'"
@click="setRange('thisYear')"
>
<q-item-section>This Year</q-item-section>
</q-item>
<q-item
clickable
:active="activeItemSelection === 'lastYear'"
@click="setRange('lastYear')"
>
<q-item-section>Last Year</q-item-section>
</q-item>
</q-list>
</div>
<div class="col-auto">
<q-separator
vertical
style="height: 100%"
/>
</div>
<div class="col column">
<div class="row q-col-gutter-md col q-pt-md q-mb-md">
<div class="col">
<month-year-chooser
:year.sync="cal1Year"
:month.sync="cal1Month"
class="q-mb-sm"
/>
<q-calendar
v-model="cal1"
view="month"
locale="en-us"
mini-mode
no-active-date
short-weekday-label
animated
:selected-start-end-dates="startEndDates"
:day-class="classDay"
@click:date="onClickDay"
/>
</div>
<div class="col-auto">
<q-separator
vertical
style="height: 100%"
/>
</div>
<div class="col">
<month-year-chooser
:year.sync="cal2Year"
:month.sync="cal2Month"
class="q-mb-sm"
/>
<q-calendar
v-model="cal2"
view="month"
locale="en-us"
mini-mode
no-active-date
short-weekday-label
animated
:selected-start-end-dates="startEndDates"
:day-class="classDay"
@click:date="onClickDay"
/>
</div>
</div>
<q-separator
class="q-mb-md"
style="margin-left: -24px; margin-right: -24px; width: calc(100% + 40px);"
/>
<div class="row items-center q-mb-md q-col-gutter-md">
<div class="col-auto">
Range: {{ range }}
</div>
<div class="col">
<slot />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mdiClose, mdiCalendar, mdiClockTimeTwelveOutline, mdiClockTimeElevenOutline } from '@quasar/extras/mdi-v5'
import dayjs from 'dayjs'
import { autoPluralize } from 'src/tools'
import isEqual from 'lodash/isEqual'
// import QCalendar from '@quasar/quasar-ui-qcalendar'
const todayFrom = dayjs()
const todayTo = dayjs()
export default {
components: {
monthYearChooser: () => import('src/modules/month-year-chooser/index.vue')
},
constants: {
mdiClose,
mdiCalendar,
mdiClockTimeTwelveOutline,
mdiClockTimeElevenOutline
},
props: {
value: {
default: () => [
dayjs().toISOString(),
dayjs().toISOString()
],
validator (input) {
if (input) return Array.isArray(input) && input.length === 2
return true
}
}
},
data () {
return {
zStartPicker: '00:00',
zEndPicker: '00:00',
zFrom: dayjs(this.value[0]).format('YYYY-MM-DD'),
zTo: dayjs(this.value[1]).format('YYYY-MM-DD'),
zStart: dayjs(this.value[0]).format('hh:mm A'),
zEnd: dayjs(this.value[1]).format('hh:mm A'),
clicked: false,
cal1: dayjs().subtract(1, 'month').format('YYYY-MM-DD'),
cal2: dayjs().format('YYYY-MM-DD'),
timeFrames: {
today: { from: todayFrom, to: todayTo },
yesterday: { from: todayFrom.add(-1, 'day'), to: todayTo.add(-1, 'day') },
thisWeek: { from: todayFrom.startOf('w'), to: todayTo },
lastWeek: { from: todayFrom.add(-1, 'w').startOf('w'), to: todayTo.add(-1, 'w').endOf('w') },
thisMonth: { from: todayFrom.startOf('M'), to: todayTo },
lastMonth: { from: todayFrom.add(-1, 'M').startOf('M'), to: todayTo.add(-1, 'M').endOf('M') },
thisYear: { from: todayFrom.startOf('y'), to: todayTo },
lastYear: { from: todayFrom.add(-1, 'y').startOf('y'), to: todayTo.add(-1, 'y').endOf('y') }
}
}
},
computed: {
activeItemSelection () {
const from = dayjs(this.zFrom, 'YYYY-MM-DD')
const to = dayjs(this.zTo, 'YYYY-MM-DD')
let active = ''
;['lastYear', 'thisYear', 'lastMonth', 'thisMonth', 'lastWeek', 'thisWeek', 'yesterday', 'today'].forEach((name) => {
if (to.isSame(this.timeFrames[name].to, 'date') && from.isSame(this.timeFrames[name].from, 'date')) active = name
})
return active
},
range () {
const days = Math.abs(dayjs(this.zFrom, 'YYYY-MM-DD').diff(this.zTo, 'day')) + 1
return days + autoPluralize(days, ' Days')
},
cal1Month: {
get () {
return (+dayjs(this.cal1, 'YYYY-MM-DD').format('MM')) - 1
},
set (month) {
const newDate = dayjs(this.cal1).month(month)
if (newDate.isValid()) this.cal1 = newDate.format('YYYY-MM-DD')
}
},
cal1Year: {
get () {
return dayjs(this.cal1, 'YYYY-MM-DD').format('YYYY')
},
set (year) {
const newDate = dayjs(this.cal1).year(year)
if (newDate.isValid()) this.cal1 = newDate.format('YYYY-MM-DD')
}
},
cal2Month: {
get () {
return (+dayjs(this.cal2, 'YYYY-MM-DD').format('MM')) - 1
},
set (month) {
const newDate = dayjs(this.cal2, 'YYYY-MM-DD').month(month)
if (newDate.isValid()) this.cal2 = newDate.format('YYYY-MM-DD')
}
},
cal2Year: {
get () {
return dayjs(this.cal2, 'YYYY-MM-DD').format('YYYY')
},
set (year) {
const newDate = dayjs(this.cal2, 'YYYY-MM-DD').year(year)
if (newDate.isValid()) this.cal2 = newDate.format('YYYY-MM-DD')
}
},
zFromUs: {
get () { return dayjs(this.zFrom, 'YYYY-MM-DD').format('MM-DD-YYYY') },
set (date) {
if (dayjs(date, 'MM-DD-YYYY').isValid()) this.zFrom = dayjs(date).format('YYYY-MM-DD')
this.cal1 = this.zFrom
if (dayjs(this.zFrom, 'YYYY-MM-DD').isSame(dayjs(this.zTo, 'YYYY-MM-DD'), 'M')) {
this.cal1 = dayjs(this.zFrom, 'YYYY-MM-DD').add(-1, 'M').format('YYYY-MM-DD')
}
}
},
zToUs: {
get () { return dayjs(this.zTo, 'YYYY-MM-DD').format('MM-DD-YYYY') },
set (date) {
if (dayjs(date, 'MM-DD-YYYY').isValid()) this.zTo = dayjs(date).format('YYYY-MM-DD')
this.cal2 = this.zTo
if (dayjs(this.zFrom, 'YYYY-MM-DD').isSame(dayjs(this.zTo, 'YYYY-MM-DD'), 'M')) {
this.cal1 = dayjs(this.zFrom, 'YYYY-MM-DD').add(-1, 'M').format('YYYY-MM-DD')
}
}
},
startEndDates () {
const dates = []
if (this.zFromUnix !== false && this.zToUnix !== false) {
if (this.zFromUnix <= this.zToUnix) {
dates.push(this.zFrom, this.zTo)
} else {
dates.push(this.zTo, this.zFrom)
}
}
return dates
},
zFromUnix () {
if (this.zFrom !== '') {
return dayjs(this.zFrom, 'YYYY-MM-DD').unix()
}
return false
},
zToUnix () {
if (this.zTo !== '') {
return dayjs(this.zTo, 'YYYY-MM-DD').unix()
}
return false
}
},
watch: {
startEndDates () { this.runUpdate() },
zStart () { this.runUpdate() },
zEnd () { this.runUpdate() },
value () {
if (isEqual(this.value.map(d => dayjs(d).format('YYYY-MM-DD')), this.startEndDates)) return
this.zFrom = dayjs(this.value[0]).format('YYYY-MM-DD')
this.zTo = dayjs(this.value[1]).format('YYYY-MM-DD')
}
},
created () {
this.cal1 = this.zFrom
this.cal2 = this.zTo
if (dayjs(this.zFrom, 'YYYY-MM-DD').isSame(dayjs(this.zTo, 'YYYY-MM-DD'), 'M')) {
this.cal1 = dayjs(this.zFrom, 'YYYY-MM-DD').add(-1, 'M').format('YYYY-MM-DD')
}
},
methods: {
runUpdate () {
const startEndISO = [
dayjs(`${this.startEndDates[0]} ${this.zStart}`, 'YYYY-MM-DD hh:mm A').toISOString(),
dayjs(`${this.startEndDates[1]} ${this.zEnd}`, 'YYYY-MM-DD hh:mm A').toISOString()
]
if (isEqual(this.value, startEndISO)) return
this.$emit('input', startEndISO)
},
saveTime (model) {
this[model] = this[`${model}Picker`]
},
classDay (timestamp) {
if (this.zFromUnix !== false && this.zToUnix !== false) {
return this.getBetween(timestamp)
}
},
getBetween ({ date }) {
const nowIdentifier = dayjs(date, 'YYYY-MM-DD').unix()
const lower = Math.min(this.zFromUnix, this.zToUnix)
const higher = Math.max(this.zFromUnix, this.zToUnix)
return {
'q-selected-day-first': lower === nowIdentifier,
'q-selected-day': lower <= nowIdentifier && higher >= nowIdentifier,
'q-selected-day-last': higher === nowIdentifier
}
},
onClickDay ({ date }) {
if (this.clicked === false) {
this.zFrom = date
this.zTo = date
this.clicked = true
return
}
// mouse is down, start selection and capture current
this.clicked = false
this.zTo = date
},
setRange (when) {
const doWhen = this.timeFrames[when]
this.zFrom = doWhen.from.format('YYYY-MM-DD')
this.zTo = doWhen.to.format('YYYY-MM-DD')
this.cal1 = this.zFrom
this.cal2 = this.zTo
if (dayjs(this.zFrom, 'YYYY-MM-DD').isSame(dayjs(this.zTo, 'YYYY-MM-DD'), 'M')) {
this.cal1 = dayjs(this.zFrom, 'YYYY-MM-DD').add(-1, 'M').format('YYYY-MM-DD')
}
}
}
}
</script>
<style lang="scss" scoped></style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment