From e4c52701b72cd84713d3ca82580036c01ae8f76b Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Mon, 10 Aug 2026 11:40:06 +0200 Subject: [PATCH 01/13] added awaits on room reservation creation/deletion requests, issue with race conditions and refetches completing before changes --- public/js/components/Cis/Mylv/RoomInformation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/components/Cis/Mylv/RoomInformation.js b/public/js/components/Cis/Mylv/RoomInformation.js index cff9f0e4b..d71ad32fd 100644 --- a/public/js/components/Cis/Mylv/RoomInformation.js +++ b/public/js/components/Cis/Mylv/RoomInformation.js @@ -75,7 +75,7 @@ export default { async handleCreateEvent(event) { event.ort_kurzbz = this.propsViewData.ort_kurzbz; - this.$api.call(ApiRoomPlan.addRoomReservation(event)); + await this.$api.call(ApiRoomPlan.addRoomReservation(event)); this.$refs.calendar.resetEventLoader(); this.$refs.calendar.closeModal(); }, @@ -87,7 +87,7 @@ export default { if (luxon.DateTime.fromISO(`${event.datum}T${event.beginn}`) < luxon.DateTime.now()) return; - this.$api.call(ApiRoomPlan.deleteRoomReservation(event.reservierung_id)); + await this.$api.call(ApiRoomPlan.deleteRoomReservation(event.reservierung_id)); this.$refs.calendar.reset(); From 4e67d7b765d4f1a215f1ad485964f697f293c015 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Mon, 10 Aug 2026 13:21:48 +0200 Subject: [PATCH 02/13] fixed issue with unresponsive reservation delete buttons in widget and my lv plan --- application/libraries/StundenplanLib.php | 1 + .../models/ressource/Reservierung_model.php | 2 +- public/js/components/Calendar/LvPlan.js | 6 ++-- public/js/components/Calendar/Widget.js | 32 ++++++++++++++++--- public/js/components/Cis/LvPlan/MyLvPlan.js | 20 ++++++++++++ 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/application/libraries/StundenplanLib.php b/application/libraries/StundenplanLib.php index a2f8bbfe6..d518bf807 100644 --- a/application/libraries/StundenplanLib.php +++ b/application/libraries/StundenplanLib.php @@ -236,6 +236,7 @@ class StundenplanLib if ($is_mitarbeiter && empty($ort_kurzbz)) { // request for personal lvplan show only reservations of logged in user + $this->_ci->addMeta("meta", "adis"); $reservierungen = $this->_ci->ReservierungModel->getReservierungenMitarbeiter($start_date, $end_date, $uid); } else { // querying the reservierungen diff --git a/application/models/ressource/Reservierung_model.php b/application/models/ressource/Reservierung_model.php index 461a009b0..e1c96925c 100644 --- a/application/models/ressource/Reservierung_model.php +++ b/application/models/ressource/Reservierung_model.php @@ -96,7 +96,7 @@ class Reservierung_model extends DB_Model $query_result = $this->execReadOnlyQuery(" SELECT DISTINCT(insertvon), - 'reservierung' as type, beginn, ende, datum, + 'reservierung' as type, beginn, ende, datum, array_agg(DISTINCT reservierung_id) AS reservierung_id, COALESCE(titel, beschreibung) as topic, array_agg(DISTINCT mitarbeiter_kurzbz) as lektor, array_agg(DISTINCT (gruppe,verband,semester,studiengang_kz,gruppen_kuerzel)) as gruppe, diff --git a/public/js/components/Calendar/LvPlan.js b/public/js/components/Calendar/LvPlan.js index 4f240cd73..a6f083b09 100644 --- a/public/js/components/Calendar/LvPlan.js +++ b/public/js/components/Calendar/LvPlan.js @@ -215,18 +215,18 @@ export default { :style="eventStyle(event)" >
@@ -98,6 +119,7 @@ export default { :is="renderers[event.type]?.calendarEvent" :event="event" :timeSlotDisplayBehavior="'always'" + @delete-event="(event) => deleteEvent(event)" >
diff --git a/public/js/components/Cis/LvPlan/MyLvPlan.js b/public/js/components/Cis/LvPlan/MyLvPlan.js index cb43a06d9..3fab090a3 100644 --- a/public/js/components/Cis/LvPlan/MyLvPlan.js +++ b/public/js/components/Cis/LvPlan/MyLvPlan.js @@ -2,6 +2,7 @@ import FhcCalendar from "../../Calendar/LvPlan.js"; import ApiLvPlan from '../../../api/factory/lvPlan.js'; import ApiAuthinfo from '../../../api/factory/authinfo.js'; +import ApiRoomPlan from '../../../api/factory/calendar/roomPlan.js'; export const DEFAULT_MODE_LVPLAN_DESKTOP = "Week"; export const DEFAULT_MODE_LVPLAN_MOBILE = "List"; @@ -134,6 +135,24 @@ export default { this.isMitarbeiter = authInfo.isMitarbeiter; this.isStudent = authInfo.isStudent; }, + deleteEvent(event) { + if (event.type === "reservierung") { + this.deleteReservation(event); + } + }, + async deleteReservation(event) { + if ( + luxon.DateTime.fromISO(`${event.datum}T${event.beginn}`) < + luxon.DateTime.now() + ) + return; + + await this.$api.call( + ApiRoomPlan.deleteRoomReservation(event.reservierung_id), + ); + + this.$refs.calendar.reset(); + }, }, async created() { await this.fetchAuthInfo(); @@ -157,6 +176,7 @@ export default { @update:date="handleChangeDate" @update:mode="handleChangeMode" @update:range="updateRange" + @delete-event="(event) => deleteEvent(event)" class="responsive-calendar" >
Date: Mon, 10 Aug 2026 13:56:13 +0200 Subject: [PATCH 03/13] not closing reservation creation modal if creation request fails --- public/js/components/Cis/Mylv/RoomInformation.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/public/js/components/Cis/Mylv/RoomInformation.js b/public/js/components/Cis/Mylv/RoomInformation.js index d71ad32fd..300a40c9e 100644 --- a/public/js/components/Cis/Mylv/RoomInformation.js +++ b/public/js/components/Cis/Mylv/RoomInformation.js @@ -72,12 +72,15 @@ export default { } }); }, - async handleCreateEvent(event) - { + async handleCreateEvent(event) { event.ort_kurzbz = this.propsViewData.ort_kurzbz; - await this.$api.call(ApiRoomPlan.addRoomReservation(event)); - this.$refs.calendar.resetEventLoader(); - this.$refs.calendar.closeModal(); + const reservationCreationResponse = await this.$api.call( + ApiRoomPlan.addRoomReservation(event), + ); + if (reservationCreationResponse.meta.status === "success") { + this.$refs.calendar.resetEventLoader(); + this.$refs.calendar.closeModal(); + } }, async handleDeleteEvent(event) { From 9a951c499be6c536b14a2cc30d6e5493b2cd3f22 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Mon, 10 Aug 2026 13:56:57 +0200 Subject: [PATCH 04/13] maxlength attr on reservation creation modal title and description inputs --- public/js/components/Cis/Renderer/Slot/roomModalContent.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/js/components/Cis/Renderer/Slot/roomModalContent.js b/public/js/components/Cis/Renderer/Slot/roomModalContent.js index e9f518c75..6cc0c9805 100644 --- a/public/js/components/Cis/Renderer/Slot/roomModalContent.js +++ b/public/js/components/Cis/Renderer/Slot/roomModalContent.js @@ -130,6 +130,7 @@ export default { :label="capitalize($p.t('global', 'titel'))" type="text" container-class="col-3" + maxlength="10" v-model="title" name="title" /> @@ -138,6 +139,7 @@ export default { :label="capitalize($p.t('global', 'beschreibung'))" type="text" container-class="col-4" + maxlength="32" v-model="beschreibung" name="Beschreibung" /> From 42fc89a944018a38daf68d243384e448b60f6551 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Mon, 10 Aug 2026 14:00:33 +0200 Subject: [PATCH 05/13] removed redundant response meta used for testing --- application/libraries/StundenplanLib.php | 1 - 1 file changed, 1 deletion(-) diff --git a/application/libraries/StundenplanLib.php b/application/libraries/StundenplanLib.php index d518bf807..a2f8bbfe6 100644 --- a/application/libraries/StundenplanLib.php +++ b/application/libraries/StundenplanLib.php @@ -236,7 +236,6 @@ class StundenplanLib if ($is_mitarbeiter && empty($ort_kurzbz)) { // request for personal lvplan show only reservations of logged in user - $this->_ci->addMeta("meta", "adis"); $reservierungen = $this->_ci->ReservierungModel->getReservierungenMitarbeiter($start_date, $end_date, $uid); } else { // querying the reservierungen From 475b6210e0b9bd670e3a7c548843519866a3c91b Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Mon, 10 Aug 2026 15:02:12 +0200 Subject: [PATCH 06/13] displaying room additional info in reservations window --- .../controllers/api/frontend/v1/Ort.php | 16 +++++++++- application/models/ressource/Ort_model.php | 6 ++++ public/js/api/factory/ort.js | 31 ++++++++++++------- .../js/components/Cis/Mylv/RoomInformation.js | 11 ++++++- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/application/controllers/api/frontend/v1/Ort.php b/application/controllers/api/frontend/v1/Ort.php index 0d8074fa9..a208e4c0d 100644 --- a/application/controllers/api/frontend/v1/Ort.php +++ b/application/controllers/api/frontend/v1/Ort.php @@ -36,7 +36,8 @@ class Ort extends FHCAPI_Controller 'ContentID' => self::PERM_LOGGED, 'getOrtKurzbzContent' => self::PERM_LOGGED, 'getRooms' => self::PERM_LOGGED, - 'getTypes' => self::PERM_LOGGED + 'getTypes' => self::PERM_LOGGED, + 'getRoomInfo' => self::PERM_LOGGED ]); $this->load->model('ressource/Ort_model', 'OrtModel'); @@ -74,6 +75,8 @@ class Ort extends FHCAPI_Controller $vonStunde = getData($this->StundeModel->getStundeForTime($von))[0]->stunde; $bisStunde = getData($this->StundeModel->getStundeForTime($bis))[0]->stunde; + $this->load->model('ressource/Ort_model', 'OrtModel'); + $params = array(); $qry = "SELECT DISTINCT tbl_ort.* FROM public.tbl_ort JOIN public.tbl_ortraumtyp USING(ort_kurzbz) @@ -174,5 +177,16 @@ class Ort extends FHCAPI_Controller $this->terminateWithSuccess($content); } + + public function getRoomInfo() + { + $ortKurzbz = $this->input->get("ortKurzbz"); + + $roomResult = $this->OrtModel->getRoomInfo($ortKurzbz); + $roomData = $this->getDataOrTerminateWithError($roomResult); + $room = count($roomData) ? $roomData[0] : null; + + $this->terminateWithSuccess($room); + } } diff --git a/application/models/ressource/Ort_model.php b/application/models/ressource/Ort_model.php index de0c8e331..9e2f286d0 100644 --- a/application/models/ressource/Ort_model.php +++ b/application/models/ressource/Ort_model.php @@ -12,6 +12,12 @@ class Ort_model extends DB_Model $this->pk = "ort_kurzbz"; } + public function getRoomInfo($ortKurzbz) + { + $this->addSelect("ort_kurzbz, ausstattung"); + return $this->OrtModel->loadWhere(array("ort_kurzbz" => $ortKurzbz)); + } + public function getAll($raumtyp_kurzbz) { $this->addOrder("ort_kurzbz"); diff --git a/public/js/api/factory/ort.js b/public/js/api/factory/ort.js index 2e06d6b71..dce61279d 100644 --- a/public/js/api/factory/ort.js +++ b/public/js/api/factory/ort.js @@ -18,23 +18,32 @@ export default { getContentID(ort_kurbz) { return { - method: 'get', - url: '/api/frontend/v1/Ort/ContentID', - params: { ort_kurzbz: ort_kurbz } + method: "get", + url: "/api/frontend/v1/Ort/ContentID", + params: { ort_kurzbz: ort_kurbz }, }; }, getRooms(datum, von, bis, typ, personenanzahl = 0) { return { - method: 'get', - url: '/api/frontend/v1/Ort/getRooms', - params: { datum, von, bis, typ, personenanzahl } + method: "get", + url: "/api/frontend/v1/Ort/getRooms", + params: { datum, von, bis, typ, personenanzahl }, }; }, getRoomTypes() { return { - method: 'get', - url: '/api/frontend/v1/Ort/getTypes', - params: { } + method: "get", + url: "/api/frontend/v1/Ort/getTypes", + params: {}, }; - } -}; \ No newline at end of file + }, + getRoomInfo(ortKurzbz) { + return { + method: "get", + url: "/api/frontend/v1/Ort/getRoomInfo", + params: { + ortKurzbz, + }, + }; + }, +}; diff --git a/public/js/components/Cis/Mylv/RoomInformation.js b/public/js/components/Cis/Mylv/RoomInformation.js index 300a40c9e..0cac6fca6 100644 --- a/public/js/components/Cis/Mylv/RoomInformation.js +++ b/public/js/components/Cis/Mylv/RoomInformation.js @@ -2,6 +2,7 @@ import FhcCalendar from "../../Calendar/LvPlan.js"; import ApiLvPlan from '../../../api/factory/lvPlan.js'; import ApiRoomPlan from '../../../api/factory/calendar/roomPlan.js'; +import ApiRoom from '../../../api/factory/ort.js'; export const DEFAULT_MODE_RAUMINFO_MOBILE = 'List'; export const DEFAULT_MODE_RAUMINFO_DESKTOP = 'Week'; @@ -40,7 +41,8 @@ export default { searchGroup: this.searchGroup, searchLektor: this.searchLektor, }, - } + }, + roomAdditionalInfo: null, } }, created() { @@ -155,9 +157,16 @@ export default { ]; } }, + async created() { + const roomInfoResponse = await this.$api.call( + ApiRoom.getRoomInfo(this.$props.propsViewData.ort_kurzbz), + ); + this.roomAdditionalInfo = roomInfoResponse.data.ausstattung; + }, template: /*html*/`

{{ $p.t('rauminfo/rauminfo') }} {{ propsViewData.ort_kurzbz }}

+
Date: Mon, 10 Aug 2026 15:15:36 +0200 Subject: [PATCH 07/13] minor mod to room reservation additional information appearance --- public/js/components/Cis/Mylv/RoomInformation.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/js/components/Cis/Mylv/RoomInformation.js b/public/js/components/Cis/Mylv/RoomInformation.js index 0cac6fca6..ec3cabb68 100644 --- a/public/js/components/Cis/Mylv/RoomInformation.js +++ b/public/js/components/Cis/Mylv/RoomInformation.js @@ -166,7 +166,10 @@ export default { template: /*html*/`

{{ $p.t('rauminfo/rauminfo') }} {{ propsViewData.ort_kurzbz }}

- + + Additional information: + +
Date: Mon, 10 Aug 2026 15:17:50 +0200 Subject: [PATCH 08/13] in room search added nullsafe to parsed room type descriptions as some are null --- public/js/components/Cis/Raumsuche/Raumsuche.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/components/Cis/Raumsuche/Raumsuche.js b/public/js/components/Cis/Raumsuche/Raumsuche.js index dea747780..37cd8dadd 100644 --- a/public/js/components/Cis/Raumsuche/Raumsuche.js +++ b/public/js/components/Cis/Raumsuche/Raumsuche.js @@ -129,7 +129,7 @@ export const Raumsuche = { this.$api.call(ApiOrt.getRoomTypes()) .then(res => { res?.data?.forEach(type => { - type.beschreibung = type.beschreibung.replace('&', '&') + type.beschreibung = type.beschreibung?.replace('&', '&') }) this.selectedType = this.defaultType this.roomtypes = res?.data ?? [] From e1e228eae20b9a6f4b633f31ee5201c354e9d846 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Mon, 10 Aug 2026 16:37:27 +0200 Subject: [PATCH 09/13] localized 'additional information' in reservations page --- public/js/components/Cis/Mylv/RoomInformation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/components/Cis/Mylv/RoomInformation.js b/public/js/components/Cis/Mylv/RoomInformation.js index ec3cabb68..3127a9794 100644 --- a/public/js/components/Cis/Mylv/RoomInformation.js +++ b/public/js/components/Cis/Mylv/RoomInformation.js @@ -167,7 +167,7 @@ export default {

{{ $p.t('rauminfo/rauminfo') }} {{ propsViewData.ort_kurzbz }}

- Additional information: + {{ $p.t("search/result_equipment") + ":" }}
From 409c0d7fb44ed6683ec7c4032d0a3a0683ae8147 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Wed, 12 Aug 2026 12:54:31 +0200 Subject: [PATCH 10/13] fixed double created hook --- .../js/components/Cis/Mylv/RoomInformation.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/public/js/components/Cis/Mylv/RoomInformation.js b/public/js/components/Cis/Mylv/RoomInformation.js index 3127a9794..30722ab62 100644 --- a/public/js/components/Cis/Mylv/RoomInformation.js +++ b/public/js/components/Cis/Mylv/RoomInformation.js @@ -45,18 +45,6 @@ export default { roomAdditionalInfo: null, } }, - created() { - - this.$api.call(ApiRoomPlan.getRoomCreationInfo()) - .then(result => result.data) - .then(result => { - if (result.berechtigt) - { - this.createContext.room_create_information.studiengaenge = result.studiengaenge - } - this.createContext.show_all_fields = result.berechtigt; - }); - }, methods:{ handleChangeDate(day, newMode) { return this.handleChangeMode(newMode, day); @@ -158,6 +146,19 @@ export default { } }, async created() { + const roomCreationInfoResponse = await this.$api.call( + ApiRoomPlan.getRoomCreationInfo(), + ); + if (roomCreationInfoResponse.meta?.status === "success") { + const berechtigt = !!roomCreationInfoResponse.data?.berechtigt; + if (roomCreationInfoResponse.data?.berechtigt) { + this.createContext.room_create_information.studiengaenge = + roomCreationInfoResponse.data?.studiengaenge; + } + this.createContext.show_all_fields = + roomCreationInfoResponse.data?.berechtigt; + } + const roomInfoResponse = await this.$api.call( ApiRoom.getRoomInfo(this.$props.propsViewData.ort_kurzbz), ); From b2af8587c2b1d5fb315f664d7042a3baf6fa7610 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Wed, 12 Aug 2026 13:27:07 +0200 Subject: [PATCH 11/13] displaying reservation group information --- application/models/ressource/Reservierung_model.php | 6 +++--- .../Cis/Renderer/Reservierungen/calendarEvent.js | 6 ++++++ .../Cis/Renderer/Reservierungen/modalContent.js | 8 ++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/application/models/ressource/Reservierung_model.php b/application/models/ressource/Reservierung_model.php index e1c96925c..0aa74fb7e 100644 --- a/application/models/ressource/Reservierung_model.php +++ b/application/models/ressource/Reservierung_model.php @@ -24,7 +24,7 @@ class Reservierung_model extends DB_Model $lvplan_reservierungen_query = "SELECT r.* , stund.beginn, stund.ende, CASE WHEN r.gruppe_kurzbz IS NOT NULL THEN r.gruppe_kurzbz - ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(r.semester AS varchar),'/'),COALESCE(CAST(r.verband AS varchar),'/')) + ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(r.semester AS varchar),'/'),COALESCE(CAST(r.verband AS varchar),'/'),COALESCE(CAST(res.gruppe AS varchar),'/')) END as gruppen_kuerzel FROM campus.vw_reservierung r JOIN public.tbl_studiengang studg ON studg.studiengang_kz=r.studiengang_kz @@ -39,7 +39,7 @@ class Reservierung_model extends DB_Model $raum_reservierungen_query = "SELECT res.*, beginn, ende, CASE WHEN res.gruppe_kurzbz IS NOT NULL THEN res.gruppe_kurzbz - ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/')) + ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/'),COALESCE(CAST(res.gruppe AS varchar),'/')) END as gruppen_kuerzel FROM campus.vw_reservierung res JOIN public.tbl_studiengang studg ON studg.studiengang_kz=res.studiengang_kz @@ -83,7 +83,7 @@ class Reservierung_model extends DB_Model $raum_reservierungen_query = "SELECT res.*, beginn, ende, CASE WHEN res.gruppe_kurzbz IS NOT NULL THEN res.gruppe_kurzbz - ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/')) + ELSE CONCAT(UPPER(studg.typ),UPPER(studg.kurzbz),'-',COALESCE(CAST(res.semester AS varchar),'/'),COALESCE(CAST(res.verband AS varchar),'/'),COALESCE(CAST(res.gruppe AS varchar),'/')) END as gruppen_kuerzel FROM campus.vw_reservierung res JOIN public.tbl_studiengang studg ON studg.studiengang_kz=res.studiengang_kz diff --git a/public/js/components/Cis/Renderer/Reservierungen/calendarEvent.js b/public/js/components/Cis/Renderer/Reservierungen/calendarEvent.js index 91c49d5e0..edae78671 100644 --- a/public/js/components/Cis/Renderer/Reservierungen/calendarEvent.js +++ b/public/js/components/Cis/Renderer/Reservierungen/calendarEvent.js @@ -32,6 +32,11 @@ export default { this.$p.t('person/ort'), this.event.ort_kurzbz ].join(": ")); + + tooltipArray.push([ + this.$p.t('lehre/gruppe'), + this.event.gruppe[0].kuerzel + ].join(": ")); if (Array.isArray(this.event.lektor) && this.event.lektor.length > 0) { if (this.event.lektor.length > 3) { @@ -103,6 +108,7 @@ export default { {{ event.topic }} {{ event.ort_kurzbz }} + {{ event.gruppe[0].kuerzel }} + + {{ + $p.t('lehre','gruppe')? + $p.t('lehre','gruppe')+':' + :'' + }} + {{event.gruppe[0].kuerzel}} + {{ $p.t('lehre','lektor')? From 4d01b608089398e64d3d30f5086ff9e635bc6bad Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Thu, 13 Aug 2026 11:36:16 +0200 Subject: [PATCH 12/13] formatting reservation group string --- .../components/Cis/Renderer/Reservierungen/calendarEvent.js | 4 ++-- .../js/components/Cis/Renderer/Reservierungen/modalContent.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/js/components/Cis/Renderer/Reservierungen/calendarEvent.js b/public/js/components/Cis/Renderer/Reservierungen/calendarEvent.js index edae78671..2a0f4db95 100644 --- a/public/js/components/Cis/Renderer/Reservierungen/calendarEvent.js +++ b/public/js/components/Cis/Renderer/Reservierungen/calendarEvent.js @@ -35,7 +35,7 @@ export default { tooltipArray.push([ this.$p.t('lehre/gruppe'), - this.event.gruppe[0].kuerzel + this.event.gruppe[0].kuerzel.split("/")[0] ].join(": ")); if (Array.isArray(this.event.lektor) && this.event.lektor.length > 0) { @@ -108,7 +108,7 @@ export default { {{ event.topic }} {{ event.ort_kurzbz }} - {{ event.gruppe[0].kuerzel }} + {{ event.gruppe[0].kuerzel.split("/")[0] }} - {{event.gruppe[0].kuerzel}} + {{event.gruppe[0].kuerzel.split("/")[0]}} {{ From ab1c8e63ee0a945e2c87f07197cba75744157ed3 Mon Sep 17 00:00:00 2001 From: adisposkofh Date: Thu, 13 Aug 2026 16:55:16 +0200 Subject: [PATCH 13/13] fetching actual semester/verband/gruppe options for reservation creation form --- .../api/frontend/v1/calendar/RoomPlan.php | 58 +++++++- public/js/api/factory/calendar/roomPlan.js | 9 ++ .../js/components/Cis/Mylv/RoomInformation.js | 3 - .../Cis/Renderer/Slot/roomModalContent.js | 131 +++++++++++++----- 4 files changed, 159 insertions(+), 42 deletions(-) diff --git a/application/controllers/api/frontend/v1/calendar/RoomPlan.php b/application/controllers/api/frontend/v1/calendar/RoomPlan.php index 489d83b53..522478f35 100644 --- a/application/controllers/api/frontend/v1/calendar/RoomPlan.php +++ b/application/controllers/api/frontend/v1/calendar/RoomPlan.php @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -if (! defined('BASEPATH')) exit('No direct script access allowed'); +if (!defined('BASEPATH')) + exit('No direct script access allowed'); class RoomPlan extends FHCAPI_Controller { @@ -33,6 +34,7 @@ class RoomPlan extends FHCAPI_Controller 'getGruppen' => self::PERM_LOGGED, 'getLektor' => self::PERM_LOGGED, 'getReservableMap' => self::PERM_LOGGED, + 'getGroupOptions' => self::PERM_LOGGED, ]); $this->load->library('LogLib'); @@ -149,16 +151,14 @@ class RoomPlan extends FHCAPI_Controller $this->GruppeModel->addOrder('gruppe_kurzbz'); $this->GruppeModel->db->group_start(); - foreach ($query_words as $word) - { + foreach ($query_words as $word) { $this->GruppeModel->db->group_start(); $this->GruppeModel->db->where('gruppe_kurzbz ILIKE', "%" . $word . "%"); $this->GruppeModel->db->or_where('bezeichnung ILIKE', "%" . $word . "%"); $this->GruppeModel->db->or_where('beschreibung ILIKE', "%" . $word . "%"); $this->GruppeModel->db->or_where('orgform_kurzbz ILIKE', "%" . $word . "%"); - if (is_numeric($word)) - { + if (is_numeric($word)) { $this->GruppeModel->db->or_where('studiengang_kz', $word); } $this->GruppeModel->db->group_end(); @@ -193,8 +193,7 @@ class RoomPlan extends FHCAPI_Controller $this->MitarbeiterModel->addJoin('public.tbl_person', 'person_id'); $this->MitarbeiterModel->db->where('public.tbl_benutzer.aktiv', true); $this->MitarbeiterModel->db->group_start(); - foreach ($query_words as $word) - { + foreach ($query_words as $word) { $this->MitarbeiterModel->db->group_start(); $this->MitarbeiterModel->db->where('tbl_person.vorname ILIKE', "%" . $word . "%"); $this->MitarbeiterModel->db->or_where('tbl_person.nachname ILIKE', "%" . $word . "%"); @@ -229,4 +228,49 @@ class RoomPlan extends FHCAPI_Controller $this->terminateWithSuccess(array('reservierbarMap' => hasData($result) ? getData($result) : [])); } + public function getGroupOptions() + { + $studiengangKz = $this->input->get('studiengangKz'); + + $this->load->model('organisation/Lehrverband_model', 'LehrverbandModel'); + $this->LehrverbandModel->addSelect('semester, verband, gruppe'); + $this->LehrverbandModel->db->where('aktiv', true); + $this->LehrverbandModel->db->where('studiengang_kz', $studiengangKz); + $lehrverbandResult = $this->LehrverbandModel->load(); + + if (isError($lehrverbandResult)) + $this->terminateWithError($lehrverbandResult); + + $lehrverbanden = hasData($lehrverbandResult) ? getData($lehrverbandResult) : []; + + $grouppedValues = []; + foreach ($lehrverbanden as $lehrverband) { + if (isset($grouppedValues[$lehrverband->semester]) && $lehrverband->verband !== ' ') { + $grouppedValues[$lehrverband->semester][] = $lehrverband; + } else if (!isset($grouppedValues[$lehrverband->semester]) && $lehrverband->verband !== ' ') { + $grouppedValues[$lehrverband->semester] = [$lehrverband]; + } else if (!isset($grouppedValues[$lehrverband->semester])) { + $grouppedValues[$lehrverband->semester] = []; + } + } + $grouppedValues = array_map( + function ($semester) { + $verbandGruppen = []; + foreach ($semester as $lehrverband) { + if (isset($verbandGruppen[$lehrverband->verband]) && $lehrverband->gruppe !== ' ') { + $verbandGruppen[$lehrverband->verband][] = $lehrverband->gruppe; + } else if (!isset($verbandGruppen[$lehrverband->verband]) && $lehrverband->gruppe !== ' ') { + $verbandGruppen[$lehrverband->verband] = [$lehrverband->gruppe]; + } else if (!isset($verbandGruppen[$lehrverband->verband])) { + $verbandGruppen[$lehrverband->verband] = []; + } + } + return $verbandGruppen; + }, + $grouppedValues + ); + + $this->terminateWithSuccess($grouppedValues); + } + } diff --git a/public/js/api/factory/calendar/roomPlan.js b/public/js/api/factory/calendar/roomPlan.js index d27843b37..dc8aee207 100644 --- a/public/js/api/factory/calendar/roomPlan.js +++ b/public/js/api/factory/calendar/roomPlan.js @@ -40,5 +40,14 @@ export default { reservierung_id: reservierung_id } }; + }, + getGroupOptions(studiengangKz) { + return { + method: 'get', + url: '/api/frontend/v1/calendar/RoomPlan/getGroupOptions', + params: { + studiengangKz + } + }; } } \ No newline at end of file diff --git a/public/js/components/Cis/Mylv/RoomInformation.js b/public/js/components/Cis/Mylv/RoomInformation.js index 30722ab62..2c6a5b77c 100644 --- a/public/js/components/Cis/Mylv/RoomInformation.js +++ b/public/js/components/Cis/Mylv/RoomInformation.js @@ -34,9 +34,6 @@ export default { scope: 'slot_room', show_all_fields: false, room_create_information: { - semester: [1, 2, 3, 4, 5, 6, 7, 8], - verband: ['A', 'B', 'C', 'D', 'E', 'F', 'V'], - gruppe: [1, 2, 3, 4], studiengaenge: [], searchGroup: this.searchGroup, searchLektor: this.searchLektor, diff --git a/public/js/components/Cis/Renderer/Slot/roomModalContent.js b/public/js/components/Cis/Renderer/Slot/roomModalContent.js index 6cc0c9805..124c25192 100644 --- a/public/js/components/Cis/Renderer/Slot/roomModalContent.js +++ b/public/js/components/Cis/Renderer/Slot/roomModalContent.js @@ -1,9 +1,10 @@ -import FormInput from '../../../Form/Input.js'; +import FormInput from "../../../Form/Input.js"; +import ApiRoomPlan from "../../../../api/factory/calendar/roomPlan.js"; export default { - emits: ['create-event'], + emits: ["create-event"], components: { - FormInput + FormInput, }, inject: { timeGrid: "timeGrid", @@ -26,38 +27,85 @@ export default { verband: null, gruppe: null, selectedGruppe: null, - selectedLektoren: [] + selectedLektoren: [], + nestedGroupOptions: {}, }; }, - mounted() { - this.syncFromEvent(this.event); - }, + computed: { + semesterOptions() { + if (!this.studiengang) return []; + return Object.keys(this.nestedGroupOptions) + .map((val) => parseInt(val)) + .sort((a, b) => a - b); + }, + verbandOptions() { + if (!this.semester) return []; + + return Object.keys(this.nestedGroupOptions[this.semester]).sort(); + }, + gruppeOptions() { + if (!this.verband) return []; + + return this.nestedGroupOptions[this.semester][this.verband] + .map((val) => parseInt(val)) + .sort((a, b) => a - b); + }, + }, watch: { event: { handler(newEvent) { this.syncFromEvent(newEvent); }, - deep: true - } + deep: true, + }, + studiengang() { + this.semester = null; + this.verband = null; + this.gruppe = null; + this.nestedGroupOptions = {}; + this.fetchGroupOptions(); + }, + semester() { + this.verband = null; + }, + verband() { + this.gruppe = null; + }, }, methods: { syncFromEvent(newEvent) { if (!newEvent) return; - const startTime = newEvent.start?.setZone?.(this.timezone)?.toFormat?.('HH:mm:ss'); - const endTime = newEvent.end?.setZone?.(this.timezone)?.toFormat?.('HH:mm:ss'); + const startTime = newEvent.start + ?.setZone?.(this.timezone) + ?.toFormat?.("HH:mm:ss"); + const endTime = newEvent.end + ?.setZone?.(this.timezone) + ?.toFormat?.("HH:mm:ss"); - this.selectedStart = this.timeGrid.find(t => t.start === startTime)?.start || this.timeGrid[0]?.start; - this.selectedEnd = this.timeGrid.find(t => t.end === endTime)?.end || this.timeGrid.at(-1)?.end; + this.selectedStart = + this.timeGrid.find((t) => t.start === startTime)?.start || + this.timeGrid[0]?.start; + this.selectedEnd = + this.timeGrid.find((t) => t.end === endTime)?.end || + this.timeGrid.at(-1)?.end; }, saveEvent() { - const [startHour, startMinute] = this.selectedStart.split(':').map(Number); - const [endHour, endMinute] = this.selectedEnd.split(':').map(Number); - const selectedStart = this.event.start.startOf('day').set({ hour: startHour, minute: startMinute }); - const selectedEnd = this.event.start.startOf('day').set({ hour: endHour, minute: endMinute }); + const [startHour, startMinute] = this.selectedStart + .split(":") + .map(Number); + const [endHour, endMinute] = this.selectedEnd + .split(":") + .map(Number); + const selectedStart = this.event.start + .startOf("day") + .set({ hour: startHour, minute: startMinute }); + const selectedEnd = this.event.start + .startOf("day") + .set({ hour: endHour, minute: endMinute }); - const lektoren_uid = this.selectedLektoren.map(m => m.uid) + const lektoren_uid = this.selectedLektoren.map((m) => m.uid); const spezialgruppe = this.selectedGruppe?.gruppe_kurzbz; @@ -71,22 +119,41 @@ export default { verband: this.verband, gruppe: this.gruppe, spezialgruppe: spezialgruppe, - lektoren: lektoren_uid - } + lektoren: lektoren_uid, + }; - this.$emit('create-event', event); + this.$emit("create-event", event); }, async searchGroup(event) { - this.filteredGroups = await this.event.createContext.room_create_information.searchGroup(event) + this.filteredGroups = + await this.event.createContext.room_create_information.searchGroup( + event, + ); }, async searchLektor(event) { - this.filteredLektoren = await this.event.createContext.room_create_information.searchLektor(event) + this.filteredLektoren = + await this.event.createContext.room_create_information.searchLektor( + event, + ); }, - capitalize(text) - { - if (!text) return '' - return text.charAt(0).toUpperCase() + text.slice(1) - } + capitalize(text) { + if (!text) return ""; + return text.charAt(0).toUpperCase() + text.slice(1); + }, + async fetchGroupOptions() { + if (!this.studiengang) return; + + const groupOptionsResponse = await this.$api.call( + ApiRoomPlan.getGroupOptions(this.studiengang), + ); + + if (groupOptionsResponse.meta.status === "success") { + this.nestedGroupOptions = groupOptionsResponse.data; + } + }, + }, + mounted() { + this.syncFromEvent(this.event); }, template: ` @@ -186,7 +253,7 @@ export default { name="semester" >
- ` + `, };