From c4daa88ea255f4b72b2b25c47c536ee6c05fa469 Mon Sep 17 00:00:00 2001 From: quirinecker Date: Mon, 16 Jun 2025 18:20:04 +0200 Subject: [PATCH] added edit modal. info popover. create modal. delete functionality. delete modal --- web/bun.lock | 9 +- web/components/ui/EventFormModal.vue | 122 +++++++++++++++ web/components/ui/calendar/Calendar.vue | 141 +++++++----------- .../ui/calendar/CalendarCollumn.vue | 16 +- web/components/ui/calendar/CalendarEvent.vue | 35 ++++- web/package.json | 3 +- web/utils/event.ts | 9 ++ 7 files changed, 239 insertions(+), 96 deletions(-) create mode 100644 web/components/ui/EventFormModal.vue diff --git a/web/bun.lock b/web/bun.lock index c593807..27559b5 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -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=="], diff --git a/web/components/ui/EventFormModal.vue b/web/components/ui/EventFormModal.vue new file mode 100644 index 0000000..3810012 --- /dev/null +++ b/web/components/ui/EventFormModal.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/web/components/ui/calendar/Calendar.vue b/web/components/ui/calendar/Calendar.vue index 09ad52e..d9a8f33 100644 --- a/web/components/ui/calendar/Calendar.vue +++ b/web/components/ui/calendar/Calendar.vue @@ -2,34 +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 { UTextarea } from '#components'; +import EventFormModal from '../EventFormModal.vue'; const events = defineModel('events', { required: true }) const date = defineModel('date', { required: true }) const draggedEvent = ref() +const createInput = ref>({}) const createModalOpened = ref(false) -const createContext = ref<{ date: DateTime, timespan: Timespan } | undefined>(undefined) -const createModal = { - open: ref(false), - fromTimeField: ref(''), - toTimeField: ref(''), - toDateField: ref(''), - fromDateField: ref(''), - nameField: ref(''), - descriptionField: ref(''), - clear: () => { - createModal.nameField.value = '' - createModal.fromDateField.value = '' - createModal.toDateField.value = '' - createModal.fromTimeField.value = '' - createModal.toTimeField.value = '' - createModal.descriptionField.value = '' - } -} - -const toast = useToast() +const editInput = ref>({}) +const editContext = ref<{ event: Event }>() +const editModalOpened = ref(false) +const deleteModalOpened = ref(false) +const deleteContext = ref<{ event: Event }>() type Day = { date: DateTime @@ -93,6 +79,8 @@ const days = computed(() => { const emits = defineEmits<{ (e: 'create', event: Event): void + (e: 'edit', event: Event): void + (e: 'delete', event: Event): void }>() const hour = (num: number) => { @@ -110,93 +98,74 @@ const seperators = ref([ ]) function openCreateModal(date: DateTime, timespan: Timespan) { - createContext.value = { - date: date, - 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 from = date.startOf('day').plus({ minutes: timespan.from * 24 * 60 }) - const to = date.startOf('day').plus({ minutes: timespan.to * 24 * 60 }) - - createModal.fromDateField.value = from.toISODate() ?? '' - createModal.toDateField.value = to.toISODate() ?? '' - createModal.fromTimeField.value = from.toFormat('HH:mm') - createModal.toTimeField.value = to.toFormat('HH:mm') - createModal.open.value = true + createModalOpened.value = true } -function create() { - const from = dateFromFields(createModal.fromDateField.value, createModal.fromTimeField.value) - const to = dateFromFields(createModal.toDateField.value, createModal.toTimeField.value) - - if (!from.isValid) { - toast.add({ - title: 'Invalid `from` date format' - }) - } - - if (!to.isValid) { - toast.add({ - title: 'Invalid `to` date format' - }) - } - - if (createModal.nameField.value.trim() === '') { - toast.add({ - title: 'Name is required' - }) - } - - const event = new Event(createModal.nameField.value, from, to, createModal.descriptionField.value) - emits('create', event) - createModal.clear() - createModal.open.value = false +function create(simple: SimpleEvent) { + const event = Event.fromSimple(simple) events.value.push(event) + emits('create', event) } -function dateFromFields(date: string, time: string) { - return DateTime.fromFormat(`${date} ${time}`, 'yyyy-MM-dd HH:mm') +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) + events.value = events.value.filter(e => e.title !== deleteContext.value?.event.title) + deleteModalOpened.value = false +} + +function moveEvent(event: Event) { + emits('edit', event) }