Compare commits

...
6 changed files with 407 additions and 0 deletions
@@ -0,0 +1,67 @@
<?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 Reservierung extends FHCAPI_Controller
{
/**
* Object initialization
*/
public function __construct()
{
parent::__construct([
'getMyReservation' => 'basis/cis:r',
'deleteReservation' => 'lehre/reservierung:rw'
]);
$this->load->model('ressource/Reservierung_model', 'ReservierungModel');
}
/**
* Retrieves all reservations from lehre.vw_reservation of the logged in user
*/
public function getMyReservation()
{
$result = $this->ReservierungModel->getMyReservation();
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
/**
* deletes a reservation by ID
*/
public function deleteReservation()
{
$reservierung_id = $this->input->post('reservierung_id');
if ($reservierung_id === NULL || trim((string)$reservierung_id) === '') {
$this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general');
}
$result = $this->ReservierungModel->deleteReservation($reservierung_id);
$data = $this->getDataOrTerminateWithError($result);
$this->terminateWithSuccess($data);
}
}
@@ -146,4 +146,40 @@ class Reservierung_model extends DB_Model
return $this->execReadOnlyQuery($qry, [$uid, $datum, $stunde]);
}
public function deleteReservation($reservierung_id) {
$uid = getAuthUID();
$result = $this->load($reservierung_id);
if (isError($result))
return $result;
if (!hasData($result))
return error('Reservierung nicht gefunden');
$row = $result->retval[0];
if ($row->uid !== $uid && $row->insertvon !== $uid)
return error('Keine Berechtigung');
return $this->delete($reservierung_id);
}
public function getMyReservation() {
$uid = getAuthUID();
$date = date("Y-m-d",time());
$qry = "SELECT vw_reservierung.*,
trim(COALESCE(vw_mitarbeiter.titelpre,'')||' '||COALESCE(vw_mitarbeiter.vorname,'')||' '||COALESCE(vw_mitarbeiter.nachname,'')||' '||COALESCE(vw_mitarbeiter.titelpost,'')) as reserviert_fuer,
trim(COALESCE(reserviert_von.titelpre,'')||' '||COALESCE(reserviert_von.vorname,'')||' '||COALESCE(reserviert_von.nachname,'')||' '||COALESCE(reserviert_von.titelpost,'')) as reserviert_von
FROM campus.vw_reservierung
JOIN campus.vw_mitarbeiter ON vw_reservierung.uid=vw_mitarbeiter.uid
LEFT JOIN campus.vw_mitarbeiter reserviert_von ON vw_reservierung.insertvon=reserviert_von.uid
WHERE datum >= ?
AND (vw_reservierung.uid= ? OR vw_reservierung.insertvon= ? )
ORDER BY datum, titel, ort_kurzbz, stunde";
return $this->execReadOnlyQuery($qry, [$date, $uid, $uid]);
}
}
+32
View File
@@ -0,0 +1,32 @@
/**
* Copyright (C) 2025 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/>.
*/
export default {
getMyReservation() {
return {
method: 'get',
url: '/api/frontend/v1/Reservierung/getMyReservation',
};
},
deleteReservation(reservierung_id) {
return {
method: 'post',
url: '/api/frontend/v1/Reservierung/deleteReservation',
params: { reservierung_id }
}
}
};
+7
View File
@@ -8,6 +8,7 @@ import MyLvPlan from "../../components/Cis/LvPlan/MyLvPlan.js";
import MylvStudent from "../../components/Cis/Mylv/Student.js";
import Profil from "../../components/Cis/Profil/Profil.js";
import Raumsuche from "../../components/Cis/Raumsuche/Raumsuche.js";
import MyReservation from "../../components/Cis/MyReservation/MyReservation.js";
import CmsNews from "../../components/Cis/Cms/News.js";
import CmsContent from "../../components/Cis/Cms/Content.js";
import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js";
@@ -77,6 +78,12 @@ const router = VueRouter.createRouter({
component: Raumsuche,
props: true
},
{
path: `/Cis/MyReservation`,
name: 'MyReservation',
component: MyReservation,
props: true
},
// Redirect old links to new format
{
path: "/CisVue/Cms/getRoomInformation/:ort_kurzbz",
@@ -0,0 +1,184 @@
import {CoreFilterCmpt} from "../../../components/filter/Filter.js";
import VueDatePicker from '../../vueDatepicker.js.php';
import ApiReservierung from '../../../api/factory/reservierung.js'
import { formatISODate } from "../Abgabetool/dateUtils.js";
export const MyReservation = {
name: "MyReservation",
components: {
VueDatePicker,
CoreFilterCmpt
},
inject: ["isMobile"],
data() {
return {
phrasenPromise: null,
phrasenResolved: false,
tabulatorUuid: Vue.ref(0),
tableBuiltResolve: null,
tableBuiltPromise: null,
myReservationTableOptions: {
minHeight: 250,
index: 'reservierung_id',
layout: 'fitColumns',
placeholder: this.$p.t('global/noDataAvailable'),
columns: [
{title: Vue.computed(() => this.$capitalize(this.$p.t('global/datum'))), field: 'datum', widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('global/titel'))), field: 'titel', widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('ui/stunde'))), field: 'stunde', widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('global/raum'))), field: 'ort_kurzbz', widthGrow: 1},
{title: Vue.computed(() => this.$capitalize(this.$p.t('reservierung/reserviert_fuer'))), field: 'reserviert_fuer', widthGrow: 2},
{title: Vue.computed(() => this.$capitalize(this.$p.t('global/beschreibung'))), field: 'beschreibung', widthGrow: 2},
{title: Vue.computed(() => this.$capitalize(this.$p.t('reservierung/reserviert_von'))), field: 'reserviert_von', widthGrow: 2},
{title: Vue.computed(() => this.$capitalize(this.$p.t('global/actions'))), field: 'aktion', formatter: this.formAction, widthGrow: 1}
],
persistence: false,
},
myReservationTableEventHandlers: [{
event: "tableBuilt",
handler: async () => {
this.tableBuiltResolve()
}
}
]};
},
computed: {
isDarkMode(){
return this.$theme.theme_name.value == 'dark';
},
generalPresets: function() {
return [
{
"id": null,
"name": "Standard",
"displayedColumns": [
"datum","titel","stunde","ort_kurzbz","reserviert_fuer",
"reserviert_fuer", "reserviert_von", "aktion"
],
"headerFilters": [],
"sort": null
}
];
},
},
methods: {
formAction(cell) {
const actionButtons = document.createElement('div');
actionButtons.className = "d-flex gap-3";
actionButtons.style.display = "flex";
actionButtons.style.alignItems = "stretch";
actionButtons.style.justifyContent = "start";
actionButtons.style.height = "100%";
const data = cell.getRow().getData();
const createButton = (iconClass, titleKey, clickHandler) => {
const btn = document.createElement('button');
btn.className = 'btn btn-outline-secondary';
btn.style.display = "flex";
btn.style.alignItems = "center"; // center icon vertically
btn.style.justifyContent = "center"; // center icon horizontally
btn.style.height = "100%"; // fill parent container height
btn.style.aspectRatio = "1 / 1"; // keep square shape (optional)
btn.style.padding = "0"; // remove extra padding for compactness
if(iconClass == 'fa fa-timeline') btn.style.transform = "rotate(90deg)";
btn.innerHTML = `<i class="${iconClass}" style="color:#00649C; font-size:1.1rem;"></i>`;
btn.title = this.$capitalize(this.$p.t(titleKey));
btn.addEventListener('click', (e) => {
e.stopPropagation();
e.stopImmediatePropagation();
clickHandler();
});
return btn;
};
// TODO: only show/enable this button if user has berechtigung 'lehre/reservierung:begrenzt'
actionButtons.append(
createButton('fa fa-trash-can', 'global/delete', () => this.deleteReservation(data)),
);
return actionButtons;
},
deleteReservation(data) {
this.$api.call(ApiReservierung.deleteReservation(data.reservierung_id))
.then(res => {
if(res?.meta.status === 'success') {
this.$refs.myReservationTable.tabulator.deleteRow(data.reservierung_id);
this.$fhcAlert.alertSuccess(this.$p.t('reservierung/reservierungWurdeGeloescht'));
}
})
},
tableResolve(resolve) {
this.tableBuiltResolve = resolve
},
setupData(data){
const d = data.map(reservation => {
return {
...reservation, // spread first, then format date
datum: formatISODate(reservation.datum),
}
})
this.$refs.myReservationTable.tabulator.setData(d);
},
handleUuidDefined(uuid) {
this.tabulatorUuid = uuid
},
loadMyReservation() {
this.$api.call(ApiReservierung.getMyReservation())
.then(res => {
if(res?.data) this.setupData(res.data)
})
},
async setupMounted() {
this.tableBuiltPromise = new Promise(this.tableResolve)
await this.tableBuiltPromise
this.loadMyReservation()
// this.loadRooms()
//
// const tableID = this.tabulatorUuid ? ('-' + this.tabulatorUuid) : ''
// const tableDataSet = document.getElementById('filterTableDataset' + tableID);
// if(!tableDataSet) return
// const rect = tableDataSet.getBoundingClientRect();
//
// const h = window.visualViewport.height - rect.top - 100
// if(this.$refs.raumsucheTable) {
// this.$refs.raumsucheTable.$refs.table.style.setProperty('height', h+'px')
// }
}
},
created() {
this.phrasenPromise = this.$p.loadCategory(['reservierung', 'global', 'ui'])
this.phrasenPromise.then(()=> {this.phrasenResolved = true})
},
mounted() {
this.setupMounted()
},
template: `
<h1 class="h3">{{$p.t('reservierung/reservierungsliste')}}</h1>
<hr>
<core-filter-cmpt
v-if="phrasenResolved"
:isUsingPresets="true"
presetsId="myReservationTable"
@uuidDefined="handleUuidDefined"
:title="''"
ref="myReservationTable"
:tabulator-options="myReservationTableOptions"
:tabulator-events="myReservationTableEventHandlers"
tableOnly
:sideMenu="false"
/>
`,
};
export default MyReservation;
+81
View File
@@ -62351,6 +62351,87 @@ I have been informed that I am under no obligation to consent to the transmissio
)
)
),
//******************* CORE/reservierung
array(
'app' => 'core',
'category' => 'reservierung',
'phrase' => 'reservierungsliste',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Reservierungsliste',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Reservation list',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'reservierung',
'phrase' => 'reserviert_fuer',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Reserviert für',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Reserved for',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'reservierung',
'phrase' => 'reserviert_von',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Reserviert von',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Reserved by',
'description' => '',
'insertvon' => 'system'
)
)
),
array(
'app' => 'core',
'category' => 'reservierung',
'phrase' => 'reservierungWurdeGeloescht',
'insertvon' => 'system',
'phrases' => array(
array(
'sprache' => 'German',
'text' => 'Reservierung wurde gelöscht',
'description' => '',
'insertvon' => 'system'
),
array(
'sprache' => 'English',
'text' => 'Reservation successfully deleted',
'description' => '',
'insertvon' => 'system'
)
)
),
);