Files
Lesstime/frontend/modules/absence/composables/useAbsenceHelpers.ts
T
Matthieu 163bf0891a feat(absence) : extract Absence front into Nuxt module layer
LST-66 (2.3) front. Companion to the backend module migration.

- Move pages (absences, team-absences), 8 components, the absences service +
  DTO and the useAbsenceHelpers composable into frontend/modules/absence/
  (auto-detected layer; composable now auto-imported).
- Rewrite consumers: AdminAbsencePolicyTab and the time-tracking calendar
  (getPublicHolidays) point to ~/modules/absence/...
- Middlewares (employee/admin) and shared services (clients, users,
  user-data DTO) stay at the root. i18n stays global.
- Routes /absences and /team-absences preserved.

nuxt build passes; routes confirmed.
2026-06-20 18:36:48 +02:00

97 lines
3.1 KiB
TypeScript

import type { AbsenceRequest, AbsenceStatus, AbsenceType, HalfDay } from '~/modules/absence/services/dto/absence'
export type BadgeVariant = 'neutral' | 'info' | 'success' | 'warning' | 'danger'
const STATUS_VARIANTS: Record<AbsenceStatus, BadgeVariant> = {
pending: 'warning',
approved: 'success',
rejected: 'danger',
cancelled: 'neutral',
}
const STATUS_ICONS: Record<AbsenceStatus, string> = {
pending: 'mdi:clock-outline',
approved: 'mdi:check-circle-outline',
rejected: 'mdi:close-circle-outline',
cancelled: 'mdi:cancel',
}
// Colours used for the calendar bars, keyed by absence type.
const TYPE_COLORS: Record<AbsenceType, string> = {
cp: '#4A90D9',
mariage_pacs: '#E91E63',
naissance: '#26A69A',
conge_parental: '#9C27B0',
deces: '#607D8B',
maladie: '#C62828',
}
export function useAbsenceHelpers() {
const { t } = useI18n()
function statusLabel(status: AbsenceStatus): string {
return t(`absences.status.${status}`)
}
function statusVariant(status: AbsenceStatus): BadgeVariant {
return STATUS_VARIANTS[status] ?? 'neutral'
}
function statusIcon(status: AbsenceStatus): string {
return STATUS_ICONS[status] ?? 'mdi:help-circle-outline'
}
function typeLabel(type: AbsenceType): string {
return t(`absences.types.${type}`)
}
function typeColor(type: AbsenceType): string {
return TYPE_COLORS[type] ?? '#9CA3AF'
}
function halfDayLabel(half: HalfDay): string {
return t(`absences.halfDay.${half}`)
}
function formatDate(iso: string | null): string {
if (!iso) return ''
const d = new Date(iso)
if (isNaN(d.getTime())) return ''
const day = String(d.getDate()).padStart(2, '0')
const month = String(d.getMonth() + 1).padStart(2, '0')
return `${day}/${month}/${d.getFullYear()}`
}
/** Human-readable period with half-day annotations. */
function formatRange(req: Pick<AbsenceRequest, 'startDate' | 'endDate' | 'startHalfDay' | 'endHalfDay'>): string {
const start = formatDate(req.startDate)
const end = formatDate(req.endDate)
const startSuffix = req.startHalfDay ? ` (${halfDayLabel(req.startHalfDay)})` : ''
const endSuffix = req.endHalfDay ? ` (${halfDayLabel(req.endHalfDay)})` : ''
if (start === end) {
return `${start}${startSuffix}`
}
return `${start}${startSuffix}${end}${endSuffix}`
}
function formatDays(days: number): string {
// Affiche la valeur réelle avec décimales (ex. 8,75) : un solde de CP se
// gère en demi/quart de journée, arrondir masquerait des droits réels.
const value = new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 2 }).format(days)
const unit = days >= 2 ? t('absences.daysPlural') : t('absences.daySingular')
return `${value} ${unit}`
}
return {
statusLabel,
statusVariant,
statusIcon,
typeLabel,
typeColor,
halfDayLabel,
formatDate,
formatRange,
formatDays,
}
}