Compare commits

..

1 Commits
v0.3.2 ... main

Author SHA1 Message Date
8befb216aa Merge pull request '[#NUMERO_TICKET] TITRE TICKET' (#2) from develop into main
Reviewed-on: #2
2026-03-18 13:16:19 +00:00
8 changed files with 71 additions and 105 deletions

View File

@@ -9,12 +9,7 @@
<div class="flex items-start justify-between gap-2">
<div class="min-w-0">
<div class="flex items-center gap-1">
<span v-if="task.project && task.number" class="text-xs font-semibold" :style="{ color: task.project.color }">{{ task.project.code }}{{ task.number }}</span>
<Icon
v-if="task.priority?.label === 'Haute'"
name="mdi:flag-variant"
class="h-3.5 w-3.5 text-red-600"
/>
<span v-if="task.project && task.number" class="text-xs font-medium text-neutral-400">{{ task.project.code }}{{ task.number }}</span>
<Icon
v-if="task.clientTicket"
name="heroicons:user-circle"

View File

@@ -29,21 +29,15 @@
<!-- Bottom: tags left, duration right -->
<div v-if="sizeLevel >= 3" class="flex items-end justify-between gap-1 min-w-0">
<div v-if="showTags && entry.tags.length" class="flex flex-wrap items-center gap-0.5 overflow-hidden min-w-0">
<div v-if="entry.tags.length" class="flex items-center gap-1 overflow-hidden min-w-0">
<span
v-for="tag in visibleTags"
v-for="tag in entry.tags"
:key="tag.id"
class="inline-flex items-center rounded-full px-1.5 py-0.5 text-[9px] font-bold text-white truncate max-w-[5rem]"
class="inline-flex shrink-0 items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-bold text-white"
:style="{ backgroundColor: tag.color }"
>
{{ tag.label }}
</span>
<span
v-if="hiddenTagCount > 0"
class="inline-flex items-center rounded-full bg-black/20 px-1 py-0.5 text-[9px] font-bold text-white"
>
+{{ hiddenTagCount }}
</span>
</div>
<span class="shrink-0 text-[10px] tabular-nums font-bold" style="color: #0A2168">{{ duration }}</span>
</div>
@@ -117,17 +111,6 @@ const sizeLevel = computed(() => {
return 0
})
const showTags = computed(() => (props.totalColumns ?? 1) <= 2)
const maxVisibleTags = computed(() => {
const total = props.totalColumns ?? 1
if (total >= 2) return 1
return 2
})
const visibleTags = computed(() => props.entry.tags.slice(0, maxVisibleTags.value))
const hiddenTagCount = computed(() => Math.max(0, props.entry.tags.length - maxVisibleTags.value))
const hasProject = computed(() => !!props.entry.project)
const blockStyle = computed(() => {

View File

@@ -105,22 +105,12 @@
>
Supprimer
</button>
<div class="flex gap-2">
<button
v-if="isEditing"
type="button"
class="rounded-md bg-blue-500 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-600 transition"
@click="onDuplicate"
>
Dupliquer
</button>
<button
type="submit"
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-primary-600 transition"
>
Enregistrer
</button>
</div>
<button
type="submit"
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-primary-600 transition"
>
Enregistrer
</button>
</div>
</form>
</AppDrawer>
@@ -241,26 +231,6 @@ watch([() => props.modelValue, () => props.entry] as const, ([open, entry]) => {
}
})
async function onDuplicate() {
if (!form.date || !form.startTime || !form.endTime) return
const { create } = useTimeEntryService()
const payload: Record<string, unknown> = {
title: form.title || null,
description: form.description || null,
startedAt: toISO(form.date, form.startTime),
stoppedAt: form.endTime ? toISO(form.date, form.endTime) : null,
user: `/api/users/${form.userId}`,
project: form.projectId ? `/api/projects/${form.projectId}` : null,
tags: form.tagIds.map(id => `/api/task_tags/${id}`),
}
await create(payload as TimeEntryWrite)
emit('saved')
isOpen.value = false
}
async function onDelete() {
if (!props.entry) return
const { remove } = useTimeEntryService()

View File

@@ -7,7 +7,7 @@
<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"
class="group flex items-center gap-4 rounded-lg border border-neutral-200 bg-white px-4 py-3 cursor-pointer transition hover:border-neutral-300 hover:shadow-sm"
@click="emit('editEntry', entry)"
>
<!-- Color bar -->
@@ -18,14 +18,14 @@
<!-- 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">
<div class="flex items-center gap-2">
<span class="truncate text-sm font-semibold text-neutral-900">
{{ entry.title || $t('common.untitled') }}
</span>
<span
v-for="tag in entry.tags"
:key="tag.id"
class="rounded-full px-2 py-0.5 text-[10px] font-semibold text-white"
class="shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold text-white"
:style="{ backgroundColor: tag.color }"
>
{{ tag.label }}

View File

@@ -201,11 +201,14 @@ function getScrollParent(): HTMLElement | null {
// Scroll to current hour on mount
onMounted(() => {
nextTick(() => {
if (!gridBodyEl.value) return
if (!calendarEl.value) return
const scrollParent = getScrollParent()
if (!scrollParent) return
const now = new Date()
const currentMinutes = now.getHours() * 60 + now.getMinutes()
const scrollTarget = (currentMinutes / 60) * hourHeight - gridBodyEl.value.clientHeight / 3
gridBodyEl.value.scrollTop = Math.max(0, scrollTarget)
const calendarTop = calendarEl.value.offsetTop
const scrollTarget = calendarTop + (currentMinutes / 60) * hourHeight - scrollParent.clientHeight / 3
scrollParent.scrollTop = Math.max(0, scrollTarget)
})
})

View File

@@ -1,5 +1,5 @@
<template>
<div class="date-filter inline-flex h-8 items-center [&>.dp__main]:!inline-flex [&>.dp__main]:!items-center">
<div class="date-filter">
<VueDatePicker
ref="datepicker"
v-model="internalValue"
@@ -14,11 +14,29 @@
@update:model-value="onUpdate"
>
<template #trigger>
<button
class="relative flex h-8 w-8 items-center justify-center rounded-full text-orange-500 transition hover:bg-orange-50"
>
<Icon name="mdi:calendar-blank" size="20" />
</button>
<div class="flex items-center gap-1">
<div class="relative cursor-pointer">
<input
:value="displayValue"
class="w-full cursor-pointer rounded-md border border-neutral-300 bg-white px-3 py-[7px] pr-8 text-sm text-neutral-700 outline-none transition placeholder:text-neutral-400 focus:border-primary-500"
:placeholder="t('common.dateFilter')"
readonly
/>
<button
v-if="internalValue"
class="absolute right-2 top-1/2 -translate-y-1/2 text-neutral-400 hover:text-neutral-600"
@click.stop="onClear"
>
<Icon name="mdi:close-circle" size="16" />
</button>
<Icon
v-else
name="mdi:calendar"
size="16"
class="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-neutral-400"
/>
</div>
</div>
</template>
<template #action-buttons>

View File

@@ -436,16 +436,10 @@ onMounted(() => {
/>
<span
v-if="task.project && task.number"
class="text-sm font-semibold"
:style="{ color: task.project.color }"
class="text-sm font-medium text-primary-500"
>
{{ task.project.code }}-{{ task.number }}
</span>
<Icon
v-if="task.priority?.label === 'Haute'"
name="mdi:flag-variant"
class="h-4 w-4 text-red-600"
/>
</div>
</div>
</div>

View File

@@ -13,30 +13,31 @@
</div>
<div class="relative z-30 mt-4 flex flex-wrap items-center gap-3 sm:gap-4">
<div class="flex shrink-0 items-center gap-1 h-8">
<button class="flex h-8 w-8 items-center justify-center rounded-full text-neutral-400 hover:text-neutral-700 transition" @click="navigatePrev">
<h2 class="shrink-0 whitespace-nowrap text-lg font-bold text-orange-500">
{{ currentMonthLabel }}
</h2>
<div class="flex shrink-0 items-center gap-3">
<button class="rounded-full p-1 text-neutral-400 hover:text-neutral-700 transition" @click="navigatePrev">
<Icon name="mdi:chevron-left" size="20" />
</button>
<DateFilter v-model="selectedDateFilter" :picker-mode="viewMode === 'day' ? 'day' : 'week'" />
<h2 class="shrink-0 whitespace-nowrap text-lg font-bold leading-8 text-orange-500">
{{ currentMonthLabel }}
</h2>
<button class="flex h-8 w-8 items-center justify-center rounded-full text-neutral-400 hover:text-neutral-700 transition" @click="navigateNext">
<Icon name="mdi:chevron-right" size="20" />
</button>
</div>
<div class="flex items-center rounded-full bg-neutral-100 p-1">
<button
v-for="mode in (['week', 'day', 'list'] as const)"
:key="mode"
class="rounded-full px-4 py-1.5 text-sm font-semibold transition-all"
:class="viewMode === mode
? 'bg-primary-500 text-white shadow-sm'
: 'text-neutral-500 hover:text-neutral-700'"
@click="viewMode = mode"
>
{{ mode === 'week' ? 'Semaine' : mode === 'day' ? 'Jour' : 'Liste' }}
<div class="flex items-center rounded-full bg-neutral-100 p-1">
<button
v-for="mode in (['week', 'day', 'list'] as const)"
:key="mode"
class="rounded-full px-4 py-1.5 text-sm font-semibold transition-all"
:class="viewMode === mode
? 'bg-primary-500 text-white shadow-sm'
: 'text-neutral-500 hover:text-neutral-700'"
@click="viewMode = mode"
>
{{ mode === 'week' ? 'Semaine' : mode === 'day' ? 'Jour' : 'Liste' }}
</button>
</div>
<button class="rounded-full p-1 text-neutral-400 hover:text-neutral-700 transition" @click="navigateNext">
<Icon name="mdi:chevron-right" size="20" />
</button>
</div>
@@ -75,6 +76,8 @@
text-value="text-sm"
/>
</div>
<DateFilter v-model="selectedDateFilter" :picker-mode="viewMode === 'day' ? 'day' : 'week'" />
</div>
</div>