diff --git a/application/config/routes.php b/application/config/routes.php index aa4ba9db8..3462113f9 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -122,6 +122,8 @@ $route['api/frontend/v1/stv/[sS]tudents/([WS]S[0-9]{4})/person/(:num)'] = 'api/f // load routes from extensions, also look for environment-specific configs $subdirs = ['application/config/extensions', 'application/config/' . ENVIRONMENT . '/extensions']; +$route['lehre/RoomManager/.*'] = 'lehre/RoomManager/index'; + foreach($subdirs as $subdir) { if(is_dir($subdir)) diff --git a/application/controllers/RoomManager.php b/application/controllers/RoomManager.php new file mode 100644 index 000000000..9591f5f36 --- /dev/null +++ b/application/controllers/RoomManager.php @@ -0,0 +1,60 @@ + array('lehre/unterrichtszeiten_gk:r'), + ) + ); + + $this->load->library('PermissionLib'); + $this->load->library('AuthLib'); + + $this->loadPhrases( + array( + 'ui', + 'global', + 'person', + 'abschlusspruefung', + 'password', + 'lehre' + ) + ); + + $this->_setAuthUID(); + + $this->setControllerId(); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + public function index() + { + return $this->load->view('room_manager/index'); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Private methods + + /** + * Retrieve the UID of the logged user and checks if it is valid + */ + private function _setAuthUID() + { + $this->_uid = getAuthUID(); + + if (!$this->_uid) show_error('User authentification failed'); + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/Ort.php b/application/controllers/api/frontend/v1/Ort.php index 0d8074fa9..c8fee5b72 100644 --- a/application/controllers/api/frontend/v1/Ort.php +++ b/application/controllers/api/frontend/v1/Ort.php @@ -33,19 +33,63 @@ class Ort extends FHCAPI_Controller { // NOTE(chris): additional permission checks will be done in SearchBarLib parent::__construct([ + 'getAllRooms' => self::PERM_LOGGED, + 'getRooms' => self::PERM_LOGGED, + 'getTypes' => self::PERM_LOGGED, 'ContentID' => self::PERM_LOGGED, 'getOrtKurzbzContent' => self::PERM_LOGGED, - 'getRooms' => self::PERM_LOGGED, - 'getTypes' => self::PERM_LOGGED + 'getRoom' => self::PERM_LOGGED, + 'createRoom' => self::PERM_LOGGED, + 'updateRoom' => self::PERM_LOGGED, + 'deleteRoom' => self::PERM_LOGGED, ]); + $this->load->library('form_validation'); + $this->load->model('ressource/Ort_model', 'OrtModel'); $this->config->load('raumsuche'); + + $this->loadPhrases([ + 'global', + 'ui', + ]); } //------------------------------------------------------------------------------------------------------------------ // Public methods + public function getAllRooms() + { + $filter = $this->input->get('filter', TRUE); + + $filterData = []; + + if (isset($filter['locationId']) && $filter['locationId'] !== '') { + $filterData['standort_id'] = $filter['locationId']; + } + if (isset($filter['organizationalUnitShortCode']) && $filter['organizationalUnitShortCode'] !== '') { + $filterData['oe_kurzbz'] = $filter['organizationalUnitShortCode']; + } + if (isset($filter['buildingComponent']) && $filter['buildingComponent'] !== '') { + $filterData['gebteil'] = $filter['buildingComponent']; + } + if (isset($filter['isForTrainingProgram']) && $filter['isForTrainingProgram'] === 'true') { + $filterData['lehre'] = $filter['isForTrainingProgram'] === 'true' ? true : false; + } + if (isset($filter['isReservationNeeded']) && $filter['isReservationNeeded'] === 'true') { + $filterData['reservieren'] = $filter['isReservationNeeded'] === 'true' ? true : false; + } + if (isset($filter['isActive']) && $filter['isActive'] === 'true') { + $filterData['aktiv'] = $filter['isActive'] === 'true' ? true : false; + } + + $this->OrtModel->addOrder("ort_kurzbz", "ASC"); + + $result = $this->OrtModel->loadWhere($filterData); + + $this->terminateWithSuccess($this->getDataOrTerminateWithError($result)); + } + /** * Retrieves all Ort entries filtered by the provided parameters */ @@ -54,7 +98,7 @@ class Ort extends FHCAPI_Controller $this->load->library('form_validation'); $this->form_validation->set_data($_GET); $this->form_validation->set_rules('datum','Datum','required'); - $this->form_validation->set_rules('von','Uhrzeit Von','required|regex_match[/^[0-9]{2}:[0-9]{2}$/]'); + $this->form_validation->set_rules('von','Uhrzeit Von','required|regexresponse_match[/^[0-9]{2}:[0-9]{2}$/]'); $this->form_validation->set_rules('bis','Uhrzeit Bis','required|regex_match[/^[0-9]{2}:[0-9]{2}$/]'); if($this->form_validation->run() == FALSE) { $this->terminateWithValidationErrors($this->form_validation->error_array()); @@ -174,5 +218,163 @@ class Ort extends FHCAPI_Controller $this->terminateWithSuccess($content); } + + public function getRoom($ort_kurzbz) + { + $result = $this->OrtModel->load($ort_kurzbz); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + + $result = hasData($result) ? current(getData($result)) : null; + + return $this->terminateWithSuccess($result); + } + + public function createRoom() + { + $this->form_validation->set_rules('ort_kurzbz', 'ort_kurzbz', 'required', [ + 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => $this->p->t('ui', 'shortCode')]) + ]); + + if($this->form_validation->run() == FALSE) $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $this->db->trans_start(); + + $result = $this->OrtModel->db->query("INSERT INTO public.tbl_ort + ( + oe_kurzbz, + content_id, + standort_id, + ort_kurzbz, + bezeichnung, + planbezeichnung, + aktiv, + lehre, + reservieren, + max_person, + stockwerk, + lageplan, + dislozierung, + kosten, + ausstattung, + telefonklappe, + m2, + gebteil, + arbeitsplaetze, + insertamum, + insertvon, + updateamum, + updatevon + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + $this->input->post('oe_kurzbz'), + $this->input->post('contentId'), + $this->input->post('standort_id'), + $this->input->post('ort_kurzbz'), + $this->input->post('bezeichnung'), + $this->input->post('planbezeichnung'), + $this->input->post('aktiv') ? true : false, + $this->input->post('lehre') ? true : false, + $this->input->post('reservieren') ? true : false, + $this->input->post('max_person'), + $this->input->post('stockwerk'), + $this->input->post('lageplan'), + $this->input->post('dislozierung'), + $this->input->post('kosten'), + $this->input->post('ausstattung'), + $this->input->post('telefonklappe'), + $this->input->post('m2'), + $this->input->post('gebteil'), + $this->input->post('arbeitsplatze'), + date('c'), + getAuthUid(), + date('c'), + getAuthUid() + ]); + + $this->db->trans_complete(); + + + return $this->terminateWithSuccess($result); + } + + public function updateRoom($ort_kurzbz) + { + if (!$ort_kurzbz) { + $this->terminateWithError("missing ort_kurzbz parameter", self::ERROR_TYPE_GENERAL); + } + + $this->db->trans_start(); + + $result = $this->OrtModel->db->query("UPDATE public.tbl_ort SET + oe_kurzbz = ?, + content_id = ?, + standort_id = ?, + bezeichnung = ?, + planbezeichnung = ?, + aktiv = ?, + lehre = ?, + reservieren = ?, + max_person = ?, + stockwerk = ?, + lageplan = ?, + dislozierung = ?, + kosten = ?, + ausstattung = ?, + telefonklappe = ?, + m2 = ?, + gebteil = ?, + arbeitsplaetze = ?, + updateamum = ?, + updatevon = ? + WHERE ort_kurzbz = ?", [ + $this->input->post('oe_kurzbz'), + $this->input->post('contentId'), + $this->input->post('standort_id'), + $this->input->post('bezeichnung'), + $this->input->post('planbezeichnung'), + $this->input->post('aktiv') ? true : false, + $this->input->post('lehre') ? true : false, + $this->input->post('reservieren') ? true : false, + $this->input->post('max_person'), + $this->input->post('stockwerk'), + $this->input->post('lageplan'), + $this->input->post('dislozierung'), + $this->input->post('kosten'), + $this->input->post('ausstattung'), + $this->input->post('telefonklappe'), + $this->input->post('m2'), + $this->input->post('gebteil'), + $this->input->post('arbeitsplatze'), + date('c'), + getAuthUid(), + $ort_kurzbz + ]); + + $this->db->trans_complete(); + + + return $this->terminateWithSuccess($result); + } + + public function deleteRoom($ort_kurzbz) + { + + $this->db->trans_start(); + + $result = $this->OrtModel->delete([ + "ort_kurzbz" => $ort_kurzbz + ]); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + + $this->db->trans_complete(); + + return $this->terminateWithSuccess(true); + } } diff --git a/application/controllers/api/frontend/v1/organisation/LocationApi.php b/application/controllers/api/frontend/v1/organisation/LocationApi.php new file mode 100644 index 000000000..8a5ac4f6e --- /dev/null +++ b/application/controllers/api/frontend/v1/organisation/LocationApi.php @@ -0,0 +1,56 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); +class LocationApi extends FHCAPI_Controller +{ + + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getLocationsByCompanyType'=> self::PERM_LOGGED, + ]); + + $this->load->library('form_validation'); + + $this->load->model('organisation/standort_model', 'StandortModel'); + + $this->loadPhrases([ + 'global', + 'ui', + ]); + + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + public function getLocationsByCompanyType() { + $companyType = $this->input->get('companyType'); + if (!isset($companyType)) { + $this->terminateWithError('companyType parameter is required', REST_Controller::HTTP_BAD_REQUEST); + return; + } + + $result = $this->StandortModel->getByCompanyType($companyType); + + return $this->terminateWithSuccess($this->getDataOrTerminateWithError($result)); + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/organisation/OrganizationalUnitApi.php b/application/controllers/api/frontend/v1/organisation/OrganizationalUnitApi.php new file mode 100644 index 000000000..dce7c5222 --- /dev/null +++ b/application/controllers/api/frontend/v1/organisation/OrganizationalUnitApi.php @@ -0,0 +1,55 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); +class OrganizationalUnitApi extends FHCAPI_Controller +{ + /** + * Object initialization + */ + public function __construct() + { + parent::__construct([ + 'getAllOrganizationalUnits'=> array('basis/organisationseinheit:r'), + ]); + + $this->load->library('form_validation'); + + $this->load->model('organisation/Organisationseinheit_model', 'OrganisationseinheitModel'); + + $this->loadPhrases([ + 'global' + ]); + + } + + //------------------------------------------------------------------------------------------------------------------ + // Public methods + + public function getAllOrganizationalUnits() + { + $entitledOrganizationalUnitsShortCodes = $this->permissionlib->getOE_isEntitledFor('basis/organisationseinheit'); + + $this->OrganisationseinheitModel->db->where_in('oe_kurzbz', $entitledOrganizationalUnitsShortCodes); + $result = $this->OrganisationseinheitModel->load(); + + $organization_units_result = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($organization_units_result); + } +} \ No newline at end of file diff --git a/application/models/organisation/Standort_model.php b/application/models/organisation/Standort_model.php index aeeab4497..0d5f157df 100644 --- a/application/models/organisation/Standort_model.php +++ b/application/models/organisation/Standort_model.php @@ -35,5 +35,15 @@ class Standort_model extends DB_Model return $this->loadWhere(array("firma_id" => $firma_id)); } + + public function getByCompanyType($companyType) + { + $query = "SELECT s.* FROM public.tbl_standort s + JOIN public.tbl_firma f ON s.firma_id = f.firma_id + JOIN public.tbl_adresse a ON s.adresse_id = a.adresse_id + WHERE f.firmentyp_kurzbz = ?;"; + + return $this->execReadOnlyQuery($query, [$companyType]); + } } diff --git a/application/views/room_manager/index.php b/application/views/room_manager/index.php new file mode 100644 index 000000000..a355fa857 --- /dev/null +++ b/application/views/room_manager/index.php @@ -0,0 +1,39 @@ + 'Raumverwaltung', + 'vue3' => true, + 'axios027' => true, + 'bootstrap5' => true, + 'tabulator5' => true, + 'fontawesome6' => true, + 'primevue3' => true, + 'navigationcomponent' => true, + 'filtercomponent' => true, + 'vuedatepicker11' => true, + 'customJSs' => array( + 'vendor/moment/luxonjs/luxon.min.js' + ), + 'customJSModules' => array( + 'public/js/apps/RoomManagerApp.js' + ), + 'customCSSs' => array( + 'public/css/components/primevue.css', + 'public/css/components/verticalsplit.css', + 'public/extensions/FHC-Core-Developer/css/FhcMain.css', + 'public/css/components/calendar.css', + 'public/css/components/vue-datepicker.css', + ) +); + +$this->load->view('templates/FHC-Header', $includesArray); +?> + +
+ + + +
+ +load->view('templates/FHC-Footer', $includesArray); ?> \ No newline at end of file diff --git a/public/js/api/factory/location.js b/public/js/api/factory/location.js new file mode 100644 index 000000000..35afcc305 --- /dev/null +++ b/public/js/api/factory/location.js @@ -0,0 +1,10 @@ + +export default { + getLocationsByCompanyType(companyType) { + return { + method: 'get', + url: '/api/frontend/v1/organisation/LocationApi/getLocationsByCompanyType', + params: { companyType } + }; + } +}; \ No newline at end of file diff --git a/public/js/api/factory/organizationalUnit.js b/public/js/api/factory/organizationalUnit.js new file mode 100644 index 000000000..1c17ab5d1 --- /dev/null +++ b/public/js/api/factory/organizationalUnit.js @@ -0,0 +1,8 @@ +export default { + getAllOrganizationalUnits() { + return { + method: "get", + url: "api/frontend/v1/organisation/organizationalUnitApi/getAllOrganizationalUnits", + }; + }, +} \ No newline at end of file diff --git a/public/js/api/factory/ort.js b/public/js/api/factory/ort.js index 2e06d6b71..ee6a477ac 100644 --- a/public/js/api/factory/ort.js +++ b/public/js/api/factory/ort.js @@ -16,11 +16,25 @@ */ export default { - getContentID(ort_kurbz) { + getAllRooms(params) { + return { + method: 'get', + url: 'api/frontend/v1/Ort/getAllRooms', + params: { + "filter[organizationalUnitShortCode]" : params.organizationalUnitShortCode, + "filter[locationId]" : params.locationId, + "filter[buildingComponent]" : params.buildingComponent, + "filter[isForTrainingProgram]" : params.isForTrainingProgram, + "filter[isReservationNeeded]" : params.isReservationNeeded, + "filter[isActive]" : params.isActive, + } + } + }, + getContentID(ort_kurzbz) { return { method: 'get', url: '/api/frontend/v1/Ort/ContentID', - params: { ort_kurzbz: ort_kurbz } + params: { ort_kurzbz: ort_kurzbz } }; }, getRooms(datum, von, bis, typ, personenanzahl = 0) { @@ -30,11 +44,37 @@ export default { params: { datum, von, bis, typ, personenanzahl } }; }, + getRoom(ort_kurzbz) { + return { + method: 'get', + url: '/api/frontend/v1/Ort/getRoom/' + ort_kurzbz, + }; + }, getRoomTypes() { return { method: 'get', url: '/api/frontend/v1/Ort/getTypes', params: { } }; + }, + createRoom(roomData) { + return { + method: 'post', + url: '/api/frontend/v1/Ort/createRoom', + params: roomData + } + }, + updateRoom(roomId, roomData) { + return { + method: 'post', + url: '/api/frontend/v1/Ort/updateRoom/' + roomId, + params: roomData + } + }, + deleteRoom(ort_kurzbz) { + return { + method: 'post', + url: '/api/frontend/v1/Ort/deleteRoom/' + ort_kurzbz, + } } }; \ No newline at end of file diff --git a/public/js/api/factory/roomManager.js b/public/js/api/factory/roomManager.js new file mode 100644 index 000000000..c538e7596 --- /dev/null +++ b/public/js/api/factory/roomManager.js @@ -0,0 +1,18 @@ + +export default { + getAllRooms(params) { + console.log("API Call with params: ", params); + return { + method: 'get', + url: 'api/frontend/v1/Ort/getAllRooms', + params: { + "filter[organizationalUnitShortCode]" : params.organizationalUnitShortCode, + "filter[locationId]" : params.locationId, + "filter[buildingComponent]" : params.buildingComponent, + "filter[isForTrainingProgram]" : params.isForTrainingProgram, + "filter[isReservationNeeded]" : params.isReservationNeeded, + "filter[isActive]" : params.isActive, + } + } + }, +}; \ No newline at end of file diff --git a/public/js/apps/RoomManagerApp.js b/public/js/apps/RoomManagerApp.js new file mode 100644 index 000000000..2e86e54e1 --- /dev/null +++ b/public/js/apps/RoomManagerApp.js @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2023 fhcomplete.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import RoomManagerOverview from "../components/RoomManager/RoomManagerOverview.js"; + +import FhcAlert from "../plugins/FhcAlert.js"; +import Phrasen from "../plugins/Phrasen.js"; +import FhcApi from "../plugins/Api.js"; + +import {capitalize} from "../helpers/StringHelpers.js"; + +const ciPath = + FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, "") + + FHC_JS_DATA_STORAGE_OBJECT.ci_router; + +const router = VueRouter.createRouter({ + history: VueRouter.createWebHistory(), + routes: [ + { + name: "overview", + path: `/${ciPath}/RoomManager`, + component: RoomManagerOverview, + }, + ], +}); + +const app = Vue.createApp({ + components: { + RoomManagerOverview + }, +}); + +app.config.globalProperties.$capitalize = capitalize; + +app.use(router) + .use(primevue.config.default, { zIndex: { overlay: 9999 } }) + .use(FhcAlert) + .use(Phrasen) + .use(FhcApi) + .mount("#main"); diff --git a/public/js/components/RoomManager/RoomFormModal.js b/public/js/components/RoomManager/RoomFormModal.js new file mode 100644 index 000000000..a8e83100c --- /dev/null +++ b/public/js/components/RoomManager/RoomFormModal.js @@ -0,0 +1,436 @@ +import ApiRoom from "../../../js/api/factory/ort.js"; +import ApiLocation from "../../../js/api/factory/location.js"; +import ApiOrganizationalUnit from "../../../js/api/factory/organizationalUnit.js"; + +import BsModal from "../Bootstrap/Modal.js"; +import CoreForm from "../Form/Form.js"; +import FormInput from "../Form/Input.js"; + +export default { + name: "RoomFormModal", + components: { + BsModal, + CoreForm, + FormInput, + }, + props: { + isVisible: { + type: Boolean, + required: true, + }, + editedRoomShortCode: { + type: String, + default: null, + }, + }, + emits: ["hideBsModal", "roomCreated", "roomUpdated"], + watch: { + isVisible(newValue) { + if (newValue) { + this.$refs.roomFormModal.show(); + } else { + this.$refs.roomFormModal.hide(); + } + }, + editedRoomShortCode(newValue) { + if (newValue) { + this.editRoom(newValue); + } else { + this.resetRoomForm(); + } + }, + }, + data: () => { + return { + isEditInProgress: false, + organizationalUnits: [], + filteredOrganizationalUnits: [], + locations: [], + editedRoom: null, + roomFormData: { + aktiv: true, + }, + }; + }, + computed: { + dropdownParsedOrganizationalUnits() { + return this.organizationalUnits.map((unit) => { + return { + label: `${unit.bezeichnung} (${unit.organisationseinheittyp_kurzbz})`, + value: unit.oe_kurzbz, + }; + }); + }, + }, + methods: { + filterOrganizationalUnits(event) { + let defaultItem = { + label: "----------", + value: null, + }; + + const query = event.query.toLowerCase(); + if (!query) { + return (this.filteredOrganizationalUnits = [ + defaultItem, + ...this.dropdownParsedOrganizationalUnits, + ]); + } + + return (this.filteredOrganizationalUnits = [defaultItem].concat( + this.dropdownParsedOrganizationalUnits.filter((unit) => { + return unit.label.toLowerCase().includes(query); + }), + )); + }, + createRoom() { + return this.$refs.roomForm + .call(ApiRoom.createRoom(this.getApiCallParsedRoomFormData())) + .then((response) => { + this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave")); + this.$emit("roomCreated"); + this.resetRoomForm(); + this.hideRoomFormModal(); + }); + }, + async editRoom(roomShortCode) { + let getLocationsResponse = await this.$api.call( + ApiRoom.getRoom(roomShortCode), + ); + if (getLocationsResponse.meta.status === "success") { + this.editedRoom = getLocationsResponse.data; + } else { + console.error( + "Error fetching room data:", + getLocationsResponse.meta.message, + ); + this.$fhcAlert.alertError(this.$p.t("ui", "errorLoadingRoomData")); + return; + } + + + this.isEditInProgress = true; + + let orgUnit = this.organizationalUnits.find( + (unit) => unit.oe_kurzbz === this.editedRoom.oe_kurzbz, + ); + + let orgUnitData = { + label: `${orgUnit.bezeichnung} (${orgUnit.organisationseinheittyp_kurzbz})`, + value: orgUnit.oe_kurzbz, + }; + + this.roomFormData = { + locationId: this.editedRoom.standort_id, + organizationalUnit: orgUnitData, + contentId: this.editedRoom.content_id, + kurzbezeichnung: this.editedRoom.ort_kurzbz, + bezeichnung: this.editedRoom.bezeichnung, + planbezeichnung: this.editedRoom.planbezeichnung, + aktiv: this.editedRoom.aktiv, + lehre: this.editedRoom.lehre, + reservieren: this.editedRoom.reservieren, + maxPerson: this.editedRoom.max_person, + stockwerk: this.editedRoom.stockwerk, + lageplan: this.editedRoom.lageplan, + dislozierung: this.editedRoom.dislozierung, + kosten: this.editedRoom.kosten, + ausstattung: this.editedRoom.ausstattung, + telefonklappe: this.editedRoom.telefonklappe, + quadratmeter: this.editedRoom.m2, + gebaudeteil: this.editedRoom.gebteil, + arbeitsplatze: this.editedRoom.arbeitsplaetze, + }; + + this.$refs.roomFormModal.show(); + }, + updateRoom() { + return this.$refs.roomForm + .call( + ApiRoom.updateRoom( + this.editedRoom.ort_kurzbz, + this.getApiCallParsedRoomFormData(), + ), + ) + .then((response) => { + this.$fhcAlert.alertSuccess(this.$p.t("ui", "successSave")); + this.$emit("roomUpdated"); + this.resetRoomForm(); + this.hideRoomFormModal(); + }); + }, + getApiCallParsedRoomFormData() { + return { + standort_id: this.roomFormData.locationId, + oe_kurzbz: this.roomFormData.organizationalUnit?.value, + content_id: this.roomFormData.contentId, + ort_kurzbz: this.roomFormData.kurzbezeichnung, + bezeichnung: this.roomFormData.bezeichnung, + planbezeichnung: this.roomFormData.planbezeichnung, + aktiv: this.roomFormData.aktiv, + lehre: this.roomFormData.lehre, + reservieren: this.roomFormData.reservieren, + max_person: this.roomFormData.maxPerson, + stockwerk: this.roomFormData.stockwerk, + lageplan: this.roomFormData.lageplan, + dislozierung: this.roomFormData.dislozierung, + kosten: this.roomFormData.kosten, + ausstattung: this.roomFormData.ausstattung, + telefonklappe: this.roomFormData.telefonklappe, + m2: this.roomFormData.quadratmeter, + gebteil: this.roomFormData.gebaudeteil, + arbeitsplatze: this.roomFormData.arbeitsplatze, + }; + }, + hideRoomFormModal() { + this.$refs.roomFormModal.hide(); + this.$emit("hideBsModal"); + this.resetRoomForm(); + }, + resetRoomForm() { + this.isEditInProgress = false; + this.editedRoom = null; + this.roomFormData = { + aktiv: true, + }; + }, + }, + async created() { + let getLocationsResponse = await this.$api.call( + ApiLocation.getLocationsByCompanyType("Intern"), + ); + if (getLocationsResponse.meta.status === "success") { + this.locations = getLocationsResponse.data; + } else { + console.error( + "Error fetching locations:", + getLocationsResponse.meta.message, + ); + } + + let getAllOrganizationalUnitsResponse = await this.$api.call( + ApiOrganizationalUnit.getAllOrganizationalUnits(), + ); + if (getAllOrganizationalUnitsResponse.meta.status === "success") { + this.organizationalUnits = getAllOrganizationalUnitsResponse.data.sort( + (a, b) => a.bezeichnung.localeCompare(b.bezeichnung), + ); + } else { + console.error( + "Error fetching organizational units:", + getAllOrganizationalUnitsResponse.meta.message, + ); + } + }, + template: /* html */ ` + + + + + `, +}; diff --git a/public/js/components/RoomManager/RoomManagerOverview.js b/public/js/components/RoomManager/RoomManagerOverview.js new file mode 100644 index 000000000..56623008e --- /dev/null +++ b/public/js/components/RoomManager/RoomManagerOverview.js @@ -0,0 +1,390 @@ +import ApiRoom from "../../../js/api/factory/ort.js"; +import ApiLocation from "../../../js/api/factory/location.js"; +import ApiOrganizationalUnit from "../../../js/api/factory/organizationalUnit.js"; + +import { CoreFilterCmpt } from "../filter/Filter.js"; +import CoreForm from "../Form/Form.js"; +import FormInput from "../Form/Input.js"; +import RoomFormModal from "./RoomFormModal.js"; + +import ApiCms from "../../../js/api/factory/cms.js"; + +export default { + name: "RoomManagerOverview", + components: { + CoreFilterCmpt, + CoreForm, + FormInput, + RoomFormModal, + }, + watch: { + filterData: { + handler(newValue) { + this.$refs.roomManagerOverviewTable.tabulator.setData("/", { + organizationalUnitShortCode: newValue.organizationalUnit?.value, + locationId: newValue.locationId, + buildingComponent: newValue.buildingComponent, + isForTrainingProgram: newValue.isForTrainingProgram, + isReservationNeeded: newValue.isReservationNeeded, + isActive: newValue.isActive, + }); + }, + deep: true, + }, + }, + data() { + return { + phrasesLoaded: false, + filterData: { + locationId: null, + organizationalUnit: null, + buildingComponent: null, + isForTrainingProgram: false, + isReservationNeeded: false, + isActive: false, + }, + locations: [], + organizationalUnits: [], + filteredOrganizationalUnits: [], + buildingComponents: ["A", "B", "C", "D", "E", "F"], + isRoomFormModalVisible: false, + editedRoomShortCode: null, + }; + }, + computed: { + tabulatorOptions() { + const options = { + ajaxURL: "dummy", + ajaxRequestFunc: async () => + this.$api.call( + ApiRoom.getAllRooms({ + organizationalUnitShortCode: + this.filterData.organizationalUnit?.value, + locationId: this.filterData.locationId, + buildingComponent: this.filterData.buildingComponent, + isForTrainingProgram: this.filterData.isForTrainingProgram, + isReservationNeeded: this.filterData.isReservationNeeded, + isActive: this.filterData.isActive, + }), + ), + ajaxResponse: (url, params, response) => response.data, + persistenceID: "core_class_schedule_validity_periods", + selectableRows: true, + columns: [ + { + title: this.$capitalize(this.$p.t("gruppenmanagement", "kurzbezeichnung")), + field: "ort_kurzbz", + }, + { + title: this.$capitalize(this.$p.t("gruppenmanagement", "bezeichnung")), + field: "bezeichnun", + }, + { + title: this.$capitalize(this.$p.t("ui", "planbezeichnung")), + field: "planbezeichnung", + }, + { + title: this.$capitalize(this.$p.t("ui", "maxPersons")), + field: "max_person", + }, + { + title: this.$capitalize(this.$p.t("ui", "arbeitsplaetze")), + field: "arbeitsplaetze", + }, + { + title: this.$capitalize(this.$p.t("ui", "quadratmeter")), + field: "m2", + }, + { + title: this.$capitalize(this.$p.t("lehre", "organisationseinheit")), + field: "oe_kurzbz", + }, + { + title: this.$capitalize(this.$p.t("ui", "lehre")), + field: "lehre", + }, + { + title: this.$capitalize(this.$p.t("ui", "reservieren")), + field: "reservieren", + }, + { + title: this.$capitalize(this.$p.t("gruppenmanagement", "aktiv")), + field: "aktiv", + }, + { + title: this.$capitalize(this.$p.t("ui", "kosten")), + field: "kosten", + }, + { + title: this.$capitalize(this.$p.t("ui", "stockwerk")), + field: "stockwerk", + }, + { + title: this.$capitalize(this.$p.t("global", "actions")), + field: "actions", + minWidth: 150, + maxWidth: 150, + formatter: (cell, formatterParams, onRendered) => { + let container = document.createElement("div"); + container.className = "d-flex gap-2"; + + let button = document.createElement("button"); + + button = document.createElement("button"); + button.className = "btn btn-outline-secondary btn-action"; + button.innerHTML = ''; + button.title = this.$p.t( + "ui", + "btn_editRoom", + ); + button.addEventListener("click", (event) => + this.editRoom( + cell.getData().ort_kurzbz, + ), + ); + container.append(button); + + button = document.createElement("button"); + button.className = + "btn btn-outline-secondary btn-action bg-danger"; + button.innerHTML = ''; + button.title = this.$p.t( + "ui", + "btn_deleteRoom", + ); + button.addEventListener("click", () => { + let isDeletionConfirmed = confirm( + this.$p.t( + "ui", + "deleteRoomConfirmation", + ), + ); + if (!isDeletionConfirmed) return; + + this.deleteRoom( + cell.getData().ort_kurzbz + ); + }); + container.append(button); + + return container; + }, + frozen: true, + }, + ], + }; + return options; + }, + tabulatorEvents() { + const events = [ + { + event: "renderComplete", + handler: async () => {}, + }, + ]; + return events; + }, + dropdownParsedOrganizationalUnits() { + return this.organizationalUnits.map((unit) => { + return { + label: `${unit.bezeichnung} (${unit.organisationseinheittyp_kurzbz})`, + value: unit.oe_kurzbz, + }; + }); + }, + }, + methods: { + filterOrganizationalUnits(event) { + let defaultItem = { + label: "----------", + value: null, + }; + + const query = event.query.toLowerCase(); + if (!query) { + return (this.filteredOrganizationalUnits = [ + defaultItem, + ...this.dropdownParsedOrganizationalUnits, + ]); + } + + return (this.filteredOrganizationalUnits = [defaultItem].concat( + this.dropdownParsedOrganizationalUnits.filter((unit) => { + return unit.label.toLowerCase().includes(query); + }), + )); + }, + showRoomFormModal() { + this.isRoomFormModalVisible = true; + }, + editRoom(roomShortCode) { + this.editedRoomShortCode = roomShortCode; + }, + deleteRoom(roomShortCode) { + this.$api + .call(ApiRoom.deleteRoom(roomShortCode)) + .then((response) => { + if (response.meta.status === "success") { + this.$refs.roomManagerOverviewTable.reloadTable(); + alert(this.$p.t("ui", "roomDeletedSuccessfully")); + } else { + console.error("Error deleting room:", response.meta.message); + alert(this.$p.t("ui", "errorDeletingRoom")); + } + }) + .catch((error) => { + console.error("Error deleting room:", error); + alert(this.$p.t("ui", "errorDeletingRoom")); + }); + } + }, + async created() { + let getContent = await this.$api.call(ApiCms.content(7601)); + if (getContent.meta.status === "success") { + console.log; + } else { + console.error("Error fetching locations:", getContent.meta.message); + } + + let getLocationsResponse = await this.$api.call( + ApiLocation.getLocationsByCompanyType("Intern"), + ); + if (getLocationsResponse.meta.status === "success") { + this.locations = getLocationsResponse.data; + } else { + console.error( + "Error fetching locations:", + getLocationsResponse.meta.message, + ); + } + + let getAllOrganizationalUnitsResponse = await this.$api.call( + ApiOrganizationalUnit.getAllOrganizationalUnits(), + ); + if (getAllOrganizationalUnitsResponse.meta.status === "success") { + this.organizationalUnits = getAllOrganizationalUnitsResponse.data.sort( + (a, b) => a.bezeichnung.localeCompare(b.bezeichnung), + ); + } else { + console.error( + "Error fetching organizational units:", + getAllOrganizationalUnitsResponse.meta.message, + ); + } + }, + mounted() { + this.$p + .loadCategory(["global", "lehre", "ui", "gruppenmanagement", "core", "person"]) + .then(() => { + this.phrasesLoaded = true; + }); + }, + template: /* html */ ` +
+

{{ $capitalize($p.t("ui", "roomManagerOverviewHeading")) }}

+ + + + + +
+ `, +}; diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 0476db2c9..e42befef2 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -58177,6 +58177,486 @@ I have been informed that I am under no obligation to consent to the transmissio ) ), // ### Phrases Dashboard Admin END + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'roomManagerOverviewHeading', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'raumverwaltung übersicht', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'room management overview', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'addRoomButton', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'raum hinzufügen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'add room', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'planbezeichnung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'planbezeichnung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'plan name', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'maxPersons', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'max. personen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'max. persons', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'arbeitsplaetze', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'arbeitsplätze', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'workplaces', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'quadratmeter', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'quadratmeter', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'square meters', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'reservieren', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'reservieren', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'reserve', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'kosten', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'kosten', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'costs', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'stockwerk', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'stockwerk', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'floor', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'btn_editRoom', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'raum bearbeiten', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'edit room', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'btn_deleteRoom', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'raum löschen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'delete room', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'deleteRoomConfirmation', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sind Sie sicher, dass Sie diesen Raum löschen möchten?', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Are you sure you want to delete this room?', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'roomDeletedSuccessfully', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Raum erfolgreich gelöscht', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Room deleted successfully', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'errorDeletingRoom', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Fehler beim Löschen des Raums', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Error deleting room', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'buildingComponent', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'gebäudekomponente', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'building component', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'lehre', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'lehre', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'teaching', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'errorLoadingRoomData', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Fehler beim Laden der Raumdaten', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Error loading room data', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'createRoomModalTitle', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'raum erstellen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'create room', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'editRoomModalTitle', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'raum bearbeiten', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'edit room', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'gebaudeteil', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'gebäudeteil', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'building component', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'contentId', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'inhalt ID', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'content ID', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'dislozierung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'dislozierung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'relocation', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'lageplan', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'lageplan', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'site plan', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'ausstattung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'ausstattung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'equipment', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), );