Files
SIRH/frontend/pages/employees/[id].vue
tristan 47f9bea57d
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
feat : sélecteur d'exercice sur l'onglet RTT de la fiche employé
Permet de consulter les exercices passés (table hebdomadaire RTT) en
réutilisant le pattern de l'onglet Congés. Plage bornée par
max(début historique contrat, RTT_START_DATE). Bouton + Payer les RTT
verrouillé sur exercices clos. Onglet masqué pour FORFAIT (inchangé).

Backend : rttStartDate désormais toujours exposé sur EmployeeRttSummary
pour que le sélecteur conserve sa borne lors de la navigation vers un
exercice passé. Le masquage existant des lignes Report continue de
fonctionner (comparaison mois-à-mois).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 09:58:50 +02:00

361 lines
16 KiB
Vue

<template>
<div class="h-full overflow-hidden flex flex-col">
<div v-if="isLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
Chargement...
</div>
<div v-else-if="!employee"
class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
Employé introuvable.
</div>
<div v-else class="flex min-h-0 flex-1 flex-col">
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-4">
<h1 class="text-[32px] font-bold">{{ employee.firstName }} {{ employee.lastName }}</h1>
<button
class="inline-flex items-center justify-center rounded-md p-1 transition-colors duration-150 focus:outline-none focus-visible:ring-2 bg-primary-500 hover:bg-secondary-500 active:bg-primary-500 text-white cursor-pointer"
title="Export heures"
@click="isYearlyHoursDrawerOpen = true"
>
<Icon name="mdi:printer" size="24" />
</button>
</div>
<p>Date d'entrée : {{ employee.entryDate ? employee.entryDate.split('-').reverse().join('/') : '-' }}</p>
</div>
<div class="text-right">
<p class="font-bold text-[22px]">{{ contractNatureLabel(employee.currentContractNature) }} {{ employeeContractWorkLabel }}{{ forfaitRemainingDaysLabel }}</p>
<p class="text-[18px]">{{ employee.site?.name ?? '-' }}</p>
</div>
</div>
<div class="mt-[44px] border-b border-primary-500">
<div class="flex justify-center gap-16 text-2xl font-bold">
<button
class="pb-2 border-b-2 flex items-center gap-3"
:class="activeTab === 'contract'
? 'border-primary-500 text-primary-500'
: 'border-transparent text-primary-500/50 hover:text-primary-500'"
@click="activeTab = 'contract'"
>
<Icon name="mdi:file-check-outline" size="24" class="align-self"/>
Suivi contrat
</button>
<button
v-if="showLeaveTab"
class="pb-2 border-b-2 flex items-center gap-3"
:class="activeTab === 'leave'
? 'border-primary-500 text-primary-500'
: 'border-transparent text-primary-500/50 hover:text-primary-500'"
@click="activeTab = 'leave'"
>
<Icon name="mdi:event-blank-outline" size="24" class="align-self"/>
Congé
</button>
<button
v-if="showRttTab"
class="pb-2 border-b-2 flex items-center gap-3"
:class="activeTab === 'rtt'
? 'border-primary-500 text-primary-500'
: 'border-transparent text-primary-500/50 hover:text-primary-500'"
@click="activeTab = 'rtt'"
>
<Icon name="mdi:schedule" size="24" class="align-self"/>
RTT
</button>
<button
class="pb-2 border-b-2 flex items-center gap-3"
:class="activeTab === 'mileage'
? 'border-primary-500 text-primary-500'
: 'border-transparent text-primary-500/50 hover:text-primary-500'"
@click="activeTab = 'mileage'"
>
<Icon name="mdi:account-cash-outline" size="24" class="align-self"/>
Frais
</button>
<button
class="pb-2 border-b-2 flex items-center gap-3"
:class="activeTab === 'formation'
? 'border-primary-500 text-primary-500'
: 'border-transparent text-primary-500/50 hover:text-primary-500'"
@click="activeTab = 'formation'"
>
<Icon name="mdi:school-outline" size="24" class="align-self"/>
Formation
</button>
<button
class="pb-2 border-b-2 flex items-center gap-3"
:class="activeTab === 'bonus'
? 'border-primary-500 text-primary-500'
: 'border-transparent text-primary-500/50 hover:text-primary-500'"
@click="activeTab = 'bonus'"
>
<Icon name="mdi:money-100" size="24" class="align-self"/>
Prime
</button>
<button
class="pb-2 border-b-2 flex items-center gap-3"
:class="activeTab === 'observation'
? 'border-primary-500 text-primary-500'
: 'border-transparent text-primary-500/50 hover:text-primary-500'"
@click="activeTab = 'observation'"
>
<Icon name="mdi:note-text-outline" size="24" class="align-self"/>
Observation
</button>
</div>
</div>
<div class="min-h-0 flex-1">
<EmployeesContractTab
v-if="activeTab === 'contract'"
class="h-full overflow-y-auto pr-1"
:contract-history="contractHistory"
:contract-nature-label="contractNatureLabel"
:contract-history-label="contractHistoryLabel"
:format-date="formatDate"
:is-contract-submitting="isContractSubmitting"
:can-close-current-contract="canCloseCurrentContract"
:is-create-contract-submitting="isCreateContractSubmitting"
:contracts="contracts"
:can-create-contract="canCreateContract"
:is-contract-drawer-open="isContractDrawerOpen"
:contract-form="contractForm"
:readonly-field-class="readonlyFieldClass"
:close-contract-worked-hours-label="closeContractWorkedHoursLabel"
:contract-end-date-field-class="contractEndDateFieldClass"
:show-contract-end-date-error="showContractEndDateError"
:is-contract-end-date-valid="isContractEndDateValid"
:is-create-contract-drawer-open="isCreateContractDrawerOpen"
:create-contract-form="createContractForm"
:create-contract-nature-field-class="createContractNatureFieldClass"
:create-contract-field-class="createContractFieldClass"
:create-contract-start-date-field-class="createContractStartDateFieldClass"
:shows-create-contract-end-date="showsCreateContractEndDate"
:requires-create-contract-end-date="requiresCreateContractEndDate"
:create-contract-end-date-field-class="createContractEndDateFieldClass"
:is-create-contract-form-valid="isCreateContractFormValid"
:requires-create-work-days-hours="requiresCreateWorkDaysHours"
:selected-create-contract="selectedCreateContract"
:on-open-close-contract-drawer="openCloseContractDrawer"
:on-open-create-contract-drawer="openCreateContractDrawer"
:on-update-contract-drawer-open="setContractDrawerOpen"
:on-update-create-contract-drawer-open="setCreateContractDrawerOpen"
:on-submit-close-contract="submitContractUpdate"
:on-submit-create-contract="submitCreateContract"
:suspension-forms="suspensionForms"
:is-suspension-submitting="isSuspensionSubmitting"
:on-submit-suspension="submitSuspension"
:on-add-suspension-form="addSuspensionForm"
:current-contract-period-id="currentActiveContractPeriodId"
:interim-agencies="interimAgencies"
/>
<div v-else-if="showLeaveTab && activeTab === 'leave'" class="h-full">
<div v-if="isLeaveLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
Chargement...
</div>
<EmployeesLeaveTab
v-else
class="h-full"
:absences="employeeAbsences"
:summary="leaveSummary"
:public-holidays="publicHolidays"
:selected-year="selectedLeaveYear"
:available-years="availableLeaveYears"
:current-year="currentLeaveYear"
@update-fractioned-days="submitFractionedDays"
@update-paid-leave-days="submitPaidLeaveDays"
@update-selected-year="setSelectedLeaveYear"
/>
</div>
<div v-else-if="showRttTab && activeTab === 'rtt'" class="h-full">
<div v-if="isRttLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
Chargement...
</div>
<EmployeesRttTab
v-else
class="h-full"
:summary="rttSummary"
:selected-year="selectedRttYear"
:available-years="availableRttYears"
:current-year="currentRttYear"
@submit-rtt-payment="submitRttPayment"
@update-selected-year="setSelectedRttYear"
/>
</div>
<div v-else-if="activeTab === 'mileage'" class="h-full">
<div v-if="isMileageLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
Chargement...
</div>
<EmployeesMileageTab
v-else
class="h-full"
:allowances="mileageAllowances"
:api-base="mileageApiBase"
@create="submitCreateMileage"
@update="submitUpdateMileage"
@delete="submitDeleteMileage"
/>
</div>
<div v-else-if="activeTab === 'formation'" class="h-full">
<div v-if="isFormationLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
Chargement...
</div>
<EmployeesFormationTab
v-else
class="h-full"
:formations="formations"
:api-base="formationApiBase"
@create="submitCreateFormation"
@update="submitUpdateFormation"
@delete="submitDeleteFormation"
/>
</div>
<div v-else-if="activeTab === 'bonus'" class="h-full">
<div v-if="isBonusLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
Chargement...
</div>
<EmployeesBonusTab
v-else
class="h-full"
:bonuses="bonuses"
@create="submitCreateBonus"
@update="submitUpdateBonus"
@delete="submitDeleteBonus"
/>
</div>
<div v-else-if="activeTab === 'observation'" class="h-full">
<div v-if="isObservationLoading" class="mt-6 rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600">
Chargement...
</div>
<EmployeesObservationTab
v-else
class="h-full"
:observations="observations"
@create="submitCreateObservation"
@update="submitUpdateObservation"
@delete="submitDeleteObservation"
/>
</div>
</div>
</div>
<EmployeeYearlyHoursDrawer
v-if="employee"
v-model="isYearlyHoursDrawerOpen"
:employee-id="employee.id"
@submit="handleYearlyHoursPrint"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import EmployeeYearlyHoursDrawer from '~/components/EmployeeYearlyHoursDrawer.vue'
import { usePdfPrinter } from '~/composables/usePdfPrinter'
const { printPdf } = usePdfPrinter()
const isYearlyHoursDrawerOpen = ref(false)
const {
employee,
isLoading,
activeTab,
contracts,
employeeAbsences,
leaveSummary,
rttSummary,
publicHolidays,
selectedLeaveYear,
currentLeaveYear,
availableLeaveYears,
setSelectedLeaveYear,
selectedRttYear,
currentRttYear,
availableRttYears,
setSelectedRttYear,
showLeaveTab,
showRttTab,
contractHistory,
employeeContractWorkLabel,
forfaitRemainingDaysLabel,
contractForm,
createContractForm,
isContractDrawerOpen,
isContractSubmitting,
isCreateContractDrawerOpen,
isCreateContractSubmitting,
canCloseCurrentContract,
canCreateContract,
readonlyFieldClass,
closeContractWorkedHoursLabel,
contractEndDateFieldClass,
showContractEndDateError,
isContractEndDateValid,
createContractNatureFieldClass,
createContractFieldClass,
createContractStartDateFieldClass,
showsCreateContractEndDate,
requiresCreateContractEndDate,
createContractEndDateFieldClass,
isCreateContractFormValid,
requiresCreateWorkDaysHours,
selectedCreateContract,
contractNatureLabel,
contractHistoryLabel,
formatDate,
openCloseContractDrawer,
openCreateContractDrawer,
setContractDrawerOpen,
setCreateContractDrawerOpen,
submitContractUpdate,
submitCreateContract,
submitFractionedDays,
submitPaidLeaveDays,
submitRttPayment,
suspensionForms,
isSuspensionSubmitting,
submitSuspension,
addSuspensionForm,
currentActiveContractPeriodId,
interimAgencies,
isLeaveLoading,
isRttLoading,
mileageAllowances,
isMileageLoading,
mileageApiBase,
submitCreateMileage,
submitUpdateMileage,
submitDeleteMileage,
formations,
isFormationLoading,
formationApiBase,
submitCreateFormation,
submitUpdateFormation,
submitDeleteFormation,
bonuses,
isBonusLoading,
submitCreateBonus,
submitUpdateBonus,
submitDeleteBonus,
observations,
isObservationLoading,
submitCreateObservation,
submitUpdateObservation,
submitDeleteObservation
} = useEmployeeDetailPage()
const handleYearlyHoursPrint = async (payload: { year: number; month: number | null }) => {
if (!employee.value) return
const monthParam = null !== payload.month ? `&month=${payload.month}` : ''
await printPdf(`/yearly-hours/print?employeeId=${employee.value.id}&year=${payload.year}${monthParam}`)
isYearlyHoursDrawerOpen.value = false
}
useHead(() => ({
title: employee.value
? `${employee.value.firstName} ${employee.value.lastName}`
: 'Détail employé'
}))
</script>