feat : ajout des jours fractionnés

This commit is contained in:
2026-03-09 14:59:36 +01:00
parent e794ad2514
commit 232bdb8ec4
16 changed files with 305 additions and 26 deletions

View File

@@ -11,12 +11,12 @@
v-for="day in daysInMonth"
:key="day.date"
class="sticky top-0 z-20 border-b border-neutral-200 px-2 py-3 text-center text-xs font-semibold transition-colors"
:class="isHoveredColumn(day.date) ? 'bg-primary-500 text-white' : 'bg-tertiary-500 text-neutral-700'"
:class="isHoveredColumn(day.date) || day.date === today ? 'bg-primary-500 text-white' : 'bg-tertiary-500 text-neutral-700'"
>
<div>{{ day.label }}</div>
<div
class="text-[10px]"
:class="isHoveredColumn(day.date) ? 'text-white/90' : 'text-neutral-500'"
:class="isHoveredColumn(day.date) || day.date === today ? 'text-white/90' : 'text-neutral-500'"
>
{{ day.weekday }}
</div>
@@ -91,6 +91,10 @@
<script setup lang="ts">
import type { Employee } from '~/services/dto/employee'
import type { HalfDay } from '~/services/dto/half-day'
import { toYmd } from '~/utils/date'
const now = new Date()
const today = toYmd(now.getFullYear(), now.getMonth(), now.getDate())
type DayInfo = {
date: string

View File

@@ -1,24 +1,29 @@
<template>
<section class="flex h-full min-h-0 flex-col overflow-hidden pt-8">
<div class="grid grid-cols-4 rounded-md bg-primary-500 text-white text-[20]">
<div class="flex flex-col jutify-center items-center border-r-4 border-white py-3">
<p><strong class="uppercase font-semibold">Acquis année :</strong> {{
<div class="flex flex-col gap-2 jutify-center items-center border-r-4 border-white py-3">
<p><strong class="uppercase font-semibold">Année acquis :</strong> {{
formatCount(summary?.acquiredDays)
}} Jours</p>
<p><strong class="uppercase font-semibold">Reste à prendre :</strong>
{{ formatCount(summary?.remainingDays) }} Jours</p>
</div>
<div class="flex flex-col jutify-center items-center border-r-4 border-white py-3">
<div class="flex flex-col gap-2 jutify-center items-center border-r-4 border-white py-3">
<p><span class="uppercase font-semibold">Samedi acquis :</span>
{{ formatCount(summary?.acquiredSaturdays) }} Jours</p>
<p><span class="uppercase font-semibold">Reste à prendre :</span>
{{ formatCount(summary?.remainingSaturdays) }} Jours</p>
</div>
<div class="flex flex-col jutify-center items-center border-r-4 border-white py-3">
<p><span class="uppercase font-semibold">Acquis fractionné :</span></p>
<p>{{ formatCount(summary?.fractionedDays) }} Jours</p>
<div class="flex flex-col gap-2 jutify-center items-center border-r-4 border-white py-3">
<p><span class="uppercase font-semibold">Fractionné acquis : </span>{{ formatCount(summary?.fractionedDays) }} Jours</p>
<button
class="flex justify-center items-center gap-2 bg-white text-primary-500 font-bold w-[150px] rounded-md py-[1px]"
@click="openFractionedDrawer"
>
<Icon name="mdi:plus-thick" size="16" />
{{ summary?.fractionedDays === 0 ? 'Ajouter' : 'Modifier' }}</button>
</div>
<div class="flex flex-col jutify-center items-center py-3">
<div class="flex flex-col jutify-center gap-2 items-center py-3">
<p><span class="uppercase font-semibold">En cours d'acquisition :</span></p>
<p>{{ formatCount(summary?.accruingDays) }} Jours</p>
</div>
@@ -53,6 +58,39 @@
</div>
</div>
</div>
<AppDrawer v-model="isFractionedDrawerOpen" title="Jours fractionnés">
<form class="space-y-4" @submit.prevent="handleSubmitFractioned">
<div>
<label class="text-md font-semibold text-neutral-700" for="fractioned-days">
Nombre de jours <span class="text-red-600">*</span>
</label>
<input
id="fractioned-days"
v-model="fractionedForm.days"
type="number"
step="0.5"
min="0"
class="mt-2 w-full rounded-md border border-neutral-300 px-3 py-2 text-md text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20"
/>
</div>
<div class="flex justify-end gap-3 pt-2">
<button
type="button"
class="rounded-lg border border-neutral-200 px-4 py-2 text-md font-semibold text-neutral-700 hover:bg-neutral-100"
@click="isFractionedDrawerOpen = false"
>
Annuler
</button>
<button
type="submit"
class="rounded-lg bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
>
Enregistrer
</button>
</div>
</form>
</AppDrawer>
</section>
</template>
@@ -60,6 +98,7 @@
import type {Absence} from '~/services/dto/absence'
import type {EmployeeLeaveSummary} from '~/services/dto/employee-leave-summary'
import {normalizeDate, toYmd} from '~/utils/date'
import AppDrawer from '~/components/AppDrawer.vue'
type DayLeaveState = {
am: boolean
@@ -74,6 +113,25 @@ const props = defineProps<{
publicHolidays: Record<string, string>
}>()
const emit = defineEmits<{
(event: 'update-fractioned-days', days: number): void
}>()
const isFractionedDrawerOpen = ref(false)
const fractionedForm = reactive({ days: 0 })
const openFractionedDrawer = () => {
fractionedForm.days = props.summary?.fractionedDays ?? 0
isFractionedDrawerOpen.value = true
}
const handleSubmitFractioned = () => {
const value = Number(fractionedForm.days)
if (Number.isNaN(value) || value < 0) return
emit('update-fractioned-days', value)
isFractionedDrawerOpen.value = false
}
const monthLabels = [
'Janvier',
'Fevrier',
@@ -223,9 +281,10 @@ const getDayStyle = (day: { leave: DayLeaveState | null; isHoliday: boolean }) =
if (day.leave.am && day.leave.pm) {
return { backgroundColor: color }
}
const colorFaded = `${color}60`
const backgroundImage = day.leave.am
? `linear-gradient(135deg, ${color} 0 50%, transparent 50% 100%)`
: `linear-gradient(135deg, transparent 0 50%, ${color} 50% 100%)`
? `linear-gradient(180deg, ${color} 0 50%, ${colorFaded} 50% 100%)`
: `linear-gradient(180deg, ${colorFaded} 0 50%, ${color} 50% 100%)`
return { backgroundImage, backgroundColor: 'transparent' }
}
if (day.isHoliday) return { backgroundColor: 'rgb(179, 229, 252)' }

View File

@@ -1,7 +1,11 @@
<template>
<section class="flex h-full min-h-0 flex-col overflow-hidden pt-8">
<div class="flex justify-center items-center bg-primary-500 rounded-md text-white py-5 text-[20px]">
<div class="flex gap-10 justify-center items-center bg-primary-500 rounded-md text-white py-5 text-[20px]">
<p><span class="font-semibold uppercase">RTT à la date du jour :</span> {{ formatDays(summary?.availableMinutes ?? 0) }}</p>
<button class="bg-white rounded-md text-primary-500 font-bold px-6 py-1">
<Icon name="mdi:plus-thick" size="16"/>
Payer les RTT
</button>
</div>
<div class="mt-8 min-h-0 flex-1 overflow-y-auto pr-2">
<div class="grid grid-cols-4 gap-10 pb-4">

View File

@@ -6,7 +6,7 @@ import type { ContractHistoryItem, Employee } from '~/services/dto/employee'
import { CONTRACT_TYPES } from '~/services/dto/contract'
import { listAbsences } from '~/services/absences'
import { listContracts } from '~/services/contracts'
import { getEmployeeLeaveSummary } from '~/services/employee-leave-summary'
import { getEmployeeLeaveSummary, updateFractionedDays } from '~/services/employee-leave-summary'
import { getEmployeeRttSummary } from '~/services/employee-rtt-summary'
import { getEmployee, updateEmployee } from '~/services/employees'
import { listPublicHolidays } from '~/services/public-holidays'
@@ -300,6 +300,13 @@ export const useEmployeeDetailPage = () => {
}
}
const submitFractionedDays = async (days: number) => {
if (!employee.value) return
const year = leaveSummary.value?.year ?? undefined
await updateFractionedDays(employee.value.id, days, year)
await loadEmployee()
}
watch(requiresCreateContractEndDate, (required) => {
if (!required) {
createContractForm.endDate = ''
@@ -350,6 +357,7 @@ export const useEmployeeDetailPage = () => {
setContractDrawerOpen,
setCreateContractDrawerOpen,
submitContractUpdate,
submitCreateContract
submitCreateContract,
submitFractionedDays
}
}

View File

@@ -94,6 +94,7 @@
:absences="employeeAbsences"
:summary="leaveSummary"
:public-holidays="publicHolidays"
@update-fractioned-days="submitFractionedDays"
/>
<EmployeesRttTab v-else class="h-full" :summary="rttSummary" />
</div>
@@ -141,7 +142,8 @@ const {
setContractDrawerOpen,
setCreateContractDrawerOpen,
submitContractUpdate,
submitCreateContract
submitCreateContract,
submitFractionedDays
} = useEmployeeDetailPage()
useHead(() => ({

View File

@@ -8,3 +8,11 @@ export const getEmployeeLeaveSummary = async (employeeId: number, year?: number)
return api.get<EmployeeLeaveSummary>(`/employees/${employeeId}/leave-summary`, query, { toast: false })
}
export const updateFractionedDays = async (employeeId: number, fractionedDays: number, year?: number) => {
const api = useApi()
const body: Record<string, unknown> = { fractionedDays }
if (year) body.year = year
return api.patch(`/employees/${employeeId}/fractioned-days`, body)
}