feat : ajout de la gestion Congé
This commit is contained in:
@@ -1,7 +1,239 @@
|
||||
<template>
|
||||
<section class="mt-8">
|
||||
<div class="rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
|
||||
Bloc Congé (à implémenter)
|
||||
<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> {{ 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">
|
||||
<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>
|
||||
<div class="flex flex-col jutify-center items-center py-3">
|
||||
<p><span class="uppercase font-semibold">En cours d'acquisition :</span></p>
|
||||
<p>{{ formatCount(summary?.accruingDays) }} Jours</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 grid grid-cols-4 gap-10">
|
||||
<div v-for="month in months" :key="month.label" class="rounded-md bg-tertiary-500 text-primary-500">
|
||||
<div class="flex justify-center rounded-t-md bg-primary-500 py-1 font-bold uppercase text-white">{{ month.label }}</div>
|
||||
<div class="grid grid-cols-7 gap-1 px-2 py-2 text-center text-md font-bold">
|
||||
<p v-for="weekday in weekDayLabels" :key="weekday">{{ weekday }}</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-7 gap-4 px-2 pb-2 text-center text-md">
|
||||
<template v-for="(day, index) in month.cells" :key="`${month.label}-${index}`">
|
||||
<div v-if="!day" class="h-6" />
|
||||
<div
|
||||
v-else
|
||||
class="flex items-center justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-6 w-6"
|
||||
:class="getDayClass(day.leave)"
|
||||
:style="getDayStyle(day.leave)"
|
||||
:title="getDayTitle(day.leave)"
|
||||
>
|
||||
{{ getDayText(day) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Absence } from '~/services/dto/absence'
|
||||
import type { EmployeeLeaveSummary } from '~/services/dto/employee-leave-summary'
|
||||
import { normalizeDate, toYmd } from '~/utils/date'
|
||||
|
||||
type DayLeaveState = {
|
||||
am: boolean
|
||||
pm: boolean
|
||||
labels: string[]
|
||||
hasCongeTypeC: boolean
|
||||
hasOtherTypes: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
absences: Absence[]
|
||||
summary: EmployeeLeaveSummary | null
|
||||
}>()
|
||||
|
||||
const monthLabels = [
|
||||
'Janvier',
|
||||
'Fevrier',
|
||||
'Mars',
|
||||
'Avril',
|
||||
'Mai',
|
||||
'Juin',
|
||||
'Juillet',
|
||||
'Aout',
|
||||
'Septembre',
|
||||
'Octobre',
|
||||
'Novembre',
|
||||
'Decembre'
|
||||
] as const
|
||||
|
||||
const weekDayLabels = ['L', 'M', 'M', 'J', 'V', 'S', 'D'] as const
|
||||
|
||||
const isForfaitRule = computed(() => props.summary?.ruleCode === 'FORFAIT_218')
|
||||
|
||||
const displayedYear = computed(() => {
|
||||
if (props.summary?.year) return props.summary.year
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
const month = today.getMonth() + 1
|
||||
return month >= 6 ? year + 1 : year
|
||||
})
|
||||
|
||||
const orderedMonthIndexes = computed(() => {
|
||||
if (isForfaitRule.value) return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
||||
return [5, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4]
|
||||
})
|
||||
|
||||
const buildDateFromYmd = (value: string) => new Date(`${value}T00:00:00`)
|
||||
|
||||
const dayLeaveMap = computed(() => {
|
||||
const map = new Map<string, DayLeaveState>()
|
||||
|
||||
for (const absence of props.absences) {
|
||||
const startYmd = normalizeDate(absence.startDate)
|
||||
const endYmd = normalizeDate(absence.endDate)
|
||||
const start = buildDateFromYmd(startYmd)
|
||||
const end = buildDateFromYmd(endYmd)
|
||||
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) continue
|
||||
|
||||
for (const cursor = new Date(start); cursor <= end; cursor.setDate(cursor.getDate() + 1)) {
|
||||
const ymd = toYmd(cursor.getFullYear(), cursor.getMonth(), cursor.getDate())
|
||||
const existing = map.get(ymd) ?? {
|
||||
am: false,
|
||||
pm: false,
|
||||
labels: [] as string[],
|
||||
hasCongeTypeC: false,
|
||||
hasOtherTypes: false
|
||||
}
|
||||
|
||||
const isStart = ymd === startYmd
|
||||
const isEnd = ymd === endYmd
|
||||
const isSingleDay = startYmd === endYmd
|
||||
|
||||
let am = false
|
||||
let pm = false
|
||||
|
||||
if (isSingleDay) {
|
||||
am = absence.startHalf === 'AM'
|
||||
pm = absence.endHalf === 'PM'
|
||||
} else if (isStart) {
|
||||
am = absence.startHalf === 'AM'
|
||||
pm = true
|
||||
} else if (isEnd) {
|
||||
am = true
|
||||
pm = absence.endHalf === 'PM'
|
||||
} else {
|
||||
am = true
|
||||
pm = true
|
||||
}
|
||||
|
||||
const typeLabel = absence.type?.label ?? absence.type?.code ?? 'Absence'
|
||||
const typeCode = (absence.type?.code ?? '').toUpperCase()
|
||||
const halfSuffix = am && !pm ? ' (Matin)' : (!am && pm ? ' (Apres-midi)' : '')
|
||||
const hoverLabel = `${typeLabel}${halfSuffix}`
|
||||
|
||||
map.set(ymd, {
|
||||
am: existing.am || am,
|
||||
pm: existing.pm || pm,
|
||||
labels: existing.labels.includes(hoverLabel)
|
||||
? existing.labels
|
||||
: [...existing.labels, hoverLabel],
|
||||
hasCongeTypeC: existing.hasCongeTypeC || typeCode === 'C',
|
||||
hasOtherTypes: existing.hasOtherTypes || typeCode !== 'C'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return map
|
||||
})
|
||||
|
||||
const months = computed(() => {
|
||||
return orderedMonthIndexes.value.map((monthIndex) => {
|
||||
const label = monthLabels[monthIndex]
|
||||
const monthYear = isForfaitRule.value
|
||||
? displayedYear.value
|
||||
: (monthIndex >= 5 ? displayedYear.value - 1 : displayedYear.value)
|
||||
|
||||
const first = new Date(monthYear, monthIndex, 1)
|
||||
const daysInMonth = new Date(monthYear, monthIndex + 1, 0).getDate()
|
||||
const mondayBasedFirstDay = (first.getDay() + 6) % 7
|
||||
|
||||
const cells: Array<{ ymd: string; label: string; leave: DayLeaveState | null } | null> = []
|
||||
|
||||
for (let i = 0; i < mondayBasedFirstDay; i += 1) {
|
||||
cells.push(null)
|
||||
}
|
||||
|
||||
for (let day = 1; day <= daysInMonth; day += 1) {
|
||||
const ymd = toYmd(monthYear, monthIndex, day)
|
||||
cells.push({
|
||||
ymd,
|
||||
label: String(day),
|
||||
leave: dayLeaveMap.value.get(ymd) ?? null
|
||||
})
|
||||
}
|
||||
|
||||
while (cells.length % 7 !== 0) {
|
||||
cells.push(null)
|
||||
}
|
||||
|
||||
return {
|
||||
label,
|
||||
cells
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const getDayClass = (leave: DayLeaveState | null) => {
|
||||
if (!leave) return 'text-primary-500'
|
||||
if (leave.am && leave.pm) {
|
||||
return leave.hasOtherTypes
|
||||
? 'bg-red-600 text-white rounded font-semibold'
|
||||
: 'bg-primary-500 text-white rounded font-semibold'
|
||||
}
|
||||
return 'rounded text-primary-700 font-semibold text-white'
|
||||
}
|
||||
|
||||
const getDayStyle = (leave: DayLeaveState | null) => {
|
||||
if (!leave || (leave.am && leave.pm)) return undefined
|
||||
|
||||
const color = leave.hasOtherTypes ? '#dc2626' : '#222783'
|
||||
const backgroundImage = leave.am
|
||||
? `linear-gradient(135deg, ${color} 0 50%, transparent 50% 100%)`
|
||||
: `linear-gradient(135deg, transparent 0 50%, ${color} 50% 100%)`
|
||||
|
||||
return {
|
||||
backgroundImage,
|
||||
backgroundColor: 'transparent'
|
||||
}
|
||||
}
|
||||
|
||||
const getDayText = (day: { label: string; leave: DayLeaveState | null }) => {
|
||||
return day.label
|
||||
}
|
||||
|
||||
const getDayTitle = (leave: DayLeaveState | null) => {
|
||||
if (!leave || leave.labels.length === 0) return ''
|
||||
return leave.labels.join(' / ')
|
||||
}
|
||||
|
||||
const formatCount = (value: number | null | undefined) => {
|
||||
if (value === null || value === undefined) return '-'
|
||||
const rounded = Math.round(value * 100) / 100
|
||||
if (Number.isInteger(rounded)) return String(rounded)
|
||||
return rounded.toFixed(2).replace('.', ',')
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user