Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions alfy-bot-frontend/src/features/calendar/lib/use-create-slot-drag.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ref, computed, type Ref, type CSSProperties } from 'vue'
import type { CSSProperties, MaybeRefOrGetter, Ref } from 'vue'
import { computed, ref, toValue } from 'vue'

const MAX_MINUTES = 23 * 60 + 55
const SHOW_OVERLAY_THRESHOLD_PX = 3
Expand All @@ -10,7 +11,7 @@ export interface CreateSlotDragOptions {
trackEl: Ref<HTMLElement | null>
dayOffset: (date: Date) => number
dateFromX: (x: number) => Date
dayWidth: number
dayWidth: MaybeRefOrGetter<number>
onCreated: (date: Date, startMinutes: number, durationMinutes: number) => Promise<void>
}

Expand Down Expand Up @@ -39,7 +40,7 @@ export function useCreateSlotDrag(options: CreateSlotDragOptions) {
position: 'absolute',
top: `${overlayTop.value}px`,
left: `${overlayLeft.value}px`,
width: `${options.dayWidth - 4}px`,
width: `${toValue(options.dayWidth) - 4}px`,
}))

const overlayTimeLabel = computed(() => {
Expand All @@ -49,14 +50,19 @@ export function useCreateSlotDrag(options: CreateSlotDragOptions) {
})

function onPointerDown(e: PointerEvent) {
if (e.button !== 0) return
if (e.ctrlKey || e.metaKey) return
if (e.button !== 0)
return
if (e.ctrlKey || e.metaKey)
return
const target = e.target as HTMLElement | null
if (!target) return
if (target.closest('[data-calendar-event-block]')) return
if (!target)
return
if (target.closest('[data-calendar-event-block]'))
return

const track = options.trackEl.value
if (!track) return
if (!track)
return

const rect = track.getBoundingClientRect()
const initialX = e.clientX - rect.left
Expand Down Expand Up @@ -94,7 +100,8 @@ export function useCreateSlotDrag(options: CreateSlotDragOptions) {
if (!showOverlay.value) {
const dx = Math.abs(ev.clientX - rect.left - initialX)
const dy = Math.abs(y - initialY)
if (dx < SHOW_OVERLAY_THRESHOLD_PX && dy < SHOW_OVERLAY_THRESHOLD_PX) return
if (dx < SHOW_OVERLAY_THRESHOLD_PX && dy < SHOW_OVERLAY_THRESHOLD_PX)
return
showOverlay.value = true
}

Expand Down Expand Up @@ -149,7 +156,8 @@ export function useCreateSlotDrag(options: CreateSlotDragOptions) {
}, LONG_PRESS_DELAY_MS)

function onWaitMove(ev: PointerEvent) {
if (timerFired) return
if (timerFired)
return
const dx = ev.clientX - initialClientX
const dy = ev.clientY - initialClientY
if (Math.sqrt(dx * dx + dy * dy) > TOUCH_DRAG_THRESHOLD_PX) {
Expand All @@ -158,7 +166,8 @@ export function useCreateSlotDrag(options: CreateSlotDragOptions) {
}

function onWaitEnd() {
if (!timerFired) cleanupWait()
if (!timerFired)
cleanupWait()
}

function cleanupWait() {
Expand Down
24 changes: 15 additions & 9 deletions alfy-bot-frontend/src/features/calendar/lib/use-pointer-dnd.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { ref, computed, type Ref, type CSSProperties } from 'vue'
import { startOfDay } from 'date-fns'
import type { CSSProperties, MaybeRefOrGetter, Ref } from 'vue'
import type { CalendarEvent } from '../model/types'
import { startOfDay } from 'date-fns'
import { computed, ref, toValue } from 'vue'

const MAX_MINUTES = 23 * 60 + 55

export interface PointerDndOptions {
trackEl: Ref<HTMLElement | null>
dayOffset: (date: Date) => number
dateFromX: (x: number) => Date
dayWidth: number
dayWidth: MaybeRefOrGetter<number>
onMoved: (taskId: string, newDate: Date, startMinutes: number) => Promise<void>
onClicked?: (event: CalendarEvent) => void
onResized?: (taskId: string, durationMinutes: number) => Promise<void>
Expand Down Expand Up @@ -39,7 +40,7 @@ export function usePointerDnd(options: PointerDndOptions) {
position: 'absolute',
top: `${overlayTop.value}px`,
left: `${overlayLeft.value}px`,
width: `${options.dayWidth - 4}px`,
width: `${toValue(options.dayWidth) - 4}px`,
}))

const overlayTimeLabel = computed(() => {
Expand All @@ -52,7 +53,8 @@ export function usePointerDnd(options: PointerDndOptions) {

function onGrab(event: CalendarEvent, e: PointerEvent) {
const track = options.trackEl.value
if (!track) return
if (!track)
return

isDragging.value = true
showOverlay.value = false
Expand Down Expand Up @@ -83,7 +85,8 @@ export function usePointerDnd(options: PointerDndOptions) {
previewDate.value = date
overlayLeft.value = options.dayOffset(date) + 2

if (!showOverlay.value) showOverlay.value = true
if (!showOverlay.value)
showOverlay.value = true
}

const cleanup = () => {
Expand All @@ -106,7 +109,8 @@ export function usePointerDnd(options: PointerDndOptions) {

if (movedTime || movedDay) {
await options.onMoved(event.taskId, previewDate.value, previewMinutes.value)
} else if (!showOverlay.value && options.onClicked) {
}
else if (!showOverlay.value && options.onClicked) {
options.onClicked(event)
}

Expand All @@ -125,7 +129,8 @@ export function usePointerDnd(options: PointerDndOptions) {

function onResizeStart(event: CalendarEvent, e: PointerEvent) {
const track = options.trackEl.value
if (!track) return
if (!track)
return

isDragging.value = true
showOverlay.value = false
Expand All @@ -147,7 +152,8 @@ export function usePointerDnd(options: PointerDndOptions) {
const newDuration = Math.max(newBottom - event.startMinutes, MIN_RESIZE_DURATION)
overlayHeight.value = newDuration

if (!showOverlay.value) showOverlay.value = true
if (!showOverlay.value)
showOverlay.value = true
}

const cleanup = () => {
Expand Down
8 changes: 6 additions & 2 deletions alfy-bot-frontend/src/features/calendar/lib/week.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { startOfWeek, addWeeks, eachDayOfInterval, addDays, isSameDay, format } from 'date-fns'
import { addDays, addWeeks, eachDayOfInterval, format, isSameDay, startOfWeek } from 'date-fns'
import { dateFnsLocale, weekStartsOn } from '@/composables/useLocale'

export function getWeekStart(date: Date): Date {
Expand Down Expand Up @@ -32,13 +32,17 @@ export function formatWeekRange(weekStart: Date): string {
return `${startDay} ${startMonth} \u2013 ${endDay} ${endMonth} ${year}`
}

export function formatDayHeader(date: Date): { dayName: string; dayNumber: string } {
export function formatDayHeader(date: Date): { dayName: string, dayNumber: string } {
return {
dayName: format(date, 'EEEEEE', { locale: dateFnsLocale.value }),
dayNumber: format(date, 'd'),
}
}

export function formatDayTitle(date: Date): string {
return format(date, 'd MMMM yyyy', { locale: dateFnsLocale.value })
}

export function formatDateRange(start: Date, end: Date): string {
const startMonth = format(start, 'LLLL', { locale: dateFnsLocale.value })
const endMonth = format(end, 'LLLL', { locale: dateFnsLocale.value })
Expand Down
4 changes: 3 additions & 1 deletion alfy-bot-frontend/src/features/calendar/model/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Task, Priority } from '@/features/tasks/model/types'
import type { Priority, Task } from '@/features/tasks/model/types'

export interface CalendarEvent {
taskId: string
Expand All @@ -16,6 +16,8 @@ export interface CalendarEvent {
pomodoroLabel?: string
}

export type CalendarViewMode = 'week' | 'day'

export interface CalendarDropPayload {
taskId: string
newDate: Date
Expand Down
60 changes: 46 additions & 14 deletions alfy-bot-frontend/src/features/calendar/ui/CalendarHeader.vue
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
<script setup lang="ts">
import type { CalendarViewMode } from '../model/types'
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'

defineProps<{
label: string
viewMode: CalendarViewMode
}>()

defineEmits<{
'navigate': [direction: number]
'today': []
'update:viewMode': [value: CalendarViewMode]
}>()

function tabClass(active: boolean) {
return [
'h-7 rounded px-2.5 text-xs font-medium transition-colors cursor-pointer',
active
? 'bg-background shadow-sm text-foreground'
: 'text-muted-foreground hover:text-foreground',
]
}
</script>

<template>
<div class="flex items-center justify-between px-4 py-3">
<div class="flex items-center gap-2">
Expand All @@ -10,21 +36,27 @@
<Button variant="outline" size="sm" @click="$emit('today')">
Сегодня
</Button>
<div class="flex items-center gap-0.5 rounded-md bg-muted p-0.5">
<button
type="button"
:class="tabClass(viewMode === 'week')"
:aria-pressed="viewMode === 'week'"
aria-label="Неделя"
@click="viewMode !== 'week' && $emit('update:viewMode', 'week')"
>
Неделя
</button>
<button
type="button"
:class="tabClass(viewMode === 'day')"
:aria-pressed="viewMode === 'day'"
aria-label="День"
@click="viewMode !== 'day' && $emit('update:viewMode', 'day')"
>
День
</button>
</div>
</div>
<span class="text-sm font-medium capitalize">{{ label }}</span>
</div>
</template>

<script setup lang="ts">
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'

defineProps<{
label: string
}>()

defineEmits<{
navigate: [direction: number]
today: []
}>()
</script>
4 changes: 2 additions & 2 deletions alfy-bot-frontend/src/features/calendar/ui/HourGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const { showOverlay, draggedTaskId, draggedEvent, overlayStyle, overlayHeight, o
trackEl: trackRef,
dayOffset: (d: Date) => props.dayOffset(d),
dateFromX: (x: number) => props.dateFromX(x),
dayWidth: props.dayWidth,
dayWidth: () => props.dayWidth,
onMoved: (taskId, newDate, startMinutes) => props.onTaskMoved(taskId, newDate, startMinutes),
onClicked: (event) => emit('open', event),
onResized: (taskId, durationMinutes) => props.onTaskResized(taskId, durationMinutes),
Expand All @@ -148,7 +148,7 @@ const {
trackEl: trackRef,
dayOffset: (d: Date) => props.dayOffset(d),
dateFromX: (x: number) => props.dateFromX(x),
dayWidth: props.dayWidth,
dayWidth: () => props.dayWidth,
onCreated: (date, startMinutes, durationMinutes) => props.onSlotCreate(date, startMinutes, durationMinutes),
})

Expand Down
Loading
Loading