feat(time-tracking) : add calendar overlap columns, responsive cards, project filter, and auto-scroll
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,10 +41,17 @@
|
||||
<option v-for="u in users" :key="u.id" :value="u.id">{{ u.username }}</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
v-model="selectedProjectId"
|
||||
class="rounded-md border border-neutral-200 px-3 py-1.5 text-sm"
|
||||
>
|
||||
<option :value="null">Projet</option>
|
||||
<option v-for="p in projects" :key="p.id" :value="p.id">{{ p.name }}</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
v-model="selectedTypeId"
|
||||
class="rounded-md border border-neutral-200 px-3 py-1.5 text-sm"
|
||||
@change="loadEntries"
|
||||
>
|
||||
<option :value="null">Type</option>
|
||||
<option v-for="t in types" :key="t.id" :value="t.id">{{ t.label }}</option>
|
||||
@@ -105,6 +112,7 @@ const viewMode = ref<'week' | 'day'>('week')
|
||||
const startDate = ref(getMonday(new Date()))
|
||||
const selectedUserId = ref<number | null>(authStore.user?.id ?? null)
|
||||
const selectedTypeId = ref<number | null>(null)
|
||||
const selectedProjectId = ref<number | null>(null)
|
||||
|
||||
const entries = ref<TimeEntry[]>([])
|
||||
const users = ref<UserData[]>([])
|
||||
@@ -131,10 +139,14 @@ const currentMonthLabel = computed(() => {
|
||||
})
|
||||
|
||||
const filteredEntries = computed(() => {
|
||||
if (!selectedTypeId.value) return entries.value
|
||||
return entries.value.filter((e) =>
|
||||
e.types.some((t) => t.id === selectedTypeId.value)
|
||||
)
|
||||
let result = entries.value
|
||||
if (selectedProjectId.value) {
|
||||
result = result.filter((e) => e.project?.id === selectedProjectId.value)
|
||||
}
|
||||
if (selectedTypeId.value) {
|
||||
result = result.filter((e) => e.types.some((t) => t.id === selectedTypeId.value))
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
function getMonday(d: Date): Date {
|
||||
@@ -173,13 +185,31 @@ function openEditDrawer(entry: TimeEntry) {
|
||||
}
|
||||
|
||||
async function onMoveEntry(entry: TimeEntry, newStartedAt: string, newStoppedAt: string) {
|
||||
await timeEntryService.update(entry.id, { startedAt: newStartedAt, stoppedAt: newStoppedAt })
|
||||
await loadEntries()
|
||||
// Optimistic update — instant visual feedback
|
||||
const idx = entries.value.findIndex((e) => e.id === entry.id)
|
||||
if (idx === -1) return
|
||||
const original = entries.value[idx]!
|
||||
entries.value[idx] = { ...original, startedAt: newStartedAt, stoppedAt: newStoppedAt }
|
||||
|
||||
try {
|
||||
await timeEntryService.update(entry.id, { startedAt: newStartedAt, stoppedAt: newStoppedAt })
|
||||
} catch {
|
||||
entries.value[idx] = original
|
||||
}
|
||||
}
|
||||
|
||||
async function onResizeEntry(entry: TimeEntry, newStoppedAt: string) {
|
||||
await timeEntryService.update(entry.id, { stoppedAt: newStoppedAt })
|
||||
await loadEntries()
|
||||
async function onResizeEntry(entry: TimeEntry, newStartedAt: string, newStoppedAt: string) {
|
||||
// Optimistic update — instant visual feedback
|
||||
const idx = entries.value.findIndex((e) => e.id === entry.id)
|
||||
if (idx === -1) return
|
||||
const original = entries.value[idx]!
|
||||
entries.value[idx] = { ...original, startedAt: newStartedAt, stoppedAt: newStoppedAt }
|
||||
|
||||
try {
|
||||
await timeEntryService.update(entry.id, { startedAt: newStartedAt, stoppedAt: newStoppedAt })
|
||||
} catch {
|
||||
entries.value[idx] = original
|
||||
}
|
||||
}
|
||||
|
||||
function onContextMenu(event: MouseEvent, entry: TimeEntry | null) {
|
||||
|
||||
Reference in New Issue
Block a user