Add new endpoint and vuejs components for room managment

This commit is contained in:
Ivymaster
2026-04-29 13:08:33 +02:00
parent 552faefa51
commit fe507e4185
15 changed files with 1865 additions and 5 deletions
+2
View File
@@ -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))
+60
View File
@@ -0,0 +1,60 @@
<?php
if (! defined('BASEPATH')) exit('No direct script access allowed');
/**
*/
class RoomManager extends Auth_Controller
{
private $_uid; // uid of the logged user
/**
* Constructor
*/
public function __construct()
{
parent::__construct(
array(
'index' => 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');
}
}
+205 -3
View File
@@ -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);
}
}
@@ -0,0 +1,56 @@
<?php
/**
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
*/
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));
}
}
@@ -0,0 +1,55 @@
<?php
/**
* Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
@@ -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]);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
$includesArray = array(
'title' => '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);
?>
<div id="main">
<router-view
cis-root="<?= CIS_ROOT; ?>"
>
</router-view>
</div>
<?php $this->load->view('templates/FHC-Footer', $includesArray); ?>
+10
View File
@@ -0,0 +1,10 @@
export default {
getLocationsByCompanyType(companyType) {
return {
method: 'get',
url: '/api/frontend/v1/organisation/LocationApi/getLocationsByCompanyType',
params: { companyType }
};
}
};
@@ -0,0 +1,8 @@
export default {
getAllOrganizationalUnits() {
return {
method: "get",
url: "api/frontend/v1/organisation/organizationalUnitApi/getAllOrganizationalUnits",
};
},
}
+42 -2
View File
@@ -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,
}
}
};
+18
View File
@@ -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,
}
}
},
};
+54
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
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");
@@ -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 */ `
<bs-modal ref="roomFormModal" size="sm" @hideBsModal="() => { $emit('hideBsModal'); resetRoomForm(); }" class="modal-lg">
<template #title>
<p v-if="!editedRoom" class="fw-bold mt-3">{{$capitalize($p.t('ui', 'createRoomModalTitle'))}}</p>
<p v-else class="fw-bold mt-3">{{$capitalize($p.t('ui', 'editRoomModalTitle'))}}</p>
</template>
<template #default>
<core-form ref="roomForm" class="row g-3 pb-3">
<div class="row mb-3">
<div class="col">
<form-input
v-model="roomFormData.organizationalUnit"
:label="$capitalize($p.t('lehre/organisationseinheit'))"
:suggestions="filteredOrganizationalUnits"
:optionValue="(option) => option.value"
:optionLabel="(option) => option.label"
@complete="filterOrganizationalUnits"
dropdown
forceSelection
type="autocomplete"
name="organizationalUnitShortCode"
>
</form-input>
</div>
<div class="col">
<form-input
v-model="roomFormData.locationId"
:label="$capitalize($p.t('global', 'raum'))"
type="select"
id="location"
name="location"
>
<option
v-for="location in locations"
:key="location.standort_id"
:value="location.standort_id"
>
{{location.kurzbz}}
</option>
</form-input>
</div>
</div>
<div class="row mb-3">
<div class="col">
<form-input
v-model="roomFormData.aktiv"
:label="$capitalize($p.t('person', 'aktiv'))"
type="checkbox"
name="aktiv"
>
</form-input>
</div>
<div class="col">
<form-input
v-model="roomFormData.lehre"
:label="$capitalize($p.t('ui', 'lehre'))"
type="checkbox"
name="lehre"
>
</form-input>
</div>
<div class="col">
<form-input
v-model="roomFormData.reservieren"
:label="$capitalize($p.t('ui', 'reservieren'))"
type="checkbox"
name="reservieren"
>
</form-input>
</div>
</div>
<div v-if='!this.editedRoom' class="row mb-3">
<form-input
v-model="roomFormData.kurzbezeichnung"
:label="$capitalize($p.t('gruppenmanagement', 'kurzbezeichnung'))"
type="text"
name="kurzbezeichnung"
>
</form-input>
</div>
<div class="row mb-3">
<form-input
v-model="roomFormData.bezeichnung"
:label="$capitalize($p.t('ui', 'bezeichnung'))"
type="text"
name="bezeichnung"
>
</form-input>
</div>
<div class="row mb-3">
<form-input
v-model="roomFormData.planbezeichnung"
:label="$capitalize($p.t('ui', 'planbezeichnung'))"
type="text"
name="planbezeichnung"
>
</form-input>
</div>
<div class="row mb-3">
<div class="col">
<form-input
v-model="roomFormData.maxPerson"
:label="$capitalize($p.t('ui', 'maxPersons'))"
type="number"
name="maxPerson"
>
</form-input>
</div>
<div class="col">
<form-input
v-model="roomFormData.stockwerk"
:label="$capitalize($p.t('ui', 'stockwerk'))"
type="number"
name="stockwerk"
>
</form-input>
</div>
<div class="col">
<form-input
v-model="roomFormData.quadratmeter"
:label="$capitalize($p.t('ui', 'quadratmeter'))"
type="number"
name="quadratmeter"
>
</form-input>
</div>
</div>
<div class="row mb-3">
<div class='col'>
<form-input
v-model="roomFormData.telefonklappe"
:label="$capitalize($p.t('person', 'telefonklappe'))"
type="number"
name="telefonklappe"
>
</form-input>
</div>
<div class='col'>
<form-input
v-model="roomFormData.arbeitsplatze"
:label="$capitalize($p.t('ui', 'arbeitsplaetze'))"
type="number"
name="arbeitsplatze"
>
</form-input>
</div>
<div class='col'>
<form-input
v-model="roomFormData.kosten"
:label="$capitalize($p.t('ui', 'kosten'))"
type="text"
name="kosten"
>
</form-input>
</div>
</div>
<div class="row mb-3">
<div class='col'>
<form-input
v-model="roomFormData.gebaudeteil"
:label="$capitalize($p.t('ui', 'gebaudeteil'))"
type="text"
name="gebaudeteil"
>
</form-input>
</div>
<div class='col'>
<form-input
v-model="roomFormData.contentId"
:label="$capitalize($p.t('ui', 'contentId'))"
type="number"
name="contentId"
>
</form-input>
</div>
<div class='col'>
<form-input
v-model="roomFormData.dislozierung"
:label="$capitalize($p.t('ui', 'dislozierung'))"
type="text"
name="dislozierung"
>
</form-input>
</div>
</div>
<div class="row mb-3">
<form-input
v-model="roomFormData.lageplan"
:label="$capitalize($p.t('ui', 'lageplan'))"
type="textarea"
name="lageplan"
>
</form-input>
</div>
<div class="row mb-3">
<form-input
v-model="roomFormData.ausstattung"
:label="$capitalize($p.t('ui', 'ausstattung'))"
type="textarea"
name="ausstattung"
>
</form-input>
</div>
<div class="col d-flex justify-content-end gap-2">
<button type="button" class="btn btn-secondary" @click="hideRoomFormModal">{{$p.t('ui', 'abbrechen')}}</button>
<button type="button" class="btn btn-primary" @click="isEditInProgress ? updateRoom() : createRoom()">{{$p.t('ui', 'speichern')}}</button>
</div>
</core-form>
</template>
</bs-modal>
`,
};
@@ -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 = '<i class="fa fa-edit"></i>';
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 = '<i class="fa fa-xmark text-white"></i>';
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 */ `
<div class="container mt-4">
<h1 class='mb-5'>{{ $capitalize($p.t("ui", "roomManagerOverviewHeading")) }}</h1>
<div class="row mb-3">
<div class="col d-flex justify-content-between">
<a class="btn btn-primary mb-3" @click="showRoomFormModal">{{$capitalize($p.t('ui', 'addRoomButton'))}}</a>
</div>
</div>
<core-filter-cmpt
v-if="phrasesLoaded"
ref="roomManagerOverviewTable"
table-only
:side-menu="false"
:tabulator-options="tabulatorOptions"
:tabulator-events="tabulatorEvents"
>
<template #search>
<slot name="filterzuruecksetzen">
<core-form class="d-flex flex-column flex-md-row align-items-md-end gap-3">
<div>
<form-input
v-model="filterData.organizationalUnit"
:label="$capitalize($p.t('lehre/organisationseinheit'))"
:suggestions="filteredOrganizationalUnits"
:optionValue="(option) => option.value"
:optionLabel="(option) => option.label"
@complete="filterOrganizationalUnits"
dropdown
forceSelection
type="autocomplete"
name="organizationalUnitShortCode"
>
</form-input>
</div>
<div>
<form-input
v-model="filterData.locationId"
:label="$capitalize($p.t('global', 'raum'))"
type="select"
id="location"
name="location"
>
<option
v-for="location in locations"
:key="location.standort_id"
:value="location.standort_id"
>
{{location.kurzbz}}
</option>
</form-input>
</div>
<div>
<form-input
v-model="filterData.buildingComponent"
:label="$capitalize($p.t('ui', 'buildingComponent'))"
type="select"
id="buildingComponent"
name="buildingComponent"
>
<option
v-for="component in buildingComponents"
:key="component"
:value="component"
>
{{component}}
</option>
</form-input>
</div>
<div>
<form-input
v-model="filterData.isForTrainingProgram"
:label="$capitalize($p.t('ui', 'lehre'))"
type="checkbox"
name="filterIsForTrainingProgram"
dropdown
></form-input>
</div>
<div>
<form-input
v-model="filterData.isReservationNeeded"
:label="$capitalize($p.t('ui', 'reservieren'))"
type="checkbox"
name="filterIsReservationNeeded"
dropdown
></form-input>
</div>
<div>
<form-input
v-model="filterData.isActive"
:label="$capitalize($p.t('person', 'aktiv'))"
type="checkbox"
name="filterIsActive"
dropdown
></form-input>
</div>
</core-form>
</slot>
</template>
</core-filter-cmpt>
<room-form-modal
:isVisible="isRoomFormModalVisible"
:editedRoomShortCode="editedRoomShortCode"
@hideBsModal="() => { isRoomFormModalVisible = false; editedRoomShortCode = null; }"
@roomCreated="() => { $refs.roomManagerOverviewTable.reloadTable(); editedRoomShortCode = null; }"
@roomUpdated="() => { $refs.roomManagerOverviewTable.reloadTable(); editedRoomShortCode = null; }"
/>
</div>
`,
};
+480
View File
@@ -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'
)
)
),
);