feat(time-tracking) : extract time-tracking front into Nuxt module layer
Companion to the backend module migration (LST-64). The Nuxt layer is auto-detected from frontend/modules/* — no nuxt.config change needed. - Move page, timer store, time-entries service + DTO and the 6 time-tracking components into frontend/modules/time-tracking/. - Rewrite explicit service/DTO imports to ~/modules/time-tracking/* (store and components stay auto-imported); update the dashboard (index.vue) consumer. - Route /time-tracking preserved; i18n keys kept in the global locale file. nuxt build passes; /time-tracking routed.
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
import type { UserData } from './user-data'
|
||||
import type { Project } from './project'
|
||||
import type { Task } from './task'
|
||||
import type { TaskTag } from './task-tag'
|
||||
|
||||
export type TimeEntry = {
|
||||
id: number
|
||||
'@id'?: string
|
||||
title: string | null
|
||||
description: string | null
|
||||
startedAt: string
|
||||
stoppedAt: string | null
|
||||
user: UserData
|
||||
project: Project | null
|
||||
task: Task | null
|
||||
tags: TaskTag[]
|
||||
}
|
||||
|
||||
export type TimeEntryWrite = {
|
||||
title?: string | null
|
||||
description?: string | null
|
||||
startedAt: string
|
||||
stoppedAt?: string | null
|
||||
user: string
|
||||
project?: string | null
|
||||
task?: string | null
|
||||
tags?: string[]
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
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/range', 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user