Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8befb216aa | |||
|
|
0113c08a60 | ||
|
|
c176511d97 | ||
|
|
64de971872 | ||
|
|
3dcc5c21a2 | ||
|
|
47768c0f02 | ||
|
|
b278b8a23a |
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.2.10'
|
app.version: '0.3.1'
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
v-model="editForm.description"
|
v-model="editForm.description"
|
||||||
rows="5"
|
rows="5"
|
||||||
class="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
|
class="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500"
|
||||||
|
style="resize: vertical; min-height: 140px; max-height: 500px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,12 @@
|
|||||||
<MalioInputTextArea
|
<MalioInputTextArea
|
||||||
v-model="form.description"
|
v-model="form.description"
|
||||||
label="Description"
|
label="Description"
|
||||||
:size="3"
|
:size="5"
|
||||||
|
resize="vertical"
|
||||||
|
:min-resize-height="140"
|
||||||
|
:max-resize-height="500"
|
||||||
|
min-resize-width="100%"
|
||||||
|
max-resize-width="100%"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,38 +17,33 @@
|
|||||||
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 h-[3px] w-8 rounded-full bg-black/0 group-hover:bg-black/20 transition" />
|
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 h-[3px] w-8 rounded-full bg-black/0 group-hover:bg-black/20 transition" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-1.5 py-0.5 h-full overflow-hidden">
|
<div class="flex flex-col h-full overflow-hidden px-1.5 py-1">
|
||||||
<!-- Full display: title + project + type dot + duration -->
|
<!-- Top: title + project -->
|
||||||
<template v-if="sizeLevel >= 3">
|
<div class="min-w-0">
|
||||||
<div class="flex items-center gap-1">
|
<div v-if="sizeLevel >= 1" class="font-bold truncate leading-tight" style="color: #0A2168">{{ entry.title || $t('common.untitled') }}</div>
|
||||||
<div class="font-semibold truncate">{{ entry.title || $t('common.untitled') }}</div>
|
<div v-if="sizeLevel >= 2 && entry.project" class="truncate text-[10px] font-semibold opacity-80 leading-tight">{{ entry.project.name }}</div>
|
||||||
<span class="ml-auto shrink-0 text-[10px] tabular-nums opacity-80">{{ duration }}</span>
|
</div>
|
||||||
</div>
|
|
||||||
<div v-if="entry.project" class="truncate text-[10px] opacity-80">{{ entry.project.name }}</div>
|
<!-- Spacer -->
|
||||||
<div v-if="entry.tags.length" class="mt-0.5 flex items-center gap-1 overflow-hidden">
|
<div class="flex-1" />
|
||||||
|
|
||||||
|
<!-- Bottom: tags left, duration right -->
|
||||||
|
<div v-if="sizeLevel >= 3" class="flex items-end justify-between gap-1 min-w-0">
|
||||||
|
<div v-if="entry.tags.length" class="flex items-center gap-1 overflow-hidden min-w-0">
|
||||||
<span
|
<span
|
||||||
v-for="tag in entry.tags"
|
v-for="tag in entry.tags"
|
||||||
:key="tag.id"
|
:key="tag.id"
|
||||||
class="inline-flex items-center gap-0.5 truncate text-[9px] opacity-90"
|
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 }"
|
||||||
>
|
>
|
||||||
<span class="inline-block h-1.5 w-1.5 shrink-0 rounded-full" :style="{ backgroundColor: tag.color }" />
|
|
||||||
{{ tag.label }}
|
{{ tag.label }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<span class="shrink-0 text-[10px] tabular-nums font-bold" style="color: #0A2168">{{ duration }}</span>
|
||||||
|
</div>
|
||||||
<!-- Medium: title + duration -->
|
<div v-else-if="sizeLevel === 2" class="flex items-end justify-end">
|
||||||
<template v-else-if="sizeLevel === 2">
|
<span class="shrink-0 text-[10px] tabular-nums font-bold" style="color: #0A2168">{{ duration }}</span>
|
||||||
<div class="font-semibold truncate">{{ entry.title || $t('common.untitled') }}</div>
|
</div>
|
||||||
<div class="text-[10px] tabular-nums opacity-80">{{ duration }}</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Small: title only -->
|
|
||||||
<template v-else-if="sizeLevel === 1">
|
|
||||||
<div class="font-semibold truncate text-[10px] leading-tight">{{ entry.title || $t('common.untitled') }}</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Tiny: just a colored bar, no text -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Resize handle bottom (outside block) -->
|
<!-- Resize handle bottom (outside block) -->
|
||||||
@@ -116,13 +111,11 @@ const sizeLevel = computed(() => {
|
|||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const hasProject = computed(() => !!props.entry.project)
|
||||||
|
|
||||||
const blockStyle = computed(() => {
|
const blockStyle = computed(() => {
|
||||||
const startMinutes = startDate.value.getHours() * 60 + startDate.value.getMinutes() + resizeTopDeltaMinutes.value
|
const startMinutes = startDate.value.getHours() * 60 + startDate.value.getMinutes() + resizeTopDeltaMinutes.value
|
||||||
const topPx = ((startMinutes - props.dayStartHour * 60) / 60) * props.hourHeight
|
const topPx = ((startMinutes - props.dayStartHour * 60) / 60) * props.hourHeight
|
||||||
const hex = (props.entry.project?.color ?? '#94a3b8').replace('#', '')
|
|
||||||
const r = parseInt(hex.substring(0, 2), 16)
|
|
||||||
const g = parseInt(hex.substring(2, 4), 16)
|
|
||||||
const b = parseInt(hex.substring(4, 6), 16)
|
|
||||||
|
|
||||||
const col = props.columnIndex ?? 0
|
const col = props.columnIndex ?? 0
|
||||||
const total = props.totalColumns ?? 1
|
const total = props.totalColumns ?? 1
|
||||||
@@ -130,14 +123,28 @@ const blockStyle = computed(() => {
|
|||||||
const leftPercent = (col / total) * 100
|
const leftPercent = (col / total) * 100
|
||||||
const widthPercent = (1 / total) * 100
|
const widthPercent = (1 / total) * 100
|
||||||
|
|
||||||
return {
|
const base: Record<string, string> = {
|
||||||
top: `${topPx}px`,
|
top: `${topPx}px`,
|
||||||
height: `${heightPx.value}px`,
|
height: `${heightPx.value}px`,
|
||||||
backgroundColor: `rgb(${Math.round(r + (255 - r) * 0.6)}, ${Math.round(g + (255 - g) * 0.6)}, ${Math.round(b + (255 - b) * 0.6)})`,
|
|
||||||
color: `rgb(${r}, ${g}, ${b})`,
|
|
||||||
left: `calc(${leftPercent}% + ${gapPx}px)`,
|
left: `calc(${leftPercent}% + ${gapPx}px)`,
|
||||||
width: `calc(${widthPercent}% - ${gapPx * 2}px)`,
|
width: `calc(${widthPercent}% - ${gapPx * 2}px)`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasProject.value) {
|
||||||
|
const hex = props.entry.project!.color.replace('#', '')
|
||||||
|
const r = parseInt(hex.substring(0, 2), 16)
|
||||||
|
const g = parseInt(hex.substring(2, 4), 16)
|
||||||
|
const b = parseInt(hex.substring(4, 6), 16)
|
||||||
|
base.backgroundColor = `rgb(${Math.round(r + (255 - r) * 0.6)}, ${Math.round(g + (255 - g) * 0.6)}, ${Math.round(b + (255 - b) * 0.6)})`
|
||||||
|
base.color = `rgb(${r}, ${g}, ${b})`
|
||||||
|
} else {
|
||||||
|
base.backgroundColor = '#e5e7eb'
|
||||||
|
base.backgroundImage = 'repeating-conic-gradient(#d1d5db 0% 25%, #f3f4f6 0% 50%)'
|
||||||
|
base.backgroundSize = '12px 12px'
|
||||||
|
base.color = '#6b7280'
|
||||||
|
}
|
||||||
|
|
||||||
|
return base
|
||||||
})
|
})
|
||||||
|
|
||||||
// --- Click / Drag detection ---
|
// --- Click / Drag detection ---
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="calendarEl" class="relative flex h-full flex-col rounded-lg border border-neutral-200 bg-white">
|
<div ref="calendarEl" class="relative flex h-full flex-col rounded-lg border border-neutral-200 bg-white">
|
||||||
<!-- Day headers -->
|
<!-- Grid body with sticky header -->
|
||||||
<div
|
<div ref="gridBodyEl" class="relative min-h-0 flex-1 overflow-y-auto">
|
||||||
class="z-20 flex flex-shrink-0 border-b border-neutral-200 bg-white rounded-t-lg"
|
<!-- Day headers (sticky inside scroll container) -->
|
||||||
>
|
<div class="sticky top-0 z-20 flex border-b border-neutral-200 bg-white rounded-t-lg">
|
||||||
<div class="w-16 shrink-0 border-r border-neutral-200" />
|
<div class="w-16 shrink-0 border-r border-neutral-200" />
|
||||||
<div
|
<div
|
||||||
v-for="day in days"
|
v-for="day in days"
|
||||||
:key="day.dateStr"
|
:key="'header-' + day.dateStr"
|
||||||
class="flex-1 border-r border-neutral-100 py-2 text-center"
|
class="flex-1 border-r border-neutral-100 py-2 text-center"
|
||||||
>
|
>
|
||||||
<div class="text-lg font-bold" :class="isToday(day.date) ? 'text-orange-500' : 'text-neutral-900'">
|
<div class="text-lg font-bold" :class="isToday(day.date) ? 'text-orange-500' : 'text-neutral-900'">
|
||||||
{{ day.dayNum }}
|
{{ day.dayNum }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs" :class="isToday(day.date) ? 'text-orange-500' : 'text-neutral-500'">
|
||||||
|
{{ day.label }}
|
||||||
|
</div>
|
||||||
|
<div class="text-[10px] text-neutral-400">{{ day.totalFormatted }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs" :class="isToday(day.date) ? 'text-orange-500' : 'text-neutral-500'">
|
|
||||||
{{ day.label }}
|
|
||||||
</div>
|
|
||||||
<div class="text-[10px] text-neutral-400">{{ day.totalFormatted }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Grid body -->
|
<!-- Columns -->
|
||||||
<div ref="gridBodyEl" class="relative flex min-h-0 flex-1 overflow-y-auto">
|
<div class="relative flex">
|
||||||
<!-- Hour labels -->
|
<!-- Hour labels -->
|
||||||
<div class="w-16 shrink-0">
|
<div class="w-16 shrink-0">
|
||||||
<div
|
<div
|
||||||
@@ -134,7 +134,8 @@
|
|||||||
<div class="text-[10px] opacity-90">{{ dragState.timeLabel }}</div>
|
<div class="text-[10px] opacity-90">{{ dragState.timeLabel }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div><!-- end columns flex -->
|
||||||
|
</div><!-- end gridBodyEl -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<header class="border-b border-neutral-200 bg-primary-500 p-3 text-white sm:p-5">
|
<header class="border-b border-neutral-200 bg-primary-500 px-3 py-2 text-white sm:px-5 sm:py-2 max-h-[60px]">
|
||||||
<div class="flex h-full items-center justify-between">
|
<div class="flex h-full items-center justify-between">
|
||||||
<button
|
<button
|
||||||
class="rounded-md p-2 text-white hover:bg-primary-600 transition-colors lg:hidden"
|
class="rounded-md p-2 text-white hover:bg-primary-600 transition-colors lg:hidden"
|
||||||
@@ -7,6 +7,17 @@
|
|||||||
>
|
>
|
||||||
<Icon name="mdi:menu" size="24" />
|
<Icon name="mdi:menu" size="24" />
|
||||||
</button>
|
</button>
|
||||||
|
<div class="hidden items-center gap-2 lg:flex">
|
||||||
|
<h1 class="text-lg font-bold tracking-tight">{{ appTitle }}</h1>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="rounded-md p-1 text-white/60 transition-colors hover:bg-primary-600 hover:text-white"
|
||||||
|
:title="appTitle === 'NeauTime' ? 'Switch to Lesstime' : 'Switch to NeauTime'"
|
||||||
|
@click="toggleTitle"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:swap-horizontal" size="18" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="ml-auto flex items-center gap-4 text-xl text-white sm:gap-8">
|
<div class="ml-auto flex items-center gap-4 text-xl text-white sm:gap-8">
|
||||||
<NotificationBell />
|
<NotificationBell />
|
||||||
<div class="group relative flex gap-2 sm:gap-4">
|
<div class="group relative flex gap-2 sm:gap-4">
|
||||||
@@ -45,6 +56,13 @@ defineProps<{
|
|||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const ui = useUiStore()
|
const ui = useUiStore()
|
||||||
|
|
||||||
|
const appTitle = ref(localStorage.getItem('appTitle') || 'NeauTime')
|
||||||
|
|
||||||
|
function toggleTitle() {
|
||||||
|
appTitle.value = appTitle.value === 'NeauTime' ? 'Lesstime' : 'NeauTime'
|
||||||
|
localStorage.setItem('appTitle', appTitle.value)
|
||||||
|
}
|
||||||
|
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
await auth.logout()
|
await auth.logout()
|
||||||
await navigateTo('/login')
|
await navigateTo('/login')
|
||||||
|
|||||||
@@ -15,22 +15,6 @@
|
|||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<div class="flex shrink-0 overflow-hidden rounded-md border border-neutral-300">
|
|
||||||
<button
|
|
||||||
class="px-2 py-[7px] text-xs font-medium transition"
|
|
||||||
:class="mode === 'day' ? 'bg-primary-500 text-white' : 'text-neutral-500 hover:bg-neutral-100'"
|
|
||||||
@click.stop="switchMode('day')"
|
|
||||||
>
|
|
||||||
{{ t('common.day') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="px-2 py-[7px] text-xs font-medium transition"
|
|
||||||
:class="mode === 'week' ? 'bg-primary-500 text-white' : 'text-neutral-500 hover:bg-neutral-100'"
|
|
||||||
@click.stop="switchMode('week')"
|
|
||||||
>
|
|
||||||
{{ t('common.weekShort') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="relative cursor-pointer">
|
<div class="relative cursor-pointer">
|
||||||
<input
|
<input
|
||||||
:value="displayValue"
|
:value="displayValue"
|
||||||
@@ -85,6 +69,7 @@ const { t } = useI18n()
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue?: Date | [Date, Date] | null
|
modelValue?: Date | [Date, Date] | null
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
|
pickerMode?: 'day' | 'week'
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -92,7 +77,7 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const datepicker = ref<InstanceType<typeof VueDatePicker> | null>(null)
|
const datepicker = ref<InstanceType<typeof VueDatePicker> | null>(null)
|
||||||
const mode = ref<'day' | 'week'>('week')
|
const mode = computed(() => props.pickerMode ?? 'week')
|
||||||
const internalValue = ref<Date | Date[] | null>(null)
|
const internalValue = ref<Date | Date[] | null>(null)
|
||||||
|
|
||||||
const displayValue = computed(() => {
|
const displayValue = computed(() => {
|
||||||
@@ -133,13 +118,6 @@ function formatShortDate(d: Date): string {
|
|||||||
return `${day}/${month}`
|
return `${day}/${month}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchMode(newMode: 'day' | 'week') {
|
|
||||||
if (mode.value === newMode) return
|
|
||||||
mode.value = newMode
|
|
||||||
internalValue.value = null
|
|
||||||
emit('update:modelValue', null)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUpdate(value: Date | Date[] | null) {
|
function onUpdate(value: Date | Date[] | null) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
emit('update:modelValue', null)
|
emit('update:modelValue', null)
|
||||||
@@ -163,7 +141,6 @@ function onClear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectToday() {
|
function selectToday() {
|
||||||
mode.value = 'day'
|
|
||||||
const today = new Date()
|
const today = new Date()
|
||||||
today.setHours(0, 0, 0, 0)
|
today.setHours(0, 0, 0, 0)
|
||||||
internalValue.value = today
|
internalValue.value = today
|
||||||
@@ -171,7 +148,6 @@ function selectToday() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectThisWeek() {
|
function selectThisWeek() {
|
||||||
mode.value = 'week'
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const day = now.getDay()
|
const day = now.getDay()
|
||||||
const monday = new Date(now)
|
const monday = new Date(now)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
class="flex w-full items-center justify-center gap-2 rounded-md py-2 text-sm font-semibold text-white transition"
|
class="flex items-center justify-center gap-2 text-sm font-semibold text-white transition"
|
||||||
:class="[
|
:class="[
|
||||||
timerStore.isRunning
|
timerStore.isRunning
|
||||||
? 'bg-[#F18619] hover:bg-[#d97314]'
|
? 'bg-[#F18619] hover:bg-[#d97314]'
|
||||||
: 'bg-primary-500 hover:bg-primary-600',
|
: 'bg-primary-500 hover:bg-primary-600',
|
||||||
collapsed ? 'px-2' : 'px-4'
|
collapsed ? 'mx-auto h-10 w-10 rounded-full' : 'w-full rounded-md px-4 py-2'
|
||||||
]"
|
]"
|
||||||
:title="timerStore.isRunning ? 'Arrêter le timer' : 'Démarrer un timer'"
|
:title="timerStore.isRunning ? 'Arrêter le timer' : 'Démarrer un timer'"
|
||||||
@click="timerStore.isRunning ? timerStore.stop() : timerStore.start()"
|
@click="timerStore.isRunning ? timerStore.stop() : timerStore.start()"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
ui.sidebarOpen ? 'w-64 translate-x-0' : '-translate-x-full',
|
ui.sidebarOpen ? 'w-64 translate-x-0' : '-translate-x-full',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between overflow-hidden" :class="sidebarIsCollapsed ? 'p-2 justify-center' : ''">
|
<div class="flex items-center overflow-hidden" :class="sidebarIsCollapsed ? 'justify-center p-3' : 'justify-between'">
|
||||||
<img
|
<img
|
||||||
v-if="!sidebarIsCollapsed"
|
v-if="!sidebarIsCollapsed"
|
||||||
src="/malio.png"
|
src="/malio.png"
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
v-else
|
v-else
|
||||||
src="/malio.png"
|
src="/LOGO_CARRE.png"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
class="h-8 w-8 object-cover object-left"
|
class="w-[46px] h-[55px]"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="mr-2 rounded-md p-2 text-neutral-500 hover:bg-neutral-200 hover:text-neutral-700 transition-colors lg:hidden"
|
class="mr-2 rounded-md p-2 text-neutral-500 hover:bg-neutral-200 hover:text-neutral-700 transition-colors lg:hidden"
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
to="/time-tracking"
|
to="/time-tracking"
|
||||||
icon="mdi:clock-outline"
|
icon="mdi:calendar-edit-outline"
|
||||||
label="Suivi de temps"
|
label="Suivi de temps"
|
||||||
:collapsed="sidebarIsCollapsed"
|
:collapsed="sidebarIsCollapsed"
|
||||||
@click="ui.closeMobileSidebar()"
|
@click="ui.closeMobileSidebar()"
|
||||||
@@ -108,19 +108,21 @@
|
|||||||
<SidebarTimer :collapsed="sidebarIsCollapsed" />
|
<SidebarTimer :collapsed="sidebarIsCollapsed" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 items-center p-4">
|
<div class="flex items-center justify-center p-4">
|
||||||
<p v-if="!sidebarIsCollapsed" class="font-bold">v {{ version }}</p>
|
<p v-if="!sidebarIsCollapsed" class="font-bold">v {{ version }}</p>
|
||||||
<button
|
|
||||||
class="hidden items-center justify-center rounded-md p-2 text-neutral-500 hover:bg-neutral-200 hover:text-neutral-700 transition-colors lg:flex"
|
|
||||||
:title="ui.sidebarCollapsed ? 'Ouvrir le menu' : 'Réduire le menu'"
|
|
||||||
@click="ui.toggleSidebar()"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
:name="ui.sidebarCollapsed ? 'mdi:chevron-right' : 'mdi:chevron-left'"
|
|
||||||
size="20"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Collapse toggle button centered vertically on the sidebar edge -->
|
||||||
|
<button
|
||||||
|
class="absolute top-1/2 -right-4 z-10 hidden h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full border border-neutral-200 bg-white text-neutral-400 shadow-sm hover:text-neutral-700 transition-colors lg:flex"
|
||||||
|
:title="ui.sidebarCollapsed ? 'Ouvrir le menu' : 'Réduire le menu'"
|
||||||
|
@click="ui.toggleSidebar()"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
:name="ui.sidebarCollapsed ? 'mdi:chevron-right' : 'mdi:chevron-left'"
|
||||||
|
size="18"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="h-full flex-1 flex flex-col min-h-0 min-w-0">
|
<div class="h-full flex-1 flex flex-col min-h-0 min-w-0">
|
||||||
|
|||||||
@@ -41,6 +41,11 @@
|
|||||||
v-model="form.description"
|
v-model="form.description"
|
||||||
:label="$t('clientTicket.description')"
|
:label="$t('clientTicket.description')"
|
||||||
:size="5"
|
:size="5"
|
||||||
|
resize="vertical"
|
||||||
|
:min-resize-height="140"
|
||||||
|
:max-resize-height="500"
|
||||||
|
min-resize-width="100%"
|
||||||
|
max-resize-width="100%"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,20 +17,26 @@
|
|||||||
{{ currentMonthLabel }}
|
{{ currentMonthLabel }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="flex shrink-0 items-center gap-1 rounded-md border border-neutral-200">
|
<div class="flex shrink-0 items-center gap-3">
|
||||||
<button class="px-2 py-1 text-neutral-500 hover:text-neutral-700" @click="navigatePrev">
|
<button class="rounded-full p-1 text-neutral-400 hover:text-neutral-700 transition" @click="navigatePrev">
|
||||||
<Icon name="mdi:chevron-left" size="20" />
|
<Icon name="mdi:chevron-left" size="20" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
v-for="mode in (['week', 'day', 'list'] as const)"
|
<div class="flex items-center rounded-full bg-neutral-100 p-1">
|
||||||
:key="mode"
|
<button
|
||||||
class="px-3 py-1 text-sm font-semibold transition"
|
v-for="mode in (['week', 'day', 'list'] as const)"
|
||||||
:class="viewMode === mode ? 'bg-primary-500 text-white rounded' : 'text-neutral-500 hover:text-neutral-700'"
|
:key="mode"
|
||||||
@click="viewMode = mode"
|
class="rounded-full px-4 py-1.5 text-sm font-semibold transition-all"
|
||||||
>
|
:class="viewMode === mode
|
||||||
{{ mode === 'week' ? 'Semaine' : mode === 'day' ? 'Jour' : 'Liste' }}
|
? 'bg-primary-500 text-white shadow-sm'
|
||||||
</button>
|
: 'text-neutral-500 hover:text-neutral-700'"
|
||||||
<button class="px-2 py-1 text-neutral-500 hover:text-neutral-700" @click="navigateNext">
|
@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" />
|
<Icon name="mdi:chevron-right" size="20" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,7 +77,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DateFilter v-model="selectedDateFilter" />
|
<DateFilter v-model="selectedDateFilter" :picker-mode="viewMode === 'day' ? 'day' : 'week'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -334,6 +340,7 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
watch(viewMode, () => {
|
watch(viewMode, () => {
|
||||||
|
selectedDateFilter.value = null
|
||||||
startDate.value = viewMode.value === 'day' ? startDate.value : getMonday(startDate.value)
|
startDate.value = viewMode.value === 'day' ? startDate.value : getMonday(startDate.value)
|
||||||
loadEntries()
|
loadEntries()
|
||||||
})
|
})
|
||||||
|
|||||||
BIN
frontend/public/LOGO_CARRE.png
Normal file
BIN
frontend/public/LOGO_CARRE.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Reference in New Issue
Block a user