|
|
@@ -1,119 +1,140 @@
|
|
|
<!--
|
|
|
-Sélecteur de dates avec Vuetify
|
|
|
+Sélecteur de dates
|
|
|
|
|
|
-@see https://vuetifyjs.com/en/components/date-pickers/
|
|
|
+@see https://vue3datepicker.com/
|
|
|
-->
|
|
|
|
|
|
<template>
|
|
|
- <v-layout row wrap>
|
|
|
- <!--
|
|
|
- TODO: remplacer par <v-date-input> quand celui ci ne sera plus expérimental
|
|
|
- (@see https://vuetifyjs.com/en/components/date-inputs)
|
|
|
- -->
|
|
|
- <v-menu
|
|
|
- v-model="menu"
|
|
|
- :close-on-content-click="false"
|
|
|
- :nudge-right="40"
|
|
|
- lazy
|
|
|
- transition="scale-transition"
|
|
|
- offset-y
|
|
|
- :max-width="290"
|
|
|
- :min-width="290"
|
|
|
- :position-x="positionX"
|
|
|
- :position-y="positionY"
|
|
|
- >
|
|
|
- <template #activator="{ props: attrs }">
|
|
|
- <v-text-field
|
|
|
- v-model="displayDate"
|
|
|
- :label="label"
|
|
|
- :readonly="true"
|
|
|
- v-bind="attrs"
|
|
|
- prepend-inner-icon="far fa-calendar"
|
|
|
- variant="outlined"
|
|
|
- density="compact"
|
|
|
- />
|
|
|
- </template>
|
|
|
-
|
|
|
- <v-date-picker
|
|
|
- :model-value="modelValue"
|
|
|
- :locale="i18n.locale.value"
|
|
|
- no-title
|
|
|
- scrollable
|
|
|
- @update:model-value="updateDate"
|
|
|
- />
|
|
|
- </v-menu>
|
|
|
- </v-layout>
|
|
|
+ <VueDatePicker
|
|
|
+ :model-value="modelValue"
|
|
|
+ :locale="locale"
|
|
|
+ :format="dateFormatPattern"
|
|
|
+ :format-locale="fnsLocale"
|
|
|
+ :range="range"
|
|
|
+ :multi-calendars="range"
|
|
|
+ :enable-time-picker="withTimePicker"
|
|
|
+ :auto-apply="autoApply"
|
|
|
+ :auto-position="true"
|
|
|
+ :start-date="today"
|
|
|
+ close-on-scroll
|
|
|
+ text-input
|
|
|
+ :readonly="readonly"
|
|
|
+ position="left"
|
|
|
+ :teleport="true"
|
|
|
+ :select-text="$t('select')"
|
|
|
+ :cancel-text="$t('cancel')"
|
|
|
+ input-class-name="date-picker-input"
|
|
|
+ class="date-picker"
|
|
|
+ :style="style"
|
|
|
+ @update:model-value="$emit('update:modelValue', $event)"
|
|
|
+ />
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, computed, nextTick, watch, type PropType } from 'vue'
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
+import DateUtils, { supportedLocales } from '~/services/utils/dateUtils'
|
|
|
+import type { PropType } from '@vue/runtime-core';
|
|
|
+import type { Locale } from 'date-fns';
|
|
|
|
|
|
const props = defineProps({
|
|
|
- modelValue: Date,
|
|
|
- label: {
|
|
|
- type: String,
|
|
|
- default: '',
|
|
|
- },
|
|
|
- format: {
|
|
|
- type: String,
|
|
|
+ modelValue: {
|
|
|
+ type: Object as PropType<Date | Array<Date> | null>,
|
|
|
+ required: false,
|
|
|
default: null,
|
|
|
},
|
|
|
- /**
|
|
|
- * Position du date-picker
|
|
|
- * @see https://vuetifyjs.com/en/api/v-menu/#props-position
|
|
|
- */
|
|
|
- position: {
|
|
|
- type: String as PropType<'left' | 'center' | 'right'>,
|
|
|
- default: 'center',
|
|
|
+ range: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ withTimePicker: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ autoApply: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true,
|
|
|
},
|
|
|
+ maxHeight: {
|
|
|
+ type: Number,
|
|
|
+ required: false,
|
|
|
+ default: null,
|
|
|
+ },
|
|
|
+ readonly: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
-const emit = defineEmits(['update:modelValue'])
|
|
|
-
|
|
|
const i18n = useI18n()
|
|
|
-const menu = ref(false)
|
|
|
-const positionX = ref(0)
|
|
|
-const positionY = ref(0)
|
|
|
-
|
|
|
-const displayDate = computed({
|
|
|
- get: () => {
|
|
|
- if (!props.modelValue) return ''
|
|
|
- if (props.format) {
|
|
|
- return new Intl.DateTimeFormat(i18n.locale.value, {
|
|
|
- year: 'numeric',
|
|
|
- month: '2-digit',
|
|
|
- day: '2-digit',
|
|
|
- }).format(props.modelValue)
|
|
|
- }
|
|
|
- return props.modelValue.toLocaleDateString(i18n.locale.value)
|
|
|
- },
|
|
|
- set: () => {},
|
|
|
-})
|
|
|
|
|
|
-function updateDate(value: Date) {
|
|
|
- emit('update:modelValue', value)
|
|
|
- menu.value = false
|
|
|
-}
|
|
|
+const locale: Ref<string> = i18n.locale
|
|
|
|
|
|
-function updatePosition() {
|
|
|
- nextTick(() => {
|
|
|
- const activator = document.querySelector('.v-menu__activator')
|
|
|
- if (activator) {
|
|
|
- const rect = activator.getBoundingClientRect()
|
|
|
- positionX.value = rect.left
|
|
|
- positionY.value = rect.bottom
|
|
|
- }
|
|
|
- })
|
|
|
-}
|
|
|
+const fnsLocale: ComputedRef<Locale> = computed(
|
|
|
+ () => DateUtils.getFnsLocale(locale.value as supportedLocales)
|
|
|
+)
|
|
|
|
|
|
-watch(menu, (val) => {
|
|
|
- if (val) updatePosition()
|
|
|
-})
|
|
|
+const dateFormatPattern: ComputedRef<string> = computed(
|
|
|
+ () => DateUtils.getShortFormatPattern(locale.value as supportedLocales)
|
|
|
+)
|
|
|
+
|
|
|
+const today = new Date()
|
|
|
+
|
|
|
+let style: ComputedRef<string> = computed(
|
|
|
+ () => props.maxHeight !== null ? 'height: ' + props.maxHeight + 'px;max-height: ' + props.maxHeight + 'px;' : ''
|
|
|
+)
|
|
|
</script>
|
|
|
|
|
|
-<style scoped>
|
|
|
-.v-menu__content {
|
|
|
- position: absolute !important;
|
|
|
+<style scoped lang="scss">
|
|
|
+// @see https://vue3datepicker.com/customization/theming/
|
|
|
+// [!] Sass variables overriding does not work in scoped mode
|
|
|
+.dp__theme_light,
|
|
|
+.dp__theme_dark {
|
|
|
+ --dp-background-color: #ffffff;
|
|
|
+ --dp-text-color: #212121;
|
|
|
+ --dp-hover-color: #f3f3f3;
|
|
|
+ --dp-hover-text-color: #212121;
|
|
|
+ --dp-hover-icon-color: #959595;
|
|
|
+ --dp-primary-color: rgb(var(--v-theme-primary)) !important;
|
|
|
+ --dp-primary-text-color: rgb(var(--v-theme-on-primary)) !important;
|
|
|
+ --dp-secondary-color: rgb(var(--v-theme-neutral-strong)) !important;
|
|
|
+ --dp-border-color: #ddd;
|
|
|
+ --dp-menu-border-color: #ddd;
|
|
|
+ --dp-border-color-hover: #aaaeb7;
|
|
|
+ --dp-disabled-color: #f6f6f6;
|
|
|
+ --dp-scroll-bar-background: #f3f3f3;
|
|
|
+ --dp-scroll-bar-color: #959595;
|
|
|
+ --dp-success-color: rgb(var(--v-theme-success)) !important;
|
|
|
+ --dp-success-color-disabled: rgb(var(--v-theme-neutral-strong)) !important;
|
|
|
+ --dp-icon-color: #959595;
|
|
|
+ --dp-danger-color: #ff6f60;
|
|
|
+ --dp-highlight-color: rgba(25, 118, 210, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.date-picker {
|
|
|
+ div {
|
|
|
+ height: 100% !important;
|
|
|
+ max-height: 100% !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dp__input_wrap {
|
|
|
+ height: 100% !important;
|
|
|
+ max-height: 100% !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .date-picker-input {
|
|
|
+ height: 100% !important;
|
|
|
+ max-height: 100% !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.dp__active_date) {
|
|
|
+ border-radius: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.dp__today) {
|
|
|
+ border-radius: 12px;
|
|
|
+ border: 1px solid rgb(var(--v-theme-neutral-strong)) !important;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
</style>
|