<template> <div class="datepicker"> <BaseButton @click.stop="toggleDatePopup" class="show" :disabled="disabled || undefined"> {{ date === null ? chooseDateLabel : formatDateShort(date) }} </BaseButton> <transition name="fade"> <div v-if="show" class="datepicker-popup" ref="datepickerPopup"> <BaseButton v-if="(new Date()).getHours() < 21" class="datepicker__quick-select-date" @click.stop="setDate('today')" > <span class="icon"><icon :icon="['far', 'calendar-alt']"/></span> <span class="text"> <span>{{ $t('input.datepicker.today') }}</span> <span class="weekday">{{ getWeekdayFromStringInterval('today') }}</span> </span> </BaseButton> <BaseButton class="datepicker__quick-select-date" @click.stop="setDate('tomorrow')" > <span class="icon"><icon :icon="['far', 'sun']"/></span> <span class="text"> <span>{{ $t('input.datepicker.tomorrow') }}</span> <span class="weekday">{{ getWeekdayFromStringInterval('tomorrow') }}</span> </span> </BaseButton> <BaseButton class="datepicker__quick-select-date" @click.stop="setDate('nextMonday')" > <span class="icon"><icon icon="coffee"/></span> <span class="text"> <span>{{ $t('input.datepicker.nextMonday') }}</span> <span class="weekday">{{ getWeekdayFromStringInterval('nextMonday') }}</span> </span> </BaseButton> <BaseButton class="datepicker__quick-select-date" @click.stop="setDate('thisWeekend')" > <span class="icon"><icon icon="cocktail"/></span> <span class="text"> <span>{{ $t('input.datepicker.thisWeekend') }}</span> <span class="weekday">{{ getWeekdayFromStringInterval('thisWeekend') }}</span> </span> </BaseButton> <BaseButton class="datepicker__quick-select-date" @click.stop="setDate('laterThisWeek')" > <span class="icon"><icon icon="chess-knight"/></span> <span class="text"> <span>{{ $t('input.datepicker.laterThisWeek') }}</span> <span class="weekday">{{ getWeekdayFromStringInterval('laterThisWeek') }}</span> </span> </BaseButton> <BaseButton class="datepicker__quick-select-date" @click.stop="setDate('nextWeek')" > <span class="icon"><icon icon="forward"/></span> <span class="text"> <span>{{ $t('input.datepicker.nextWeek') }}</span> <span class="weekday">{{ getWeekdayFromStringInterval('nextWeek') }}</span> </span> </BaseButton> <flat-pickr :config="flatPickerConfig" class="input" v-model="flatPickrDate" /> <x-button class="datepicker__close-button is-fullwidth" :shadow="false" @click="close" v-cy="'closeDatepicker'" > {{ $t('misc.confirm') }} </x-button> </div> </transition> </div> </template> <script lang="ts"> import {defineComponent} from 'vue' import flatPickr from 'vue-flatpickr-component' import 'flatpickr/dist/flatpickr.css' import {i18n} from '@/i18n' import BaseButton from '@/components/base/BaseButton.vue' import {format} from 'date-fns' import {calculateDayInterval} from '@/helpers/time/calculateDayInterval' import {calculateNearestHours} from '@/helpers/time/calculateNearestHours' import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside' import {createDateFromString} from '@/helpers/time/createDateFromString' export default defineComponent({ name: 'datepicker', data() { return { date: null, show: false, changed: false, } }, components: { flatPickr, BaseButton, }, props: { modelValue: { validator: prop => prop instanceof Date || prop === null || typeof prop === 'string', }, chooseDateLabel: { type: String, default() { return i18n.global.t('input.datepicker.chooseDate') }, }, disabled: { type: Boolean, default: false, }, }, emits: ['update:modelValue', 'change', 'close', 'close-on-change'], mounted() { document.addEventListener('click', this.hideDatePopup) }, beforeUnmount() { document.removeEventListener('click', this.hideDatePopup) }, watch: { modelValue: { handler: 'setDateValue', immediate: true, }, }, computed: { flatPickerConfig() { return { altFormat: this.$t('date.altFormatLong'), altInput: true, dateFormat: 'Y-m-d H:i', enableTime: true, time_24hr: true, inline: true, locale: { firstDayOfWeek: this.$store.state.auth.settings.weekStart, }, } }, // Since flatpickr dates are strings, we need to convert them to native date objects. // To make that work, we need a separate variable since flatpickr does not have a change event. flatPickrDate: { set(newValue) { this.date = createDateFromString(newValue) this.updateData() }, get() { if (!this.date) { return '' } return format(this.date, 'yyy-LL-dd H:mm') }, }, }, methods: { setDateValue(newVal) { if (newVal === null) { this.date = null return } this.date = createDateFromString(newVal) }, updateData() { this.changed = true this.$emit('update:modelValue', this.date) this.$emit('change', this.date) }, toggleDatePopup() { if (this.disabled) { return } this.show = !this.show }, hideDatePopup(e) { if (this.show) { closeWhenClickedOutside(e, this.$refs.datepickerPopup, this.close) } }, close() { // Kind of dirty, but the timeout allows us to enter a time and click on "confirm" without // having to click on another input field before it is actually used. setTimeout(() => { this.show = false this.$emit('close', this.changed) if (this.changed) { this.changed = false this.$emit('close-on-change', this.changed) } }, 200) }, setDate(date) { if (this.date === null) { this.date = new Date() } const interval = calculateDayInterval(date) const newDate = new Date() newDate.setDate(newDate.getDate() + interval) newDate.setHours(calculateNearestHours(newDate)) newDate.setMinutes(0) newDate.setSeconds(0) this.date = newDate this.flatPickrDate = newDate this.updateData() }, getDayIntervalFromString(date) { return calculateDayInterval(date) }, getWeekdayFromStringInterval(date) { const interval = calculateDayInterval(date) const newDate = new Date() newDate.setDate(newDate.getDate() + interval) return format(newDate, 'E') }, }, }) </script> <style lang="scss" scoped> .datepicker { input.input { display: none; } } .datepicker-popup { position: absolute; z-index: 99; width: 320px; background: var(--white); border-radius: $radius; box-shadow: $shadow; @media screen and (max-width: ($tablet)) { width: calc(100vw - 5rem); } } .datepicker__quick-select-date { display: flex; align-items: center; padding: 0 .5rem; width: 100%; height: 2.25rem; color: var(--text); transition: all $transition; &:first-child { border-radius: $radius $radius 0 0; } &:hover { background: var(--grey-100); } .text { width: 100%; font-size: .85rem; display: flex; justify-content: space-between; padding-right: .25rem; .weekday { color: var(--text-light); text-transform: capitalize; } } .icon { width: 2rem; text-align: center; } } .datepicker__close-button { margin: 1rem; width: calc(100% - 2rem); } :deep(.flatpickr-calendar) { margin: 0 auto 8px; box-shadow: none; } </style>