Files
Lesstime/frontend/components/time-tracking/TimeEntryList.vue
Matthieu ec35a1b2aa
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build Release Artefact / build (push) Successful in 1m26s
feat(ui) : improve time-tracking UX, responsive tags, and task priority flag
- Add duplicate button in time entry drawer
- Make time entry blocks and list responsive (tags wrap, hide on narrow)
- Replace date filter input with calendar icon next to month title
- Fix scroll to current hour in calendar (use gridBodyEl)
- Show project color on ticket code in task cards and my-tasks
- Add red flag icon for high priority tasks in kanban and my-tasks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 15:44:36 +01:00

105 lines
4.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="space-y-2">
<div v-if="entries.length === 0" class="rounded-lg border border-neutral-200 bg-neutral-50 py-12 text-center text-sm text-neutral-400">
{{ $t('timeEntries.noEntries') }}
</div>
<div
v-for="entry in sortedEntries"
:key="entry.id"
class="group flex items-center gap-2 sm:gap-4 rounded-lg border border-neutral-200 bg-white px-3 sm:px-4 py-3 cursor-pointer transition hover:border-neutral-300 hover:shadow-sm"
@click="emit('editEntry', entry)"
>
<!-- Color bar -->
<div
class="h-10 w-1 shrink-0 rounded-full"
:style="{ backgroundColor: entry.project?.color ?? '#94a3b8' }"
/>
<!-- Main info -->
<div class="min-w-0 flex-1">
<div class="truncate text-sm font-semibold text-neutral-900">
{{ entry.title || $t('common.untitled') }}
</div>
<div v-if="entry.tags.length" class="mt-1 flex flex-wrap gap-1">
<span
v-for="tag in entry.tags"
:key="tag.id"
class="rounded-full px-2 py-0.5 text-[10px] font-semibold text-white"
:style="{ backgroundColor: tag.color }"
>
{{ tag.label }}
</span>
</div>
<div class="mt-0.5 flex items-center gap-2 text-xs text-neutral-500">
<span v-if="entry.project">{{ entry.project.name }}</span>
<span v-if="entry.project && entry.description" class="text-neutral-300">·</span>
<span v-if="entry.description" class="truncate">{{ entry.description }}</span>
</div>
</div>
<!-- Time info -->
<div class="shrink-0 text-right">
<div class="text-sm font-semibold tabular-nums text-neutral-900">
{{ formatDuration(entry) }}
</div>
<div class="text-xs tabular-nums text-neutral-400">
{{ formatTime(entry.startedAt) }} {{ entry.stoppedAt ? formatTime(entry.stoppedAt) : '...' }}
</div>
</div>
<!-- Date -->
<div class="hidden shrink-0 text-xs text-neutral-400 sm:block">
{{ formatDate(entry.startedAt) }}
</div>
<!-- Delete action -->
<button
class="shrink-0 rounded-md p-1.5 text-neutral-300 opacity-0 transition hover:bg-red-50 hover:text-red-500 group-hover:opacity-100"
:title="$t('common.delete')"
@click.stop="emit('deleteEntry', entry)"
>
<Icon name="mdi:delete-outline" size="18" />
</button>
</div>
</div>
</template>
<script setup lang="ts">
import type { TimeEntry } from '~/services/dto/time-entry'
const props = defineProps<{
entries: TimeEntry[]
}>()
const emit = defineEmits<{
(e: 'editEntry', entry: TimeEntry): void
(e: 'deleteEntry', entry: TimeEntry): void
}>()
const sortedEntries = computed(() => {
return [...props.entries].sort((a, b) => {
return new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()
})
})
function formatDuration(entry: TimeEntry): string {
const start = new Date(entry.startedAt).getTime()
const end = entry.stoppedAt ? new Date(entry.stoppedAt).getTime() : Date.now()
const diff = end - start
const h = Math.floor(diff / 3600000)
const m = Math.floor((diff % 3600000) / 60000)
return m > 0 ? `${h}h${String(m).padStart(2, '0')}` : `${h}h`
}
function formatTime(iso: string): string {
const d = new Date(iso)
return d.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })
}
function formatDate(iso: string): string {
const d = new Date(iso)
return d.toLocaleDateString('fr-FR', { weekday: 'short', day: 'numeric', month: 'short' })
}
</script>