mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2025-07-16 09:38:51 +02:00
Merge branch '30-information-at-freerooms-for-occupancy' into 'main'
Resolve "information at freeRooms for occupancy" Closes #30 See merge request ekresse/htwkalender!22
This commit is contained in:
@ -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:
|
||||
|
@ -10,7 +10,11 @@ export async function createIndividualFeed(modules: Module[]): Promise<string> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,9 @@ const columns = computed(() => [
|
||||
const toast = useToast();
|
||||
|
||||
async function finalStep() {
|
||||
const createFeed: Promise<string>= createIndividualFeed(store.getAllModules());
|
||||
const createFeed: Promise<string> = createIndividualFeed(
|
||||
store.getAllModules(),
|
||||
);
|
||||
|
||||
// Check if createFeed Promise is resolved
|
||||
createFeed.then(async (token: string) => {
|
||||
|
@ -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({
|
||||
@ -23,6 +26,29 @@ type CalenderEvent = {
|
||||
showFree: boolean;
|
||||
};
|
||||
|
||||
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 currentDateTo: Ref<string> = ref("");
|
||||
const occupations: Ref<CalenderEvent[]> = ref([]);
|
||||
@ -64,14 +90,13 @@ async function getOccupation() {
|
||||
});
|
||||
}
|
||||
|
||||
import allLocales from "@fullcalendar/core/locales-all";
|
||||
|
||||
const calendarOptions: ComputedRef<CalendarOptions> = 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: date.value,
|
||||
dayHeaderFormat: { weekday: "short", omitCommas: true },
|
||||
slotDuration: "00:15:00",
|
||||
eventTimeFormat: {
|
||||
@ -129,6 +154,12 @@ const calendarOptions: ComputedRef<CalendarOptions> = computed(() => ({
|
||||
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 (
|
||||
|
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, "");
|
||||
}
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -110,6 +110,17 @@
|
||||
@input="filterCallback()"
|
||||
/>
|
||||
</template>
|
||||
<template #body="slotProps">
|
||||
<div class="flex justify-content-between flex-wrap">
|
||||
<div class="align-self-center">{{ slotProps.data.room }}</div>
|
||||
<Button
|
||||
:label="$t('freeRooms.viewOccupancy')"
|
||||
icon="pi pi-hourglass"
|
||||
class="p-button-rounded p-button-outlined"
|
||||
@click="occupationRoute(slotProps.data.room)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</template>
|
||||
@ -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";
|
||||
import { formatYearMonthDay } from "@/helpers/dates";
|
||||
|
||||
const mobilePage = inject("mobilePage") as Ref<boolean>;
|
||||
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: formatYearMonthDay(date.value),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const date: Ref<Date> = ref(new Date(Date.now()));
|
||||
const start: Ref<Date> = ref(new Date(0));
|
||||
const end: Ref<Date> = ref(new Date(0));
|
||||
|
@ -1,18 +1,67 @@
|
||||
<script lang="ts" setup>
|
||||
import { Ref, ref } from "vue";
|
||||
import { Ref, computed, ref, watch } from "vue";
|
||||
import { fetchRoom } from "../api/fetchRoom.ts";
|
||||
import DynamicPage from "./DynamicPage.vue";
|
||||
import RoomOccupation from "../components/RoomOccupation.vue";
|
||||
import { computedAsync } from "@vueuse/core";
|
||||
import router from "@/router";
|
||||
|
||||
const roomsList = computedAsync(async () => {
|
||||
return await fetchRoom().then((data) =>
|
||||
data.map((room) => {
|
||||
return { name: room };
|
||||
}),
|
||||
);
|
||||
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
|
||||
const roomInList: boolean = rooms.has(room);
|
||||
if (roomInList) {
|
||||
selectedRoom.value.name = room;
|
||||
}
|
||||
}
|
||||
|
||||
watch(selectedRoom, (newRoom: Room) => {
|
||||
if (newRoom.name !== "") {
|
||||
router.push({
|
||||
query: { ...router.currentRoute.value.query, room: newRoom.name },
|
||||
});
|
||||
}
|
||||
});
|
||||
const selectedRoom: Ref<{ name: string }> = ref({ name: "" });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user