diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1ec4da8..85eb207 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,7 @@ "@fullcalendar/vue3": "^6.1.11", "@tanstack/vue-query": "^5.28.9", "@tanstack/vue-query-devtools": "^5.28.10", + "@types/ical": "^0.8.3", "@vueuse/core": "^10.9.0", "country-flag-emoji-polyfill": "^0.1.8", "ical.js": "^1.5.0", @@ -3081,6 +3082,14 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/ical": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@types/ical/-/ical-0.8.3.tgz", + "integrity": "sha512-qPejGORaXOstmqyKzp0Qw9nXDPiWiahiJJcx4zMB0zJVg0rLfJ6bDip/naqagEqYTjKl/LI91399hR8zFwRJ5A==", + "dependencies": { + "rrule": "2.6.4" + } + }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", @@ -6361,6 +6370,15 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/magic-string": { "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", @@ -7200,6 +7218,17 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrule": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.4.tgz", + "integrity": "sha512-sLdnh4lmjUqq8liFiOUXD5kWp/FcnbDLPwq5YAc/RrN6120XOPb86Ae5zxF7ttBVq8O3LxjjORMEit1baluahA==", + "dependencies": { + "tslib": "^1.10.0" + }, + "optionalDependencies": { + "luxon": "^1.21.3" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7881,6 +7910,11 @@ "typescript": ">=4.2.0" } }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 27f6925..5dacd9c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "@fullcalendar/vue3": "^6.1.11", "@tanstack/vue-query": "^5.28.9", "@tanstack/vue-query-devtools": "^5.28.10", + "@types/ical": "^0.8.3", "@vueuse/core": "^10.9.0", "country-flag-emoji-polyfill": "^0.1.8", "ical.js": "^1.5.0", diff --git a/frontend/src/api/loadICal.ts b/frontend/src/api/loadICal.ts new file mode 100644 index 0000000..e835dd9 --- /dev/null +++ b/frontend/src/api/loadICal.ts @@ -0,0 +1,23 @@ +//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format. +//Copyright (C) 2024 HTWKalender support@htwkalender.de + +//This program is free software: you can redistribute it and/or modify +//it under the terms of the GNU Affero General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU Affero General Public License for more details. + +//You should have received a copy of the GNU Affero General Public License +//along with this program. If not, see . + +export async function fetchICalendarEvents() { + const response = await fetch("https://cal.htwk-leipzig.de/api/feed?token=rk47mvraeb43t3g"); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.text(); +} \ No newline at end of file diff --git a/frontend/src/components/CalendarViewer.vue b/frontend/src/components/CalendarViewer.vue index c54b41d..8d36be1 100644 --- a/frontend/src/components/CalendarViewer.vue +++ b/frontend/src/components/CalendarViewer.vue @@ -11,12 +11,30 @@ import iCalenderPlugin from "@fullcalendar/icalendar"; import router from "@/router"; import { formatYearMonthDay } from "@/helpers/dates.ts"; import { useI18n } from "vue-i18n"; +import { useQuery } from "@tanstack/vue-query"; +import tokenStore from "@/store/tokenStore.ts"; +import { parseICalData } from "@/helpers/ical.ts"; +import { fetchICalendarEvents } from "@/api/loadICal.ts"; const { t } = useI18n({ useScope: "global" }); const mobilePage = inject("mobilePage") as Ref; const date: Ref = ref(new Date()); -const feedUrl: Ref = ref("https://cal.htwk-leipzig.de/api/feed?token=rk47mvraeb43t3g"); + +tokenStore().setToken("3rz8vb3974tr2b") + + +const { data: calendar } = useQuery({ + queryKey: ["userCalendar", tokenStore().token], + queryFn: () => + fetchICalendarEvents(), + select: (data) => { + return data; + }, + networkMode: "offlineFirst", + enabled: () => tokenStore().token !== "", + staleTime: 5000000, // 500 seconds +}); const fullCalendar = ref>(); @@ -88,13 +106,7 @@ const calendarOptions: ComputedRef = computed(() => ({ }, }); }, - events: { - url: feedUrl.value, - format: "ics", - failure: function () { - alert("There was an error while fetching the calendar events!"); - }, - }, + events: parseICalData(calendar.value), })); watch(mobilePage, () => { diff --git a/frontend/src/components/MenuBar.vue b/frontend/src/components/MenuBar.vue index 0de1280..995d208 100644 --- a/frontend/src/components/MenuBar.vue +++ b/frontend/src/components/MenuBar.vue @@ -27,14 +27,26 @@ const isDark = ref(true); const items = computed(() => [ { - label: t("createCalendar"), - icon: "pi pi-fw pi-plus", - route: "/", - }, - { - label: t("editCalendar"), - icon: "pi pi-fw pi-pencil", - route: "/edit", + label: t("calendar"), + icon: "pi pi-fw pi-angle-down", + info: "calendar", + items: [ + { + label: t("createCalendar"), + icon: "pi pi-fw pi-plus", + route: "/calendar/create", + }, + { + label: t("editCalendar"), + icon: "pi pi-fw pi-pencil", + route: "/calendar/edit", + }, + { + label: t("userCalendar"), + icon: "pi pi-fw pi-calendar", + route: "/calendar/view", + }, + ], }, { label: t("rooms"), @@ -53,11 +65,6 @@ const items = computed(() => [ }, ], }, - { - label: t("userCalendar"), - icon: "pi pi-fw pi-calendar", - route: "/user/calendar", - }, { label: t("faq"), icon: "pi pi-fw pi-book", diff --git a/frontend/src/helpers/ical.ts b/frontend/src/helpers/ical.ts index e69de29..7af483a 100644 --- a/frontend/src/helpers/ical.ts +++ b/frontend/src/helpers/ical.ts @@ -0,0 +1,24 @@ +import ICAL from 'ical.js'; +import { CalendarComponent } from 'ical'; + +export function parseICalData(icalData: string | undefined) { + if (icalData === undefined || !icalData) { + return []; + } + + const jCalData = ICAL.parse(icalData); + const comp = new ICAL.Component(jCalData); + const vEvents = comp.getAllSubcomponents('vevent'); + + return vEvents.map((vevent: CalendarComponent) => { + const event = new ICAL.Event(vevent); + + return { + title: event.summary, + start: event.startDate.toJSDate(), + end: event.endDate.toJSDate(), + allDay: event.startDate.isDate, + // Include other properties as needed + }; + }); +} \ No newline at end of file diff --git a/frontend/src/i18n/translations/de.json b/frontend/src/i18n/translations/de.json index 567f440..f6862ad 100644 --- a/frontend/src/i18n/translations/de.json +++ b/frontend/src/i18n/translations/de.json @@ -3,6 +3,7 @@ "createCalendar": "Kalender erstellen", "editCalendar": "Kalender bearbeiten", "userCalendar": "Dein Kalender", + "calendar": "Kalender", "rooms": "Räume", "faq": "FAQ", "imprint": "Impressum", diff --git a/frontend/src/i18n/translations/en.json b/frontend/src/i18n/translations/en.json index b1edb9f..e843cef 100644 --- a/frontend/src/i18n/translations/en.json +++ b/frontend/src/i18n/translations/en.json @@ -3,6 +3,7 @@ "createCalendar": "create calendar", "editCalendar": "edit calendar", "userCalendar": "user calendar", + "calendar": "calendar", "rooms": "rooms", "faq": "faq", "imprint": "imprint", diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index a50391a..8e107bd 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -36,7 +36,12 @@ const router = createRouter({ routes: [ { path: "/", - name: "course-selection", + name: "home", + component: CourseSelection, + }, + { + path: "/calendar/create", + name: "calendar-create", component: CourseSelection, }, { @@ -50,8 +55,8 @@ const router = createRouter({ component: FreeRooms, }, { - path: "/user/calendar", - name: "user-calendar", + path: "/calendar/view", + name: "calendar-view", component: CalenderViewer, }, { @@ -80,7 +85,7 @@ const router = createRouter({ component: CalendarLink, }, { - path: "/edit", + path: "/calendar/edit", name: "edit", component: EditCalendarView, }, diff --git a/frontend/src/view/UserCalendar.vue b/frontend/src/view/UserCalendar.vue index ce1cffa..e7f5e7a 100644 --- a/frontend/src/view/UserCalendar.vue +++ b/frontend/src/view/UserCalendar.vue @@ -1,10 +1,25 @@