Compare commits

...

2 Commits

Author SHA1 Message Date
gitea-actions
36fe9ae54c chore: bump version to v0.1.17
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build Release Artefact / build (push) Successful in 1m11s
2026-03-02 09:50:14 +00:00
6395ffbe1c feat : modification des sélecteurs de date sur le calendrier
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
2026-03-02 10:50:02 +01:00
4 changed files with 121 additions and 68 deletions

View File

@@ -1,2 +1,2 @@
parameters: parameters:
app.version: '0.1.16' app.version: '0.1.17'

View File

@@ -0,0 +1,77 @@
<template>
<div class="relative inline-flex h-10 items-center overflow-hidden rounded-md border border-primary-500 bg-white" :class="widthClass">
<input
ref="nativeInput"
:value="pickerValue"
:type="pickerType"
class="pointer-events-none absolute inset-0 h-full w-full opacity-0"
tabindex="-1"
aria-hidden="true"
@input="onPickerInput"
@change="onPickerInput"
/>
<button
type="button"
class="h-10 px-3 text-lg font-semibold text-primary-500 hover:bg-tertiary-500 active:bg-tertiary-500 active:scale-[0.96]"
:aria-label="prevAriaLabel"
@click="emit('prev')"
>
</button>
<button
type="button"
class="h-10 flex-1 border-x border-primary-500 px-4 text-sm font-semibold text-primary-500 text-center hover:bg-tertiary-500 active:bg-tertiary-500"
@click="openPicker"
>
{{ label }}
</button>
<button
type="button"
class="h-10 px-3 text-lg font-semibold text-primary-500 hover:bg-tertiary-500 active:bg-tertiary-500 active:scale-[0.96]"
:aria-label="nextAriaLabel"
@click="emit('next')"
>
</button>
</div>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
label: string
pickerType: 'date' | 'week' | 'month'
pickerValue: string
widthClass?: string
prevAriaLabel?: string
nextAriaLabel?: string
}>(), {
widthClass: 'w-[320px]',
prevAriaLabel: 'Précédent',
nextAriaLabel: 'Suivant'
})
const emit = defineEmits<{
(e: 'prev'): void
(e: 'next'): void
(e: 'pick', value: string): void
}>()
const nativeInput = ref<HTMLInputElement | null>(null)
const openPicker = () => {
const input = nativeInput.value
if (!input) return
if (typeof input.showPicker === 'function') {
input.showPicker()
return
}
input.focus()
input.click()
}
const onPickerInput = (event: Event) => {
const value = (event.target as HTMLInputElement).value
if (!value) return
emit('pick', value)
}
</script>

View File

@@ -64,41 +64,17 @@
</button> </button>
</div> </div>
<div class="relative inline-flex h-10 w-[320px] items-center overflow-hidden rounded-md border border-primary-500 bg-white"> <PeriodStepperPicker
<input width-class="w-[320px]"
ref="nativeDateInput" :label="formattedSelectedDate"
:value="pickerValue" :picker-type="viewMode === 'week' ? 'week' : 'date'"
:type="viewMode === 'week' ? 'week' : 'date'" :picker-value="pickerValue"
class="pointer-events-none absolute inset-0 h-full w-full opacity-0" prev-aria-label="Période précédente"
tabindex="-1" next-aria-label="Période suivante"
aria-hidden="true" @prev="emit('shift-date', -1)"
@input="onPickerInput" @next="emit('shift-date', 1)"
@change="onPickerInput" @pick="onPickerValue"
/> />
<button
type="button"
class="h-10 px-3 text-lg font-semibold text-primary-500 hover:bg-tertiary-500 active:bg-tertiary-500 active:scale-[0.96] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40"
aria-label="Période précédente"
@click="emit('shift-date', -1)"
>
</button>
<button
type="button"
class="h-10 flex-1 border-x border-primary-500 px-4 text-sm font-semibold text-primary-500 text-center hover:bg-tertiary-500 active:bg-tertiary-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40"
@click="openDatePicker"
>
{{ formattedSelectedDate }}
</button>
<button
type="button"
class="h-10 px-3 text-lg font-semibold text-primary-500 hover:bg-tertiary-500 active:bg-tertiary-500 active:scale-[0.96] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/40"
aria-label="Période suivante"
@click="emit('shift-date', 1)"
>
</button>
</div>
</div> </div>
<div v-if="isAdmin" class="inline-flex h-10 overflow-hidden rounded-md border border-primary-500 bg-white"> <div v-if="isAdmin" class="inline-flex h-10 overflow-hidden rounded-md border border-primary-500 bg-white">
@@ -145,6 +121,7 @@ import type { Site } from '~/services/dto/site'
import type { AbsenceType } from '~/services/dto/absence-type' import type { AbsenceType } from '~/services/dto/absence-type'
import EmployeeNameFilterInput from '~/components/EmployeeNameFilterInput.vue' import EmployeeNameFilterInput from '~/components/EmployeeNameFilterInput.vue'
import SiteFilterSelector from '~/components/SiteFilterSelector.vue' import SiteFilterSelector from '~/components/SiteFilterSelector.vue'
import PeriodStepperPicker from '~/components/PeriodStepperPicker.vue'
import { weekInputValueToYmd, ymdToWeekInputValue } from '~/utils/date' import { weekInputValueToYmd, ymdToWeekInputValue } from '~/utils/date'
const selectedDate = defineModel<string>('selectedDate', { required: true }) const selectedDate = defineModel<string>('selectedDate', { required: true })
@@ -172,7 +149,6 @@ const emit = defineEmits<{
(e: 'shift-date', value: number): void (e: 'shift-date', value: number): void
}>() }>()
const nativeDateInput = ref<HTMLInputElement | null>(null)
const pickerValue = computed(() => { const pickerValue = computed(() => {
if (viewMode.value === 'week') return ymdToWeekInputValue(selectedDate.value) if (viewMode.value === 'week') return ymdToWeekInputValue(selectedDate.value)
return selectedDate.value return selectedDate.value
@@ -186,19 +162,7 @@ const viewModeButtonClass = (mode: 'day' | 'week') => {
return 'bg-white text-primary-500 hover:bg-tertiary-500' return 'bg-white text-primary-500 hover:bg-tertiary-500'
} }
const openDatePicker = () => { const onPickerValue = (value: string) => {
const input = nativeDateInput.value
if (!input) return
if (typeof input.showPicker === 'function') {
input.showPicker()
return
}
input.focus()
input.click()
}
const onPickerInput = (event: Event) => {
const value = (event.target as HTMLInputElement).value
if (!value) return if (!value) return
if (viewMode.value === 'week') { if (viewMode.value === 'week') {

View File

@@ -30,22 +30,17 @@
<div class="w-80"> <div class="w-80">
<EmployeeNameFilterInput v-model="employeeFilter"/> <EmployeeNameFilterInput v-model="employeeFilter"/>
</div> </div>
<select <PeriodStepperPicker
v-model="selectedMonth" width-class="w-[260px]"
class="h-10 rounded-md border border-neutral-300 bg-white px-3 text-md text-neutral-900" :label="selectedMonthLabel"
> picker-type="month"
<option v-for="month in months" :key="month.value" :value="month.value"> :picker-value="monthPickerValue"
{{ month.label }} prev-aria-label="Mois précédent"
</option> next-aria-label="Mois suivant"
</select> @prev="shiftMonth(-1)"
<select @next="shiftMonth(1)"
v-model="selectedYear" @pick="onMonthPickerValue"
class="h-10 rounded-md border border-neutral-300 bg-white px-3 text-md text-neutral-900" />
>
<option v-for="year in years" :key="year" :value="year">
{{ year }}
</option>
</select>
</div> </div>
</div> </div>
<div class="flex flex-wrap items-center gap-6 py-2"> <div class="flex flex-wrap items-center gap-6 py-2">
@@ -111,6 +106,7 @@ import CalendarGrid from '~/components/CalendarGrid.vue'
import AbsenceFormDrawer from '~/components/AbsenceFormDrawer.vue' import AbsenceFormDrawer from '~/components/AbsenceFormDrawer.vue'
import AbsencePrintDrawer from '~/components/AbsencePrintDrawer.vue' import AbsencePrintDrawer from '~/components/AbsencePrintDrawer.vue'
import EmployeeNameFilterInput from '~/components/EmployeeNameFilterInput.vue' import EmployeeNameFilterInput from '~/components/EmployeeNameFilterInput.vue'
import PeriodStepperPicker from '~/components/PeriodStepperPicker.vue'
import SiteFilterSelector from '~/components/SiteFilterSelector.vue' import SiteFilterSelector from '~/components/SiteFilterSelector.vue'
useHead({ useHead({
@@ -195,8 +191,8 @@ const months = [
{value: 11, label: 'Décembre'} {value: 11, label: 'Décembre'}
] ]
const years = Array.from({length: 5}, (unusedValue, index) => now.getFullYear() - 2 + index) const selectedMonthLabel = computed(() => `${months[selectedMonth.value]?.label ?? ''}`)
const monthPickerValue = computed(() => `${selectedYear.value}-${String(selectedMonth.value + 1).padStart(2, '0')}`)
// Infos de calendrier calculées. // Infos de calendrier calculées.
const daysInMonth = computed(() => getDaysInMonth(selectedYear.value, selectedMonth.value)) const daysInMonth = computed(() => getDaysInMonth(selectedYear.value, selectedMonth.value))
@@ -316,6 +312,22 @@ const addMonths = (date: Date, months: number) => {
return next return next
} }
const shiftMonth = (delta: number) => {
const next = new Date(selectedYear.value, selectedMonth.value + delta, 1)
selectedYear.value = next.getFullYear()
selectedMonth.value = next.getMonth()
}
const onMonthPickerValue = (value: string) => {
if (!value) return
const [yearStr, monthStr] = value.split('-')
const year = Number(yearStr)
const month = Number(monthStr)
if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) return
selectedYear.value = year
selectedMonth.value = month - 1
}
// Limite l'intervalle d'impression à 2 mois max. // Limite l'intervalle d'impression à 2 mois max.
const enforcePrintRange = () => { const enforcePrintRange = () => {
if (!printForm.from) return if (!printForm.from) return