mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2026-01-17 04:42:26 +01:00
feat:#3 frontend room occupancy decoder
This commit is contained in:
@@ -51,6 +51,11 @@ const items = computed(() => [
|
||||
icon: "pi pi-fw pi-calendar",
|
||||
route: "/rooms/free",
|
||||
},
|
||||
{
|
||||
label: t("roomFinderPage.roomSchedule") + " (offline)",
|
||||
icon: "pi pi-fw pi-ban",
|
||||
route: "/rooms/occupancy/offline",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -30,6 +30,7 @@ import router from "@/router";
|
||||
import { formatYearMonthDay } from "@/helpers/dates";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { watch } from "vue";
|
||||
import { isValid } from "date-fns";
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
|
||||
@@ -50,11 +51,13 @@ function setDateFromQuery() {
|
||||
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}`);
|
||||
if (!isValid(date.value)) {
|
||||
date.value = new Date();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
203
frontend/src/components/RoomOccupationOffline.vue
Normal file
203
frontend/src/components/RoomOccupationOffline.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<!--
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<script lang="ts" setup>
|
||||
import FullCalendar from "@fullcalendar/vue3";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||
import { computed, ComputedRef, inject, ref, Ref } from "vue";
|
||||
import { CalendarOptions, DatesSetArg, EventInput } from "@fullcalendar/core";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import allLocales from "@fullcalendar/core/locales-all";
|
||||
import router from "@/router";
|
||||
import { formatYearMonthDay } from "@/helpers/dates";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { watch } from "vue";
|
||||
import { fetchRoomOccupancy } from "@/api/fetchRoomOccupancy";
|
||||
import { isValid } from "date-fns";
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
|
||||
const props = defineProps({
|
||||
room: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const date: Ref<Date> = ref(new Date());
|
||||
|
||||
// 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
|
||||
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}`);
|
||||
if (!isValid(date.value)) {
|
||||
date.value = new Date();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDateFromQuery();
|
||||
|
||||
const currentDateFrom: Ref<string> = ref("");
|
||||
const currentDateTo: Ref<string> = ref("");
|
||||
|
||||
const mobilePage = inject("mobilePage") as Ref<boolean>;
|
||||
|
||||
const selectedRoom = computed(() => props.room);
|
||||
|
||||
const { data: occupations } = useQuery({
|
||||
queryKey: ["roomOccupation", selectedRoom, currentDateFrom, currentDateTo],
|
||||
queryFn: () =>
|
||||
fetchRoomOccupancy(
|
||||
new Date(currentDateFrom.value).toISOString(),
|
||||
new Date(currentDateTo.value).toISOString()
|
||||
),
|
||||
select: (data) => data
|
||||
.decodeOccupancy(selectedRoom.value, new Date(currentDateFrom.value), new Date(currentDateTo.value))
|
||||
.map((event, index) => ({
|
||||
id: index,
|
||||
event: event,
|
||||
})),
|
||||
enabled: () => selectedRoom.value !== "" && currentDateFrom.value !== "",
|
||||
staleTime: 5000000, // 500 seconds
|
||||
});
|
||||
|
||||
watch(occupations, () => fullCalendar.value?.getApi().refetchEvents());
|
||||
|
||||
const fullCalendar = ref<InstanceType<typeof FullCalendar>>();
|
||||
|
||||
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: {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
},
|
||||
height: "auto",
|
||||
views: {
|
||||
week: {
|
||||
type: "timeGrid",
|
||||
slotLabelFormat: {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
omitZeroMinute: false,
|
||||
meridiem: false,
|
||||
hour12: false,
|
||||
},
|
||||
dateAlignment: "week",
|
||||
titleFormat: { month: "short", day: "numeric" },
|
||||
slotMinTime: "06:00:00",
|
||||
slotMaxTime: "22:00:00",
|
||||
duration: { days: 7 },
|
||||
firstDay: 1,
|
||||
allDaySlot: false,
|
||||
hiddenDays: [0],
|
||||
},
|
||||
Day: {
|
||||
type: "timeGrid",
|
||||
slotLabelFormat: {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
omitZeroMinute: false,
|
||||
meridiem: false,
|
||||
hour12: false,
|
||||
},
|
||||
titleFormat: { month: "short", day: "numeric" },
|
||||
slotMinTime: "06:00:00",
|
||||
slotMaxTime: "22:00:00",
|
||||
duration: { days: 1 },
|
||||
allDaySlot: false,
|
||||
hiddenDays: [0],
|
||||
},
|
||||
},
|
||||
headerToolbar: {
|
||||
end: "prev,next today",
|
||||
center: "title",
|
||||
start: "week,Day",
|
||||
},
|
||||
|
||||
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];
|
||||
router.replace({
|
||||
query: {
|
||||
...router.currentRoute.value.query,
|
||||
date: formatYearMonthDay(endDate),
|
||||
},
|
||||
});
|
||||
},
|
||||
events: function (
|
||||
_info: unknown,
|
||||
successCallback: (events: EventInput[]) => void,
|
||||
) {
|
||||
if (!occupations.value) return;
|
||||
successCallback(
|
||||
occupations.value.map((event) => {
|
||||
return {
|
||||
id: event.id.toString(),
|
||||
start: event.event.start,
|
||||
end: event.event.end,
|
||||
color: event.event.free
|
||||
? "var(--htwk-gruen-500)"
|
||||
: "var(--htwk-grau-60-500)",
|
||||
textColor: event.event.free
|
||||
? "var(--green-50)"
|
||||
: "white",
|
||||
title: event.event.stub
|
||||
? t("roomFinderPage.stub")
|
||||
: event.event.free
|
||||
? t("roomFinderPage.available")
|
||||
: t("roomFinderPage.occupied"),
|
||||
} as EventInput;
|
||||
}),
|
||||
);
|
||||
},
|
||||
}));
|
||||
</script>
|
||||
<template>
|
||||
<FullCalendar ref="fullCalendar" :options="calendarOptions" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.fc-toolbar.fc-header-toolbar) {
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user