217 lines
4.5 KiB
Vue
217 lines
4.5 KiB
Vue
<template>
|
|
<div :class="cn.b()">
|
|
<div :class="cn.e('header')">
|
|
<UiButton
|
|
icon="chevron-left"
|
|
size="small"
|
|
type="ghost"
|
|
color="secondary"
|
|
@click="previousMonth"
|
|
/>
|
|
<p :class="cn.e('title')">
|
|
{{ currentMonth }}
|
|
</p>
|
|
<UiButton
|
|
icon="chevron-right"
|
|
size="small"
|
|
type="ghost"
|
|
color="secondary"
|
|
@click="nextMonth"
|
|
/>
|
|
</div>
|
|
<div :class="cn.e('week-days')">
|
|
<div
|
|
v-for="dayOfWeek in daysOfWeek"
|
|
:key="dayOfWeek"
|
|
>
|
|
{{ dayOfWeek }}
|
|
</div>
|
|
</div>
|
|
<div :class="cn.e('days')">
|
|
<div
|
|
v-for="day in monthDays"
|
|
:key="day.date"
|
|
:class="getDayClasses(day)"
|
|
@click="selectDay(day)"
|
|
>
|
|
{{ day.day }}
|
|
</div>
|
|
</div>
|
|
<UiButton
|
|
type="outlined"
|
|
color="secondary"
|
|
:class="cn.e('apply-button')"
|
|
@click="apply"
|
|
>
|
|
{{ $t('apply') }}
|
|
</UiButton>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
// TODO: TypeScript
|
|
import { computed, reactive, ref, toRef, watch } from 'vue'
|
|
import dayjs from 'dayjs'
|
|
import UiButton from '../button/index.vue'
|
|
import useClassname from '../../composables/use-classname'
|
|
|
|
const props = defineProps({
|
|
allowRangeSelection: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
modelValue: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
})
|
|
|
|
const emit = defineEmits(['update:modelValue'])
|
|
|
|
const cn = useClassname('ui-calendar')
|
|
|
|
const modelValue = toRef(props, 'modelValue')
|
|
|
|
const today = dayjs()
|
|
const dateRange = reactive({
|
|
start: null,
|
|
end: null,
|
|
})
|
|
const currentDate = ref(today)
|
|
const daysOfWeek = dayjs.weekdaysMin()
|
|
|
|
const currentMonth = computed(() => currentDate.value.format('MMMM, YYYY'))
|
|
|
|
const monthDays = computed(() => {
|
|
const daysInMonth = currentDate.value.daysInMonth()
|
|
const leadingDays = currentDate.value.startOf('month').day()
|
|
|
|
const days = []
|
|
|
|
for (let i = 0; i < leadingDays; i++) {
|
|
const date = currentDate.value
|
|
.subtract(1, 'month')
|
|
.endOf('month')
|
|
.subtract(leadingDays - i - 1, 'day')
|
|
|
|
days.push(date)
|
|
}
|
|
|
|
for (let i = 0; i < daysInMonth; i++) {
|
|
const date = currentDate.value.date(i + 1)
|
|
|
|
days.push(date)
|
|
}
|
|
|
|
const num = 7 - (days.length % 7 || 7)
|
|
|
|
for (let i = 0; i < num; i++) {
|
|
const date = currentDate.value.add(1, 'month').date(i + 1)
|
|
|
|
days.push(date)
|
|
}
|
|
|
|
return days.map(formatDateObject)
|
|
})
|
|
|
|
function formatDateObject(date) {
|
|
return {
|
|
raw: date,
|
|
day: date.date(),
|
|
month: date.get('month'),
|
|
isToday: date.isToday(),
|
|
isCurrentMonth: date.isSame(currentDate.value, 'month'),
|
|
isPeriod:
|
|
!!dateRange.start
|
|
&& !!dateRange.end
|
|
&& date.isBetween(dateRange.start, dateRange.end, 'day', '[]'),
|
|
isPeriodStart: !!dateRange.start && date.isSame(dateRange.start, 'day'),
|
|
isPeriodEnd: !!dateRange.end && date.isSame(dateRange.end, 'day'),
|
|
}
|
|
}
|
|
|
|
function nextMonth() {
|
|
currentDate.value = currentDate.value.add(1, 'month')
|
|
}
|
|
function previousMonth() {
|
|
currentDate.value = currentDate.value.subtract(1, 'month')
|
|
}
|
|
|
|
function selectDay(day) {
|
|
let { start, end } = dateRange
|
|
|
|
if (props.allowRangeSelection) {
|
|
const isSameStartDay = day.raw.isSame(start, 'day')
|
|
|
|
if (isSameStartDay) {
|
|
start = null
|
|
end = null
|
|
}
|
|
else if (!start || end) {
|
|
start = day.raw
|
|
end = null
|
|
}
|
|
else {
|
|
end = day.raw
|
|
|
|
if (dayjs(start).isAfter(dayjs(end), 'day'))
|
|
[start, end] = [end, start]
|
|
}
|
|
}
|
|
else {
|
|
start = day.raw
|
|
end = null
|
|
}
|
|
|
|
dateRange.start = start
|
|
dateRange.end = end
|
|
}
|
|
|
|
watch(
|
|
modelValue,
|
|
(value) => {
|
|
const [start, end] = value
|
|
|
|
dateRange.start = !!start && dayjs(start)
|
|
dateRange.end = !!end && dayjs(end)
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
function getDayClasses(day) {
|
|
const classes = [cn.e('cell')]
|
|
|
|
if (day.isCurrentMonth)
|
|
classes.push(cn.em('cell', 'current-month'))
|
|
|
|
if (day.isPeriod)
|
|
classes.push(cn.em('cell', 'period'))
|
|
|
|
if (day.isPeriodStart)
|
|
classes.push(cn.em('cell', 'period-start'))
|
|
else if (day.isPeriodEnd)
|
|
classes.push(cn.em('cell', 'period-end'))
|
|
|
|
if (day.isToday)
|
|
classes.push(cn.em('cell', 'current-day'))
|
|
|
|
return classes
|
|
}
|
|
|
|
function apply() {
|
|
const value = []
|
|
|
|
if (dateRange.start)
|
|
value.push(dateRange.start.startOf('day').valueOf())
|
|
|
|
if (dateRange.end)
|
|
value.push(dateRange.end.startOf('day').valueOf())
|
|
|
|
emit('update:modelValue', value)
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
@use 'styles';
|
|
</style>
|