Merge pull request #7 from quirinecker/feature/ui-improvements

Feature/UI improvements
This commit is contained in:
2025-06-16 20:41:37 +02:00
committed by GitHub
11 changed files with 397 additions and 94 deletions

View File

@@ -1,6 +1,13 @@
@import "tailwindcss";
@import "@nuxt/ui";
.overflow-shadow {
overflow-x: auto;
position: relative;
mask-image: linear-gradient(to right, rgba(0,0,0,1) 90%, rgba(0,0,0,0));
-webkit-mask-image: linear-gradient(to right, rgba(0,0,0,1) 90%, rgba(0,0,0,0));
}
:root {
--ui-primary: #C02942;
--ui-text-dimmed: var(--ui-color-neutral-400);

View File

@@ -21,6 +21,7 @@
"typescript": "^5.6.3",
"vue": "^3.5.13",
"vue-router": "^4.5.1",
"zod": "^3.25.64",
},
},
},
@@ -2045,7 +2046,7 @@
"zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="],
"zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="],
"zod": ["zod@3.25.64", "", {}, "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
@@ -2077,6 +2078,8 @@
"@mapbox/node-pre-gyp/detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"@modelcontextprotocol/sdk/zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="],
"@netlify/functions/is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
"@netlify/zip-it-and-ship-it/@babel/types": ["@babel/types@7.26.10", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ=="],
@@ -2099,6 +2102,8 @@
"@netlify/zip-it-and-ship-it/resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="],
"@netlify/zip-it-and-ship-it/zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="],
"@nodelib/fs.scandir/@nodelib/fs.stat": ["@nodelib/fs.stat@4.0.0", "", {}, "sha512-ctr6bByzksKRCV0bavi8WoQevU6plSp2IkllIsEqaiKe2mwNNnaluhnRhcsgGZHrrHk57B3lf95MkLMO3STYcg=="],
"@nuxt/cli/pkg-types": ["pkg-types@2.1.0", "", { "dependencies": { "confbox": "^0.2.1", "exsolve": "^1.0.1", "pathe": "^2.0.3" } }, "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A=="],
@@ -2191,6 +2196,8 @@
"eslint/find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"eslint/zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="],
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-plugin-import-x/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],

View File

@@ -0,0 +1,123 @@
<script setup lang="ts">
import { type SimpleEvent } from '#imports';
import { DateTime } from 'luxon';
import * as z from 'zod';
const open = defineModel<boolean>('open', { required: true })
const emit = defineEmits<{
(e: 'submnitted', event: SimpleEvent): void
(e: 'canceled'): void
}>()
const props = defineProps<{
action: 'create' | 'edit'
input: Partial<SimpleEvent>,
}>()
const toast = useToast()
const titleField = ref('')
const descriptionField = ref('')
const fromTimeField = ref('')
const fromDateField = ref('')
const toTimeField = ref('')
const toDateField = ref('')
const modalTitle = computed(() => {
return props.action === 'create' ? 'Create Event' : 'Edit Event'
})
const modalDescription = computed(() => {
return props.action === 'create' ? 'Create event and set name, description and time' : 'Edit name, description and time'
})
watchEffect(() => {
titleField.value = props.input.title ?? ''
descriptionField.value = props.input.description ?? ''
fromTimeField.value = props.input.from?.toFormat('HH:mm') ?? ''
fromDateField.value = props.input.from?.toFormat('yyyy-MM-dd') ?? ''
toTimeField.value = props.input.to?.toFormat('HH:mm') ?? ''
toDateField.value = props.input.to?.toFormat('yyyy-MM-dd') ?? ''
})
const formSchema = z.object({
title: z.string().trim().min(1, { message: 'Title is required' }),
description: z.string().default(''),
from: z.string().datetime({ message: 'Invalid start date', local: true }),
to: z.string().datetime({ message: 'Invalid end date', local: true })
})
function submit() {
console.log({
from: dateStringFromFields(fromDateField.value, fromTimeField.value),
to: dateStringFromFields(toDateField.value, toTimeField.value)
})
const form = formSchema.safeParse({
title: titleField.value,
description: descriptionField.value,
from: dateStringFromFields(fromDateField.value, fromTimeField.value),
to: dateStringFromFields(toDateField.value, toTimeField.value)
})
if (form.error) {
for (const error of form.error.errors) {
toast.add({
title: error.message
})
}
return
}
emit('submnitted', {
id: props.input.id,
title: form.data.title,
description: form.data.description,
from: DateTime.fromISO(form.data.from),
to: DateTime.fromISO(form.data.to)
})
open.value = false
}
function dateStringFromFields(date: string, time: string) {
return `${date}T${time}:00`
}
function cancel() {
emit('canceled')
open.value = false
}
</script>
<template>
<UModal v-model:open="open" :title="modalTitle" :description="modalDescription">
<template #body>
<div class="flex flex-col gap-2">
<UInput type="text" placeholder="Name" v-model="titleField" required />
<div class="flex flex-row gap-2">
<UInput class="grow" placeholder="2025-06-16" v-model="fromDateField" icon="i-lucide-calendar"
required />
<UInput class="grow" placeholder="15:34" v-model="fromTimeField" icon="i-lucide-clock" required />
</div>
<div class="flex flex-row gap-2">
<UInput class="grow" placeholder="2025-06-16" v-model="toDateField" icon="i-lucide-calendar"
required />
<UInput class="grow" placeholder="15:34" v-model="toTimeField" icon="i-lucide-clock" required />
</div>
<UTextarea type="text" placeholder="Description" v-model="descriptionField" required />
</div>
</template>
<template #footer>
<UButton variant="solid" @click="submit">
{{ action === 'create' ? 'Create' : 'Edit' }}
</UButton>
<UButton variant="soft" @click="cancel">
Cancel
</UButton>
</template>
</UModal>
</template>
<style scoped></style>

View File

@@ -3,7 +3,7 @@
</script>
<template>
<UCard class="[&>*]:p-3">
<UCard class="[&>*]:p-3 w-full">
<slot />
</UCard>
</template>

View File

@@ -75,19 +75,19 @@ const selectedDate = computed({
})
type Task = {
id: number
userid: string
title: string
description: string
done: number
estimated_time: string
due_date: string
created_at: string
updated_at: string
id: number
userid: string
title: string
description: string
done: number
estimated_time: string
due_date: string
created_at: string
updated_at: string
}
defineProps<{
todos: Task[]
todos: Task[]
}>()
@@ -109,7 +109,7 @@ function editTodo() {
</script>
<template>
<UCard class="flex w-64 h-full">
<UCard class="flex w-64 h-full" :ui="{ body: 'w-full' }">
<div class="flex flex-col h-full w-full gap-5">
<header class="flex flex-col gap-2">
<Title1>Calendar</Title1>
@@ -120,20 +120,37 @@ function editTodo() {
<Title1>Todos</Title1>
<div class="flex gap-2 flex-col">
<ListItem v-for="todo in todos">
<div class="flex justify-between">
<span>
<div class="flex w-full gap-4 items-center">
<span class="grow overflow-scroll py-3 overflow-shadow">
{{ todo.title }}
</span>
<div class="flex gap-1">
<UButton size="xs" color="neutral" class="flex justify-center" @click="editTodo">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pencil"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"></path><path d="m15 5 4 4"></path></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pencil">
<path
d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z">
</path>
<path d="m15 5 4 4"></path>
</svg>
</UButton>
<UButton size="xs" class="flex justify-center" color="primary" @click="() => deleteTodo(todo)">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trash-2"><path d="M3 6h18"></path><path d="M19 6v14c0 1-2 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path><line x1="10" x2="10" y1="11" y2="17"></line><line x1="14" x2="14" y1="11" y2="17"></line></svg>
<UButton size="xs" class="flex justify-center" color="primary"
@click="() => deleteTodo(todo)">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="lucide lucide-trash-2">
<path d="M3 6h18"></path>
<path d="M19 6v14c0 1-2 2-2 2H7c-1 0-2-1-2-2V6"></path>
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
<line x1="10" x2="10" y1="11" y2="17"></line>
<line x1="14" x2="14" y1="11" y2="17"></line>
</svg>
</UButton>
</div>
</div>
</ListItem>
</ListItem>
</div>
</div>
<div class="flex">
@@ -157,4 +174,6 @@ function editTodo() {
</UCard>
</template>
<style scoped></style>
<style scoped>
</style>

View File

@@ -2,12 +2,20 @@
import { computed, ref } from 'vue';
import CalendarHeader from './CalendarHeader.vue';
import CalendarCollumn from './CalendarCollumn.vue';
import { Event } from '~/utils/event';
import { Event, type SimpleEvent } from '~/utils/event';
import { DateTime } from 'luxon';
import EventFormModal from '../EventFormModal.vue';
const events = defineModel<Event[]>('events', { required: true })
const date = defineModel<DateTime>('date', { required: true })
const draggedEvent = ref<DraggedEvent | undefined>()
const createInput = ref<Partial<SimpleEvent>>({})
const createModalOpened = ref(false)
const editInput = ref<Partial<SimpleEvent>>({})
const editContext = ref<{ event: Event }>()
const editModalOpened = ref(false)
const deleteModalOpened = ref(false)
const deleteContext = ref<{ event: Event }>()
type Day = {
date: DateTime
@@ -71,6 +79,8 @@ const days = computed<Day[]>(() => {
const emits = defineEmits<{
(e: 'create', event: Event): void
(e: 'edit', event: Event): void
(e: 'delete', event: Event): void
}>()
const hour = (num: number) => {
@@ -87,33 +97,87 @@ const seperators = ref<Seperator[]>([
{ text: '9 PM', time: hour(21) },
])
function quickCreate(date: DateTime, timespan: Timespan) {
const eventTitle = prompt("Event title")
if (eventTitle === null) {
return
function openCreateModal(date: DateTime, timespan: Timespan) {
createInput.value = {
from: date.startOf('day').plus({ minutes: timespan.from * 24 * 60 }),
to: date.startOf('day').plus({ minutes: timespan.to * 24 * 60 }),
}
const newEvent: Event = new Event(
eventTitle,
date.startOf('day').plus({ minutes: timespan.from * 24 * 60 }),
date.startOf('day').plus({ minutes: timespan.to * 24 * 60 })
)
createModalOpened.value = true
}
emits('create', newEvent)
events.value.push(newEvent)
function create(simple: SimpleEvent) {
const event = Event.fromSimple(simple)
events.value.push(event)
emits('create', event)
}
function openEditModal(event: Event) {
editInput.value = event.toSimple()
editContext.value = { event: event }
editModalOpened.value = true
}
function edit(simple: SimpleEvent) {
editContext.value?.event.updateWithSimple(simple)
if (editContext.value === undefined) return
emits('edit', editContext.value.event)
}
function openDeleteModal(event: Event) {
deleteContext.value = { event: event }
deleteModalOpened.value = true
}
function deleteEvent() {
if (deleteContext.value === undefined) return
emits('delete', deleteContext.value?.event)
console.log(events.value)
events.value = events.value.filter(e => {
if (e.id === undefined || deleteContext.value?.event.id === undefined) {
return true
}
if (e.id === deleteContext.value?.event.id) {
return false
}
return true
})
deleteModalOpened.value = false
}
function moveEvent(event: Event) {
emits('edit', event)
}
</script>
<template>
<div class="w-full h-full flex flex-col">
<EventFormModal action="create" @submnitted="event => create(event)" :input="createInput"
v-model:open="createModalOpened" />
<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?">
<template #footer>
<UButton variant="solid" @click="deleteEvent">
Delete
</UButton>
<UButton variant="solid" @click="deleteModalOpened = false">
Cancel
</UButton>
</template>
</UModal>
<div class="calendar flex flex-row w-full flex-1 items-stretch divide-x divide-muted">
<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="quickCreate" />
:date="date" v-model:draggedEvent="draggedEvent" @quick-create="openCreateModal"
@edit="openEditModal" @delete="openDeleteModal" @moved="moveEvent" />
</div>
</div>

View File

@@ -15,6 +15,9 @@ const props = defineProps<{
const emit = defineEmits<{
(e: 'quick-create', day: DateTime, event: EventDimensions): void,
(e: 'edit', event: Event): void
(e: 'moved', event: Event): void
(e: 'delete', event: Event): void
}>()
const isDragging = ref(false)
@@ -59,6 +62,13 @@ function mouseup(_: MouseEvent) {
const timeFrom = Math.min(endY.value, startY.value) / column.value.offsetHeight
const timeTo = Math.max(endY.value, startY.value) / column.value.offsetHeight
if (timeTo * column.value.offsetHeight - timeFrom * column.value.offsetHeight <= 10) {
startY.value = 0
endY.value = 0
return
}
emit('quick-create', props.day, {
from: timeFrom,
to: timeTo
@@ -87,6 +97,13 @@ function dragover(e: DragEvent) {
function dragDrop(_: DragEvent) {
draggedEvent.value?.target.updateWithDraggedEvent(draggedEvent.value, column.value?.offsetHeight ?? 0)
if (draggedEvent.value === undefined){
draggedEvent.value = undefined
return
}
emit('moved', draggedEvent.value.target)
draggedEvent.value = undefined
}
@@ -96,7 +113,8 @@ function dragDrop(_: DragEvent) {
<div class="flex flex-col h-full grow">
<div class="flex justify-center items-center flex-col h-18 border-b-1 border-muted">
<div>{{ props.day.toFormat('ccc').toUpperCase() }}</div>
<UBadge class="rounded-full" v-if="date.startOf('day').equals(day.startOf('day'))">{{ props.day.day }}</UBadge>
<UBadge class="rounded-full" v-if="date.startOf('day').equals(day.startOf('day'))">{{ props.day.day }}
</UBadge>
<div v-else>{{ props.day.day }}</div>
</div>
@@ -109,7 +127,8 @@ function dragDrop(_: DragEvent) {
:style="{ height: `${height}px`, top: `${top}px` }"></div>
<div v-for="[index, column] in events.entries()" class="flex flex-row w-11/12 h-full absolute top-0">
<CalendarEvent v-for="event in column" :event="event" :columnIndex="index" @move="eventMove" />
<CalendarEvent v-for="event in column" :event="event" :columnIndex="index" @move="eventMove"
@edit="event => emit(`edit`, event)" @delete="event => emit(`delete`, event)" />
</div>
<div v-if="draggedEvent !== undefined && draggedEvent.date.equals(props.day)"

View File

@@ -9,6 +9,8 @@ const props = defineProps<{
const emit = defineEmits<{
(e: 'move', mouseEvent: MouseEvent, event: Event): void,
(e: 'edit', event: Event): void
(e: 'delete', event: Event): void
}>()
const visible = ref(true)
@@ -42,10 +44,31 @@ function dragStart(e: DragEvent) {
</script>
<template>
<div class="absolute rounded-lg h-0 top-20 bg-black opacity-45 p-2 flex flex-col z-10" @mousedown.stop
@mouseover.stop @mouseup.stop draggable="true" @dragstart="dragStart"
:style="{ top: `${top}%`, height: `${height}%`, left: `${left}%`, width: `${widht}%` }">
<div>{{ event.event.from.toFormat('HH:mm') }} - {{ event.event.to.toFormat('HH:mm') }}</div>
<div>{{ event.event.title }}</div>
</div>
<UPopover :content="{ side: 'right' }" arrow>
<div class="absolute rounded-lg h-0 top-20 bg-black opacity-45 p-2 flex flex-col z-10" @mousedown.stop
@mouseover.stop @mouseup.stop draggable="true" @dragstart="dragStart"
:style="{ top: `${top}%`, height: `${height}%`, left: `${left}%`, width: `${widht}%` }">
<div>{{ event.event.from.toFormat('HH:mm') }} - {{ event.event.to.toFormat('HH:mm') }}</div>
<div>{{ event.event.title }}</div>
</div>
<template #content>
<UCard class="w-xl">
<template #header>
<div class="flex flex-row justify-between items-center">
<h1>{{ event.event.title }}</h1>
<nav class="flex flex-row gap-2">
<UButton icon="i-lucide-pencil" @click="emit('edit', event.event)"></UButton>
<UButton icon="i-lucide-trash" @click="emit('delete', event.event)"></UButton>
</nav>
</div>
</template>
<template #default>
<div>{{ event.event.from.toFormat('HH:mm') }} - {{ event.event.to.toFormat('HH:mm') }}</div>
<div class="overflow-scroll pb-5 overflow-shadow">
{{ event.event.description }}
</div>
</template>
</UCard>
</template>
</UPopover>
</template>

View File

@@ -26,6 +26,7 @@
"nuxt-app": "file:",
"typescript": "^5.6.3",
"vue": "^3.5.13",
"vue-router": "^4.5.1"
"vue-router": "^4.5.1",
"zod": "^3.25.64"
}
}

View File

@@ -1,5 +1,4 @@
<script setup lang="ts">
import { Body } from '#components';
import axios from 'axios';
import { DateTime } from 'luxon';
import MainContent from '~/components/ui/MainContent.vue';
@@ -11,35 +10,35 @@ const date = ref<DateTime>(DateTime.now())
const events = ref<Event[]>([])
const { data: eventsResponse } = await useAsyncData<SerializableEvent[]>(
'events',
() => axios.get('/events').then(res => res.data)
'events',
() => axios.get('/events').then(res => res.data)
);
onMounted(() => {
events.value = eventsResponse.value?.map(Event.fromSerializable) ?? []
events.value = eventsResponse.value?.map(Event.fromSerializable) ?? []
})
type Task = {
id: number
userid: string
title: string
description: string
done: number
estimated_time: string
due_date: string
created_at: string
updated_at: string
id: number
userid: string
title: string
description: string
done: number
estimated_time: string
due_date: string
created_at: string
updated_at: string
}
const { data: tasks, refresh } = await useAsyncData<Task[]>(
'tasks',
() => {
'tasks',
() => {
return axios.get("/tasks").then(result => {
console.log(result.data)
return result.data
})
}
}
)
async function postEvent(event: Event) {
@@ -49,30 +48,31 @@ async function postEvent(event: Event) {
async function postTask(name: string) {
console.log('posting Task')
await axios.post('/task', {
title: name,
description: "",
done: false,
estimated_time: (new Date()).toISOString(), //TODO
due_date: (new Date()).toISOString(),
})
await refresh()
await axios.post('/task', {
title: name,
description: "",
done: false,
estimated_time: (new Date()).toISOString(), //TODO
due_date: (new Date()).toISOString(),
})
await refresh()
}
async function deleteTask(id: number) {
console.log('deleting Task')
await axios.delete(`/task/${id}`)
await refresh()
console.log('deleting Task')
await axios.delete(`/task/${id}`)
await refresh()
}
</script>
<template>
<div class="h-screen w-screen p-4 flex flex-row gap-5">
<Sidebar v-if="tasks !== null" :todos="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"/>
</div>
<div class="h-screen w-screen p-4 flex flex-row gap-5">
<Sidebar v-if="tasks !== null" :todos="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" />
</div>
</template>
<style scoped></style>
<style scoped></style>

View File

@@ -4,9 +4,11 @@ export class Event {
private static readonly MINUTES_IN_DAY = 24 * 60
constructor(
public readonly title: string,
public id: number | undefined,
public title: string,
public from: DateTime,
public to: DateTime
public to: DateTime,
public description: string
) { }
@@ -28,29 +30,52 @@ export class Event {
}
}
updateWithSimple(simple: SimpleEvent): Event {
this.title = simple.title
this.from = simple.from
this.to = simple.to
this.description = simple.description
return this
}
static fromSimple(event: SimpleEvent): Event {
return new Event(event.title, event.from, event.to)
}
static fromSerializable(event: SerializableEvent) {
return new Event(event.title, DateTime.fromISO(event.from), DateTime.fromISO(event.to))
}
static fromPercentDimensions(title: string, dimensions: EventDimensions, date: DateTime): Event {
return new Event(
title,
date.startOf('day').plus({ minutes: (dimensions.from / 100) * Event.MINUTES_IN_DAY }),
date.startOf('day').plus({ minutes: (dimensions.to / 100) * Event.MINUTES_IN_DAY })
event.id,
event.title,
event.from,
event.to,
event.description
)
}
static fromPixelDimensions(title: string, dimensions: EventDimensions, height: number, date: DateTime): Event {
static fromSerializable(event: SerializableEvent) {
return new Event(
event.id,
event.title,
DateTime.fromISO(event.from),
DateTime.fromISO(event.to),
event.description
)
}
static fromPercentDimensions(id: number | undefined, title: string, dimensions: EventDimensions, date: DateTime, description: string): Event {
return new Event(
id,
title,
date.startOf('day').plus({ minutes: (dimensions.from / 100) * Event.MINUTES_IN_DAY }),
date.startOf('day').plus({ minutes: (dimensions.to / 100) * Event.MINUTES_IN_DAY }),
description
)
}
static fromPixelDimensions(id: number | undefined, title: string, dimensions: EventDimensions, height: number, date: DateTime, description: string): Event {
const percentDimensions: EventDimensions = {
from: dimensions.from * 100 / height,
to: dimensions.to * 100 / height
}
return Event.fromPercentDimensions(title, percentDimensions, date)
return Event.fromPercentDimensions(id, title, percentDimensions, date, description)
}
static fromDraggedEvent(draggedEvent: DraggedEvent, height: number): Event {
@@ -59,7 +84,14 @@ export class Event {
to: draggedEvent.top + draggedEvent.height
}
return Event.fromPixelDimensions(draggedEvent.target.title, pixelDimensions, height, draggedEvent.date)
return Event.fromPixelDimensions(
draggedEvent.target.id,
draggedEvent.target.title,
pixelDimensions,
height,
draggedEvent.date,
draggedEvent.target.description
)
}
updateWithDraggedEvent(draggedEvent: DraggedEvent, height: number): Event {
@@ -74,17 +106,21 @@ export class Event {
toSimple(): SimpleEvent {
return {
id: this.id,
title: this.title,
from: this.from,
to: this.to
to: this.to,
description: this.description
}
}
toSerializable(): SerializableEvent {
return {
id: this.id,
title: this.title,
from: this.from.toISO() ?? '',
to: this.to.toISO() ?? ''
to: this.to.toISO() ?? '',
description: this.description
}
}
@@ -116,15 +152,19 @@ export type EventDimensions = {
}
export type SimpleEvent = {
id: number | undefined,
title: string,
from: DateTime,
to: DateTime
description: string
}
export type SerializableEvent = {
id: number | undefined,
title: string,
from: string,
to: string
description: string
}
export type CollissionWrapper = {