Ajout des notification + page employé (#6)
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
| Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [ ] Pas de régression - [ ] TU/TI/TF rédigée - [ ] TU/TI/TF OK - [ ] CHANGELOG modifié Reviewed-on: #6 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #6.
This commit is contained in:
@@ -1,526 +0,0 @@
|
||||
<template>
|
||||
<div class="h-full overflow-hidden flex flex-col">
|
||||
<div class="shrink-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-4xl font-bold text-primary-500">Employés</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 py-6">
|
||||
<div class="flex justify-between">
|
||||
<SiteFilterSelector v-if="sites.length > 0" v-model="selectedSiteIds" :sites="sites" />
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
||||
@click="openCreate"
|
||||
>
|
||||
Ajouter un employé
|
||||
</button>
|
||||
</div>
|
||||
<div class="w-80">
|
||||
<EmployeeNameFilterInput v-model="employeeFilter" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!isLoading && filteredEmployees.length === 0"
|
||||
class="rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600"
|
||||
>
|
||||
Aucun employé pour le moment.
|
||||
</div>
|
||||
|
||||
<div v-else class="flex-1 min-h-0 rounded-lg border border-neutral-200 bg-white overflow-hidden">
|
||||
<div class="h-full overflow-auto">
|
||||
<div class="min-w-[900px]">
|
||||
<div class="grid grid-cols-[120px_1fr_1fr_180px_180px_200px] gap-4 border-b border-neutral-200 bg-tertiary-500 px-6 py-3 text-md font-semibold text-neutral-700 sticky top-0 z-10">
|
||||
<span class="text-left">Prénom</span>
|
||||
<span class="text-left">Nom</span>
|
||||
<span class="text-left">Site</span>
|
||||
<span class="text-left">Nature</span>
|
||||
<span class="text-left">Contrat</span>
|
||||
<span class="text-right">Actions</span>
|
||||
</div>
|
||||
<div v-if="isLoading" class="px-6 py-4 text-md text-neutral-500">
|
||||
Chargement...
|
||||
</div>
|
||||
<div v-else>
|
||||
<div
|
||||
v-for="employee in filteredEmployees"
|
||||
:key="employee.id"
|
||||
class="grid grid-cols-[120px_1fr_1fr_180px_180px_200px] items-center gap-4 border-b border-neutral-100 px-6 py-3 text-md text-neutral-800 last:border-b-0"
|
||||
>
|
||||
<span>{{ employee.firstName }}</span>
|
||||
<span>{{ employee.lastName }}</span>
|
||||
<span
|
||||
class="inline-flex w-fit max-w-full rounded-md px-2 py-1 text-sm font-semibold"
|
||||
:style="employee.site ? { backgroundColor: employee.site.color, color: '#0f172a' } : {}"
|
||||
:class="employee.site ? '' : 'bg-neutral-100 text-neutral-600'"
|
||||
>
|
||||
{{ employee.site?.name ?? '-' }}
|
||||
</span>
|
||||
<span>{{ contractNatureLabel(employee.currentContractNature) }}</span>
|
||||
<span>{{ employee.contract?.name ?? '-' }}</span>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-neutral-200 px-2 py-1 text-md font-semibold text-neutral-700 hover:bg-neutral-100"
|
||||
@click="openEdit(employee)"
|
||||
>
|
||||
Modifier
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-red-200 px-2 py-1 text-md font-semibold text-red-600 hover:bg-red-50"
|
||||
@click="confirmDelete(employee)"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AppDrawer v-model="isDrawerOpen" :title="drawerTitle">
|
||||
<form class="space-y-4" @submit.prevent="handleSubmit">
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="first-name">
|
||||
Prénom <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="first-name"
|
||||
v-model="form.firstName"
|
||||
type="text"
|
||||
:class="firstNameFieldClass"
|
||||
/>
|
||||
<p v-if="showFirstNameError" class="mt-1 text-sm text-red-600">
|
||||
Le prénom est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="last-name">
|
||||
Nom <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="last-name"
|
||||
v-model="form.lastName"
|
||||
type="text"
|
||||
:class="lastNameFieldClass"
|
||||
/>
|
||||
<p v-if="showLastNameError" class="mt-1 text-sm text-red-600">
|
||||
Le nom est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="site">
|
||||
Site <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="site"
|
||||
v-model="form.siteId"
|
||||
:class="siteFieldClass"
|
||||
>
|
||||
<option value="">Aucun site</option>
|
||||
<option v-for="site in sites" :key="site.id" :value="site.id">
|
||||
{{ site.name }}
|
||||
</option>
|
||||
</select>
|
||||
<p v-if="showSiteError" class="mt-1 text-sm text-red-600">
|
||||
Le site est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<template v-if="!editingEmployee">
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="contract-nature">
|
||||
Type de contrat <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="contract-nature"
|
||||
v-model="form.contractNature"
|
||||
:class="contractNatureFieldClass"
|
||||
>
|
||||
<option value="CDI">CDI</option>
|
||||
<option value="CDD">CDD</option>
|
||||
<option value="INTERIM">Intérim</option>
|
||||
</select>
|
||||
<p v-if="showContractNatureError" class="mt-1 text-sm text-red-600">
|
||||
Le type de contrat est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="contract">
|
||||
Temps de travail <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="contract"
|
||||
v-model="form.contractId"
|
||||
:class="contractFieldClass"
|
||||
>
|
||||
<option value="">Sélectionner un contrat</option>
|
||||
<option v-for="contract in contracts" :key="contract.id" :value="contract.id">
|
||||
{{ contract.name }}
|
||||
</option>
|
||||
</select>
|
||||
<p v-if="showContractError" class="mt-1 text-sm text-red-600">
|
||||
Le temps de travail est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="contract-start-date">
|
||||
Début contrat <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="contract-start-date"
|
||||
v-model="form.contractStartDate"
|
||||
type="date"
|
||||
:class="contractStartDateFieldClass"
|
||||
/>
|
||||
<p v-if="showContractStartDateError" class="mt-1 text-sm text-red-600">
|
||||
La date de début est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="requiresContractEndDate">
|
||||
<label class="text-md font-semibold text-neutral-700" for="contract-end-date">
|
||||
Fin contrat
|
||||
<span v-if="requiresContractEndDate" class="text-red-600">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="contract-end-date"
|
||||
v-model="form.contractEndDate"
|
||||
type="date"
|
||||
:class="contractEndDateFieldClass"
|
||||
/>
|
||||
<p v-if="showContractEndDateError" class="mt-1 text-sm text-red-600">
|
||||
La date de fin est obligatoire pour un CDD ou un intérim.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<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="isDrawerOpen = 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"
|
||||
:class="submitButtonClass"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</AppDrawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Contract } from '~/services/dto/contract'
|
||||
import type { Employee } from '~/services/dto/employee'
|
||||
import type { Site } from '~/services/dto/site'
|
||||
import { listContracts } from '~/services/contracts'
|
||||
import { createEmployee, deleteEmployee, listEmployees, updateEmployee } from '~/services/employees'
|
||||
import { listSites } from '~/services/sites'
|
||||
import SiteFilterSelector from '~/components/SiteFilterSelector.vue'
|
||||
useHead({
|
||||
title: 'Employés'
|
||||
})
|
||||
|
||||
const isDrawerOpen = ref(false)
|
||||
const isSubmitting = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const sitesInitialized = ref(false)
|
||||
const editingEmployee = ref<Employee | null>(null)
|
||||
const drawerTitle = computed(() =>
|
||||
editingEmployee.value ? 'Modifier un employé' : 'Ajouter un employé'
|
||||
)
|
||||
|
||||
const employees = ref<Employee[]>([])
|
||||
const sites = ref<Site[]>([])
|
||||
const contracts = ref<Contract[]>([])
|
||||
const employeeFilter = ref('')
|
||||
const selectedSiteIds = ref<number[]>([])
|
||||
|
||||
const filteredEmployees = computed(() => {
|
||||
if (selectedSiteIds.value.length === 0) return []
|
||||
|
||||
const filter = employeeFilter.value.trim().toLowerCase()
|
||||
const bySite = employees.value.filter((employee) => {
|
||||
const siteId = employee.site?.id
|
||||
return !!siteId && selectedSiteIds.value.includes(siteId)
|
||||
})
|
||||
|
||||
if (!filter) return bySite
|
||||
|
||||
return bySite.filter((employee) => {
|
||||
const firstName = employee.firstName?.toLowerCase() ?? ''
|
||||
const lastName = employee.lastName?.toLowerCase() ?? ''
|
||||
return firstName.includes(filter) || lastName.includes(filter)
|
||||
})
|
||||
})
|
||||
|
||||
const contractNatureLabel = (value?: 'CDI' | 'CDD' | 'INTERIM') => {
|
||||
if (value === 'CDD') return 'CDD'
|
||||
if (value === 'INTERIM') return 'Intérim'
|
||||
return 'CDI'
|
||||
}
|
||||
|
||||
const form = reactive({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
siteId: '' as number | '',
|
||||
contractId: '' as number | '',
|
||||
contractNature: 'CDI' as 'CDI' | 'CDD' | 'INTERIM',
|
||||
contractStartDate: '',
|
||||
contractEndDate: ''
|
||||
})
|
||||
|
||||
const validationTouched = reactive({
|
||||
firstName: false,
|
||||
lastName: false,
|
||||
siteId: false,
|
||||
contractId: false,
|
||||
contractNature: false,
|
||||
contractStartDate: false,
|
||||
contractEndDate: false
|
||||
})
|
||||
|
||||
const isFirstNameValid = computed(() => form.firstName.trim() !== '')
|
||||
const isLastNameValid = computed(() => form.lastName.trim() !== '')
|
||||
const isSiteValid = computed(() => form.siteId !== '')
|
||||
const isContractValid = computed(() => form.contractId !== '')
|
||||
const isContractNatureValid = computed(() => ['CDI', 'CDD', 'INTERIM'].includes(form.contractNature))
|
||||
const isContractStartDateValid = computed(() => form.contractStartDate !== '')
|
||||
const requiresContractEndDate = computed(() => form.contractNature === 'CDD' || form.contractNature === 'INTERIM')
|
||||
const isContractEndDateValid = computed(() => {
|
||||
if (!requiresContractEndDate.value) return true
|
||||
return form.contractEndDate !== ''
|
||||
})
|
||||
const isFormValid = computed(
|
||||
() =>
|
||||
isFirstNameValid.value &&
|
||||
isLastNameValid.value &&
|
||||
isSiteValid.value &&
|
||||
(editingEmployee.value
|
||||
? true
|
||||
: (isContractValid.value &&
|
||||
isContractNatureValid.value &&
|
||||
isContractStartDateValid.value &&
|
||||
isContractEndDateValid.value))
|
||||
)
|
||||
|
||||
const showFirstNameError = computed(
|
||||
() => validationTouched.firstName && !isFirstNameValid.value
|
||||
)
|
||||
const showLastNameError = computed(
|
||||
() => validationTouched.lastName && !isLastNameValid.value
|
||||
)
|
||||
const showSiteError = computed(
|
||||
() => validationTouched.siteId && !isSiteValid.value
|
||||
)
|
||||
const showContractError = computed(
|
||||
() => validationTouched.contractId && !isContractValid.value
|
||||
)
|
||||
const showContractNatureError = computed(
|
||||
() => !editingEmployee.value && validationTouched.contractNature && !isContractNatureValid.value
|
||||
)
|
||||
const showContractStartDateError = computed(
|
||||
() => !editingEmployee.value && validationTouched.contractStartDate && !isContractStartDateValid.value
|
||||
)
|
||||
const showContractEndDateError = computed(
|
||||
() => !editingEmployee.value && validationTouched.contractEndDate && !isContractEndDateValid.value
|
||||
)
|
||||
|
||||
const baseInputClass =
|
||||
'mt-2 w-full rounded-md border px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20'
|
||||
const firstNameFieldClass = computed(() => {
|
||||
if (showFirstNameError.value) {
|
||||
return `${baseInputClass} border-red-500`
|
||||
}
|
||||
return `${baseInputClass} border-neutral-300`
|
||||
})
|
||||
const lastNameFieldClass = computed(() => {
|
||||
if (showLastNameError.value) {
|
||||
return `${baseInputClass} border-red-500`
|
||||
}
|
||||
return `${baseInputClass} border-neutral-300`
|
||||
})
|
||||
const siteFieldClass = computed(() => {
|
||||
const baseSelectClass =
|
||||
'mt-2 w-full rounded-md border bg-white px-3 py-2 text-md text-neutral-900'
|
||||
if (showSiteError.value) {
|
||||
return `${baseSelectClass} border-red-500`
|
||||
}
|
||||
return `${baseSelectClass} border-neutral-300`
|
||||
})
|
||||
const contractFieldClass = computed(() => {
|
||||
const baseClass =
|
||||
'mt-2 w-full rounded-md border bg-white px-3 py-2 text-md text-neutral-900'
|
||||
if (showContractError.value) {
|
||||
return `${baseClass} border-red-500`
|
||||
}
|
||||
return `${baseClass} border-neutral-300`
|
||||
})
|
||||
const contractNatureFieldClass = computed(() => {
|
||||
const baseClass =
|
||||
'mt-2 w-full rounded-md border bg-white px-3 py-2 text-md text-neutral-900'
|
||||
if (showContractNatureError.value) {
|
||||
return `${baseClass} border-red-500`
|
||||
}
|
||||
return `${baseClass} border-neutral-300`
|
||||
})
|
||||
const contractStartDateFieldClass = computed(() => {
|
||||
if (showContractStartDateError.value) {
|
||||
return `${baseInputClass} border-red-500`
|
||||
}
|
||||
return `${baseInputClass} border-neutral-300`
|
||||
})
|
||||
const contractEndDateFieldClass = computed(() => {
|
||||
if (showContractEndDateError.value) {
|
||||
return `${baseInputClass} border-red-500`
|
||||
}
|
||||
return `${baseInputClass} border-neutral-300`
|
||||
})
|
||||
|
||||
const submitButtonClass = computed(() => {
|
||||
if (isSubmitting.value || !isFormValid.value) {
|
||||
return 'opacity-50 cursor-not-allowed'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const loadEmployees = async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
employees.value = await listEmployees()
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadSites = async () => {
|
||||
sites.value = await listSites()
|
||||
}
|
||||
|
||||
const loadContracts = async () => {
|
||||
contracts.value = await listContracts()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadEmployees(), loadSites(), loadContracts()])
|
||||
if (form.contractStartDate === '') {
|
||||
form.contractStartDate = new Date().toISOString().slice(0, 10)
|
||||
}
|
||||
})
|
||||
|
||||
watch(sites, (nextSites) => {
|
||||
const currentSiteIds = nextSites.map((site) => site.id)
|
||||
|
||||
if (!sitesInitialized.value) {
|
||||
if (currentSiteIds.length === 0) return
|
||||
selectedSiteIds.value = currentSiteIds
|
||||
sitesInitialized.value = true
|
||||
return
|
||||
}
|
||||
|
||||
selectedSiteIds.value = selectedSiteIds.value.filter((siteId) => currentSiteIds.includes(siteId))
|
||||
}, { immediate: true })
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting.value) return
|
||||
validationTouched.firstName = true
|
||||
validationTouched.lastName = true
|
||||
validationTouched.siteId = true
|
||||
if (!editingEmployee.value) {
|
||||
validationTouched.contractId = true
|
||||
validationTouched.contractNature = true
|
||||
validationTouched.contractStartDate = true
|
||||
validationTouched.contractEndDate = true
|
||||
}
|
||||
if (!isFormValid.value) return
|
||||
|
||||
isSubmitting.value = true
|
||||
try {
|
||||
if (editingEmployee.value) {
|
||||
await updateEmployee(editingEmployee.value.id, {
|
||||
firstName: form.firstName,
|
||||
lastName: form.lastName,
|
||||
siteId: form.siteId === '' ? null : Number(form.siteId),
|
||||
contractId: editingEmployee.value.contract?.id ?? Number(form.contractId)
|
||||
})
|
||||
} else {
|
||||
await createEmployee({
|
||||
firstName: form.firstName,
|
||||
lastName: form.lastName,
|
||||
siteId: form.siteId === '' ? null : Number(form.siteId),
|
||||
contractId: Number(form.contractId),
|
||||
contractNature: form.contractNature,
|
||||
contractStartDate: form.contractStartDate,
|
||||
contractEndDate: requiresContractEndDate.value ? form.contractEndDate : null
|
||||
})
|
||||
}
|
||||
|
||||
form.firstName = ''
|
||||
form.lastName = ''
|
||||
form.siteId = ''
|
||||
form.contractId = ''
|
||||
form.contractNature = 'CDI'
|
||||
form.contractStartDate = new Date().toISOString().slice(0, 10)
|
||||
form.contractEndDate = ''
|
||||
editingEmployee.value = null
|
||||
isDrawerOpen.value = false
|
||||
await loadEmployees()
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(isDrawerOpen, (isOpen) => {
|
||||
if (!isOpen) {
|
||||
validationTouched.firstName = false
|
||||
validationTouched.lastName = false
|
||||
validationTouched.siteId = false
|
||||
validationTouched.contractId = false
|
||||
validationTouched.contractNature = false
|
||||
validationTouched.contractStartDate = false
|
||||
validationTouched.contractEndDate = false
|
||||
}
|
||||
})
|
||||
|
||||
watch(requiresContractEndDate, (required) => {
|
||||
if (!required) {
|
||||
form.contractEndDate = ''
|
||||
}
|
||||
})
|
||||
|
||||
const openEdit = (employee: Employee) => {
|
||||
editingEmployee.value = employee
|
||||
form.firstName = employee.firstName
|
||||
form.lastName = employee.lastName
|
||||
form.siteId = employee.site?.id ?? ''
|
||||
isDrawerOpen.value = true
|
||||
}
|
||||
|
||||
const openCreate = () => {
|
||||
editingEmployee.value = null
|
||||
form.firstName = ''
|
||||
form.lastName = ''
|
||||
form.siteId = ''
|
||||
form.contractId = ''
|
||||
form.contractNature = 'CDI'
|
||||
form.contractStartDate = new Date().toISOString().slice(0, 10)
|
||||
form.contractEndDate = ''
|
||||
isDrawerOpen.value = true
|
||||
}
|
||||
|
||||
const confirmDelete = async (employee: Employee) => {
|
||||
const ok = window.confirm(`Supprimer ${employee.firstName} ${employee.lastName} ?`)
|
||||
if (!ok) return
|
||||
|
||||
await deleteEmployee(employee.id)
|
||||
await loadEmployees()
|
||||
}
|
||||
</script>
|
||||
155
frontend/pages/employees/[id].vue
Normal file
155
frontend/pages/employees/[id].vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<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">
|
||||
<h1 class="text-4xl font-bold text-primary-500">{{ employee.firstName }} {{ employee.lastName }}</h1>
|
||||
<div class="text-right">
|
||||
<p class="font-bold text-[20px]">{{ contractNatureLabel(employee.currentContractNature) }} {{ employeeContractWorkLabel }}</p>
|
||||
<p class="text-[18px]">{{ employee.site?.name ?? '-' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-12 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
|
||||
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>
|
||||
</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"
|
||||
:requires-create-contract-end-date="requiresCreateContractEndDate"
|
||||
:create-contract-end-date-field-class="createContractEndDateFieldClass"
|
||||
:is-create-contract-form-valid="isCreateContractFormValid"
|
||||
: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"
|
||||
/>
|
||||
<EmployeesLeaveTab
|
||||
v-else-if="showLeaveTab && activeTab === 'leave'"
|
||||
class="h-full"
|
||||
:absences="employeeAbsences"
|
||||
:summary="leaveSummary"
|
||||
:public-holidays="publicHolidays"
|
||||
@update-fractioned-days="submitFractionedDays"
|
||||
/>
|
||||
<EmployeesRttTab v-else class="h-full" :summary="rttSummary" @submit-rtt-payment="submitRttPayment" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const {
|
||||
employee,
|
||||
isLoading,
|
||||
activeTab,
|
||||
contracts,
|
||||
employeeAbsences,
|
||||
leaveSummary,
|
||||
rttSummary,
|
||||
publicHolidays,
|
||||
showLeaveTab,
|
||||
contractHistory,
|
||||
employeeContractWorkLabel,
|
||||
contractForm,
|
||||
createContractForm,
|
||||
isContractDrawerOpen,
|
||||
isContractSubmitting,
|
||||
isCreateContractDrawerOpen,
|
||||
isCreateContractSubmitting,
|
||||
canCloseCurrentContract,
|
||||
canCreateContract,
|
||||
readonlyFieldClass,
|
||||
closeContractWorkedHoursLabel,
|
||||
contractEndDateFieldClass,
|
||||
showContractEndDateError,
|
||||
isContractEndDateValid,
|
||||
createContractNatureFieldClass,
|
||||
createContractFieldClass,
|
||||
createContractStartDateFieldClass,
|
||||
requiresCreateContractEndDate,
|
||||
createContractEndDateFieldClass,
|
||||
isCreateContractFormValid,
|
||||
contractNatureLabel,
|
||||
contractHistoryLabel,
|
||||
formatDate,
|
||||
openCloseContractDrawer,
|
||||
openCreateContractDrawer,
|
||||
setContractDrawerOpen,
|
||||
setCreateContractDrawerOpen,
|
||||
submitContractUpdate,
|
||||
submitCreateContract,
|
||||
submitFractionedDays,
|
||||
submitRttPayment
|
||||
} = useEmployeeDetailPage()
|
||||
|
||||
useHead(() => ({
|
||||
title: employee.value
|
||||
? `${employee.value.firstName} ${employee.value.lastName}`
|
||||
: 'Détail employé'
|
||||
}))
|
||||
</script>
|
||||
496
frontend/pages/employees/index.vue
Normal file
496
frontend/pages/employees/index.vue
Normal file
@@ -0,0 +1,496 @@
|
||||
<template>
|
||||
<div class="flex-col">
|
||||
<div class="shrink-0">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-4xl font-bold text-primary-500">Employés</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-2 rounded-lg bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
||||
@click="openCreate"
|
||||
>
|
||||
+ Ajouter un employé
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex gap-10 py-7">
|
||||
<div class="w-80">
|
||||
<EmployeeNameFilterInput v-model="employeeFilter"/>
|
||||
</div>
|
||||
<SiteFilterSelector v-if="sites.length > 0" v-model="selectedSiteIds" :sites="sites"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!isLoading && filteredEmployees.length === 0"
|
||||
class="rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600"
|
||||
>
|
||||
Aucun employé pour le moment.
|
||||
</div>
|
||||
|
||||
<div v-else class="grid gap-8 [grid-template-columns:repeat(auto-fill,minmax(260px,1fr))]">
|
||||
<NuxtLink
|
||||
v-for="employee in filteredEmployees"
|
||||
:key="employee.id"
|
||||
:to="`/employees/${employee.id}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="group relative min-h-[328px] overflow-hidden rounded-lg bg-tertiary-500 p-4 transition-all duration-200 hover:shadow-md"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-7 transition-opacity duration-200 group-hover:opacity-0">
|
||||
<div class="rounded-full bg-primary-500 h-[175px] w-[175px] flex justify-center items-center text-white font-bold text-5xl">{{ employee.initials}}</div>
|
||||
<div class="text-center text-[20px]">
|
||||
<p class="text-primary-500 font-bold">{{ employee.firstName }} {{ employee.lastName }}</p>
|
||||
<p>Nom du poste occupé</p>
|
||||
<p>Site ({{ employee.site?.name ?? '-' }})</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-center bg-primary-500 p-4 text-white opacity-0 transition-opacity duration-200 group-hover:opacity-100">
|
||||
<div class="w-full rounded-md bg-white/15 p-4 text-sm">
|
||||
<p class="text-base font-semibold">{{ employee.lastName }} {{ employee.firstName }}</p>
|
||||
<p><strong>Type:</strong> {{ contractNatureLabel(employee.currentContractNature) }}</p>
|
||||
<p><strong>Temps de travail:</strong> {{ employee.contract?.name ?? '-' }}</p>
|
||||
<p><strong>Site:</strong> {{ employee.site?.name ?? '-' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<AppDrawer v-model="isDrawerOpen" :title="drawerTitle">
|
||||
<form class="space-y-4" @submit.prevent="handleSubmit">
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="first-name">
|
||||
Prénom <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="first-name"
|
||||
v-model="form.firstName"
|
||||
type="text"
|
||||
:class="firstNameFieldClass"
|
||||
/>
|
||||
<p v-if="showFirstNameError" class="mt-1 text-sm text-red-600">
|
||||
Le prénom est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="last-name">
|
||||
Nom <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="last-name"
|
||||
v-model="form.lastName"
|
||||
type="text"
|
||||
:class="lastNameFieldClass"
|
||||
/>
|
||||
<p v-if="showLastNameError" class="mt-1 text-sm text-red-600">
|
||||
Le nom est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="site">
|
||||
Site <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="site"
|
||||
v-model="form.siteId"
|
||||
:class="siteFieldClass"
|
||||
>
|
||||
<option value="">Aucun site</option>
|
||||
<option v-for="site in sites" :key="site.id" :value="site.id">
|
||||
{{ site.name }}
|
||||
</option>
|
||||
</select>
|
||||
<p v-if="showSiteError" class="mt-1 text-sm text-red-600">
|
||||
Le site est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<template v-if="!editingEmployee">
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="contract-nature">
|
||||
Type de contrat <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="contract-nature"
|
||||
v-model="form.contractNature"
|
||||
:class="contractNatureFieldClass"
|
||||
>
|
||||
<option value="CDI">CDI</option>
|
||||
<option value="CDD">CDD</option>
|
||||
<option value="INTERIM">Intérim</option>
|
||||
</select>
|
||||
<p v-if="showContractNatureError" class="mt-1 text-sm text-red-600">
|
||||
Le type de contrat est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="contract">
|
||||
Temps de travail <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="contract"
|
||||
v-model="form.contractId"
|
||||
:class="contractFieldClass"
|
||||
>
|
||||
<option value="">Sélectionner un contrat</option>
|
||||
<option v-for="contract in contracts" :key="contract.id" :value="contract.id">
|
||||
{{ contract.name }}
|
||||
</option>
|
||||
</select>
|
||||
<p v-if="showContractError" class="mt-1 text-sm text-red-600">
|
||||
Le temps de travail est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-md font-semibold text-neutral-700" for="contract-start-date">
|
||||
Début contrat <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="contract-start-date"
|
||||
v-model="form.contractStartDate"
|
||||
type="date"
|
||||
:class="contractStartDateFieldClass"
|
||||
/>
|
||||
<p v-if="showContractStartDateError" class="mt-1 text-sm text-red-600">
|
||||
La date de début est obligatoire.
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="requiresContractEndDateComputed">
|
||||
<label class="text-md font-semibold text-neutral-700" for="contract-end-date">
|
||||
Fin contrat
|
||||
<span v-if="requiresContractEndDateComputed" class="text-red-600">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="contract-end-date"
|
||||
v-model="form.contractEndDate"
|
||||
type="date"
|
||||
:class="contractEndDateFieldClass"
|
||||
/>
|
||||
<p v-if="showContractEndDateError" class="mt-1 text-sm text-red-600">
|
||||
La date de fin est obligatoire pour un CDD ou un intérim.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<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="isDrawerOpen = 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"
|
||||
:class="submitButtonClass"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</AppDrawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type {Contract} from '~/services/dto/contract'
|
||||
import type {Employee} from '~/services/dto/employee'
|
||||
import type {Site} from '~/services/dto/site'
|
||||
import {listContracts} from '~/services/contracts'
|
||||
import {createEmployee, deleteEmployee, listEmployees, updateEmployee} from '~/services/employees'
|
||||
import {listSites} from '~/services/sites'
|
||||
import SiteFilterSelector from '~/components/SiteFilterSelector.vue'
|
||||
import {contractNatureLabel, isContractNature, requiresContractEndDate} from '~/utils/contract'
|
||||
|
||||
useHead({
|
||||
title: 'Employés'
|
||||
})
|
||||
|
||||
const isDrawerOpen = ref(false)
|
||||
const isSubmitting = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const sitesInitialized = ref(false)
|
||||
const editingEmployee = ref<Employee | null>(null)
|
||||
const drawerTitle = computed(() =>
|
||||
editingEmployee.value ? 'Modifier un employé' : 'Ajouter un employé'
|
||||
)
|
||||
|
||||
const employees = ref<Employee[]>([])
|
||||
const sites = ref<Site[]>([])
|
||||
const contracts = ref<Contract[]>([])
|
||||
const employeeFilter = ref('')
|
||||
const selectedSiteIds = ref<number[]>([])
|
||||
|
||||
const filteredEmployees = computed<Employee[]>(() => {
|
||||
if (selectedSiteIds.value.length === 0) return []
|
||||
|
||||
const filter = employeeFilter.value.trim().toLowerCase()
|
||||
const bySite = employees.value.filter((employee) => {
|
||||
const siteId = employee.site?.id
|
||||
return !!siteId && selectedSiteIds.value.includes(siteId)
|
||||
})
|
||||
|
||||
if (!filter) return bySite
|
||||
|
||||
return bySite.filter((employee) => {
|
||||
const firstName = employee.firstName?.toLowerCase() ?? ''
|
||||
const lastName = employee.lastName?.toLowerCase() ?? ''
|
||||
return firstName.includes(filter) || lastName.includes(filter)
|
||||
})
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
siteId: '' as number | '',
|
||||
contractId: '' as number | '',
|
||||
contractNature: 'CDI' as 'CDI' | 'CDD' | 'INTERIM',
|
||||
contractStartDate: '',
|
||||
contractEndDate: ''
|
||||
})
|
||||
|
||||
const validationTouched = reactive({
|
||||
firstName: false,
|
||||
lastName: false,
|
||||
siteId: false,
|
||||
contractId: false,
|
||||
contractNature: false,
|
||||
contractStartDate: false,
|
||||
contractEndDate: false
|
||||
})
|
||||
|
||||
const isFirstNameValid = computed(() => form.firstName.trim() !== '')
|
||||
const isLastNameValid = computed(() => form.lastName.trim() !== '')
|
||||
const isSiteValid = computed(() => form.siteId !== '')
|
||||
const isContractValid = computed(() => form.contractId !== '')
|
||||
const isContractNatureValid = computed(() => isContractNature(form.contractNature))
|
||||
const isContractStartDateValid = computed(() => form.contractStartDate !== '')
|
||||
const requiresContractEndDateComputed = computed(() => requiresContractEndDate(form.contractNature))
|
||||
const isContractEndDateValid = computed(() => {
|
||||
if (!requiresContractEndDateComputed.value) return true
|
||||
return form.contractEndDate !== ''
|
||||
})
|
||||
const isFormValid = computed(
|
||||
() =>
|
||||
isFirstNameValid.value &&
|
||||
isLastNameValid.value &&
|
||||
isSiteValid.value &&
|
||||
(editingEmployee.value
|
||||
? true
|
||||
: (isContractValid.value &&
|
||||
isContractNatureValid.value &&
|
||||
isContractStartDateValid.value &&
|
||||
isContractEndDateValid.value))
|
||||
)
|
||||
|
||||
const showFirstNameError = computed(
|
||||
() => validationTouched.firstName && !isFirstNameValid.value
|
||||
)
|
||||
const showLastNameError = computed(
|
||||
() => validationTouched.lastName && !isLastNameValid.value
|
||||
)
|
||||
const showSiteError = computed(
|
||||
() => validationTouched.siteId && !isSiteValid.value
|
||||
)
|
||||
const showContractError = computed(
|
||||
() => validationTouched.contractId && !isContractValid.value
|
||||
)
|
||||
const showContractNatureError = computed(
|
||||
() => !editingEmployee.value && validationTouched.contractNature && !isContractNatureValid.value
|
||||
)
|
||||
const showContractStartDateError = computed(
|
||||
() => !editingEmployee.value && validationTouched.contractStartDate && !isContractStartDateValid.value
|
||||
)
|
||||
const showContractEndDateError = computed(
|
||||
() => !editingEmployee.value && validationTouched.contractEndDate && !isContractEndDateValid.value
|
||||
)
|
||||
|
||||
const baseInputClass =
|
||||
'mt-2 w-full rounded-md border px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20'
|
||||
const firstNameFieldClass = computed(() => {
|
||||
if (showFirstNameError.value) {
|
||||
return `${baseInputClass} border-red-500`
|
||||
}
|
||||
return `${baseInputClass} border-neutral-300`
|
||||
})
|
||||
const lastNameFieldClass = computed(() => {
|
||||
if (showLastNameError.value) {
|
||||
return `${baseInputClass} border-red-500`
|
||||
}
|
||||
return `${baseInputClass} border-neutral-300`
|
||||
})
|
||||
const siteFieldClass = computed(() => {
|
||||
const baseSelectClass =
|
||||
'mt-2 w-full rounded-md border bg-white px-3 py-2 text-md text-neutral-900'
|
||||
if (showSiteError.value) {
|
||||
return `${baseSelectClass} border-red-500`
|
||||
}
|
||||
return `${baseSelectClass} border-neutral-300`
|
||||
})
|
||||
const contractFieldClass = computed(() => {
|
||||
const baseClass =
|
||||
'mt-2 w-full rounded-md border bg-white px-3 py-2 text-md text-neutral-900'
|
||||
if (showContractError.value) {
|
||||
return `${baseClass} border-red-500`
|
||||
}
|
||||
return `${baseClass} border-neutral-300`
|
||||
})
|
||||
const contractNatureFieldClass = computed(() => {
|
||||
const baseClass =
|
||||
'mt-2 w-full rounded-md border bg-white px-3 py-2 text-md text-neutral-900'
|
||||
if (showContractNatureError.value) {
|
||||
return `${baseClass} border-red-500`
|
||||
}
|
||||
return `${baseClass} border-neutral-300`
|
||||
})
|
||||
const contractStartDateFieldClass = computed(() => {
|
||||
if (showContractStartDateError.value) {
|
||||
return `${baseInputClass} border-red-500`
|
||||
}
|
||||
return `${baseInputClass} border-neutral-300`
|
||||
})
|
||||
const contractEndDateFieldClass = computed(() => {
|
||||
if (showContractEndDateError.value) {
|
||||
return `${baseInputClass} border-red-500`
|
||||
}
|
||||
return `${baseInputClass} border-neutral-300`
|
||||
})
|
||||
|
||||
const submitButtonClass = computed(() => {
|
||||
if (isSubmitting.value || !isFormValid.value) {
|
||||
return 'opacity-50 cursor-not-allowed'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const loadEmployees = async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
employees.value = await listEmployees()
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadSites = async () => {
|
||||
sites.value = await listSites()
|
||||
}
|
||||
|
||||
const loadContracts = async () => {
|
||||
contracts.value = await listContracts()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadEmployees(), loadSites(), loadContracts()])
|
||||
if (form.contractStartDate === '') {
|
||||
form.contractStartDate = new Date().toISOString().slice(0, 10)
|
||||
}
|
||||
})
|
||||
|
||||
watch(sites, (nextSites) => {
|
||||
const currentSiteIds = nextSites.map((site) => site.id)
|
||||
|
||||
if (!sitesInitialized.value) {
|
||||
if (currentSiteIds.length === 0) return
|
||||
selectedSiteIds.value = currentSiteIds
|
||||
sitesInitialized.value = true
|
||||
return
|
||||
}
|
||||
|
||||
selectedSiteIds.value = selectedSiteIds.value.filter((siteId) => currentSiteIds.includes(siteId))
|
||||
}, {immediate: true})
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting.value) return
|
||||
validationTouched.firstName = true
|
||||
validationTouched.lastName = true
|
||||
validationTouched.siteId = true
|
||||
if (!editingEmployee.value) {
|
||||
validationTouched.contractId = true
|
||||
validationTouched.contractNature = true
|
||||
validationTouched.contractStartDate = true
|
||||
validationTouched.contractEndDate = true
|
||||
}
|
||||
if (!isFormValid.value) return
|
||||
|
||||
isSubmitting.value = true
|
||||
try {
|
||||
if (editingEmployee.value) {
|
||||
await updateEmployee(editingEmployee.value.id, {
|
||||
firstName: form.firstName,
|
||||
lastName: form.lastName,
|
||||
siteId: form.siteId === '' ? null : Number(form.siteId),
|
||||
contractId: editingEmployee.value.contract?.id ?? Number(form.contractId)
|
||||
})
|
||||
} else {
|
||||
await createEmployee({
|
||||
firstName: form.firstName,
|
||||
lastName: form.lastName,
|
||||
siteId: form.siteId === '' ? null : Number(form.siteId),
|
||||
contractId: Number(form.contractId),
|
||||
contractNature: form.contractNature,
|
||||
contractStartDate: form.contractStartDate,
|
||||
contractEndDate: requiresContractEndDateComputed.value ? form.contractEndDate : null
|
||||
})
|
||||
}
|
||||
|
||||
form.firstName = ''
|
||||
form.lastName = ''
|
||||
form.siteId = ''
|
||||
form.contractId = ''
|
||||
form.contractNature = 'CDI'
|
||||
form.contractStartDate = new Date().toISOString().slice(0, 10)
|
||||
form.contractEndDate = ''
|
||||
editingEmployee.value = null
|
||||
isDrawerOpen.value = false
|
||||
await loadEmployees()
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(isDrawerOpen, (isOpen) => {
|
||||
if (!isOpen) {
|
||||
validationTouched.firstName = false
|
||||
validationTouched.lastName = false
|
||||
validationTouched.siteId = false
|
||||
validationTouched.contractId = false
|
||||
validationTouched.contractNature = false
|
||||
validationTouched.contractStartDate = false
|
||||
validationTouched.contractEndDate = false
|
||||
}
|
||||
})
|
||||
|
||||
watch(requiresContractEndDateComputed, (required) => {
|
||||
if (!required) {
|
||||
form.contractEndDate = ''
|
||||
}
|
||||
})
|
||||
|
||||
const openEdit = (employee: Employee) => {
|
||||
editingEmployee.value = employee
|
||||
form.firstName = employee.firstName
|
||||
form.lastName = employee.lastName
|
||||
form.siteId = employee.site?.id ?? ''
|
||||
isDrawerOpen.value = true
|
||||
}
|
||||
|
||||
const openCreate = () => {
|
||||
editingEmployee.value = null
|
||||
form.firstName = ''
|
||||
form.lastName = ''
|
||||
form.siteId = ''
|
||||
form.contractId = ''
|
||||
form.contractNature = 'CDI'
|
||||
form.contractStartDate = new Date().toISOString().slice(0, 10)
|
||||
form.contractEndDate = ''
|
||||
isDrawerOpen.value = true
|
||||
}
|
||||
|
||||
const confirmDelete = async (employee: Employee) => {
|
||||
const ok = window.confirm(`Supprimer ${employee.firstName} ${employee.lastName} ?`)
|
||||
if (!ok) return
|
||||
|
||||
await deleteEmployee(employee.id)
|
||||
await loadEmployees()
|
||||
}
|
||||
</script>
|
||||
@@ -54,6 +54,7 @@
|
||||
:is-site-validation-pending="isSiteValidationPending"
|
||||
:can-toggle-validation="canToggleValidation"
|
||||
:can-toggle-site-validation="canToggleSiteValidation"
|
||||
:can-create-site-validation-row-from-absence="canCreateSiteValidationRowFromAbsence"
|
||||
:is-bulk-validation-checked="isBulkValidationChecked"
|
||||
:is-bulk-validation-indeterminate="isBulkValidationIndeterminate"
|
||||
:is-bulk-site-validation-checked="isBulkSiteValidationChecked"
|
||||
@@ -66,6 +67,7 @@
|
||||
:get-row-metrics="getRowMetrics"
|
||||
:get-row-absence-label="getRowAbsenceLabel"
|
||||
:get-row-absence-style="getRowAbsenceStyle"
|
||||
:get-row-updated-at="getRowUpdatedAt"
|
||||
:get-presence-day-value="getPresenceDayValue"
|
||||
:on-absence-click="openAbsenceDrawer"
|
||||
:format-minutes="formatMinutes"
|
||||
@@ -161,6 +163,7 @@ const {
|
||||
isSiteValidationPending,
|
||||
canToggleValidation,
|
||||
canToggleSiteValidation,
|
||||
canCreateSiteValidationRowFromAbsence,
|
||||
isBulkValidationChecked,
|
||||
isBulkValidationIndeterminate,
|
||||
isBulkSiteValidationChecked,
|
||||
@@ -173,6 +176,7 @@ const {
|
||||
getRowMetrics,
|
||||
getRowAbsenceLabel,
|
||||
getRowAbsenceStyle,
|
||||
getRowUpdatedAt,
|
||||
getPresenceDayValue,
|
||||
openAbsenceDrawer,
|
||||
submitAbsence,
|
||||
|
||||
Reference in New Issue
Block a user