dragging task in the calendar works visually

This commit is contained in:
2025-07-06 11:52:00 +02:00
parent 22ca3e9645
commit 0297fab83b
7 changed files with 94 additions and 17 deletions

View File

@@ -6,6 +6,7 @@ import type { DateTime } from 'luxon';
const events = defineModel<Event[]>('events', { required: true })
const date = defineModel<DateTime>('date', { required: true })
const draggedTask = defineModel<DraggedTask | undefined>('draggedTask', { required: true })
const emits = defineEmits<{
(e: 'createEvent', event: Event): void
@@ -15,7 +16,9 @@ const emits = defineEmits<{
<template>
<UCard class="flex grow" :ui="{ body: 'w-full h-full' }">
<Calendar @create="(event) => emits('createEvent', event)"v-model:events="events" v-model:date="date"></Calendar>
<Calendar @create="(event) => emits('createEvent', event)" v-model:events="events" v-model:date="date" ,
v-model:dragged-task="draggedTask">
</Calendar>
</UCard>
</template>

View File

@@ -7,6 +7,7 @@ import { DateTime } from 'luxon';
const colorMode = useColorMode();
const toast = useToast()
const instance = getCurrentInstance()
const currentTheme = ref<'dark' | 'system' | 'light'>(colorMode.preference as 'dark' | 'system' | 'light');
const showTaskCreateModal = ref(false);
@@ -20,6 +21,7 @@ const emits = defineEmits<{
(e: 'createTask', task: Task): void
(e: 'deleteTask', id: number): void
(e: 'editTask', task: Task): void
(e: 'scheduleTask', task: Task): void
}>()
const isLight = computed(() => currentTheme.value === 'light');
@@ -96,6 +98,7 @@ function deleteTask(task: Task) {
emits('deleteTask', task.id)
}
function editTask(task: Task) {
emits('editTask', task)
}
@@ -110,6 +113,10 @@ function openTaskEditModal(task: Task) {
showTaskEditModal.value = true
}
function scheduleTask(task: Task) {
emits('scheduleTask', task)
}
</script>
<template>
@@ -129,7 +136,8 @@ function openTaskEditModal(task: Task) {
<Title1>Tasks</Title1>
<div class="flex gap-2 flex-col">
<ListItem v-for="task in todoTasks">
<div class="flex w-full gap-4 items-center">
<div class="flex w-full gap-4 items-center" @dragstart="scheduleTask(task)"
draggable="true">
<span
class="grow overflow-scroll py-3 overflow-shadow flex flex-row gap-2 items-center">
<UCheckbox v-model="task.done" @change="() => editTask(task)" />{{ task.title }}
@@ -144,7 +152,8 @@ function openTaskEditModal(task: Task) {
</ListItem>
<USeparator label="Done" v-if="todoTasks.length !== 0" />
<ListItem v-for="task in doneTasks">
<div class="flex w-full gap-4 items-center">
<div class="flex w-full gap-4 items-center" @dragstart="scheduleTask(task)"
draggable="true">
<span
class="grow overflow-scroll py-3 overflow-shadow flex flex-row gap-2 items-center">
<UCheckbox v-model="task.done" @change="() => editTask(task)" />{{ task.title }}

View File

@@ -5,7 +5,7 @@ import * as z from 'zod';
const open = defineModel<boolean>('open', { required: true })
const emit = defineEmits<{
(e: 'submnitted', event: SimpleTask): void
(e: 'submnitted', event: Task): void
(e: 'canceled'): void
}>()
@@ -63,14 +63,15 @@ function submit() {
return
}
emit('submnitted', {
emit('submnitted', Task.fromSimpleTask({
id: props.input.id,
title: form.data.title,
done: props.input.done ?? false,
description: form.data.description,
estimated_time: form.data.estimated_time,
due_date: DateTime.fromISO(form.data.due_date)
})
due_date: DateTime.fromISO(form.data.due_date),
scheduled_at: props.input.scheduled_at
}))
open.value = false
}

View File

@@ -8,6 +8,7 @@ import EventFormModal from '../EventFormModal.vue';
const events = defineModel<Event[]>('events', { required: true })
const date = defineModel<DateTime>('date', { required: true })
const draggedTask = defineModel<DraggedTask | undefined>('draggedTask', { required: true })
const draggedEvent = ref<DraggedEvent | undefined>()
const createInput = ref<Partial<SimpleEvent>>({})
const createModalOpened = ref(false)
@@ -161,7 +162,8 @@ function moveEvent(event: Event) {
<EventFormModal action="edit" @submnitted="event => edit(event)" :input="editInput"
v-model:open="editModalOpened" />
<UModal v-model:open="deleteModalOpened" title="Delete Event" description="Are you sure you want to delete this event?">
<UModal v-model:open="deleteModalOpened" title="Delete Event"
description="Are you sure you want to delete this event?">
<template #footer>
<UButton variant="solid" @click="deleteEvent">
Delete
@@ -176,8 +178,8 @@ function moveEvent(event: Event) {
<CalendarHeader :seperators="seperators" />
<CalendarCollumn v-for="day in days" :seperators="seperators" :day="day.date" :events="day.events"
:date="date" v-model:draggedEvent="draggedEvent" @quick-create="openCreateModal"
@edit="openEditModal" @delete="openDeleteModal" @moved="moveEvent" />
:date="date" v-model:draggedEvent="draggedEvent" @quick-create="openCreateModal" @edit="openEditModal"
@delete="openDeleteModal" @moved="moveEvent" v-model:dragged-task="draggedTask" />
</div>
</div>

View File

@@ -25,6 +25,7 @@ const startY = ref(0)
const endY = ref(0)
const column = useTemplateRef('column')
const draggedEvent = defineModel<DraggedEvent | undefined>('draggedEvent')
const draggedTask = defineModel<DraggedTask | undefined>('draggedTask')
const height = computed(() => {
return Math.abs(endY.value - startY.value)
@@ -83,6 +84,29 @@ function eventMove(mouseEvent: MouseEvent, event: Event) {
}
function dragover(e: DragEvent) {
drawDraggedEvent(e)
drawDraggedTask(e)
}
function drawDraggedTask(event: DragEvent) {
if (draggedTask.value === undefined) {
return;
}
if (draggedTask.value.dragInfo === undefined) {
draggedTask.value.dragInfo = {
height: (draggedTask.value.target.estimated_time / 60 / 24) * (column.value?.offsetHeight ?? 0),
top: absoluteToRelativeY(event.clientY),
date: props.day
}
drawDraggedTask(event)
}
draggedTask.value.dragInfo.top = absoluteToRelativeY(event.clientY)
draggedTask.value.dragInfo.date = props.day
}
function drawDraggedEvent(event: DragEvent) {
if (draggedEvent.value === undefined) {
return
}
@@ -92,13 +116,13 @@ function dragover(e: DragEvent) {
}
draggedEvent.value.top = absoluteToRelativeY(e.clientY) - draggedEvent.value.offset
draggedEvent.value.top = absoluteToRelativeY(event.clientY) - draggedEvent.value.offset
}
function dragDrop(_: DragEvent) {
draggedEvent.value?.target.updateWithDraggedEvent(draggedEvent.value, column.value?.offsetHeight ?? 0)
if (draggedEvent.value === undefined){
if (draggedEvent.value === undefined) {
draggedEvent.value = undefined
return
}
@@ -134,6 +158,9 @@ function dragDrop(_: DragEvent) {
<div v-if="draggedEvent !== undefined && draggedEvent.date.equals(props.day)"
class="absolute w-11/12 top-20 bg-black opacity-45 rounded-lg"
:style="{ height: `${draggedEvent.height}px`, top: `${draggedEvent.top}px` }"></div>
<div v-if="draggedTask !== undefined && draggedTask.dragInfo !== undefined && draggedTask.dragInfo.date.equals(props.day)"
class="absolute w-11/12 top-20 bg-black opacity-45 rounded-lg"
:style="{ height: `${draggedTask.dragInfo.height}px`, top: `${draggedTask.dragInfo.top}px` }"></div>
</div>
</div>
</template>

View File

@@ -8,6 +8,7 @@ import { Event, type SerializableEvent } from '~/utils/event';
const date = ref<DateTime>(DateTime.now())
const events = ref<Event[]>([])
const tasks = ref<Task[]>([])
const draggedTask = ref<DraggedTask | undefined>(undefined)
const { data: eventsResponse } = await useAsyncData<SerializableEvent[]>(
'events',
@@ -42,13 +43,18 @@ async function deleteTask(id: number) {
await refresh()
}
function scheduleTask(task: Task) {
draggedTask.value = { target: task, dragInfo: undefined }
}
</script>
<template>
<div class="h-screen w-screen p-4 flex flex-row gap-5">
<Sidebar v-if="tasks !== null" v-model:tasks="tasks" v-model:date="date" @create-task="postTask"
@delete-task="deleteTask" />
<MainContent v-if="events !== null" v-model:events="events" v-model:date="date" @create-event="postEvent" />
@delete-task="deleteTask" @schedule-task="scheduleTask"/>
<MainContent v-if="events !== null" v-model:events="events" v-model:date="date"
v-model:dragged-task="draggedTask" @create-event="postEvent" />
</div>
</template>

View File

@@ -7,7 +7,8 @@ export class Task {
public description: string,
public done: boolean,
public estimated_time: number,
public due_date: DateTime | undefined
public due_date: DateTime | undefined,
public scheduled_at: DateTime | undefined
) { }
static fromSimpleTask(simpleTask: SimpleTask) {
@@ -17,7 +18,8 @@ export class Task {
simpleTask.description,
simpleTask.done,
simpleTask.estimated_time,
simpleTask.due_date
simpleTask.due_date,
simpleTask.scheduled_at
)
}
@@ -29,9 +31,18 @@ export class Task {
serializableTask.description,
serializableTask.done,
serializableTask.estimated_time,
DateTime.now()
stringToDate(serializableTask.due_date),
stringToDate(serializableTask.scheduled_at),
)
}
isPersistent() {
return this.id !== undefined
}
isScheduled() {
return this.scheduled_at !== undefined
}
}
export type SimpleTask = {
@@ -40,6 +51,7 @@ export type SimpleTask = {
description: string
done: boolean
estimated_time: number
scheduled_at: DateTime | undefined
due_date: DateTime | undefined
}
@@ -50,7 +62,24 @@ export type SerializableTask = {
done: boolean
estimated_time: number
due_date: string | undefined
scheduled_at: string | undefined
created_at: string
updated_at: string
userid: string
}
export type DraggedTask = {
target: Task,
dragInfo: {
top: number,
date: DateTime
height: number
} | undefined
}
function stringToDate(date: string | undefined) {
if (date === undefined) {
return undefined
}
return DateTime.fromISO(date)
}