205 lines
5.8 KiB
Vue
205 lines
5.8 KiB
Vue
<template>
|
|
<div
|
|
class="q-ma-sm q-gutter-sm"
|
|
:style="{ height: props.height + 'px', width: props.width + 'px' }"
|
|
>
|
|
<h6 class="text-center text-bold q-ma-md text-primary">{{ props.title }}</h6>
|
|
<div class="row">
|
|
<q-checkbox
|
|
style="min-width: 75px"
|
|
dense
|
|
v-for="opt in weekdayOptions"
|
|
:key="opt.value"
|
|
v-model="selectedWeekdays"
|
|
:val="opt.value"
|
|
:label="opt.label"
|
|
/>
|
|
<q-tabs
|
|
v-model="activeTab"
|
|
dense
|
|
class="text-primary"
|
|
active-color="primary"
|
|
indicator-color="primary"
|
|
align="justify"
|
|
narrow-indicator
|
|
@update:model-value="onTabChange"
|
|
>
|
|
<q-tab no-caps name="today" :label="$t('today')" />
|
|
<q-tab no-caps name="week" :label="$t('week')" />
|
|
<q-tab no-caps name="month" :label="$t('month')" />
|
|
<q-tab no-caps name="year" :label="$t('year')" />
|
|
</q-tabs>
|
|
</div>
|
|
<div class="row">
|
|
<q-date :locale="calendarLanguage" v-model="dateRange" range flat />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, watch, type PropType, computed } from 'vue';
|
|
import { date } from 'quasar';
|
|
import { i18n } from 'src/boot/lang';
|
|
import type { QDateLocale } from 'src/vueLib/models/qDateLocale';
|
|
const props = defineProps({
|
|
title: String,
|
|
height: { type: Number, default: 400 },
|
|
width: { type: Number, default: 300 },
|
|
});
|
|
|
|
const weekdays = defineModel('weekdays', {
|
|
type: Array as PropType<number[]>,
|
|
default: () => [0, 3],
|
|
});
|
|
|
|
const startDate = new Date();
|
|
|
|
const activeTab = ref('');
|
|
// Initial range (format: YYYY-MM-DD)
|
|
const dateRange = ref<string | { to: string; from: string }>('');
|
|
const selectedWeekdays = ref(weekdays); // Default to weekdays
|
|
|
|
const emit = defineEmits(['update:dates']);
|
|
onMounted(() => {
|
|
dateRange.value = date.formatDate(startDate, 'YYYY-MM-DD');
|
|
});
|
|
|
|
const weekdayOptions = [
|
|
{ label: i18n.global.t('MondayShort'), value: 1 },
|
|
{ label: i18n.global.t('TuesdayShort'), value: 2 },
|
|
{ label: i18n.global.t('WednesdayShort'), value: 3 },
|
|
{ label: i18n.global.t('ThursdayShort'), value: 4 },
|
|
{ label: i18n.global.t('FridayShort'), value: 5 },
|
|
{ label: i18n.global.t('SaturdayShort'), value: 6 },
|
|
{ label: i18n.global.t('SundayShort'), value: 0 },
|
|
];
|
|
|
|
const calendarLanguage = computed(() => {
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
const localeData = i18n.global.tm('calendar') as unknown as QDateLocale;
|
|
return {
|
|
days: localeData.days,
|
|
daysShort: localeData.daysShort,
|
|
months: localeData.months,
|
|
monthsShort: localeData.monthsShort,
|
|
firstDayOfWeek: localeData.firstDayOfWeek,
|
|
format24h: localeData.format24h,
|
|
pluralDay: localeData.pluralDay,
|
|
};
|
|
});
|
|
|
|
const onTabChange = (val: 'today' | 'week' | 'month' | 'year') => {
|
|
if (val) setRange(val);
|
|
// Optional: Reset tab to empty so user can click the same tab again later
|
|
setTimeout(() => {
|
|
activeTab.value = '';
|
|
}, 500);
|
|
};
|
|
|
|
// The Logic: Calculate all specific dates within the range that match weekdays
|
|
watch(dateRange, () => {
|
|
if (!dateRange.value) {
|
|
return [];
|
|
} else if (typeof dateRange.value === 'string') {
|
|
const current = new Date(dateRange.value);
|
|
if (current !== undefined && selectedWeekdays.value.includes(current.getDay())) {
|
|
emit('update:dates', [date.formatDate(current, 'YYYY-MM-DD')]);
|
|
return [date.formatDate(current, 'YYYY-MM-DD')];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
const end = new Date(dateRange.value.to);
|
|
const result = [];
|
|
|
|
let current = new Date(dateRange.value.from);
|
|
|
|
while (current <= end) {
|
|
if (selectedWeekdays.value.includes(current.getDay())) {
|
|
result.push(date.formatDate(current, 'YYYY-MM-DD'));
|
|
}
|
|
current = date.addToDate(current, { days: 1 });
|
|
}
|
|
|
|
emit('update:dates', result);
|
|
return result;
|
|
});
|
|
|
|
/**
|
|
* Logic for 1 Week / 1 Month / 1 Year
|
|
*/
|
|
const setRange = (type: 'today' | 'week' | 'month' | 'year') => {
|
|
const anchor =
|
|
typeof dateRange.value === 'string'
|
|
? new Date(dateRange.value)
|
|
: new Date(dateRange.value.from);
|
|
|
|
let from: Date;
|
|
let to: Date;
|
|
|
|
if (type === 'today') {
|
|
const now = new Date();
|
|
dateRange.value = date.formatDate(date.startOfDate(now, 'day'), 'YYYY-MM-DD');
|
|
|
|
return;
|
|
} else if (type === 'week') {
|
|
// getDay() returns 0 for Sunday.
|
|
// We calculate how many days to subtract to get to Monday (1).
|
|
const day = anchor.getDay();
|
|
const diffToMonday = day === 0 ? -6 : 1 - day;
|
|
|
|
from = date.addToDate(anchor, { days: diffToMonday });
|
|
from = date.startOfDate(from, 'day'); // Reset time to 00:00
|
|
|
|
to = date.addToDate(from, { days: 6 });
|
|
to = date.endOfDate(to, 'day'); // Set time to 23:59
|
|
} else if (type === 'month') {
|
|
// 'month' is a valid unit for startOfDate
|
|
from = date.startOfDate(anchor, 'month');
|
|
to = date.endOfDate(anchor, 'month');
|
|
} else {
|
|
// 'year' is a valid unit for startOfDate
|
|
from = date.startOfDate(anchor, 'year');
|
|
to = date.endOfDate(anchor, 'year');
|
|
}
|
|
|
|
dateRange.value = {
|
|
from: date.formatDate(from, 'YYYY/MM/DD'),
|
|
to: date.formatDate(to, 'YYYY/MM/DD'),
|
|
};
|
|
};
|
|
|
|
watch(
|
|
[dateRange, selectedWeekdays],
|
|
() => {
|
|
if (!dateRange.value) {
|
|
emit('update:dates', []);
|
|
return;
|
|
}
|
|
|
|
let start: Date, end: Date;
|
|
|
|
if (typeof dateRange.value === 'string') {
|
|
start = new Date(dateRange.value);
|
|
end = new Date(dateRange.value);
|
|
} else {
|
|
start = new Date(dateRange.value.from);
|
|
end = new Date(dateRange.value.to);
|
|
}
|
|
|
|
const result = [];
|
|
let current = start;
|
|
|
|
while (current <= end) {
|
|
if (selectedWeekdays.value.includes(current.getDay())) {
|
|
result.push(date.formatDate(current, 'YYYY-MM-DD'));
|
|
}
|
|
current = date.addToDate(current, { days: 1 });
|
|
}
|
|
|
|
emit('update:dates', result);
|
|
},
|
|
{ deep: true },
|
|
);
|
|
</script>
|