fix : affichage des absences dans les heures vue semaine + refacto
This commit is contained in:
@@ -126,11 +126,23 @@
|
||||
<div v-if="isAdmin" class="w-80 max-w-full">
|
||||
<EmployeeNameFilterInput v-model="employeeFilter" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isAdmin && viewMode === 'week' && absenceTypes.length > 0"
|
||||
class="flex flex-wrap items-center gap-6"
|
||||
>
|
||||
<p class="font-bold">Légende :</p>
|
||||
<div v-for="type in absenceTypes" :key="type.id" class="flex items-center gap-2">
|
||||
<div :style="{ backgroundColor: type.color }" class="h-4 w-4 rounded"></div>
|
||||
<p>{{ type.label }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Site } from '~/services/dto/site'
|
||||
import type { AbsenceType } from '~/services/dto/absence-type'
|
||||
import EmployeeNameFilterInput from '~/components/EmployeeNameFilterInput.vue'
|
||||
import SiteFilterSelector from '~/components/SiteFilterSelector.vue'
|
||||
import { weekInputValueToYmd, ymdToWeekInputValue } from '~/utils/date'
|
||||
@@ -143,6 +155,7 @@ const employeeFilter = defineModel<string>('employeeFilter', { required: true })
|
||||
defineProps<{
|
||||
isAdmin: boolean
|
||||
sites: Site[]
|
||||
absenceTypes: AbsenceType[]
|
||||
formattedSelectedDate: string
|
||||
shortcutButtonClass: (target: 'yesterday' | 'today' | 'tomorrow') => string
|
||||
weekShortcutButtonClass: (target: 'previousWeek' | 'thisWeek' | 'nextWeek') => string
|
||||
|
||||
@@ -30,7 +30,14 @@
|
||||
<p class="text-[11px] text-neutral-500 truncate">{{ row.siteName ?? 'Sans site' }}</p>
|
||||
</div>
|
||||
|
||||
<div v-for="daily in row.daily" :key="daily.date" class="text-left leading-4">
|
||||
<div
|
||||
v-for="daily in row.daily"
|
||||
:key="daily.date"
|
||||
class="text-left leading-4 rounded-md px-2 py-1"
|
||||
:class="daily.hasAbsence ? 'text-white' : ''"
|
||||
:style="getDailyCellStyle(daily)"
|
||||
:title="daily.absenceLabel ?? ''"
|
||||
>
|
||||
<template v-if="row.trackingMode === 'PRESENCE'">{{ daily.present ?? 0 }}</template>
|
||||
<template v-else>
|
||||
<div>J {{ formatMinutes(daily.dayMinutes) }}</div>
|
||||
@@ -52,13 +59,13 @@
|
||||
{{ row.trackingMode === 'PRESENCE' ? '-' : formatMinutes(row.weeklyOvertimeTotalMinutes ?? 0) }}
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
{{ row.trackingMode === 'PRESENCE' ? '-' : formatMinutes(row.weeklyOvertime25Minutes ?? 0) }}
|
||||
{{ row.trackingMode === 'PRESENCE' || isInterimContract(row.contractType) ? '-' : formatMinutes(row.weeklyOvertime25Minutes ?? 0) }}
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
{{ row.trackingMode === 'PRESENCE' ? '-' : formatMinutes(row.weeklyOvertime50Minutes ?? 0) }}
|
||||
{{ row.trackingMode === 'PRESENCE' || isInterimContract(row.contractType) ? '-' : formatMinutes(row.weeklyOvertime50Minutes ?? 0) }}
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
{{ row.trackingMode === 'PRESENCE' ? '-' : formatMinutes(row.weeklyRecoveryMinutes ?? 0) }}
|
||||
{{ row.trackingMode === 'PRESENCE' || isInterimContract(row.contractType) ? '-' : formatMinutes(row.weeklyRecoveryMinutes ?? 0) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,6 +74,19 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { WeeklyWorkHourSummary } from '~/services/dto/work-hour'
|
||||
import { CONTRACT_TYPES, type ContractType } from '~/services/dto/contract'
|
||||
|
||||
const isInterimContract = (contractType?: ContractType | null) => {
|
||||
return contractType === CONTRACT_TYPES.INTERIM
|
||||
}
|
||||
|
||||
const getDailyCellStyle = (daily: {
|
||||
hasAbsence?: boolean
|
||||
absenceColor?: string | null
|
||||
}) => {
|
||||
if (!daily.hasAbsence) return undefined
|
||||
return { backgroundColor: daily.absenceColor || '#dc2626' }
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
isWeekLoading: boolean
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { WorkHour, WorkHourDayContext, WeeklyWorkHourSummary } from '~/serv
|
||||
import type { AbsenceType } from '~/services/dto/absence-type'
|
||||
import type { Absence } from '~/services/dto/absence'
|
||||
import type { HalfDay } from '~/services/dto/half-day'
|
||||
import { CONTRACT_TYPES, TRACKING_MODES } from '~/services/dto/contract'
|
||||
import type { HourRow } from '~/components/hours/types'
|
||||
import { listScopedEmployees } from '~/services/employees'
|
||||
import { listAbsenceTypes } from '~/services/absence-types'
|
||||
@@ -275,14 +276,17 @@ export const useHoursPage = () => {
|
||||
isValid: false
|
||||
})
|
||||
|
||||
const isPresenceTracking = (employee: Employee) => employee.contract?.trackingMode === 'PRESENCE'
|
||||
const isPresenceTracking = (employee: Employee) => employee.contract?.trackingMode === TRACKING_MODES.PRESENCE
|
||||
const isTimeTracking = (employee: Employee) => !isPresenceTracking(employee)
|
||||
const isRowLocked = (employeeId: number) => rows.value[employeeId]?.isValid ?? false
|
||||
|
||||
const contractLabel = (employee: Employee) => {
|
||||
const contract = employee.contract
|
||||
if (!contract) return '-'
|
||||
if (contract.weeklyHours !== null && contract.weeklyHours !== undefined && contract.trackingMode === 'TIME') {
|
||||
if (contract.type === CONTRACT_TYPES.INTERIM) {
|
||||
return contract.name
|
||||
}
|
||||
if (contract.weeklyHours !== null && contract.weeklyHours !== undefined && contract.trackingMode === TRACKING_MODES.TIME) {
|
||||
return `${contract.weeklyHours}h`
|
||||
}
|
||||
return contract.name
|
||||
@@ -392,7 +396,7 @@ export const useHoursPage = () => {
|
||||
const isEveningLockedByAbsence = (employeeId: number) => {
|
||||
const dayRow = dayContextByEmployeeId.value.get(employeeId)
|
||||
if (!dayRow) return false
|
||||
return dayRow.absentMorning && dayRow.absentAfternoon
|
||||
return dayRow.absentAfternoon
|
||||
}
|
||||
|
||||
const formatMinutes = (minutes: number) => {
|
||||
@@ -475,6 +479,42 @@ export const useHoursPage = () => {
|
||||
isAbsenceDrawerOpen.value = true
|
||||
}
|
||||
|
||||
const applyLocalClearFromAbsence = (employeeId: number, startHalf: HalfDay, endHalf: HalfDay) => {
|
||||
const row = rows.value[employeeId]
|
||||
if (!row) return
|
||||
|
||||
if (startHalf === 'AM' && endHalf === 'AM') {
|
||||
row.morningFrom = ''
|
||||
row.morningTo = ''
|
||||
return
|
||||
}
|
||||
|
||||
if (startHalf === 'PM' && endHalf === 'PM') {
|
||||
row.afternoonFrom = ''
|
||||
row.afternoonTo = ''
|
||||
row.eveningFrom = ''
|
||||
row.eveningTo = ''
|
||||
return
|
||||
}
|
||||
|
||||
row.morningFrom = ''
|
||||
row.morningTo = ''
|
||||
row.afternoonFrom = ''
|
||||
row.afternoonTo = ''
|
||||
row.eveningFrom = ''
|
||||
row.eveningTo = ''
|
||||
}
|
||||
|
||||
const refreshAfterAbsenceChange = async () => {
|
||||
if (isAdmin.value) {
|
||||
await Promise.all([loadWeeklySummary(), loadDayContext(), loadAbsences()])
|
||||
return
|
||||
}
|
||||
|
||||
weeklySummary.value = null
|
||||
await Promise.all([loadDayContext(), loadAbsences()])
|
||||
}
|
||||
|
||||
const submitAbsence = async () => {
|
||||
const form = absenceForm.value
|
||||
if (isAbsenceSubmitting.value || form.employeeId === '' || form.typeId === '') return
|
||||
@@ -504,9 +544,9 @@ export const useHoursPage = () => {
|
||||
})
|
||||
}
|
||||
|
||||
applyLocalClearFromAbsence(Number(form.employeeId), form.startHalf, form.endHalf)
|
||||
closeAbsenceDrawer()
|
||||
await refreshByDate()
|
||||
await loadAbsences()
|
||||
await refreshAfterAbsenceChange()
|
||||
} finally {
|
||||
isAbsenceSubmitting.value = false
|
||||
}
|
||||
@@ -519,8 +559,7 @@ export const useHoursPage = () => {
|
||||
try {
|
||||
await deleteAbsence(editingAbsence.value.id)
|
||||
closeAbsenceDrawer()
|
||||
await refreshByDate()
|
||||
await loadAbsences()
|
||||
await refreshAfterAbsenceChange()
|
||||
} finally {
|
||||
isAbsenceSubmitting.value = false
|
||||
}
|
||||
|
||||
@@ -6,17 +6,10 @@
|
||||
<img src="/malio.png" alt="Logo" class="w-auto"/>
|
||||
</div>
|
||||
<nav class="flex-1 px-4 pb-6">
|
||||
<NuxtLink
|
||||
to="/hours"
|
||||
class="flex items-center gap-3 px-4 pb-3 pt-6 text-md font-semibold text-neutral-700 hover:bg-tertiary-500 hover:text-primary-500 border-t border-secondary-500"
|
||||
active-class="bg-tertiary-500 text-primary-500"
|
||||
>
|
||||
Heures
|
||||
</NuxtLink>
|
||||
<template v-if="isAdmin">
|
||||
<NuxtLink
|
||||
to="/"
|
||||
class="flex items-center gap-3 px-4 py-3 text-md font-semibold text-neutral-700 hover:bg-tertiary-500 hover:text-primary-500"
|
||||
class="flex items-center gap-3 px-4 pb-3 pt-6 text-md font-semibold text-neutral-700 hover:bg-tertiary-500 hover:text-primary-500 border-t border-secondary-500"
|
||||
active-class="bg-tertiary-500 text-primary-500"
|
||||
>
|
||||
Tableau de bord
|
||||
@@ -28,6 +21,15 @@
|
||||
>
|
||||
Calendrier
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<NuxtLink
|
||||
to="/hours"
|
||||
class="flex items-center gap-3 px-4 py-3 text-md font-semibold text-neutral-700 hover:bg-tertiary-500 hover:text-primary-500"
|
||||
active-class="bg-tertiary-500 text-primary-500"
|
||||
>
|
||||
Heures
|
||||
</NuxtLink>
|
||||
<template v-if="isAdmin">
|
||||
<NuxtLink
|
||||
to="/employees"
|
||||
class="flex items-center gap-3 px-4 py-3 text-md font-semibold text-neutral-700 hover:bg-tertiary-500 hover:text-primary-500"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
v-model:employee-filter="employeeFilter"
|
||||
:is-admin="isAdmin"
|
||||
:sites="sites"
|
||||
:absence-types="absenceTypes"
|
||||
:formatted-selected-date="formattedSelectedDate"
|
||||
:shortcut-button-class="shortcutButtonClass"
|
||||
:week-shortcut-button-class="weekShortcutButtonClass"
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
export const TRACKING_MODES = {
|
||||
TIME: 'TIME',
|
||||
PRESENCE: 'PRESENCE'
|
||||
} as const
|
||||
|
||||
export type TrackingMode = (typeof TRACKING_MODES)[keyof typeof TRACKING_MODES]
|
||||
|
||||
export const CONTRACT_TYPES = {
|
||||
FORFAIT: 'FORFAIT',
|
||||
H35: '35H',
|
||||
H39: '39H',
|
||||
INTERIM: 'INTERIM',
|
||||
CUSTOM: 'CUSTOM'
|
||||
} as const
|
||||
|
||||
export type ContractType = (typeof CONTRACT_TYPES)[keyof typeof CONTRACT_TYPES]
|
||||
|
||||
export type Contract = {
|
||||
id: number
|
||||
name: string
|
||||
trackingMode: 'TIME' | 'PRESENCE'
|
||||
trackingMode: TrackingMode
|
||||
type: ContractType
|
||||
weeklyHours?: number | null
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Employee } from './employee'
|
||||
import type { ContractType, TrackingMode } from './contract'
|
||||
|
||||
export type WorkHour = {
|
||||
id: number
|
||||
@@ -33,6 +34,9 @@ export type WeeklyWorkHourDailySummary = {
|
||||
nightMinutes: number
|
||||
totalMinutes: number
|
||||
present?: number | null
|
||||
hasAbsence?: boolean
|
||||
absenceLabel?: string | null
|
||||
absenceColor?: string | null
|
||||
}
|
||||
|
||||
export type WeeklyWorkHourRowSummary = {
|
||||
@@ -41,7 +45,8 @@ export type WeeklyWorkHourRowSummary = {
|
||||
lastName: string
|
||||
siteName?: string | null
|
||||
contractName?: string | null
|
||||
trackingMode?: 'TIME' | 'PRESENCE' | null
|
||||
contractType?: ContractType | null
|
||||
trackingMode?: TrackingMode | null
|
||||
daily: WeeklyWorkHourDailySummary[]
|
||||
weeklyDayMinutes: number
|
||||
weeklyNightMinutes: number
|
||||
|
||||
Reference in New Issue
Block a user