feat(time-tracking) : add TimeEntry DTO, service, timer store, and i18n keys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
109
frontend/stores/timer.ts
Normal file
109
frontend/stores/timer.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { TimeEntry } from '~/services/dto/time-entry'
|
||||
import type { Task } from '~/services/dto/task'
|
||||
import { useTimeEntryService } from '~/services/time-entries'
|
||||
|
||||
export const useTimerStore = defineStore('timer', () => {
|
||||
const activeEntry = ref<TimeEntry | null>(null)
|
||||
const now = ref(Date.now())
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const isRunning = computed(() => activeEntry.value !== null)
|
||||
|
||||
const elapsed = computed(() => {
|
||||
if (!activeEntry.value) return 0
|
||||
const start = new Date(activeEntry.value.startedAt).getTime()
|
||||
return Math.floor((now.value - start) / 1000)
|
||||
})
|
||||
|
||||
const elapsedFormatted = computed(() => {
|
||||
const total = elapsed.value
|
||||
const h = Math.floor(total / 3600)
|
||||
const m = Math.floor((total % 3600) / 60)
|
||||
const s = total % 60
|
||||
return [h, m, s].map((v) => String(v).padStart(2, '0')).join(':')
|
||||
})
|
||||
|
||||
function startTicking() {
|
||||
stopTicking()
|
||||
now.value = Date.now()
|
||||
intervalId = setInterval(() => {
|
||||
now.value = Date.now()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function stopTicking() {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId)
|
||||
intervalId = null
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchActive() {
|
||||
const { getActive } = useTimeEntryService()
|
||||
activeEntry.value = await getActive()
|
||||
if (activeEntry.value) {
|
||||
startTicking()
|
||||
} else {
|
||||
stopTicking()
|
||||
}
|
||||
}
|
||||
|
||||
async function start() {
|
||||
const authStore = useAuthStore()
|
||||
if (!authStore.user) return
|
||||
|
||||
if (isRunning.value) {
|
||||
await stop()
|
||||
}
|
||||
|
||||
const { create } = useTimeEntryService()
|
||||
activeEntry.value = await create({
|
||||
startedAt: new Date().toISOString(),
|
||||
user: `/api/users/${authStore.user.id}`,
|
||||
})
|
||||
startTicking()
|
||||
}
|
||||
|
||||
async function startFromTask(task: Task) {
|
||||
const authStore = useAuthStore()
|
||||
if (!authStore.user) return
|
||||
|
||||
if (isRunning.value) {
|
||||
await stop()
|
||||
}
|
||||
|
||||
const { create } = useTimeEntryService()
|
||||
activeEntry.value = await create({
|
||||
startedAt: new Date().toISOString(),
|
||||
user: `/api/users/${authStore.user.id}`,
|
||||
title: task.title,
|
||||
project: task.project?.['@id'] ?? `/api/projects/${task.project?.id}`,
|
||||
task: task['@id'] ?? `/api/tasks/${task.id}`,
|
||||
types: task.types.map((t) => t['@id'] ?? `/api/task_types/${t.id}`),
|
||||
})
|
||||
startTicking()
|
||||
}
|
||||
|
||||
async function stop() {
|
||||
if (!activeEntry.value) return
|
||||
|
||||
const { update } = useTimeEntryService()
|
||||
await update(activeEntry.value.id, {
|
||||
stoppedAt: new Date().toISOString(),
|
||||
})
|
||||
activeEntry.value = null
|
||||
stopTicking()
|
||||
}
|
||||
|
||||
return {
|
||||
activeEntry,
|
||||
isRunning,
|
||||
elapsed,
|
||||
elapsedFormatted,
|
||||
fetchActive,
|
||||
start,
|
||||
startFromTask,
|
||||
stop,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user