122 lines
4.3 KiB
Vue
122 lines
4.3 KiB
Vue
<template>
|
|
<div class="h-full flex flex-col overflow-hidden">
|
|
<div class="flex flex-wrap items-center justify-between gap-4 pb-8">
|
|
<h1 class="text-4xl font-bold text-primary-500">Récap. congés</h1>
|
|
<span
|
|
v-if="cutoffLabel"
|
|
class="inline-flex items-center gap-2 rounded-full bg-tertiary-500 px-4 py-1 text-sm font-semibold text-primary-500"
|
|
>
|
|
<Icon name="mdi:calendar-check-outline" size="18"/>
|
|
{{ cutoffLabel }}
|
|
</span>
|
|
</div>
|
|
|
|
<div
|
|
v-if="isLoading"
|
|
class="rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600"
|
|
>
|
|
Chargement...
|
|
</div>
|
|
|
|
<div
|
|
v-else-if="rows.length === 0"
|
|
class="rounded-lg border border-neutral-200 bg-white p-6 text-md text-neutral-600"
|
|
>
|
|
Aucun employé à afficher.
|
|
</div>
|
|
|
|
<div v-else class="min-h-0 overflow-auto rounded-md bg-white">
|
|
<div
|
|
:class="`grid ${gridColsClass} gap-4 border border-black bg-tertiary-500 px-6 py-3 text-[20px] font-semibold text-black rounded-t-md sticky top-0 z-10`"
|
|
>
|
|
<span v-if="showSiteColumn" class="text-left">Site</span>
|
|
<span class="text-left">Nom</span>
|
|
<span class="text-left">Prénom</span>
|
|
<span class="text-left">Contrat</span>
|
|
<span class="text-right">CP N-1 restant</span>
|
|
<span class="text-right">CP N</span>
|
|
<span class="text-right">Samedis</span>
|
|
<span class="text-right">RTT</span>
|
|
</div>
|
|
<div class="border-x border-b border-primary-500 rounded-b-md">
|
|
<div
|
|
v-for="row in rows"
|
|
:key="row.employeeId"
|
|
:class="`grid ${gridColsClass} items-center gap-4 border-b border-primary-500 px-6 py-3 text-md font-bold text-primary-500 last:border-b-0`"
|
|
>
|
|
<span v-if="showSiteColumn" class="truncate">
|
|
<span
|
|
v-if="row.siteName"
|
|
class="inline-block rounded-full px-3 py-1 text-sm"
|
|
:style="{ backgroundColor: row.siteColor || '#ffd7d7', color: '#1a1a1a' }"
|
|
>
|
|
{{ row.siteName }}
|
|
</span>
|
|
<span v-else class="text-neutral-500">-</span>
|
|
</span>
|
|
<span class="truncate">{{ row.lastName }}</span>
|
|
<span class="truncate">{{ row.firstName }}</span>
|
|
<span class="truncate">{{ row.contractName ?? '-' }}</span>
|
|
<span class="text-right tabular-nums">{{ formatNumber(row.cpN1Remaining) }}</span>
|
|
<span class="text-right tabular-nums">{{ row.cpN }}</span>
|
|
<span class="text-right tabular-nums">{{ row.acquiredSaturdays }}</span>
|
|
<span class="text-right tabular-nums">{{ row.rtt }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { LeaveRecapRow } from '~/services/dto/leave-recap'
|
|
import { fetchLeaveRecap } from '~/services/leave-recap'
|
|
import { formatYmdToFr, getIsoWeekNumber, parseYmd } from '~/utils/date'
|
|
|
|
definePageMeta({ middleware: ['leave-recap-access'] })
|
|
useHead({ title: 'Récap. congés' })
|
|
|
|
const auth = useAuthStore()
|
|
const rows = ref<LeaveRecapRow[]>([])
|
|
const isLoading = ref(false)
|
|
|
|
const isAdmin = computed(() => auth.user?.roles?.includes('ROLE_ADMIN') ?? false)
|
|
const isSelfOnly = computed(() => {
|
|
const roles = auth.user?.roles ?? []
|
|
return roles.includes('ROLE_SELF') && !roles.includes('ROLE_ADMIN')
|
|
})
|
|
const showSiteColumn = computed(() => !isSelfOnly.value)
|
|
const gridColsClass = computed(() =>
|
|
showSiteColumn.value
|
|
? 'grid-cols-[1.2fr_1fr_1fr_1.2fr_140px_100px_100px_120px]'
|
|
: 'grid-cols-[1fr_1fr_1.2fr_140px_100px_100px_120px]'
|
|
)
|
|
|
|
const cutoffLabel = computed(() => {
|
|
const ymd = rows.value[0]?.cutoffDate
|
|
if (!ymd) return ''
|
|
const parsed = parseYmd(ymd)
|
|
if (!parsed) return ''
|
|
const week = getIsoWeekNumber(parsed)
|
|
return `Arrêté au ${formatYmdToFr(ymd)} (fin S${week})`
|
|
})
|
|
|
|
const formatNumber = (value: number) => {
|
|
if (!Number.isFinite(value)) return '-'
|
|
return value.toLocaleString('fr-FR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })
|
|
}
|
|
|
|
const load = async () => {
|
|
isLoading.value = true
|
|
try {
|
|
rows.value = await fetchLeaveRecap()
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(load)
|
|
|
|
// Silence unused linter warning for isAdmin (kept for future site grouping)
|
|
void isAdmin
|
|
</script>
|