263 lines
6.9 KiB
Vue
263 lines
6.9 KiB
Vue
<template>
|
|
<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>
|
|
|
|
<div class="flatpickr-container">
|
|
<flat-pickr
|
|
ref="flatPickrRef"
|
|
v-model="flatPickrDate"
|
|
:config="flatPickerConfig"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import {computed, onBeforeUnmount, onMounted, type PropType, ref, toRef, watch} from 'vue'
|
|
import flatPickr from 'vue-flatpickr-component'
|
|
import 'flatpickr/dist/flatpickr.css'
|
|
|
|
import BaseButton from '@/components/base/BaseButton.vue'
|
|
|
|
import {formatDate} from '@/helpers/time/formatDate'
|
|
import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
|
|
import {calculateNearestHours} from '@/helpers/time/calculateNearestHours'
|
|
import {createDateFromString} from '@/helpers/time/createDateFromString'
|
|
import {useI18n} from 'vue-i18n'
|
|
import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
|
|
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: [Date, null, String] as PropType<Date | null | string>,
|
|
validator: prop => prop instanceof Date || prop === null || typeof prop === 'string',
|
|
default: null,
|
|
},
|
|
})
|
|
|
|
const emit = defineEmits(['update:modelValue', 'close-on-change'])
|
|
|
|
const {t} = useI18n({useScope: 'global'})
|
|
|
|
const date = ref<Date | null>()
|
|
const changed = ref(false)
|
|
|
|
const modelValue = toRef(props, 'modelValue')
|
|
watch(
|
|
modelValue,
|
|
setDateValue,
|
|
{immediate: true},
|
|
)
|
|
|
|
const flatPickrRef = ref<HTMLElement | null>(null)
|
|
const flatPickerConfig = computed(() => ({
|
|
altFormat: t('date.altFormatLong'),
|
|
altInput: true,
|
|
dateFormat: 'Y-m-d H:i',
|
|
enableTime: true,
|
|
time_24hr: true,
|
|
inline: true,
|
|
locale: getFlatpickrLanguage(),
|
|
}))
|
|
|
|
// 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.
|
|
const flatPickrDate = computed({
|
|
set(newValue: string | Date | null) {
|
|
if (newValue === null) {
|
|
date.value = null
|
|
return
|
|
}
|
|
|
|
if (date.value !== null) {
|
|
const oldDate = formatDate(date.value, 'yyy-LL-dd H:mm')
|
|
if (oldDate === newValue) {
|
|
return
|
|
}
|
|
}
|
|
date.value = createDateFromString(newValue)
|
|
updateData()
|
|
},
|
|
get() {
|
|
if (!date.value) {
|
|
return ''
|
|
}
|
|
|
|
return formatDate(date.value, 'yyy-LL-dd H:mm')
|
|
},
|
|
})
|
|
|
|
onMounted(() => {
|
|
const inputs = flatPickrRef.value?.$el.parentNode.querySelectorAll('.numInputWrapper > input.numInput')
|
|
inputs.forEach(i => {
|
|
i.addEventListener('input', handleFlatpickrInput)
|
|
})
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
const inputs = flatPickrRef.value?.$el.parentNode.querySelectorAll('.numInputWrapper > input.numInput')
|
|
inputs.forEach(i => {
|
|
i.removeEventListener('input', handleFlatpickrInput)
|
|
})
|
|
})
|
|
|
|
// Flatpickr only returns a change event when the value in the input it's referring to changes.
|
|
// That means it will usually only trigger when the focus is moved out of the input field.
|
|
// This is fine most of the time. However, since we're displaying flatpickr in a popup,
|
|
// the whole html dom instance might get destroyed, before the change event had a
|
|
// chance to fire. In that case, it would not update the date value. To fix
|
|
// this, we're now listening on every change and bubble them up as soon
|
|
// as they happen.
|
|
function handleFlatpickrInput(e) {
|
|
const newDate = new Date(date?.value || 'now')
|
|
if (e.target.classList.contains('flatpickr-minute')) {
|
|
newDate.setMinutes(e.target.value)
|
|
}
|
|
if (e.target.classList.contains('flatpickr-hour')) {
|
|
newDate.setHours(e.target.value)
|
|
}
|
|
if (e.target.classList.contains('cur-year')) {
|
|
newDate.setFullYear(e.target.value)
|
|
}
|
|
flatPickrDate.value = newDate
|
|
}
|
|
|
|
|
|
function setDateValue(dateString: string | Date | null) {
|
|
if (dateString === null) {
|
|
date.value = null
|
|
return
|
|
}
|
|
date.value = createDateFromString(dateString)
|
|
}
|
|
|
|
function updateData() {
|
|
changed.value = true
|
|
emit('update:modelValue', date.value)
|
|
}
|
|
|
|
function setDate(dateString: string) {
|
|
const interval = calculateDayInterval(dateString)
|
|
const newDate = new Date()
|
|
newDate.setDate(newDate.getDate() + interval)
|
|
newDate.setHours(calculateNearestHours(newDate))
|
|
newDate.setMinutes(0)
|
|
newDate.setSeconds(0)
|
|
date.value = newDate
|
|
updateData()
|
|
}
|
|
|
|
function getWeekdayFromStringInterval(dateString: string) {
|
|
const interval = calculateDayInterval(dateString)
|
|
const newDate = new Date()
|
|
newDate.setDate(newDate.getDate() + interval)
|
|
return formatDate(newDate, 'E')
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.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;
|
|
}
|
|
}
|
|
|
|
.flatpickr-container {
|
|
:deep(.flatpickr-calendar) {
|
|
margin: 0 auto 8px;
|
|
box-shadow: none;
|
|
}
|
|
|
|
:deep(.input) {
|
|
border: none;
|
|
}
|
|
}
|
|
</style>
|