stylingaenderungen

coursepicker umgebaut, keine backend suche mehr
hinzugefuegt:
- resizeHanlder funktion
- broadcastchannel postMessage "dropped"
- tbl_kalender_status column bezeichnung_mehrsprachig
- tbl_kalender_status column sort
This commit is contained in:
ma0048
2026-04-01 10:53:31 +02:00
parent 8dd42361a0
commit ab5294de2f
14 changed files with 553 additions and 188 deletions
@@ -27,6 +27,11 @@ class Config extends FHCAPI_Controller
public function get()
{
$language = getUserLanguage() == 'German' ? 0 : 1;
$this->_ci->KalenderStatusModel->addSelect('*, array_to_json(bezeichnung_mehrsprachig::varchar[])->>' . $language .' AS status');
$this->_ci->KalenderStatusModel->addOrder('sort');
$visible_status = $this->_ci->KalenderStatusModel->load();
$visible_status = getData($visible_status);
@@ -34,28 +39,32 @@ class Config extends FHCAPI_Controller
$config['visible_status'] = [
"type" => "select",
"label" => $this->p->t('ui', 'status'),
"multiple" => true,
"value" => 'all'
];
foreach ($visible_status as $status)
{
$config['visible_status']['options'][$status->status_kurzbz] = $status->status_kurzbz;
$config['visible_status']['options'][$status->status_kurzbz] = $status->status;
}
$this->terminateWithSuccess($config);
}
public function getHeader()
{
$language = getUserLanguage() == 'German' ? 0 : 1;
$this->_ci->KalenderStatusModel->addSelect('*, array_to_json(bezeichnung_mehrsprachig::varchar[])->>' . $language .' AS status');
$this->_ci->KalenderStatusModel->addOrder('sort');
$visible_status = $this->_ci->KalenderStatusModel->load();
$visible_status = getData($visible_status);
$config['visible_status']['all'] = 'all';
$config['visible_status']['all'] = 'Alle';
foreach ($visible_status as $status)
{
$config['visible_status'][$status->status_kurzbz] = $status->bezeichnung;
$config['visible_status'][$status->status_kurzbz] = $status->status;
}
$this->terminateWithSuccess($config);
@@ -9,6 +9,7 @@ class Coursepicker extends FHCAPI_Controller
{
parent::__construct([
'search' => self::PERM_LOGGED,
'getByStg' => self::PERM_LOGGED
]);
$this->_ci = &get_instance();
@@ -18,9 +19,7 @@ class Coursepicker extends FHCAPI_Controller
$this->_ci->load->model('education/Lehreinheitmitarbeiter_model', 'LehreinheitmitarbeiterModel');
$this->loadPhrases([
'ui'
]);
$this->loadPhrases(['ui']);
}
public function search()
@@ -93,4 +92,163 @@ class Coursepicker extends FHCAPI_Controller
$this->terminateWithSuccess(hasData($result) ? getData($result) : array());
}
public function getByStg()
{
//TODO check einbauen ob studiensemester und stg vorhanden ist
$stg = $this->input->get('stg');
$studiensemester_kurzbz = $this->input->get('studiensemester_kurzbz');
if (is_null($stg) || is_null($studiensemester_kurzbz))
$this->terminateWithError($this->p->t('ui', 'ungueltigeParameter'), self::ERROR_TYPE_GENERAL);
$this->_ci->LehreinheitModel->addSelect('
tbl_lehreinheit.lehreinheit_id,
tbl_lehreinheit.unr,
tbl_lehreinheit.lvnr,
tbl_lehreinheit.lehrfach_id,
lehrfach.kurzbz AS lehrfach,
lehrfach.bezeichnung AS lehrfach_bez,
lehrfach.farbe AS lehrfach_farbe,
tbl_lehreinheit.lehrform_kurzbz AS lehrform,
lema.mitarbeiter_uid AS lektor_uid,
ma.kurzbz AS lektor,
tbl_person.vorname,
tbl_person.nachname,
tbl_studiengang.studiengang_kz,
upper(tbl_studiengang.typ::character varying::text || tbl_studiengang.kurzbz::text) AS studiengang,
lvb.semester,
lvb.verband,
lvb.gruppe,
lvb.gruppe_kurzbz,
tbl_lehreinheit.raumtyp,
tbl_lehreinheit.raumtypalternativ,
tbl_lehreinheit.stundenblockung,
tbl_lehreinheit.wochenrythmus,
lema.semesterstunden,
lema.planstunden,
tbl_lehreinheit.start_kw,
tbl_lehreinheit.anmerkung,
tbl_lehreinheit.studiensemester_kurzbz
');
$this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehreinheitmitarbeiter lema', 'tbl_lehreinheit.lehreinheit_id = lema.lehreinheit_id');
$this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehreinheitgruppe lvb', 'tbl_lehreinheit.lehreinheit_id = lvb.lehreinheit_id');
$this->_ci->LehreinheitModel->addJoin('public.tbl_studiengang', 'lvb.studiengang_kz = tbl_studiengang.studiengang_kz');
$this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehrveranstaltung lehrfach', 'tbl_lehreinheit.lehrfach_id = lehrfach.lehrveranstaltung_id');
$this->_ci->LehreinheitModel->addJoin('public.tbl_mitarbeiter ma', 'lema.mitarbeiter_uid = ma.mitarbeiter_uid');
$this->_ci->LehreinheitModel->addJoin('lehre.tbl_lehrform', 'tbl_lehrform.lehrform_kurzbz = tbl_lehreinheit.lehrform_kurzbz');
$this->_ci->LehreinheitModel->addJoin('public.tbl_benutzer', 'ma.mitarbeiter_uid = tbl_benutzer.uid');
$this->_ci->LehreinheitModel->addJoin('public.tbl_person', 'tbl_benutzer.person_id = tbl_person.person_id');
$result = $this->_ci->LehreinheitModel->loadWhere(array(
'tbl_lehrform.verplanen' => true,
'tbl_studiengang.studiengang_kz' => $stg,
'tbl_lehreinheit.studiensemester_kurzbz' => $studiensemester_kurzbz
));
$result = hasData($result) ? getData($result) : array();
$grouped = array();
foreach ($result as $row)
{
$unr = $row->unr;
if (!isset($grouped[$unr]))
{
$grouped[$unr] = (object)array(
'unr' => $row->unr,
'lehrfach_id' => $row->lehrfach_id,
'lehrfach_bez' => $row->lehrfach_bez,
'lehrfach_farbe' => $row->lehrfach_farbe,
'studiengang_kz' => $row->studiengang_kz,
'studiengang' => $row->studiengang,
'semester' => $row->semester,
'verband' => $row->verband,
'gruppe' => $row->gruppe,
'gruppe_kurzbz' => $row->gruppe_kurzbz,
'raumtyp' => $row->raumtyp,
'raumtypalternativ' => $row->raumtypalternativ,
'anmerkung' => $row->anmerkung,
'studiensemester_kurzbz' => $row->studiensemester_kurzbz,
'fachbereich_kurzbz' => isset($row->fachbereich_kurzbz) ? $row->fachbereich_kurzbz : null,
'lektoren' => array(),
'lehreinheit_id' => array(),
'lvnr' => array(),
'lehrfach' => array(),
'lehrform' => array(),
'stundenblockung' => array(),
'wochenrythmus' => array(),
'planstunden' => array(),
'start_kw' => array(),
'verplant' => array(),
'offenestunden' => array(),
'lehrverband' => array(),
'lem' => array(),
'verplant_gesamt' => 0,
);
}
$group = $grouped[$unr];
$group->lektoren[$row->lektor_uid] = (object)array(
'uid' => $row->lektor_uid,
'kurzbz' => trim($row->lektor),
'name' => $row->vorname . ' ' . $row->nachname,
);
$group->lehreinheit_id[] = $row->lehreinheit_id;
$group->lvnr[] = $row->lvnr;
$group->lehrfach[] = $row->lehrfach;
$group->lehrform[] = $row->lehrform;
$group->stundenblockung[] = $row->stundenblockung;
$group->wochenrythmus[] = $row->wochenrythmus;
$group->planstunden[] = $row->planstunden;
$group->start_kw[] = $row->start_kw;
$group->verplant[] = isset($row->verplant) ? $row->verplant : 0;
$group->offenestunden[] = isset($row->offenestunden) ? $row->offenestunden : 0;
$group->verplant_gesamt += isset($row->verplant) ? $row->verplant : 0;
$lvb = $row->studiengang . '-' . $row->semester;
if ($row->verband != '' && $row->verband != ' ' && $row->verband != '0' && $row->verband != null)
$lvb .= $row->verband;
if ($row->gruppe != '' && $row->gruppe != ' ' && $row->gruppe != '0' && $row->gruppe != null)
$lvb .= $row->gruppe;
$group->lehrverband[] = ($row->gruppe_kurzbz != '' && $row->gruppe_kurzbz != null) ? $row->gruppe_kurzbz : $lvb;
$group->lem[] = array(
'lehreinheit_id' => $row->lehreinheit_id,
'mitarbeiter_uid' => $row->lektor_uid,
);
}
foreach ($grouped as $group)
{
$group->lektoren = array_values($group->lektoren);
$group->lehrverband = array_values(array_unique($group->lehrverband));
$group->lehrfach = $this->_formatArr($group->lehrfach);
$group->lehrform = $this->_formatArr($group->lehrform);
$group->stundenblockung = $this->_formatArr($group->stundenblockung);
$group->wochenrythmus = $this->_formatArr($group->wochenrythmus);
$group->planstunden = $this->_formatArr($group->planstunden);
$group->start_kw = $this->_formatArr($group->start_kw);
$group->verplant = $this->_formatArr($group->verplant);
$group->offenestunden = $this->_formatArr($group->offenestunden);
}
$this->terminateWithSuccess(array_values($grouped));
}
private function _formatArr($arr)
{
$values = array_values(array_unique($arr));
$formatted = implode(' ', $values);
if (count($formatted) > 1)
$formatted .= ' ?';
return $formatted;
}
}
+2 -5
View File
@@ -5,9 +5,6 @@ if (! defined("BASEPATH")) exit("No direct script access allowed");
class KalenderLib
{
private $_ci;
/**
* Loads model OrganisationseinheitModel
*/
public function __construct()
{
$this->_ci =& get_instance();
@@ -453,8 +450,8 @@ class KalenderLib
{
return $this->_ci->KalenderOrtModel->insert(
array (
'kalender_id'=>$kalender_id,
'ort_kurzbz'=>$ort_kurzbz
'kalender_id' => $kalender_id,
'ort_kurzbz' => $ort_kurzbz
)
);
}
+25 -19
View File
@@ -82,12 +82,32 @@ body {
#parkingslot {
border: 1px dashed;
max-height: 400px;
min-height: 5vh;
max-height: 15vh;
color: #AAAAAA;
text-align: center;
font-size: large;
margin-top: 20px;
padding-top: 0.5rem;
margin-top: 1.25rem;
overflow-y: auto;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.course-picker {
border: 1px dashed #AAAAAA;
display: flex;
flex-direction: column;
flex: 1 1 0;
min-height: 0;
}
.course-picker-row {
padding: 0.5rem;
margin-bottom: 0.5rem;
border: 1px solid var(--bs-border-color);
background-color: var(--bs-tertiary-bg);
}
.parkingevent {
@@ -148,30 +168,16 @@ body {
.lecture-selection {
border: 1px dashed #AAAAAA;
margin-bottom: 0.5rem;
max-height: 300px;
max-height: 20vh;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.course-picker {
border: 1px dashed #AAAAAA;
height: 400px;
max-height: 400px;
display: flex;
flex-direction: column;
}
.course-picker-row {
padding: 0.5rem;
margin-bottom: 0.5rem;
border: 1px solid var(--bs-border-color);
background-color: var(--bs-tertiary-bg);
}
.room-selection {
border: 1px dashed #AAAAAA;
margin-bottom: 0.5rem;
padding: 0.5rem 0.0rem 0.5rem 0.5rem;
padding: 0.5rem 0 0.5rem 0.5rem;
}
.btn-link.text-danger {
+10 -2
View File
@@ -3,7 +3,15 @@ export default {
return {
method: 'get',
url: 'api/frontend/v1/tempus/coursepicker/search',
params: { query }
params: { query }
};
}
},
getByStg(stg, studiensemester_kurzbz) {
return {
method: 'get',
url: 'api/frontend/v1/tempus/coursepicker/getByStg',
params: { stg, studiensemester_kurzbz }
};
},
};
+1
View File
@@ -39,4 +39,5 @@ app
}
})
.use(Phrasen)
.directive('tooltip', primevue.tooltip)
.mount('#main');
+5 -3
View File
@@ -63,7 +63,8 @@ export default {
return () => true;
}),
hasDragoverFunc: Vue.computed(() => this.onDragover),
mode: Vue.computed(() => this.mode)
mode: Vue.computed(() => this.mode),
onResize: Vue.computed(() => this.onResize || null),
};
},
props: {
@@ -121,7 +122,8 @@ export default {
dropableEvents: [Boolean, Array, Function],
resizableEvents: [Boolean, Array, Function],
onDragover: Function,
onDrop: Function
onDrop: Function,
onResize: Function
},
emits: [
"click:next",
@@ -212,7 +214,7 @@ export default {
},
watch: {
sDate(n, o) {
if (this.sDate.isValid && !this.sDate.hasSame(this.internalDate, 'day'))
if (this.sDate.isValid && (!this.internalDate || !this.sDate.hasSame(this.internalDate, 'day')))
this.internalDate = this.sDate;
},
sMode() {
+2 -1
View File
@@ -18,6 +18,7 @@ export default {
originalBackgrounds: "backgrounds",
dropAllowed: "dropAllowed",
onDrop: "onDrop",
onResize: "onResize",
timeGrid: {
from: "timeGrid",
default: () => []
@@ -454,7 +455,7 @@ export default {
if (!orig || !newStart || !newEnd)
return;
this.onDrop?.({
this.onResize?.({
item: [{ type: 'kalender', id: orig.kalender_id, orig }],
start: newStart,
end: newEnd
+29 -40
View File
@@ -54,13 +54,18 @@ export default {
extraBackgrounds: {
type: Array,
default: () => []
}
},
visibleStatus: {
type: Array,
default: () => ['all']
},
},
emits: [
"update:date",
"update:mode",
"update:range",
"drop"
"drop",
"resize"
],
data() {
@@ -80,8 +85,7 @@ export default {
}
},
teachingunits: null,
visibleStatusArray: [],
visibleStatus: []
showRaster: true,
};
},
computed: {
@@ -132,25 +136,6 @@ export default {
},
},
methods: {
toggleStatus(status) {
if (status === 'all')
{
this.visibleStatus = ['all'];
return;
}
this.visibleStatus = this.visibleStatus.filter(visibleStatus => visibleStatus !== 'all');
let found = this.visibleStatus.indexOf(status);
if (found === -1)
this.visibleStatus.push(status);
else
this.visibleStatus.splice(found, 1);
if (this.visibleStatus.length < 1)
this.visibleStatus.push('all');
},
eventStyle(event) {
if (!event.farbe)
return undefined;
@@ -163,6 +148,9 @@ export default {
ondrop(payload){
this.$emit('drop', payload);
},
onresize(payload){
this.$emit('resize', payload);
},
resetEventLoader() {
this.reset();
},
@@ -177,6 +165,14 @@ export default {
context.emit('update:lv', newValue);
});
const bcc = new BroadcastChannel('fhc-dnd');
bcc.onmessage = e => {
if (e.data === 'dropped')
{
reset()
}
};
return {
rangeInterval,
events,
@@ -195,11 +191,6 @@ export default {
end: el.ende
}));
});
this.$api.call(ApiTempusConfig.getHeader())
.then(res => {
this.visibleStatusArray = res.data.visible_status
this.visibleStatus = ['all']
});
},
template: /* html */`
<fhc-calendar
@@ -213,11 +204,12 @@ export default {
:locale="$p.user_locale.value"
:events="visibleEvents || []"
:backgrounds="backgrounds"
:time-grid="teachingunits"
:time-grid="showRaster ? teachingunits : null"
show-btns
:draggable-events="true"
:resizable-events="true"
:on-drop="ondrop"
:on-resize="onresize"
@update:date="(newDate, newMode) => $emit('update:date', newDate, newMode)"
@update:mode="(newMode, newDate) => $emit('update:mode', newMode, newDate)"
@update:range="updateRange"
@@ -246,17 +238,14 @@ export default {
</div>
</template>
<template #actions>
<slot>
<button
v-for="(status, key) in visibleStatusArray"
:key="key"
class="btn btn-sm me-1"
:class="visibleStatus.includes(key) ? 'btn-secondary' : 'btn-outline-secondary'"
@click="toggleStatus(key)"
>
{{ status }}
</button>
</slot>
<div
class="d-flex align-items-center gap-2"
style="cursor:pointer"
@click="showRaster = !showRaster"
>
<i :class="showRaster ? 'fa-solid fa-toggle-on text-primary' : 'fa-solid fa-toggle-off text-muted'"></i>
<span class="form-check-label">Stundenraster</span>
</div>
</template>
</fhc-calendar>`
}
+114 -53
View File
@@ -9,54 +9,96 @@ export default {
directives: {
draggable
},
props: {
stg: {
type: [String, Number],
default: null,
},
studiensemester: {
type: String,
default: null
},
},
emits: ['select-lecturer', 'select-kw'],
data() {
return {
searchparam: '',
courses: null,
allCourses: [],
abortController: null,
}
},
methods: {
async loadCourses() {
const query = (this.searchparam ?? '').trim();
computed: {
courses() {
const query = (this.searchparam ?? '').trim().toLowerCase();
if (!query)
{
this.courses = [];
return this.allCourses;
return this.allCourses.filter(course =>
course.showname.toLowerCase().includes(query) ||
course.lektoren?.some(l =>
l.name.toLowerCase().includes(query) ||
l.kurzbz.toLowerCase().includes(query)
)
);
}
},
watch: {
stg(val) {
this.searchparam = '';
this.loadCoursesByStg(val);
},
studiensemester() {
if (this.stg)
this.loadCoursesByStg(this.stg);
},
},
methods: {
async loadCoursesByStg(stg) {
if (!stg) {
this.allCourses = [];
return;
}
if (query.length < 3)
return;
if (this.abortController)
{
this.abortController.abort();
}
this.abortController = new AbortController();
const signal = this.abortController.signal;
this.$api.call(ApiCoursePicker.search(query), { signal })
this.$api.call(ApiCoursePicker.getByStg(this.stg, this.studiensemester))
.then(result => {
this.courses = result.data.map(entry => ({
lehreinheit_id: entry.lehreinheit_id,
lektoren: entry.lektor,
studiengang: entry.studiengang,
semester: entry.semester,
stundenblockung: entry.stundenblockung,
wochenrythmus: entry.wochenrythmus,
showname: `${entry.lehrfach} ${entry.lehrform}`,
this.allCourses = result.data.map(e => ({
lvnr: e.lvnr.join(' '),
unr: e.unr,
lektoren: e.lektoren,
lehrfach_id: e.lehrfach_id,
studiengang_kz: e.studiengang_kz,
fachbereich_kurzbz: e.fachbereich_kurzbz,
semester: e.semester,
verband: e.verband,
gruppe: e.gruppe,
gruppe_kurzbz: e.gruppe_kurzbz,
raumtyp: e.raumtyp,
raumtypalternativ: e.raumtypalternativ,
semesterstunden: e.planstunden,
stundenblockung: e.stundenblockung,
wochenrythmus: e.wochenrythmus,
verplant: e.verplant,
offenestunden: e.offenestunden,
start_kw: e.start_kw,
anmerkung: e.anmerkung,
studiensemester_kurzbz: e.studiensemester_kurzbz,
lehrfach: e.lehrfach,
lehrform: e.lehrform,
lehrfach_bez: e.lehrfach_bez,
lehrfach_farbe: e.lehrfach_farbe,
lehrverband: e.lehrverband.join(' '),
lehreinheit_id: e.lehreinheit_id,
lem: e.lem,
showname: `${e.lehrfach} ${e.lehrform}`,
orig: {
type: 'lehreinheit',
lehreinheit_id: entry.lehreinheit_id,
blockung: entry.stundenblockung,
entry,
lehreinheit_id: e.lehreinheit_id[0],
blockung: e.stundenblockung,
entry: e,
}
}));
})
.catch(this.$fhcAlert.handleSystemError)
.catch(this.$fhcAlert.handleSystemError);
},
dragLehreinheitCollection(course) {
const orig = course.orig;
@@ -80,19 +122,25 @@ export default {
course.mode = 'multi';
break
}
},
selectLecturer: function(lektor) {
this.$emit('select-lecturer', lektor);
}
},
template: `
<div class="course-picker">
<div class="course-picker d-flex flex-column h-100">
<div class="p-2">
<form-input
:label="$p.t('ui', 'suche')"
type="text"
v-model="searchparam"
@input="loadCourses"
/>
</div>
<div class="overflow-auto px-2 pb-2 flex-grow-1">
<form-input
:label="$p.t('ui', 'suche')"
type="text"
v-model="searchparam"
/>
</div>
<div v-if="!stg" class="d-flex flex-column align-items-center justify-content-center text-center text-muted py-5 px-3 h-100">
<span class="small fw-semibold mb-1">Keine Lehreinheiten</span>
<span class="small">Wähle einen Studiengang, um Lehreinheiten anzuzeigen.</span>
</div>
<div v-else class="overflow-auto px-2 pb-2 flex-grow-1">
<div
v-for="course in courses"
:key="course.lehreinheit_id"
@@ -100,23 +148,36 @@ export default {
v-draggable:move.noimage="dragLehreinheitCollection(course)"
tabindex="0"
>
<div class="d-flex justify-content-between align-items-start">
<span class="fw-semibold small">{{ course.showname }}</span>
<div class="d-flex gap-1">
<span class="fw-semibold small w-50" :tooltip="course.lehrfach_bez">{{ course.lehrfach }} {{ course.lehrform }}</span>
<span class="fw-semibold small w-50" :tooltip="course.raumtypalternativ">{{ course.raumtyp }}</span>
</div>
<div class="text-muted">
<span>{{ course.studiengang }} - {{ course.semester }}</span>
<div class="d-flex gap-1 text-muted">
<span class="w-50" :tooltip="course.anmerkung">{{ course.lehrverband }}</span>
<span
style="cursor:pointer"
class="text-decoration-underline w-50"
@click.stop="$emit('select-kw', course.start_kw)">KW: {{ course.start_kw }}
</span>
</div>
<div class="text-muted">
<span>{{ course.lektoren }}</span>
<div class="d-flex gap-1 text-muted">
<span
v-for="(lektor, i) in course.lektoren"
:key="lektor.uid"
style="cursor:pointer"
class="text-decoration-underline w-50"
@click.stop="selectLecturer(lektor)">
{{ lektor.kurzbz }}
<span v-if="i < course.lektoren.length - 1"><br /></span>
</span>
<span class="w-50">WR: {{ course.wochenrythmus }} Bl: {{ course.stundenblockung }}</span>
</div>
<div class="text-muted">
<span>WR: {{ course.wochenrythmus }} Bl: {{ course.stundenblockung }}</span>
<div class="d-flex gap-1 text-muted">
<span class="w-50">Offen: {{ course.offenestunden }}</span>
<span class="w-50">{{ course.semesterstunden }}</span>
</div>
</div>
</div>
<div class="mt-auto px-2 py-2 small text-muted border-top">
Drag & Drop on Calendar
</div>
</div>
`
}
}
+1 -1
View File
@@ -64,7 +64,7 @@ export default {
class="parkingslot"
v-drop:move.kalender-collection="(evt, item) => park(evt, item)"
>
<i class="fa-solid fa-square-parking"></i>
<i v-if="!parked.length" class="fa-solid fa-square-parking"></i>
<event-card
class="parkingevent"
v-for="parkedEvent in parked"
+178 -49
View File
@@ -34,6 +34,7 @@ import BsModal from "../Bootstrap/Modal.js";
import StvVerband from "../Stv/Studentenverwaltung/Verband.js";
import ApiStudiengangTree from "../../api/lehrveranstaltung/studiengangtree.js";
import StvStudiensemester from "../Stv/Studentenverwaltung/Studiensemester.js";
export default {
name: "Tempus",
@@ -48,7 +49,9 @@ export default {
AppMenu,
NavLanguage,
BsModal,
StvVerband
StvVerband,
StvStudiensemester,
Multiselect: primevue.multiselect,
},
props: {
defaultSemester: String,
@@ -115,7 +118,6 @@ export default {
lv_id: null,
events: null,
minimized: false,
calendarDate: luxon.DateTime.local(), //new CalendarDate(new Date()),
currentlySelectedEvent: null,
//currentDay: new Date(),
studiensemesterKurzbz: this.defaultSemester,
@@ -142,6 +144,10 @@ export default {
vorschlaege: [],
event: null
},
visibleStatusArray: {},
visibleStatus: ['all'],
selectedStudiensemester: this.studiensemester_kurzbz ?? this.defaultSemester,
calendarDate: luxon.DateTime.now().setZone(this.config.timezone).toISODate(),
}
},
computed: {
@@ -166,7 +172,15 @@ export default {
if (!this.lecturers.length)
return null;
return this.lecturers.filter(lecture => lecture.showEvents).map(lecture => lecture.uid);
}
},
visibleStatusOptions() {
return Object.entries(this.visibleStatusArray).map(([key, label]) => ({ key, label }));
},
visibleStatusValue() {
if (this.visibleStatus.includes('all'))
return this.visibleStatusOptions.filter(visibleStatus => visibleStatus.key === 'all');
return this.visibleStatus.map(status => ({ key: status, label: this.visibleStatusArray[status] }));
},
},
methods: {
async openRaumauswahl(orig) {
@@ -201,11 +215,14 @@ export default {
this.ort_kurzbz = data.ort_kurzbz;
this.$refs.calendar.resetEventLoader();
},
onSelectVerbandAndClose(payload) {
this.onSelectVerband(payload);
bootstrap.Offcanvas.getOrCreateInstance(this.$refs.verbandMenu).hide();
},
onSelectVerband({link, name})
{
let stg = null;
let semester = null;
let studiensemester_kurzbz = this.selectedStudiensemester;
this.show_stg = name
if (typeof link === 'number')
stg = link;
@@ -216,8 +233,7 @@ export default {
this.stg = stg;
if (semester !== null)
this.semester = semester;
if (studiensemester_kurzbz)
this.studiensemester_kurzbz = studiensemester_kurzbz;
this.$refs.calendar.resetEventLoader();
},
@@ -239,12 +255,42 @@ export default {
if (this.lastRange)
this.handleRange(this.lastRange);
},
handleChangeDate() {
console.log("handleChangeDate");
jumpToKw(kw) {
const num = parseInt(kw);
if (!num)
return;
const date = luxon.DateTime.fromObject({
weekYear: luxon.DateTime.now().setZone(this.config.timezone).weekYear,
weekNumber: num,
weekday: 1,
}, { zone: this.config.timezone });
this.calendarDate = date.toISODate();
},
handleChangeDate(newDate) {
if (newDate && luxon.DateTime.isDateTime(newDate) && newDate.isValid)
this.calendarDate = newDate.toISODate();
},
handleChangeMode() {
console.log("handleChangeMode")
},
toggleStatus(selected) {
if (!selected || selected.length === 0) {
this.visibleStatus = ['all'];
return;
}
const hasAll = selected.includes('all');
const hadAll = this.visibleStatus.includes('all');
if (hasAll && !hadAll)
{
this.visibleStatus = ['all'];
return;
}
this.visibleStatus = selected.filter(k => k !== 'all');
if (this.visibleStatus.length === 0)
this.visibleStatus = ['all'];
},
searchfunction(params) {
return this.$api.call(ApiSearchbar.search(params));
},
@@ -309,9 +355,64 @@ export default {
return calculatedEnd > lastGridEndSameDay ? lastGridEndSameDay : calculatedEnd;
},
_parseDates(start, end)
{
const startDT = luxon.DateTime.fromISO(start);
const endDT = luxon.DateTime.fromISO(end);
if (!startDT.isValid || !endDT.isValid)
{
alert("Ungültiges Datum");
return null;
}
return {
startDT,
endDT,
start_time: startDT.toFormat('yyyy-MM-dd HH:mm'),
end_time: endDT.toFormat('yyyy-MM-dd HH:mm'),
};
},
_updateKalenderEvent(obj, startDT, endDT, start_time, end_time, onSuccess)
{
const origStart = luxon.DateTime.fromISO(obj.orig.isostart);
const origEnd = luxon.DateTime.fromISO(obj.orig.isoend);
if (origStart.toMillis() === startDT.toMillis() && origEnd.toMillis() === endDT.toMillis())
return;
const updatedInfos = {
ort_kurzbz: this.ort_kurzbz ? this.ort_kurzbz : obj.orig.ort_kurzbz,
start_time,
end_time,
};
this.$api.call(ApiKalender.updateKalenderEvent(obj.orig.kalender_id, updatedInfos))
.then(() => {
if (onSuccess)
onSuccess();
});
},
resizeHandler(payload) {
const { item, start, end } = payload;
const obj = item[0];
if (!obj?.orig?.kalender_id)
return alert("Kein gültiges Kalender-Event zum Resizen");
const dates = this._parseDates(start, end);
if (!dates)
return;
this._updateKalenderEvent(obj, dates.startDT, dates.endDT, dates.start_time, dates.end_time, () => {
this.$refs.calendar.resetEventLoader();
});
},
dropHandler(payload) {
const { item, start, end } = payload;
if (!item?.length)
return alert("Keine Daten gedroppt");
@@ -319,18 +420,12 @@ export default {
if (!obj?.type)
return alert("Unbekannter Drop-Typ");
const startDT = luxon.DateTime.fromISO(start);
const endDT = luxon.DateTime.fromISO(end);
const dates = this._parseDates(start, end);
if (!dates) return;
if (!startDT.isValid || !endDT.isValid)
return alert("Ungültiges Datum");
const { startDT, endDT, start_time, end_time } = dates;
const start_time = startDT.toFormat('yyyy-MM-dd HH:mm');
const end_time = endDT.toFormat('yyyy-MM-dd HH:mm');
if (obj.type === 'lehreinheit')
{
if (obj.type === 'lehreinheit') {
this.$api.call(
ApiKalender.addKalenderEvent(
obj.orig.lehreinheit_id,
@@ -338,29 +433,13 @@ export default {
start_time,
end_time
)
).then(() => {
this.$refs.calendar.resetEventLoader();
});
);
}
else if (obj.type === 'kalender')
{
let updatedInfos = {
ort_kurzbz: this.ort_kurzbz ? this.ort_kurzbz : obj.orig.ort_kurzbz,
start_time: start_time,
end_time: end_time
}
this.$api.call(
ApiKalender.updateKalenderEvent(
obj.orig.kalender_id,
updatedInfos
)
).then(() => {
this.$refs.parking.unpark({
type: obj.type,
id: obj.orig.kalender_id
});
this.$refs.calendar.resetEventLoader();
this._updateKalenderEvent(obj, startDT, endDT, start_time, end_time, () =>
{
this.$refs.parking.unpark({ type: obj.type, id: obj.orig.kalender_id });
});
}
else
@@ -538,6 +617,12 @@ export default {
this.renderers[rendertype].calendarEvent = calendarEvent;
}
});
this.$api.call(ApiTempusConfig.getHeader())
.then(res => {
this.visibleStatusArray = res.data.visible_status;
this.visibleStatus = ['all'];
});
},
template: `
<div class="tempus">
@@ -632,10 +717,42 @@ export default {
</div>
</aside>
<nav id="sidebarMenu" class="bg-light offcanvas offcanvas-start col-md p-md-0 h-100 d-flex flex-column">
<div class="offcanvas-header justify-content-end px-1 d-md-none">
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
<div class="sidebar-icons d-flex flex-row align-items-start py-2 gap-1 ps-2">
<button
class="btn btn-outline-secondary"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#verbandMenu"
aria-controls="verbandMenu"
aria-expanded="false"
title="Verband"
>
<span class="fa-solid fa-university"></span>
</button>
<button
class="btn btn-outline-secondary"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#verbandMenu"
aria-controls="verbandMenu"
aria-expanded="false"
title="Verband"
>
<span class="fa-solid fa-door-open"></span>
</button>
</div>
<div class="px-2 py-1 w-100">
<Multiselect
:model-value="visibleStatusValue"
@update:model-value="val => toggleStatus(val.map(o => o.key))"
option-label="label"
:options="visibleStatusOptions"
placeholder="Status filtern"
:hide-selected="false"
:show-toggle-all="false"
class="w-100"
/>
</div>
<div class="room-selection" v-if="ort_kurzbz">
<div class="fw-semibold px-2 d-flex align-items-center justify-content-between">
<span><i class="fa-solid fa-door-open me-2"></i>{{ ort_kurzbz }}</span>
@@ -667,28 +784,31 @@ export default {
v-if="lecturers.length"
:lecturers="lecturers"
@remove="removeLecturer"
></lecture-selection>
<div class="overflow-auto flex-grow-1 d-flex flex-column gap-2" style="min-height: 0">
<div class="verband-selection">
<stv-verband :endpoint="endpoint" @select-verband="onSelectVerband" class="col" style="height:0%"></stv-verband>
</div>
<fhc-coursepicker></fhc-coursepicker>
></lecture-selection>
<div class="d-flex flex-column flex-grow-1" style="min-height: 0">
<parking-slot
ref="parking"
v-model:parked-keys="parkedKeys"
></parking-slot>
<fhc-coursepicker :stg="stg" @select-lecturer="setEmp" @select-kw="jumpToKw" :studiensemester="selectedStudiensemester"></fhc-coursepicker>
</div>
<stv-studiensemester v-model:studiensemester-kurzbz="selectedStudiensemester"></stv-studiensemester>
</nav>
<main class="col-md-8 ms-sm-auto col-lg-9 col-xl-10">
<fhc-calendar
ref="calendar"
:timezone="config.timezone"
:get-promise-func="getPromiseFunc"
:date="currentDay"
:visible-status="visibleStatus"
:date="calendarDate"
:mode="currentMode"
:parkedEvents="parkedKeys"
:visible-lecturers="visibleLecturerUids"
@drop="dropHandler"
@resize="resizeHandler"
@update:date="handleChangeDate"
@update:mode="handleChangeMode"
:extra-backgrounds="extraBackgrounds"
@@ -699,6 +819,15 @@ export default {
</div>
</div>
<app-config ref="config" v-model="appconfig" :endpoints="configEndpoints"></app-config>
<div id="verbandMenu" ref="verbandMenu" class="offcanvas offcanvas-start col-md p-md-0 h-100" tabindex="-1">
<div class="offcanvas-header justify-content-end px-1 d-md-none">
<h5 class="offcanvas-title" id="verbandMenuLabel">
<i class="fa-solid fa-university me-2"></i>Verband
</h5>
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" :aria-label="$p.t('ui/schliessen')"></button>
</div>
<stv-verband :endpoint="endpoint" @select-verband="onSelectVerbandAndClose" class="col" style="height:0%"></stv-verband>
</div>
<bs-modal ref="raumModal" class="bootstrap-prompt">
<template #title>Raumauswahl</template>
+2
View File
@@ -54,10 +54,12 @@ export default {
if (res instanceof Promise) {
res.then(r => {
bcc.postMessage('release');
bcc.postMessage('dropped');
return r;
});
} else {
bcc.postMessage('release');
bcc.postMessage('dropped');
}
}
+9 -7
View File
@@ -70,18 +70,20 @@ if(!$result = @$db->db_query("SELECT kalender_id FROM lehre.tbl_kalender LIMIT 1
CREATE TABLE lehre.tbl_kalender_status (
status_kurzbz character varying(32) NOT NULL,
bezeichnung text,
bezeichnung_mehrsprachig character varying(255)[] NOT NULL,
sort smallint,
CONSTRAINT tbl_kalender_status_pk PRIMARY KEY (status_kurzbz)
);
COMMENT ON TABLE lehre.tbl_kalender_status IS 'Calender visibility Status';
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'planning', E'planning');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'tosync', E'tosync');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'todelete', E'todelete');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'visible_lektor', E'Sichtbar für Lektoren');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'deleted', E'deleted');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'archived', E'archived');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung) VALUES (E'visible_student', E'Sichtbar für Studierende');
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung, bezeichnung_mehrsprachig, sort) VALUES (E'planning', E'planning', E'{\"In Planung\", \"Planning\"}', 1);
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung, bezeichnung_mehrsprachig, sort) VALUES (E'tosync', E'tosync', E'{\"Zu synchronisieren\", \"To sync\"}', 2);
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung, bezeichnung_mehrsprachig, sort) VALUES (E'todelete', E'todelete', E'{\"Zu löschen\", \"To delete\"}', 3);
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung, bezeichnung_mehrsprachig, sort) VALUES (E'visible_lektor', E'Sichtbar für Lektoren', E'{\"Sichtbar für Lektoren\", \"Visible for lecturers\"}', 4);
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung, bezeichnung_mehrsprachig, sort) VALUES (E'deleted', E'deleted', E'{\"Gelöscht\", \"Deleted\"}', 5);
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung, bezeichnung_mehrsprachig, sort) VALUES (E'archived', E'archived', E'{\"Archiviert\", \"Archived\"}', 6);
INSERT INTO lehre.tbl_kalender_status (status_kurzbz, bezeichnung, bezeichnung_mehrsprachig, sort) VALUES (E'visible_student', E'Sichtbar für Studierende', E'{\"Sichtbar für Studierende\", \"Visible for students\"}', 7);
ALTER TABLE lehre.tbl_kalender ADD CONSTRAINT tbl_kalender_status_fk FOREIGN KEY (status_kurzbz)
REFERENCES lehre.tbl_kalender_status (status_kurzbz) MATCH FULL