From 44c905b190f43af9e16c748cce156bd2390410eb Mon Sep 17 00:00:00 2001 From: Elmar Kresse Date: Fri, 23 Feb 2024 01:48:26 +0100 Subject: [PATCH 1/4] feat:#30 added query params to occupancy view --- frontend/src/components/RoomOccupation.vue | 35 +++++++++++++++--- frontend/src/view/RoomFinder.vue | 43 +++++++++++++++++++--- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/RoomOccupation.vue b/frontend/src/components/RoomOccupation.vue index 1f13108..20db0e5 100644 --- a/frontend/src/components/RoomOccupation.vue +++ b/frontend/src/components/RoomOccupation.vue @@ -14,6 +14,11 @@ const props = defineProps({ type: String, required: true, }, + date: { + type: Date, + required: false, + default: null, + } }); type CalenderEvent = { @@ -23,6 +28,8 @@ type CalenderEvent = { showFree: boolean; }; +const propDate: Ref = ref(props.date); + const currentDateFrom: Ref = ref(""); const currentDateTo: Ref = ref(""); const occupations: Ref = ref([]); @@ -72,6 +79,7 @@ const calendarOptions: ComputedRef = computed(() => ({ plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin], // local debugging of mobilePage variable in object creation on ternary expression initialView: mobilePage.value ? "Day" : "week", + initialDate: propDate.value ? propDate.value : new Date(), dayHeaderFormat: { weekday: "short", omitCommas: true }, slotDuration: "00:15:00", eventTimeFormat: { @@ -123,12 +131,27 @@ const calendarOptions: ComputedRef = computed(() => ({ }, datesSet: function (dateInfo: DatesSetArg) { - const view = dateInfo.view; - const offset = new Date().getTimezoneOffset(); - const startDate = new Date(view.activeStart.getTime() - offset * 60 * 1000); - const endDate = new Date(view.activeEnd.getTime() - offset * 60 * 1000); - currentDateFrom.value = startDate.toISOString().split("T")[0]; - currentDateTo.value = endDate.toISOString().split("T")[0]; + + if(propDate.value !== null) { + console.debug("props.date: ", props.date); + const date = new Date(props.date); + + // calculate the week start and end date which includes the date + const weekStart = new Date(date); + weekStart.setDate(date.getDate() - date.getDay()+1); + const weekEnd = new Date(date); + weekEnd.setDate(date.getDate() - date.getDay() + 6); + currentDateFrom.value = weekStart.toISOString().split("T")[0]; + currentDateTo.value = weekEnd.toISOString().split("T")[0]; + propDate.value = null; + } else { + const view = dateInfo.view; + const offset = new Date().getTimezoneOffset(); + const startDate = new Date(view.activeStart.getTime() - offset * 60 * 1000); + const endDate = new Date(view.activeEnd.getTime() - offset * 60 * 1000); + currentDateFrom.value = startDate.toISOString().split("T")[0]; + currentDateTo.value = endDate.toISOString().split("T")[0]; + } getOccupation(); }, events: function ( diff --git a/frontend/src/view/RoomFinder.vue b/frontend/src/view/RoomFinder.vue index 9abbf36..bafabca 100644 --- a/frontend/src/view/RoomFinder.vue +++ b/frontend/src/view/RoomFinder.vue @@ -5,14 +5,47 @@ import DynamicPage from "./DynamicPage.vue"; import RoomOccupation from "../components/RoomOccupation.vue"; import { computedAsync } from "@vueuse/core"; +function isRoomAvailable(room: string, rooms: { name: string }[]) { + // wait for the roomsList to be available + const roomInList = rooms.some((r) => r.name === room); + if (roomInList) { + selectedRoom.value.name = room; + } +} + +const selectedRoom: Ref<{ name: string }> = ref({ name: "" }); + +// Get Query Parameters if available +// room, date +const urlParams = new URLSearchParams(window.location.search); +const room = urlParams.get("room"); + const roomsList = computedAsync(async () => { + let rooms: { name: string }[] = []; return await fetchRoom().then((data) => - data.map((room) => { + rooms = data.map((room: string) => { return { name: room }; }), - ); -}); -const selectedRoom: Ref<{ name: string }> = ref({ name: "" }); + ).finally(() => { + if (room) { + // check if room is available in roomsList + isRoomAvailable(room, rooms) + } + }); +}, +[]); + +const selectedDate: Ref = ref(null); + +// Set the selected date from the URL +const date = urlParams.get("date"); +if (date) { + // date is in format like YYYYMMDD + const year = date.substring(0, 4); + const month = date.substring(4, 6); + const day = date.substring(6, 8); + selectedDate.value = new Date(`${year}-${month}-${day}`); +} From b4caa9e94b17bb9d40f20c9c0389ed2e87dc852d Mon Sep 17 00:00:00 2001 From: Elmar Kresse Date: Fri, 23 Feb 2024 02:46:38 +0100 Subject: [PATCH 2/4] feat:#30 added linkout to room finder --- frontend/src/i18n/translations/de.json | 3 ++- frontend/src/i18n/translations/en.json | 3 ++- frontend/src/view/FreeRooms.vue | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/frontend/src/i18n/translations/de.json b/frontend/src/i18n/translations/de.json index f073849..0094cfb 100644 --- a/frontend/src/i18n/translations/de.json +++ b/frontend/src/i18n/translations/de.json @@ -33,7 +33,8 @@ "searchByRoom": "Suche nach Räumen", "pleaseSelectDate": "Bitte wähle ein Datum aus", "room": "Raum", - "search": "Suchen" + "search": "Suchen", + "viewOccupancy": "Belegung anzeigen" }, "moduleSelection": { "selectAll": "Alle anwählen", diff --git a/frontend/src/i18n/translations/en.json b/frontend/src/i18n/translations/en.json index d082f39..7498f40 100644 --- a/frontend/src/i18n/translations/en.json +++ b/frontend/src/i18n/translations/en.json @@ -33,7 +33,8 @@ "searchByRoom": "search by room", "pleaseSelectDate": "please select a date", "room": "room", - "search": "search" + "search": "search", + "viewOccupancy": "View Occupancy" }, "moduleSelection": { "selectAll": "select all", diff --git a/frontend/src/view/FreeRooms.vue b/frontend/src/view/FreeRooms.vue index 5a0539a..0fc62f2 100644 --- a/frontend/src/view/FreeRooms.vue +++ b/frontend/src/view/FreeRooms.vue @@ -110,6 +110,17 @@ @input="filterCallback()" /> + @@ -122,12 +133,25 @@ import DynamicPage from "@/view/DynamicPage.vue"; import { requestFreeRooms } from "@/api/requestFreeRooms.ts"; import { FilterMatchMode } from "primevue/api"; import { padStart } from "@fullcalendar/core/internal"; +import router from "@/router"; const mobilePage = inject("mobilePage") as Ref; const filters = ref({ room: { value: null, matchMode: FilterMatchMode.CONTAINS, label: "Room" }, }); +function occupationRoute(room: string): void { + // date is in format like YYYYMMDD + router.push({ + name: "room-schedule", + query: { + room: room, + date: date.value.toISOString().split("T")[0].replace(/-/g, ""), + } + } + ); +} + const date: Ref = ref(new Date(Date.now())); const start: Ref = ref(new Date(0)); const end: Ref = ref(new Date(0)); From 27b1b591cceae7dedc1dfc12752a5c9d9944b514 Mon Sep 17 00:00:00 2001 From: Elmar Kresse Date: Fri, 23 Feb 2024 02:50:01 +0100 Subject: [PATCH 3/4] fix:#30 format, lint and fixed types --- frontend/src/api/createFeed.ts | 6 ++++- frontend/src/components/RenameModules.vue | 4 ++- frontend/src/components/RoomOccupation.vue | 13 +++++----- frontend/src/view/FreeRooms.vue | 15 +++++------ frontend/src/view/RoomFinder.vue | 30 ++++++++++++---------- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/frontend/src/api/createFeed.ts b/frontend/src/api/createFeed.ts index 2c89049..126f87b 100644 --- a/frontend/src/api/createFeed.ts +++ b/frontend/src/api/createFeed.ts @@ -10,7 +10,11 @@ export async function createIndividualFeed(modules: Module[]): Promise { body: JSON.stringify(modules), }); - if (response.status === 429 || response.status === 500 || response.status != 200) { + if ( + response.status === 429 || + response.status === 500 || + response.status != 200 + ) { return Promise.reject(response.statusText); } diff --git a/frontend/src/components/RenameModules.vue b/frontend/src/components/RenameModules.vue index 17f2eee..dfbadbc 100644 --- a/frontend/src/components/RenameModules.vue +++ b/frontend/src/components/RenameModules.vue @@ -37,7 +37,9 @@ const columns = computed(() => [ const toast = useToast(); async function finalStep() { - const createFeed: Promise= createIndividualFeed(store.getAllModules()); + const createFeed: Promise = createIndividualFeed( + store.getAllModules(), + ); // Check if createFeed Promise is resolved createFeed.then(async (token: string) => { diff --git a/frontend/src/components/RoomOccupation.vue b/frontend/src/components/RoomOccupation.vue index 20db0e5..6a01952 100644 --- a/frontend/src/components/RoomOccupation.vue +++ b/frontend/src/components/RoomOccupation.vue @@ -18,7 +18,7 @@ const props = defineProps({ type: Date, required: false, default: null, - } + }, }); type CalenderEvent = { @@ -28,7 +28,7 @@ type CalenderEvent = { showFree: boolean; }; -const propDate: Ref = ref(props.date); +const propDate: Ref = ref(props.date); const currentDateFrom: Ref = ref(""); const currentDateTo: Ref = ref(""); @@ -131,14 +131,13 @@ const calendarOptions: ComputedRef = computed(() => ({ }, datesSet: function (dateInfo: DatesSetArg) { - - if(propDate.value !== null) { + if (propDate.value !== null) { console.debug("props.date: ", props.date); const date = new Date(props.date); // calculate the week start and end date which includes the date const weekStart = new Date(date); - weekStart.setDate(date.getDate() - date.getDay()+1); + weekStart.setDate(date.getDate() - date.getDay() + 1); const weekEnd = new Date(date); weekEnd.setDate(date.getDate() - date.getDay() + 6); currentDateFrom.value = weekStart.toISOString().split("T")[0]; @@ -147,7 +146,9 @@ const calendarOptions: ComputedRef = computed(() => ({ } else { const view = dateInfo.view; const offset = new Date().getTimezoneOffset(); - const startDate = new Date(view.activeStart.getTime() - offset * 60 * 1000); + const startDate = new Date( + view.activeStart.getTime() - offset * 60 * 1000, + ); const endDate = new Date(view.activeEnd.getTime() - offset * 60 * 1000); currentDateFrom.value = startDate.toISOString().split("T")[0]; currentDateTo.value = endDate.toISOString().split("T")[0]; diff --git a/frontend/src/view/FreeRooms.vue b/frontend/src/view/FreeRooms.vue index 0fc62f2..9b65bb1 100644 --- a/frontend/src/view/FreeRooms.vue +++ b/frontend/src/view/FreeRooms.vue @@ -142,14 +142,13 @@ const filters = ref({ function occupationRoute(room: string): void { // date is in format like YYYYMMDD - router.push({ - name: "room-schedule", - query: { - room: room, - date: date.value.toISOString().split("T")[0].replace(/-/g, ""), - } - } - ); + router.push({ + name: "room-schedule", + query: { + room: room, + date: date.value.toISOString().split("T")[0].replace(/-/g, ""), + }, + }); } const date: Ref = ref(new Date(Date.now())); diff --git a/frontend/src/view/RoomFinder.vue b/frontend/src/view/RoomFinder.vue index bafabca..8834715 100644 --- a/frontend/src/view/RoomFinder.vue +++ b/frontend/src/view/RoomFinder.vue @@ -22,22 +22,24 @@ const room = urlParams.get("room"); const roomsList = computedAsync(async () => { let rooms: { name: string }[] = []; - return await fetchRoom().then((data) => - rooms = data.map((room: string) => { - return { name: room }; - }), - ).finally(() => { - if (room) { - // check if room is available in roomsList - isRoomAvailable(room, rooms) - } - }); -}, -[]); + return await fetchRoom() + .then( + (data) => + (rooms = data.map((room: string) => { + return { name: room }; + })), + ) + .finally(() => { + if (room) { + // check if room is available in roomsList + isRoomAvailable(room, rooms); + } + }); +}, []); -const selectedDate: Ref = ref(null); +const selectedDate: Ref = ref(undefined); -// Set the selected date from the URL +// Set the selected date from the UR const date = urlParams.get("date"); if (date) { // date is in format like YYYYMMDD From 62acdcbc8817cd14630309c50cf0b9a87038420e Mon Sep 17 00:00:00 2001 From: survellow <59056368+survellow@users.noreply.github.com.> Date: Sat, 24 Feb 2024 04:14:11 +0100 Subject: [PATCH 4/4] feat:#30 reactive URL parameters RoomOccupation --- docker-compose.yml | 2 +- frontend/src/components/RoomOccupation.vue | 69 +++++++++-------- frontend/src/helpers/dates.ts | 8 ++ frontend/src/view/FreeRooms.vue | 3 +- frontend/src/view/RoomFinder.vue | 90 +++++++++++++--------- reverseproxy.conf | 7 +- 6 files changed, 105 insertions(+), 74 deletions(-) create mode 100644 frontend/src/helpers/dates.ts diff --git a/docker-compose.yml b/docker-compose.yml index 6e3e589..c3ec523 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: - "8090:8090" volumes: - pb_data:/htwkalender/data # for production with volume - # - ./backend/pb_data:/pb_data # for development with bind mount from project directory + # - ./backend:/htwkalender/data # for development with bind mount from project directory htwkalender-frontend: build: diff --git a/frontend/src/components/RoomOccupation.vue b/frontend/src/components/RoomOccupation.vue index 6a01952..5acb041 100644 --- a/frontend/src/components/RoomOccupation.vue +++ b/frontend/src/components/RoomOccupation.vue @@ -7,6 +7,9 @@ import { computed, ComputedRef, inject, ref, Ref, watch } from "vue"; import { CalendarOptions, DatesSetArg, EventInput } from "@fullcalendar/core"; import { fetchEventsByRoomAndDuration } from "../api/fetchRoom.ts"; import { useI18n } from "vue-i18n"; +import allLocales from "@fullcalendar/core/locales-all"; +import router from "@/router"; +import { formatYearMonthDay } from "@/helpers/dates"; const { t } = useI18n({ useScope: "global" }); const props = defineProps({ @@ -14,11 +17,6 @@ const props = defineProps({ type: String, required: true, }, - date: { - type: Date, - required: false, - default: null, - }, }); type CalenderEvent = { @@ -28,7 +26,28 @@ type CalenderEvent = { showFree: boolean; }; -const propDate: Ref = ref(props.date); +const date: Ref = ref(new Date()); + +// Watch for changes in URL parameter +router.afterEach(setDateFromQuery); + +// Set the selected date from the URL +function setDateFromQuery() { + const queryDate = router.currentRoute.value.query.date; + if (typeof queryDate === "string") { + if (queryDate === formatYearMonthDay(date.value)) { + return; + } + // date is in format like YYYYMMDD + // TODO check if date is valid + const year = queryDate.substring(0, 4); + const month = queryDate.substring(4, 6); + const day = queryDate.substring(6, 8); + date.value = new Date(`${year}-${month}-${day}`); + } +} + +setDateFromQuery(); const currentDateFrom: Ref = ref(""); const currentDateTo: Ref = ref(""); @@ -71,15 +90,13 @@ async function getOccupation() { }); } -import allLocales from "@fullcalendar/core/locales-all"; - const calendarOptions: ComputedRef = computed(() => ({ locales: allLocales, locale: t("languageCode"), plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin], // local debugging of mobilePage variable in object creation on ternary expression initialView: mobilePage.value ? "Day" : "week", - initialDate: propDate.value ? propDate.value : new Date(), + initialDate: date.value, dayHeaderFormat: { weekday: "short", omitCommas: true }, slotDuration: "00:15:00", eventTimeFormat: { @@ -131,28 +148,18 @@ const calendarOptions: ComputedRef = computed(() => ({ }, datesSet: function (dateInfo: DatesSetArg) { - if (propDate.value !== null) { - console.debug("props.date: ", props.date); - const date = new Date(props.date); - - // calculate the week start and end date which includes the date - const weekStart = new Date(date); - weekStart.setDate(date.getDate() - date.getDay() + 1); - const weekEnd = new Date(date); - weekEnd.setDate(date.getDate() - date.getDay() + 6); - currentDateFrom.value = weekStart.toISOString().split("T")[0]; - currentDateTo.value = weekEnd.toISOString().split("T")[0]; - propDate.value = null; - } else { - const view = dateInfo.view; - const offset = new Date().getTimezoneOffset(); - const startDate = new Date( - view.activeStart.getTime() - offset * 60 * 1000, - ); - const endDate = new Date(view.activeEnd.getTime() - offset * 60 * 1000); - currentDateFrom.value = startDate.toISOString().split("T")[0]; - currentDateTo.value = endDate.toISOString().split("T")[0]; - } + const view = dateInfo.view; + const offset = new Date().getTimezoneOffset(); + const startDate = new Date(view.activeStart.getTime() - offset * 60 * 1000); + const endDate = new Date(view.activeEnd.getTime() - offset * 60 * 1000); + currentDateFrom.value = startDate.toISOString().split("T")[0]; + currentDateTo.value = endDate.toISOString().split("T")[0]; + router.replace({ + query: { + ...router.currentRoute.value.query, + date: formatYearMonthDay(startDate), + }, + }); getOccupation(); }, events: function ( diff --git a/frontend/src/helpers/dates.ts b/frontend/src/helpers/dates.ts new file mode 100644 index 0000000..48c3527 --- /dev/null +++ b/frontend/src/helpers/dates.ts @@ -0,0 +1,8 @@ +/** + * Format a date to a string in the format "YYYYMMDD". + * @param date - The date to format + * @returns string - The formatted date + */ +export function formatYearMonthDay(date: Date): string { + return date.toISOString().split("T")[0].replace(/-/g, ""); +} diff --git a/frontend/src/view/FreeRooms.vue b/frontend/src/view/FreeRooms.vue index 9b65bb1..4740528 100644 --- a/frontend/src/view/FreeRooms.vue +++ b/frontend/src/view/FreeRooms.vue @@ -134,6 +134,7 @@ import { requestFreeRooms } from "@/api/requestFreeRooms.ts"; import { FilterMatchMode } from "primevue/api"; import { padStart } from "@fullcalendar/core/internal"; import router from "@/router"; +import { formatYearMonthDay } from "@/helpers/dates"; const mobilePage = inject("mobilePage") as Ref; const filters = ref({ @@ -146,7 +147,7 @@ function occupationRoute(room: string): void { name: "room-schedule", query: { room: room, - date: date.value.toISOString().split("T")[0].replace(/-/g, ""), + date: formatYearMonthDay(date.value), }, }); } diff --git a/frontend/src/view/RoomFinder.vue b/frontend/src/view/RoomFinder.vue index 8834715..54cd924 100644 --- a/frontend/src/view/RoomFinder.vue +++ b/frontend/src/view/RoomFinder.vue @@ -1,53 +1,67 @@ diff --git a/reverseproxy.conf b/reverseproxy.conf index 9776180..79e4081 100644 --- a/reverseproxy.conf +++ b/reverseproxy.conf @@ -73,9 +73,10 @@ http { geo $admin { default 1; 10.0.0.0/8 0; # Private Network - 192.168.0.0/24 0; # Localhost Network + 127.0.0.0/8 0; # Localhost Network + 192.168.0.0/16 0; # Localhost Network 141.57.0.0/16 0; # HTWK Leipzig Network - 172.18.0.0/24 0; # Docker Internal Network + 172.16.0.0/12 0; # Private Network } map $admin $limit_key { @@ -154,7 +155,7 @@ http { proxy_cache_lock on; proxy_cache_use_stale timeout updating; add_header X-Proxy-Cache $upstream_cache_status; - limit_req zone=modules burst=5 nodelay; + limit_req zone=modules burst=30 nodelay; } location /api/courses {