feat : integrate export drawer with async background download
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -78,7 +78,7 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="flex shrink-0 items-center gap-2 rounded-md border border-neutral-200 bg-white px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 transition"
|
class="flex shrink-0 items-center gap-2 rounded-md border border-neutral-200 bg-white px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 transition"
|
||||||
@click="exportTimeEntries"
|
@click="exportDrawerOpen = true"
|
||||||
>
|
>
|
||||||
<Icon name="mdi:download" size="18" />
|
<Icon name="mdi:download" size="18" />
|
||||||
{{ $t('timeEntries.export') }}
|
{{ $t('timeEntries.export') }}
|
||||||
@@ -128,6 +128,15 @@
|
|||||||
@paste="onPaste"
|
@paste="onPaste"
|
||||||
@delete="onDelete"
|
@delete="onDelete"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<TimeTrackingExportDrawer
|
||||||
|
v-model="exportDrawerOpen"
|
||||||
|
:users="users"
|
||||||
|
:projects="projects"
|
||||||
|
:tags="tags"
|
||||||
|
:clients="clients"
|
||||||
|
@export="onExport"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -136,6 +145,7 @@ import type { TimeEntry } from '~/services/dto/time-entry'
|
|||||||
import type { UserData } from '~/services/dto/user-data'
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import type { Project } from '~/services/dto/project'
|
import type { Project } from '~/services/dto/project'
|
||||||
import type { TaskTag } from '~/services/dto/task-tag'
|
import type { TaskTag } from '~/services/dto/task-tag'
|
||||||
|
import type { Client } from '~/services/dto/client'
|
||||||
import { useTimeEntryService } from '~/services/time-entries'
|
import { useTimeEntryService } from '~/services/time-entries'
|
||||||
import type { HydraCollection } from '~/utils/api'
|
import type { HydraCollection } from '~/utils/api'
|
||||||
import { extractHydraMembers } from '~/utils/api'
|
import { extractHydraMembers } from '~/utils/api'
|
||||||
@@ -156,6 +166,8 @@ const entries = ref<TimeEntry[]>([])
|
|||||||
const users = ref<UserData[]>([])
|
const users = ref<UserData[]>([])
|
||||||
const projects = ref<Project[]>([])
|
const projects = ref<Project[]>([])
|
||||||
const tags = ref<TaskTag[]>([])
|
const tags = ref<TaskTag[]>([])
|
||||||
|
const clients = ref<Client[]>([])
|
||||||
|
const exportDrawerOpen = ref(false)
|
||||||
|
|
||||||
const drawerOpen = ref(false)
|
const drawerOpen = ref(false)
|
||||||
const editingEntry = ref<TimeEntry | null>(null)
|
const editingEntry = ref<TimeEntry | null>(null)
|
||||||
@@ -305,38 +317,35 @@ async function onDelete(entry: TimeEntry) {
|
|||||||
await loadEntries()
|
await loadEntries()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExportDateRange(): { after: string, before: string } {
|
async function onExport(params: {
|
||||||
if (Array.isArray(selectedDateFilter.value) && selectedDateFilter.value.length === 2) {
|
after: string
|
||||||
return {
|
before: string
|
||||||
after: selectedDateFilter.value[0].toISOString().slice(0, 10),
|
users?: number[]
|
||||||
before: selectedDateFilter.value[1].toISOString().slice(0, 10),
|
projects?: number[]
|
||||||
}
|
client?: number
|
||||||
|
tags?: number[]
|
||||||
|
}) {
|
||||||
|
const { success, error } = useToast()
|
||||||
|
const { t } = useNuxtApp().$i18n as { t: (key: string) => string }
|
||||||
|
|
||||||
|
success(t('timeEntries.exportLoading'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await timeEntryService.downloadExport(params)
|
||||||
|
|
||||||
|
const url = URL.createObjectURL(result.blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = result.filename
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
|
||||||
|
success(t('timeEntries.exportSuccess'))
|
||||||
|
} catch {
|
||||||
|
error(t('timeEntries.exportError'))
|
||||||
}
|
}
|
||||||
const end = new Date(startDate.value)
|
|
||||||
end.setDate(end.getDate() + (viewMode.value === 'day' ? 1 : 7))
|
|
||||||
return {
|
|
||||||
after: startDate.value.toISOString().slice(0, 10),
|
|
||||||
before: end.toISOString().slice(0, 10),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function exportTimeEntries() {
|
|
||||||
const { after, before } = getExportDateRange()
|
|
||||||
|
|
||||||
const url = timeEntryService.getExportUrl({
|
|
||||||
after,
|
|
||||||
before,
|
|
||||||
user: selectedUserId.value ?? undefined,
|
|
||||||
project: selectedProjectId.value ?? undefined,
|
|
||||||
tags: selectedTagId.value ? [selectedTagId.value] : undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
const a = document.createElement('a')
|
|
||||||
a.href = url
|
|
||||||
a.download = ''
|
|
||||||
document.body.appendChild(a)
|
|
||||||
a.click()
|
|
||||||
document.body.removeChild(a)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadEntries() {
|
async function loadEntries() {
|
||||||
@@ -353,15 +362,17 @@ async function loadEntries() {
|
|||||||
async function loadReferenceData() {
|
async function loadReferenceData() {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
const [usersData, projectsData, typesData] = await Promise.all([
|
const [usersData, projectsData, typesData, clientsData] = await Promise.all([
|
||||||
api.get<HydraCollection<UserData>>('/users'),
|
api.get<HydraCollection<UserData>>('/users'),
|
||||||
api.get<HydraCollection<Project>>('/projects'),
|
api.get<HydraCollection<Project>>('/projects'),
|
||||||
api.get<HydraCollection<TaskTag>>('/task_tags'),
|
api.get<HydraCollection<TaskTag>>('/task_tags'),
|
||||||
|
api.get<HydraCollection<Client>>('/clients'),
|
||||||
])
|
])
|
||||||
|
|
||||||
users.value = extractHydraMembers(usersData)
|
users.value = extractHydraMembers(usersData)
|
||||||
projects.value = extractHydraMembers(projectsData)
|
projects.value = extractHydraMembers(projectsData)
|
||||||
tags.value = extractHydraMembers(typesData)
|
tags.value = extractHydraMembers(typesData)
|
||||||
|
clients.value = extractHydraMembers(clientsData)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user