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.
This commit is contained in:
Matthieu
2026-06-20 18:36:48 +02:00
parent 306cfd34cd
commit 163bf0891a
16 changed files with 21 additions and 27 deletions
@@ -0,0 +1,137 @@
import type {
AbsenceBalance,
AbsencePolicy,
AbsencePolicyWrite,
AbsencePreviewPayload,
AbsencePreviewResult,
AbsenceRequest,
AbsenceRequestWrite,
AbsenceStatus,
AbsenceType,
} from './dto/absence'
import type { HydraCollection } from '~/utils/api'
import { extractHydraMembers } from '~/utils/api'
export type AbsenceRequestFilters = {
status?: AbsenceStatus
type?: AbsenceType
year?: number
user?: number
}
export function useAbsenceService() {
const api = useApi()
// --- Requests ---
async function getRequests(filters: AbsenceRequestFilters = {}): Promise<AbsenceRequest[]> {
const query: Record<string, unknown> = {}
if (filters.status) query.status = filters.status
if (filters.type) query.type = filters.type
if (filters.year) query.year = filters.year
if (filters.user) query.user = `/api/users/${filters.user}`
const data = await api.get<HydraCollection<AbsenceRequest>>('/absence_requests', query)
return extractHydraMembers(data)
}
async function getRequest(id: number): Promise<AbsenceRequest> {
return api.get<AbsenceRequest>(`/absence_requests/${id}`)
}
async function create(payload: AbsenceRequestWrite): Promise<AbsenceRequest> {
return api.post<AbsenceRequest>('/absence_requests', payload as Record<string, unknown>, {
toastSuccessKey: 'absences.toast.created',
})
}
async function preview(payload: AbsencePreviewPayload): Promise<AbsencePreviewResult> {
return api.post<AbsencePreviewResult>('/absence_requests/preview', payload as Record<string, unknown>, {
toast: false,
})
}
async function approve(id: number): Promise<AbsenceRequest> {
return api.patch<AbsenceRequest>(`/absence_requests/${id}/approve`, {}, {
toastSuccessKey: 'absences.toast.approved',
})
}
async function reject(id: number, rejectionReason: string): Promise<AbsenceRequest> {
return api.patch<AbsenceRequest>(`/absence_requests/${id}/reject`, { rejectionReason }, {
toastSuccessKey: 'absences.toast.rejected',
})
}
async function cancel(id: number): Promise<AbsenceRequest> {
return api.patch<AbsenceRequest>(`/absence_requests/${id}/cancel`, {}, {
toastSuccessKey: 'absences.toast.cancelled',
})
}
async function uploadJustification(id: number, file: File): Promise<AbsenceRequest> {
const form = new FormData()
form.append('file', file)
return api.post<AbsenceRequest>(`/absence_requests/${id}/justificatif`, form as unknown as Record<string, unknown>, {
toastSuccessKey: 'absences.toast.justificationUploaded',
})
}
// --- Balances ---
async function getBalances(filters: { user?: number; period?: string; type?: AbsenceType } = {}): Promise<AbsenceBalance[]> {
const query: Record<string, unknown> = {}
if (filters.user) query.user = `/api/users/${filters.user}`
if (filters.period) query.period = filters.period
if (filters.type) query.type = filters.type
const data = await api.get<HydraCollection<AbsenceBalance>>('/absence_balances', query)
return extractHydraMembers(data)
}
async function adjustBalance(id: number, payload: { acquired?: number; acquiring?: number; taken?: number }): Promise<AbsenceBalance> {
return api.patch<AbsenceBalance>(`/absence_balances/${id}`, payload as Record<string, unknown>, {
toastSuccessKey: 'absences.toast.balanceAdjusted',
})
}
// --- Policies ---
async function getPolicies(): Promise<AbsencePolicy[]> {
const data = await api.get<HydraCollection<AbsencePolicy>>('/absence_policies')
return extractHydraMembers(data)
}
async function updatePolicy(id: number, payload: AbsencePolicyWrite): Promise<AbsencePolicy> {
return api.patch<AbsencePolicy>(`/absence_policies/${id}`, payload as Record<string, unknown>, {
toastSuccessKey: 'absences.toast.policyUpdated',
})
}
// --- Admin calendar ---
async function getCalendar(from: string, to: string): Promise<AbsenceRequest[]> {
return api.get<AbsenceRequest[]>('/admin/absences/calendar', { from, to })
}
// --- Public holidays (computed server-side) ---
async function getPublicHolidays(from: string, to: string): Promise<Record<string, string>> {
return api.get<Record<string, string>>('/public_holidays', { from, to }, { toast: false })
}
return {
getRequests,
getRequest,
create,
preview,
approve,
reject,
cancel,
uploadJustification,
getBalances,
adjustBalance,
getPolicies,
updatePolicy,
getCalendar,
getPublicHolidays,
}
}