diff --git a/web/assets/css/main.css b/web/assets/css/main.css index f59a066..7a2fbdf 100644 --- a/web/assets/css/main.css +++ b/web/assets/css/main.css @@ -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); 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..666a10d --- /dev/null +++ b/web/components/ui/EventFormModal.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/web/components/ui/ListItem.vue b/web/components/ui/ListItem.vue index b16cc92..a115f61 100644 --- a/web/components/ui/ListItem.vue +++ b/web/components/ui/ListItem.vue @@ -3,7 +3,7 @@ diff --git a/web/components/ui/Sidebar.vue b/web/components/ui/Sidebar.vue index d8f7ee7..5e0445e 100644 --- a/web/components/ui/Sidebar.vue +++ b/web/components/ui/Sidebar.vue @@ -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() { - + diff --git a/web/components/ui/calendar/Calendar.vue b/web/components/ui/calendar/Calendar.vue index 3738fc1..91f1499 100644 --- a/web/components/ui/calendar/Calendar.vue +++ b/web/components/ui/calendar/Calendar.vue @@ -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('events', { required: true }) const date = defineModel('date', { required: true }) const draggedEvent = ref() +const createInput = ref>({}) +const createModalOpened = ref(false) +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 @@ -71,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) => { @@ -87,33 +97,87 @@ const seperators = ref([ { 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) }