mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender.git
synced 2025-07-16 17:48:49 +02:00
feat:#30 reactive URL parameters RoomOccupation
This commit is contained in:
@ -11,7 +11,7 @@ services:
|
|||||||
- "8090:8090"
|
- "8090:8090"
|
||||||
volumes:
|
volumes:
|
||||||
- pb_data:/htwkalender/data # for production with volume
|
- 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:
|
htwkalender-frontend:
|
||||||
build:
|
build:
|
||||||
|
@ -7,6 +7,9 @@ import { computed, ComputedRef, inject, ref, Ref, watch } from "vue";
|
|||||||
import { CalendarOptions, DatesSetArg, EventInput } from "@fullcalendar/core";
|
import { CalendarOptions, DatesSetArg, EventInput } from "@fullcalendar/core";
|
||||||
import { fetchEventsByRoomAndDuration } from "../api/fetchRoom.ts";
|
import { fetchEventsByRoomAndDuration } from "../api/fetchRoom.ts";
|
||||||
import { useI18n } from "vue-i18n";
|
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 { t } = useI18n({ useScope: "global" });
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -14,11 +17,6 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
date: {
|
|
||||||
type: Date,
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type CalenderEvent = {
|
type CalenderEvent = {
|
||||||
@ -28,7 +26,28 @@ type CalenderEvent = {
|
|||||||
showFree: boolean;
|
showFree: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const propDate: Ref<Date | null> = ref(props.date);
|
const date: Ref<Date> = 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<string> = ref("");
|
const currentDateFrom: Ref<string> = ref("");
|
||||||
const currentDateTo: Ref<string> = ref("");
|
const currentDateTo: Ref<string> = ref("");
|
||||||
@ -71,15 +90,13 @@ async function getOccupation() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
import allLocales from "@fullcalendar/core/locales-all";
|
|
||||||
|
|
||||||
const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
|
const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
|
||||||
locales: allLocales,
|
locales: allLocales,
|
||||||
locale: t("languageCode"),
|
locale: t("languageCode"),
|
||||||
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
|
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
|
||||||
// local debugging of mobilePage variable in object creation on ternary expression
|
// local debugging of mobilePage variable in object creation on ternary expression
|
||||||
initialView: mobilePage.value ? "Day" : "week",
|
initialView: mobilePage.value ? "Day" : "week",
|
||||||
initialDate: propDate.value ? propDate.value : new Date(),
|
initialDate: date.value,
|
||||||
dayHeaderFormat: { weekday: "short", omitCommas: true },
|
dayHeaderFormat: { weekday: "short", omitCommas: true },
|
||||||
slotDuration: "00:15:00",
|
slotDuration: "00:15:00",
|
||||||
eventTimeFormat: {
|
eventTimeFormat: {
|
||||||
@ -131,28 +148,18 @@ const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
datesSet: function (dateInfo: DatesSetArg) {
|
datesSet: function (dateInfo: DatesSetArg) {
|
||||||
if (propDate.value !== null) {
|
const view = dateInfo.view;
|
||||||
console.debug("props.date: ", props.date);
|
const offset = new Date().getTimezoneOffset();
|
||||||
const date = new Date(props.date);
|
const startDate = new Date(view.activeStart.getTime() - offset * 60 * 1000);
|
||||||
|
const endDate = new Date(view.activeEnd.getTime() - offset * 60 * 1000);
|
||||||
// calculate the week start and end date which includes the date
|
currentDateFrom.value = startDate.toISOString().split("T")[0];
|
||||||
const weekStart = new Date(date);
|
currentDateTo.value = endDate.toISOString().split("T")[0];
|
||||||
weekStart.setDate(date.getDate() - date.getDay() + 1);
|
router.replace({
|
||||||
const weekEnd = new Date(date);
|
query: {
|
||||||
weekEnd.setDate(date.getDate() - date.getDay() + 6);
|
...router.currentRoute.value.query,
|
||||||
currentDateFrom.value = weekStart.toISOString().split("T")[0];
|
date: formatYearMonthDay(startDate),
|
||||||
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();
|
getOccupation();
|
||||||
},
|
},
|
||||||
events: function (
|
events: function (
|
||||||
|
8
frontend/src/helpers/dates.ts
Normal file
8
frontend/src/helpers/dates.ts
Normal file
@ -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, "");
|
||||||
|
}
|
@ -134,6 +134,7 @@ import { requestFreeRooms } from "@/api/requestFreeRooms.ts";
|
|||||||
import { FilterMatchMode } from "primevue/api";
|
import { FilterMatchMode } from "primevue/api";
|
||||||
import { padStart } from "@fullcalendar/core/internal";
|
import { padStart } from "@fullcalendar/core/internal";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
import { formatYearMonthDay } from "@/helpers/dates";
|
||||||
|
|
||||||
const mobilePage = inject("mobilePage") as Ref<boolean>;
|
const mobilePage = inject("mobilePage") as Ref<boolean>;
|
||||||
const filters = ref({
|
const filters = ref({
|
||||||
@ -146,7 +147,7 @@ function occupationRoute(room: string): void {
|
|||||||
name: "room-schedule",
|
name: "room-schedule",
|
||||||
query: {
|
query: {
|
||||||
room: room,
|
room: room,
|
||||||
date: date.value.toISOString().split("T")[0].replace(/-/g, ""),
|
date: formatYearMonthDay(date.value),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,67 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Ref, ref } from "vue";
|
import { Ref, computed, ref, watch } from "vue";
|
||||||
import { fetchRoom } from "../api/fetchRoom.ts";
|
import { fetchRoom } from "../api/fetchRoom.ts";
|
||||||
import DynamicPage from "./DynamicPage.vue";
|
import DynamicPage from "./DynamicPage.vue";
|
||||||
import RoomOccupation from "../components/RoomOccupation.vue";
|
import RoomOccupation from "../components/RoomOccupation.vue";
|
||||||
import { computedAsync } from "@vueuse/core";
|
import { computedAsync } from "@vueuse/core";
|
||||||
|
import router from "@/router";
|
||||||
|
|
||||||
function isRoomAvailable(room: string, rooms: { name: string }[]) {
|
type Room = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedRoom: Ref<Room> = ref({ name: "" });
|
||||||
|
|
||||||
|
// Watch for changes in URL parameter
|
||||||
|
router.afterEach(async (to) => {
|
||||||
|
const room = to.query.room;
|
||||||
|
if (room && typeof room === "string") {
|
||||||
|
setRoomFromList(room, rooms.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const rooms = computedAsync<Set<string>>(async () => {
|
||||||
|
let rooms: Set<string> = new Set();
|
||||||
|
return await fetchRoom()
|
||||||
|
.then((data) => {
|
||||||
|
rooms = new Set(data);
|
||||||
|
return rooms;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
const room = router.currentRoute.value.query.room;
|
||||||
|
if (room && typeof room === "string") {
|
||||||
|
// check if room is available in roomsList
|
||||||
|
setRoomFromList(room, rooms);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, new Set());
|
||||||
|
|
||||||
|
const roomsList = computed(() => {
|
||||||
|
return Array.from(rooms.value).map((room) => {
|
||||||
|
return { name: room } as Room;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the room from the list of rooms
|
||||||
|
* @param room Name of the room
|
||||||
|
* @param rooms List of available rooms
|
||||||
|
*/
|
||||||
|
function setRoomFromList(room: string, rooms: Set<string>) {
|
||||||
// wait for the roomsList to be available
|
// wait for the roomsList to be available
|
||||||
const roomInList = rooms.some((r) => r.name === room);
|
const roomInList: boolean = rooms.has(room);
|
||||||
if (roomInList) {
|
if (roomInList) {
|
||||||
selectedRoom.value.name = room;
|
selectedRoom.value.name = room;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedRoom: Ref<{ name: string }> = ref({ name: "" });
|
watch(selectedRoom, (newRoom: Room) => {
|
||||||
|
if (newRoom.name !== "") {
|
||||||
// Get Query Parameters if available
|
router.push({
|
||||||
// room, date
|
query: { ...router.currentRoute.value.query, room: newRoom.name },
|
||||||
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) =>
|
|
||||||
(rooms = data.map((room: string) => {
|
|
||||||
return { name: room };
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.finally(() => {
|
|
||||||
if (room) {
|
|
||||||
// check if room is available in roomsList
|
|
||||||
isRoomAvailable(room, rooms);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, []);
|
}
|
||||||
|
});
|
||||||
const selectedDate: Ref<Date | undefined> = ref(undefined);
|
|
||||||
|
|
||||||
// Set the selected date from the UR
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -70,7 +84,7 @@ if (date) {
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<RoomOccupation :room="selectedRoom.name" :date="selectedDate" />
|
<RoomOccupation :room="selectedRoom.name" />
|
||||||
</template>
|
</template>
|
||||||
</DynamicPage>
|
</DynamicPage>
|
||||||
</template>
|
</template>
|
||||||
|
@ -73,9 +73,10 @@ http {
|
|||||||
geo $admin {
|
geo $admin {
|
||||||
default 1;
|
default 1;
|
||||||
10.0.0.0/8 0; # Private Network
|
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
|
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 {
|
map $admin $limit_key {
|
||||||
@ -154,7 +155,7 @@ http {
|
|||||||
proxy_cache_lock on;
|
proxy_cache_lock on;
|
||||||
proxy_cache_use_stale timeout updating;
|
proxy_cache_use_stale timeout updating;
|
||||||
add_header X-Proxy-Cache $upstream_cache_status;
|
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 {
|
location /api/courses {
|
||||||
|
Reference in New Issue
Block a user