diff --git a/public/js/components/Tempus/Tempus.js b/public/js/components/Tempus/Tempus.js
index 6bad39517..a7e7c3442 100644
--- a/public/js/components/Tempus/Tempus.js
+++ b/public/js/components/Tempus/Tempus.js
@@ -14,14 +14,26 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-import VueDatePicker from '../vueDatepicker.js.php'
import CoreSearchbar from "../searchbar/searchbar.js";
+import NavLanguage from "../navigation/Language.js";
import VerticalSplit from "../verticalsplit/verticalsplit.js";
import FhcCalendar from "../Calendar/Tempus.js";
import FhcCoursepicker from "../Tempus/Coursepicker.js";
-import ApiKalender from '../../api/factory/kalender.js';
+import LectureSelection from "../Tempus/LectureSelection.js";
+import ParkingSlot from "../Tempus/ParkingSlot.js";
+import ApiKalender from '../../api/factory/tempus/kalender.js';
import ApiSearchbar from "../../api/factory/searchbar.js";
import ApiRenderers from '../../api/factory/renderers.js';
+import ApiTempusConfig from '../../api/factory/tempus/config.js';
+import AppMenu from "../AppMenu.js";
+import drop from '../../directives/drop.js';
+import AppConfig from "../AppConfig.js";
+
+import BsModal from "../Bootstrap/Modal.js";
+
+
+import StvVerband from "../Stv/Studentenverwaltung/Verband.js";
+import ApiStudiengangTree from "../../api/lehrveranstaltung/studiengangtree.js";
export default {
name: "Tempus",
@@ -29,7 +41,14 @@ export default {
CoreSearchbar,
VerticalSplit,
FhcCalendar,
- FhcCoursepicker
+ FhcCoursepicker,
+ LectureSelection,
+ ParkingSlot,
+ AppConfig,
+ AppMenu,
+ NavLanguage,
+ BsModal,
+ StvVerband
},
props: {
defaultSemester: String,
@@ -39,19 +58,28 @@ export default {
cisRoot: String,
activeAddons: String, // semicolon separated list of active addons
viewData: Object,
+ logoutUrl: String,
+ avatarUrl: String
+ },
+ directives: {
+ drop
},
provide() {
return {
cisRoot: this.cisRoot,
defaultSemester: this.defaultSemester,
- $reloadList: () => {
- this.$refs.stvList.reload();
- },
+ currentSemester: this.defaultSemester,
renderers: Vue.computed(() => this.renderers),
+ appConfig: Vue.computed(() => this.appconfig),
+ contextMenuActions: Vue.computed(() => this.contextMenuActions),
}
},
data() {
return {
+ appconfig: {},
+ configEndpoints: ApiTempusConfig,
+ endpoint: ApiStudiengangTree,
+ raumVorschlaege: [],
selected: [],
searchbaroptions: {
origin: 'tempus',
@@ -60,7 +88,8 @@ export default {
types: [
//"student",
"raum",
- //"mitarbeiter"
+ "mitarbeiter",
+ "mitarbeiter_ohne_zuordnung"
],
actions: {
raum: {
@@ -70,7 +99,17 @@ export default {
},
childactions: [
]
- }
+ },
+ employee: {
+ defaultaction: {
+ type: "function",
+ action: (data) => {
+ this.setEmp(data);
+ }
+ },
+ childactions: [
+ ]
+ },
}
},
lv_id: null,
@@ -86,98 +125,379 @@ export default {
geschlechter: []
},
renderers: null,
- ort_kurzbz: 'EDV_A5.08',
+ ort_kurzbz: null,
+ view: 'room',
+ parkedKeys: new Set(),
+ lecturers: [],
+ overlayCache: [],
+ extraBackgrounds: [],
+ lastRange: null,
+ stg: null,
+ show_stg: null,
+ semester: null,
+ studiensemester_kurzbz: null,
+ raumModal: {
+ show: false,
+ loading: false,
+ vorschlaege: [],
+ event: null
+ },
}
},
- methods: {
- setOrt: function(data)
- {
- // Wenn bei der Suche ein Ort ausgewaehlt wird, dann wir der Ort gesetzt und ein Reload getriggert durch den watcher
- this.ort_kurzbz = data.ort_kurzbz;
- },
- handleChangeDate() {
- },
- handleChangeMode() {
- },
- searchfunction(params) {
- return this.$api.call(ApiSearchbar.search(params));
- },
- getPromiseFunc(start, end) {
- return [
- this.$api.call(ApiKalender.getRoomplan(this.ort_kurzbz, '2025-10-01','2025-10-30')),//start.toISODate(), end.toISODate())),
- ];
- },
- parkingdrop: function(evt)
- {
- evt.preventDefault();
- var data = JSON.parse(evt.dataTransfer.getData("text"));
- alert('parked Data:'+data.id);
- console.log(data);
- },
- dropHandler: function(event, start, end)
- {
- let day = start.date.toFormat('yyyy-MM-dd');
- let time = start.date.toFormat('hh:mm');
-
- let dropdata = JSON.parse(event.dataTransfer.getData('text'))
-
- if(dropdata.type=='kalender')
- {
- let kalender_id = dropdata.id;
-
- Promise.allSettled([
- this.$api.call(ApiKalender.updateKalenderEvent(kalender_id, this.ort_kurzbz, day+' '+time, null))
- ]).then((result) => {
- let promise_events = [];
- result.forEach((promise_result) => {
- if (promise_result.status === 'fulfilled' && promise_result.value.meta.status === "success")
- {
- // TODO - reload
- }
- })
- });
- }
- else if(dropdata.type=='lehreinheit')
- {
- // TODO Calculate end time
- let lehreinheit_id = dropdata.id;
- let start_time = day+' '+time;
- let end_time = start.date.plus({ minutes: 45 }).toFormat('yyyy-MM-dd hh:mm');
- alert("mode:"+dropdata.mode);
-
- Promise.allSettled([
- this.$api.call(ApiKalender.addKalenderEvent(lehreinheit_id, this.ort_kurzbz, start_time, end_time))
- ]).then((result) => {
- let promise_events = [];
- result.forEach((promise_result) => {
- if (promise_result.status === 'fulfilled' && promise_result.value.meta.status === "success") {
-
- // TODO - reload
- }
- })
- });
- }
- else
- {
- alert("Unbekannte Daten gedroppt");
- }
- },
- onRightClick: function(evt) {
- this.$refs.EventContextMenu.show(evt);
- }
- },
- watch: {
- ort_kurzbz: function (newValue, oldValue) {
- // Raumansicht laden wenn der Ort geaendert wird
- }
- },
computed: {
+ contextMenuActions() {
+ return {
+ lehreinheit: [
+ {
+ label: 'Raumauswahl',
+ icon: 'fa-solid fa-door-open',
+ action: this.openRaumauswahl
+ }
+ ]
+ };
+ },
currentDay() {
return luxon.DateTime.now().setZone(this.config.timezone).toISODate();
},
currentMode() {
return 'week';
},
+ visibleLecturerUids() {
+ if (!this.lecturers.length)
+ return null;
+ return this.lecturers.filter(lecture => lecture.showEvents).map(lecture => lecture.uid);
+ }
+ },
+ methods: {
+ async openRaumauswahl(orig) {
+ if (!orig?.lehreinheit_id)
+ return;
+ this.raumModal = orig;
+
+ await this.$api.call(ApiKalender.getRaumvorschlag(
+ orig.isostart,
+ orig.isoend,
+ orig.lehreinheit_id[0]
+ )).then(result => {
+
+ this.raumVorschlaege = result.data ?? [];
+ this.$refs.raumModal.show();
+
+ });
+
+ },
+ async selectRaum(ort_kurzbz) {
+ const orig = this.raumModal;
+ await this.$api.call(
+ ApiKalender.updateKalenderEvent(orig.kalender_id, {
+ ort_kurzbz,
+ start_time: orig.von,
+ end_time: orig.bis
+ })).then(() => this.$refs.raumModal.hide());
+ this.$refs.calendar.resetEventLoader();
+ },
+ setOrt: function(data)
+ {
+ this.ort_kurzbz = data.ort_kurzbz;
+ this.$refs.calendar.resetEventLoader();
+ },
+ onSelectVerband({link, name})
+ {
+ let stg = null;
+ let semester = null;
+ let studiensemester_kurzbz = this.selectedStudiensemester;
+ this.show_stg = name
+ if (typeof link === 'number')
+ stg = link;
+ else if (typeof link === 'string')
+ {
+ [stg, semester] = link.split('/');
+ }
+ this.stg = stg;
+ if (semester !== null)
+ this.semester = semester;
+ if (studiensemester_kurzbz)
+ this.studiensemester_kurzbz = studiensemester_kurzbz;
+
+ this.$refs.calendar.resetEventLoader();
+ },
+ setEmp: function(data)
+ {
+ const uid = data.uid;
+ const label = data.name;
+ if (!this.lecturers.some(l => l.uid === uid))
+ {
+ this.lecturers.push({
+ uid,
+ label,
+ showEvents: true,
+ overlays: { blocks: true, wishes: true },
+ });
+ }
+
+ this.$refs.calendar.resetEventLoader();
+ if (this.lastRange)
+ this.handleRange(this.lastRange);
+ },
+ handleChangeDate() {
+ console.log("handleChangeDate");
+ },
+ handleChangeMode() {
+ console.log("handleChangeMode")
+ },
+ searchfunction(params) {
+ return this.$api.call(ApiSearchbar.search(params));
+ },
+ getPromiseFunc(start, end) {
+ const hasRoom = !!this.ort_kurzbz;
+ const hasLektoren = this.lecturers.length > 0;
+ const hasStg = !!this.stg;
+
+ const filter = {};
+
+ if (hasRoom)
+ filter.ort = this.ort_kurzbz;
+ if (hasStg)
+ filter.stg = this.stg;
+ if (hasLektoren)
+ filter.uid = this.lecturers.map(l => l.uid);
+
+ return [this.$api.call(ApiKalender.getPlan(filter, start.toISODate(), end.toISODate()))];
+ },
+ toDateTime(value, timezone){
+ if (luxon.DateTime.isDateTime(value)) return value;
+
+ if (value?.date?.isValid)
+ return value.date;
+
+ if (typeof value === 'number')
+ return luxon.DateTime.fromMillis(value, { zone: timezone });
+
+ if (value instanceof Date)
+ return luxon.DateTime.fromJSDate(value, { zone: timezone });
+
+ if (typeof value === 'string')
+ return luxon.DateTime.fromISO(value, { zone: timezone });
+
+ return luxon.DateTime.invalid("invalid datetime");
+ },
+ getLastEndOfSameDay(startDT, ends) {
+ if (!ends?.length) return null;
+
+ const dayKey = startDT.toISODate();
+ let lastSameDay = null;
+
+ for (const end of ends) {
+ const dt = luxon.DateTime.isDateTime(end) ? end : luxon.DateTime.fromISO(String(end), { zone: startDT.zoneName });
+
+ if (!dt.isValid)
+ continue;
+
+ if (dt.toISODate() === dayKey)
+ lastSameDay = dt;
+ }
+
+ return lastSameDay;
+ },
+ clampEndToGrid(startDT, durationMin, ends) {
+ const calculatedEnd = startDT.plus({ minutes: durationMin });
+
+ const lastGridEndSameDay = this.getLastEndOfSameDay(startDT, ends);
+
+ if (!lastGridEndSameDay)
+ return calculatedEnd;
+
+ return calculatedEnd > lastGridEndSameDay ? lastGridEndSameDay : calculatedEnd;
+ },
+ dropHandler(payload) {
+ const { item, start, end } = payload;
+
+ if (!item?.length)
+ return alert("Keine Daten gedroppt");
+
+ const obj = item[0];
+ if (!obj?.type)
+ return alert("Unbekannter Drop-Typ");
+
+ const startDT = luxon.DateTime.fromISO(start);
+ const endDT = luxon.DateTime.fromISO(end);
+
+ if (!startDT.isValid || !endDT.isValid)
+ return alert("Ungültiges Datum");
+
+ const start_time = startDT.toFormat('yyyy-MM-dd HH:mm');
+ const end_time = endDT.toFormat('yyyy-MM-dd HH:mm');
+
+
+ if (obj.type === 'lehreinheit')
+ {
+ this.$api.call(
+ ApiKalender.addKalenderEvent(
+ obj.orig.lehreinheit_id,
+ this.ort_kurzbz ? this.ort_kurzbz : obj.orig.ort_kurzbz,
+ 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();
+ });
+ }
+ else
+ {
+ alert("Unbekannter Drop-Typ: " + obj.type);
+ }
+ },
+ handleRange(range) {
+ if (!range?.start || !range?.end)
+ return;
+
+ if (this.currentMode === 'week')
+ {
+ //Workaround because, updateRange is emitting 2 times
+ const startDay = range.start.startOf('day');
+ const endDay = range.end.startOf('day');
+
+ const days = Math.round(endDay.diff(startDay, 'days').days) + 1;
+ if (days > 8)
+ return;
+ }
+
+ this.lastRange = range;
+
+ const key = `${range.start.toISODate()}_${range.end.toISODate()}_${this.currentMode}`;
+
+ for (const lect of this.lecturers)
+ {
+ this.getOverlays(lect.uid, range, key);
+ }
+
+ this.rebuildExtraBackgrounds();
+ },
+
+ getOverlays(uid, range, rangeKey)
+ {
+ if (!this.overlayCache[uid])
+ this.overlayCache[uid] = {};
+
+ let entry = this.overlayCache[uid][rangeKey];
+
+ if (entry?.loaded || entry?.loading)
+ return;
+
+ entry = this.overlayCache[uid][rangeKey] = {
+ blocks: [],
+ wishes: [],
+ loading: true,
+ loaded: false
+ };
+
+ const promises = [];
+ const lect = this.lecturers.find(lecture => lecture.uid === uid);
+
+ if (lect.overlays.wishes)
+ {
+ promises.push(
+ this.$api.call(ApiKalender.getLektorZeitwuensche(uid, range.start.toISODate(), range.end.toISODate()))
+ .then(result => {
+ entry.wishes = (result.data || []).map(zeitwunsch => ({
+ class: `bg-lecturer-wish bg-uid-${uid} wish-w-${zeitwunsch.gewicht}`,
+ start: zeitwunsch.isostart,
+ end: zeitwunsch.isoend,
+ label: zeitwunsch.label
+ }));
+ })
+ );
+ }
+
+ if (lect.overlays.blocks)
+ {
+ promises.push(
+ this.$api.call(ApiKalender.getLektorZeitsperren(uid, range.start.toISODate(), range.end.toISODate()))
+ .then(result => {
+ entry.blocks = (result.data || []).map(zeitsperre => ({
+ class: `bg-lecturer-block bg-uid-${uid}`,
+ start: zeitsperre.isostart,
+ end: zeitsperre.isoend,
+ label: zeitsperre.label
+ }));
+ })
+ );
+ }
+
+ Promise.allSettled(promises).then(() => {
+ entry.loading = false;
+ entry.loaded = true;
+ this.rebuildExtraBackgrounds();
+ });
+ },
+
+ rebuildExtraBackgrounds() {
+ if (!this.lastRange)
+ return;
+
+ const key = `${this.lastRange.start.toISODate()}_` + `${this.lastRange.end.toISODate()}_` + `${this.currentMode}`;
+ let res = [];
+
+ for (let lect of this.lecturers)
+ {
+ const entry = this.overlayCache[lect.uid]?.[key];
+ if (!entry)
+ continue;
+
+ if (lect.overlays.blocks)
+ res.push(...(entry.blocks || []));
+
+ if (lect.overlays.wishes)
+ res.push(...(entry.wishes || []));
+ }
+
+ this.extraBackgrounds = res;
+ },
+
+ removeLecturer(uid)
+ {
+ this.lecturers = this.lecturers.filter(lecture => lecture.uid !== uid);
+ delete this.overlayCache[uid];
+ this.$refs.calendar.resetEventLoader();
+ },
+ clearOrt() {
+ this.ort_kurzbz = null;
+ this.$refs.calendar.resetEventLoader();
+ },
+ clearStg() {
+ this.stg = null;
+ this.show_stg = null;
+ this.$refs.calendar.resetEventLoader();
+ }
+ },
+ watch: {
+ lecturers: {
+ deep: true,
+ handler() {
+ this.rebuildExtraBackgrounds();
+ }
+ }
},
async created()
{
@@ -218,70 +538,184 @@ export default {
this.renderers[rendertype].calendarEvent = calendarEvent;
}
});
- },
- mounted() {
-
-
},
template: `
- Tempus
-
-
+
+
+
+
+
+
-
-
`
-};
+};
\ No newline at end of file