feat : ajout de la page de résumé employé
This commit is contained in:
@@ -131,6 +131,9 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer.
|
|||||||
- Modification employé:
|
- Modification employé:
|
||||||
- uniquement prénom, nom, site
|
- uniquement prénom, nom, site
|
||||||
- pas de modification de contrat depuis ce drawer
|
- pas de modification de contrat depuis ce drawer
|
||||||
|
- Détail employé:
|
||||||
|
- onglet `Suivi contrat` avec affichage de l'historique des périodes de contrat
|
||||||
|
- chaque ligne expose: nature (`CDI`/`CDD`/`INTERIM`), contrat/temps de travail, date de début, date de fin (ou "En cours")
|
||||||
|
|
||||||
## 10) Notifications
|
## 10) Notifications
|
||||||
|
|
||||||
|
|||||||
@@ -62,13 +62,6 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 items-center p-4">
|
<div class="flex flex-col gap-2 items-center p-4">
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="w-full rounded-lg px-4 py-2 text-md font-semibold text-white bg-primary-500"
|
|
||||||
@click="handleLogout"
|
|
||||||
>
|
|
||||||
Déconnexion
|
|
||||||
</button>
|
|
||||||
<p class="font-bold">v{{ version }}</p>
|
<p class="font-bold">v{{ version }}</p>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -13,34 +13,96 @@
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h1 class="text-4xl font-bold text-primary-500">{{ employee.firstName }} {{ employee.lastName }}</h1>
|
<h1 class="text-4xl font-bold text-primary-500">{{ employee.firstName }} {{ employee.lastName }}</h1>
|
||||||
<NuxtLink
|
<div class="text-right">
|
||||||
to="/employees"
|
<p class="font-bold text-[20px]">{{ contractNatureLabel(employee.currentContractNature) }} {{ employee.contract?.weeklyHours ?? '-' }} heures</p>
|
||||||
class="rounded-lg border border-neutral-200 px-4 py-2 text-md font-semibold text-neutral-700 hover:bg-neutral-100"
|
<p class="text-[18px]">{{ employee.site?.name ?? '-' }}</p>
|
||||||
>
|
</div>
|
||||||
Retour
|
</div>
|
||||||
</NuxtLink>
|
<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:magnify" size="24" class="align-self"/>
|
||||||
|
Suivi contrat
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
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:magnify" 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:magnify" size="24" class="align-self"/>
|
||||||
|
RTT
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 rounded-lg border border-neutral-200 bg-white p-6">
|
<section v-if="activeTab === 'contract'" class="mt-8">
|
||||||
<p class="text-xl font-semibold text-neutral-900">{{ employee.firstName }} {{ employee.lastName }}</p>
|
<div class="overflow-hidden rounded-lg border border-neutral-200 bg-white">
|
||||||
<p class="mt-2 text-md text-neutral-700">Site: {{ employee.site?.name ?? '-' }}</p>
|
<div class="grid grid-cols-4 border-b border-neutral-200 bg-neutral-50 px-6 py-3 text-md font-semibold text-neutral-700">
|
||||||
<p class="text-md text-neutral-700">Type de contrat: {{
|
<p>Contrat</p>
|
||||||
contractNatureLabel(employee.currentContractNature)
|
<p>Heures</p>
|
||||||
}}</p>
|
<p>Date de début</p>
|
||||||
<p class="text-md text-neutral-700">Temps de travail: {{ employee.contract?.name ?? '-' }}</p>
|
<p>Date de fin</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="contractHistory.length === 0" class="px-6 py-4 text-md text-neutral-600">
|
||||||
|
Aucun historique de contrat.
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div
|
||||||
|
v-for="item in contractHistory"
|
||||||
|
:key="`${item.startDate}-${item.endDate ?? 'open'}-${item.contractId ?? item.contractName}`"
|
||||||
|
class="grid grid-cols-4 border-b border-neutral-100 px-6 py-3 text-md text-primary-500 last:border-b-0"
|
||||||
|
>
|
||||||
|
<p>{{ contractNatureLabel(item.contractNature) }}</p>
|
||||||
|
<p>{{ contractHistoryLabel(item) }}</p>
|
||||||
|
<p>{{ formatDate(item.startDate) }}</p>
|
||||||
|
<p>{{ formatDate(item.endDate) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center mt-8 gap-12">
|
||||||
|
<button class="bg-blue-500 text-white rounded-md w-[200px]">Modifier</button>
|
||||||
|
<button class="bg-primary-500 px-4 py-2 text-white text-md rounded-md flex justify-center items-center gap-2 w-[200px]">
|
||||||
|
<Icon name="mdi:plus-thick" size="16" />
|
||||||
|
Ajouter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section v-else-if="activeTab === 'leave'" class="mt-8">
|
||||||
|
<!-- Bloc Congé -->
|
||||||
|
</section>
|
||||||
|
<section v-else class="mt-8">
|
||||||
|
<!-- Bloc RTT -->
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {Employee} from '~/services/dto/employee'
|
import type {ContractHistoryItem, Employee} from '~/services/dto/employee'
|
||||||
import {getEmployee} from '~/services/employees'
|
import {getEmployee} from '~/services/employees'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const employee = ref<Employee | null>(null)
|
const employee = ref<Employee | null>(null)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
const activeTab = ref<'contract' | 'leave' | 'rtt'>('contract')
|
||||||
|
|
||||||
useHead(() => ({
|
useHead(() => ({
|
||||||
title: employee.value
|
title: employee.value
|
||||||
@@ -54,6 +116,22 @@ const contractNatureLabel = (value?: 'CDI' | 'CDD' | 'INTERIM') => {
|
|||||||
return 'CDI'
|
return 'CDI'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const contractHistory = computed(() => employee.value?.contractHistory ?? [])
|
||||||
|
|
||||||
|
const formatDate = (value?: string | null) => {
|
||||||
|
if (!value) return 'En cours'
|
||||||
|
const [year, month, day] = value.split('-')
|
||||||
|
if (!year || !month || !day) return value
|
||||||
|
return `${day}/${month}/${year}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const contractHistoryLabel = (item: ContractHistoryItem) => {
|
||||||
|
if (item.weeklyHours !== null && item.weeklyHours !== undefined) {
|
||||||
|
return `${item.weeklyHours} heures`
|
||||||
|
}
|
||||||
|
return item.contractName ?? '-'
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const idParam = Array.isArray(route.params.id) ? route.params.id[0] : route.params.id
|
const idParam = Array.isArray(route.params.id) ? route.params.id[0] : route.params.id
|
||||||
const employeeId = Number(idParam)
|
const employeeId = Number(idParam)
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import type { Site } from './site'
|
import type { Site } from './site'
|
||||||
import type { Contract } from './contract'
|
import type { Contract } from './contract'
|
||||||
|
|
||||||
|
export type ContractHistoryItem = {
|
||||||
|
contractId?: number | null
|
||||||
|
contractName?: string | null
|
||||||
|
weeklyHours?: number | null
|
||||||
|
contractNature: 'CDI' | 'CDD' | 'INTERIM'
|
||||||
|
startDate: string
|
||||||
|
endDate?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
export type Employee = {
|
export type Employee = {
|
||||||
id: number
|
id: number
|
||||||
firstName: string
|
firstName: string
|
||||||
@@ -10,5 +19,6 @@ export type Employee = {
|
|||||||
currentContractNature?: 'CDI' | 'CDD' | 'INTERIM'
|
currentContractNature?: 'CDI' | 'CDD' | 'INTERIM'
|
||||||
currentContractStartDate?: string | null
|
currentContractStartDate?: string | null
|
||||||
currentContractEndDate?: string | null
|
currentContractEndDate?: string | null
|
||||||
|
contractHistory?: ContractHistoryItem[]
|
||||||
displayOrder?: number
|
displayOrder?: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ export default <Partial<Config>>{
|
|||||||
},
|
},
|
||||||
tertiary: {
|
tertiary: {
|
||||||
500: '#F3F4F8'
|
500: '#F3F4F8'
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
500: '#056CF2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/Dto/Employees/ContractHistoryItem.php
Normal file
25
src/Dto/Employees/ContractHistoryItem.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Employees;
|
||||||
|
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
|
final class ContractHistoryItem
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
#[Groups(['employee:read'])]
|
||||||
|
public ?int $contractId,
|
||||||
|
#[Groups(['employee:read'])]
|
||||||
|
public ?string $contractName,
|
||||||
|
#[Groups(['employee:read'])]
|
||||||
|
public ?float $weeklyHours,
|
||||||
|
#[Groups(['employee:read'])]
|
||||||
|
public string $contractNature,
|
||||||
|
#[Groups(['employee:read'])]
|
||||||
|
public string $startDate,
|
||||||
|
#[Groups(['employee:read'])]
|
||||||
|
public ?string $endDate,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use App\Dto\Employees\ContractHistoryItem;
|
||||||
use App\Enum\ContractNature;
|
use App\Enum\ContractNature;
|
||||||
use App\Repository\EmployeeRepository;
|
use App\Repository\EmployeeRepository;
|
||||||
use App\State\EmployeeWriteProcessor;
|
use App\State\EmployeeWriteProcessor;
|
||||||
@@ -204,6 +205,35 @@ class Employee
|
|||||||
return $this->resolveCurrentContractPeriod()?->getEndDate()?->format('Y-m-d');
|
return $this->resolveCurrentContractPeriod()?->getEndDate()?->format('Y-m-d');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<ContractHistoryItem>
|
||||||
|
*/
|
||||||
|
#[Groups(['employee:read'])]
|
||||||
|
public function getContractHistory(): array
|
||||||
|
{
|
||||||
|
$periods = $this->contractPeriods->toArray();
|
||||||
|
usort(
|
||||||
|
$periods,
|
||||||
|
static fn (EmployeeContractPeriod $a, EmployeeContractPeriod $b): int => $b->getStartDate() <=> $a->getStartDate()
|
||||||
|
);
|
||||||
|
|
||||||
|
return array_map(
|
||||||
|
static function (EmployeeContractPeriod $period): ContractHistoryItem {
|
||||||
|
$contract = $period->getContract();
|
||||||
|
|
||||||
|
return new ContractHistoryItem(
|
||||||
|
contractId: $contract?->getId(),
|
||||||
|
contractName: $contract?->getName(),
|
||||||
|
weeklyHours: $contract?->getWeeklyHours(),
|
||||||
|
contractNature: $period->getContractNatureEnum()->value,
|
||||||
|
startDate: $period->getStartDate()->format('Y-m-d'),
|
||||||
|
endDate: $period->getEndDate()?->format('Y-m-d'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
$periods
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private function resolveCurrentContractPeriod(): ?EmployeeContractPeriod
|
private function resolveCurrentContractPeriod(): ?EmployeeContractPeriod
|
||||||
{
|
{
|
||||||
$today = new DateTimeImmutable('today');
|
$today = new DateTimeImmutable('today');
|
||||||
|
|||||||
Reference in New Issue
Block a user