Files
Lesstime/frontend/services/time-entries.ts
Matthieu 3e6b0e877a
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
fix(time-tracking) : filtres projet/tag server-side et vue liste au mois
- Pousse les filtres projet et tag a l'API (au lieu d'un filtrage client-side
  partiel sur la page courante) pour eviter les resultats incomplets en cas
  de pagination
- Ajoute les watchers selectedProjectId/selectedTagId qui declenchent un reload
- Mode liste : navigation et plage de chargement passent a 1 mois (au lieu
  d'une fenetre de 7 jours qui rendait le mode liste inutilisable)
- Renomme l'option vide du filtre User en "Tous" (etait "User", ambigu)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 09:51:18 +02:00

102 lines
3.5 KiB
TypeScript

import type { TimeEntry, TimeEntryWrite } from './dto/time-entry'
import type { HydraCollection } from '~/utils/api'
import { extractHydraMembers } from '~/utils/api'
export function useTimeEntryService() {
const api = useApi()
async function getByDateRange(params: {
after: string
before: string
user?: number
project?: number
tag?: number
}): Promise<TimeEntry[]> {
const query: Record<string, unknown> = {
'startedAt[after]': params.after,
'startedAt[before]': params.before,
}
if (params.user) {
query.user = `/api/users/${params.user}`
}
if (params.project) {
query.project = `/api/projects/${params.project}`
}
if (params.tag) {
query['tags[]'] = `/api/task_tags/${params.tag}`
}
const data = await api.get<HydraCollection<TimeEntry>>('/time_entries', query)
return extractHydraMembers(data)
}
async function getActive(): Promise<TimeEntry | null> {
try {
const data = await api.get<HydraCollection<TimeEntry>>('/time_entries/active', {}, { toast: false })
const members = extractHydraMembers(data)
return members[0] ?? null
} catch {
return null
}
}
async function create(payload: TimeEntryWrite): Promise<TimeEntry> {
return api.post<TimeEntry>('/time_entries', payload as Record<string, unknown>, {
toastSuccessKey: 'timeEntries.created',
})
}
async function update(id: number, payload: Partial<TimeEntryWrite>): Promise<TimeEntry> {
return api.patch<TimeEntry>(`/time_entries/${id}`, payload as Record<string, unknown>, {
toastSuccessKey: 'timeEntries.updated',
})
}
async function remove(id: number): Promise<void> {
await api.delete(`/time_entries/${id}`, {}, {
toastSuccessKey: 'timeEntries.deleted',
})
}
function getExportUrl(params: {
after: string
before: string
users?: number[]
projects?: number[]
client?: number
tags?: number[]
}): string {
const query = new URLSearchParams()
query.set('after', params.after)
query.set('before', params.before)
if (params.users?.length) {
params.users.forEach(id => query.append('users[]', String(id)))
}
if (params.client) query.set('client', String(params.client))
if (params.projects?.length) {
params.projects.forEach(id => query.append('projects[]', String(id)))
}
if (params.tags?.length) {
params.tags.forEach(id => query.append('tags[]', String(id)))
}
return `/time_entries/export?${query.toString()}`
}
async function downloadExport(params: {
after: string
before: string
users?: number[]
projects?: number[]
client?: number
tags?: number[]
}): Promise<{ blob: Blob; filename: string }> {
const url = getExportUrl(params)
const response = await api.getBlob(url)
const disposition = response.headers.get('content-disposition') ?? ''
const filenameMatch = disposition.match(/filename="?([^";\n]+)"?/)
const filename = filenameMatch?.[1] ?? `export-temps-${params.after}_${params.before}.xlsx`
return { blob: response.data, filename }
}
return { getByDateRange, getActive, create, update, remove, getExportUrl, downloadExport }
}