diff --git a/public/js/components/Calendar/Base/Grid/Line.js b/public/js/components/Calendar/Base/Grid/Line.js index 501faed6d..705de0eb2 100644 --- a/public/js/components/Calendar/Base/Grid/Line.js +++ b/public/js/components/Calendar/Base/Grid/Line.js @@ -60,6 +60,7 @@ export default { template: /* html */`
@@ -71,7 +72,7 @@ export default { > new Set() - }, - visibleLecturers: { - type: Array, - default: null - }, - extraBackgrounds: { - type: Array, - default: () => [] - }, - visibleStatus: { - type: Array, - default: () => ['all'] - }, - }, - emits: [ - "update:date", - "update:mode", - "update:range", - "drop", - "resize" - ], + name: "CalendarTempus", + components: { + FhcCalendar, + }, + inject: { + renderers: { from: "renderers" }, + appConfig: { + from: "appConfig", + default: { + visible_status: "all", + }, + }, + }, + directives: { + draggable, + }, + props: { + timezone: { + type: String, + required: true, + }, + date: { + type: [Date, String, Number, luxon.DateTime], + default: luxon.DateTime.local(), + }, + mode: { + type: String, + default: "Week", + }, + getPromiseFunc: { + type: Function, + required: true, + }, + parkedEvents: { + type: Object, + default: () => new Set(), + }, + visibleLecturers: { + type: Array, + default: null, + }, + extraBackgrounds: { + type: Array, + default: () => [], + }, + visibleStatus: { + type: Array, + default: () => ["all"], + }, + }, + emits: ["update:date", "update:mode", "update:range", "drop", "resize"], - data() { - return { - modes: { - week: Vue.markRaw(ModeWeek), - month: Vue.markRaw(ModeMonth), - tableList: Vue.markRaw(ModeTable), - }, - modeOptions: { - day: { - emptyMessage: Vue.computed(() => this.$p.t('lehre/noLvFound')), - emptyMessageDetails: Vue.computed(() => this.$p.t('lehre/noLvFound')) - }, - week: { - collapseEmptyDays: false - } - }, - currentMode: this.mode, - teachingunits: null, - hoursplan: null, - showRaster: true, - }; - }, - computed: { - backgrounds() { - let now = luxon.DateTime.now().setZone(this.timezone); + data() { + return { + modes: { + week: Vue.markRaw(ModeWeek), + month: Vue.markRaw(ModeMonth), + tableList: Vue.markRaw(ModeTable), + }, + modeOptions: { + day: { + emptyMessage: Vue.computed(() => this.$p.t("lehre/noLvFound")), + emptyMessageDetails: Vue.computed(() => this.$p.t("lehre/noLvFound")), + }, + week: { + collapseEmptyDays: false, + }, + }, + currentMode: this.mode, + teachingunits: null, + hoursplan: null, + showRaster: true, + }; + }, + computed: { + backgrounds() { + let now = luxon.DateTime.now().setZone(this.timezone); - let past = []; - if (this.mode == 'Month') - { - past = [{ - class: 'background-past', - end: now.startOf('day') - }]; - } - else - { - past = [{ - class: 'background-past', - end: now, - label: now.startOf('minute').toISOTime({ suppressSeconds: true, includeOffset: false }) - }]; - } + let past = []; + if (this.mode == "Month") { + past = [ + { + class: "background-past", + end: now.startOf("day"), + }, + ]; + } else { + past = [ + { + class: "background-past", + end: now, + label: now + .startOf("minute") + .toISOTime({ suppressSeconds: true, includeOffset: false }), + }, + ]; + } - return [ - ...past, - ...(this.extraBackgrounds || []) - ]; - }, - visibleEvents() - { - let list = this.events; + return [...past, ...(this.extraBackgrounds || [])]; + }, + visibleEvents() { + let list = this.events; - if (Array.isArray(this.visibleLecturers)) - { - const visibleLectures = new Set(this.visibleLecturers); + if (Array.isArray(this.visibleLecturers)) { + const visibleLectures = new Set(this.visibleLecturers); - list = list.filter(event => { - if (!event.lektor?.length) - return true; - return event.lektor.some(lektor => visibleLectures.has(lektor.mitarbeiter_uid)); - }); - } + list = list.filter((event) => { + if (!event.lektor?.length) return true; + return event.lektor.some((lektor) => + visibleLectures.has(lektor.mitarbeiter_uid), + ); + }); + } - if (!this.visibleStatus.length || this.visibleStatus.includes('all')) - return list; + if (!this.visibleStatus.length || this.visibleStatus.includes("all")) + return list; - return list.filter(event => this.visibleStatus.includes(event.status_kurzbz)); - }, - }, - methods: { - eventStyle(event) { - if (!event.farbe) - return undefined; - return '--event-bg:#' + event.farbe; - }, - updateRange(rangeInterval) { - this.rangeInterval = rangeInterval; - this.$emit('update:range', rangeInterval); - }, - ondrop(payload){ - this.$emit('drop', payload); - }, - onresize(payload){ - this.$emit('resize', payload); - }, - resetEventLoader() { - this.reset(); - }, + return list.filter((event) => + this.visibleStatus.includes(event.status_kurzbz), + ); + }, + }, + methods: { + eventStyle(event) { + if (!event.farbe) return undefined; + return "--event-bg:#" + event.farbe; + }, + updateRange(rangeInterval) { + this.rangeInterval = rangeInterval; + this.$emit("update:range", rangeInterval); + }, + ondrop(payload) { + this.$emit("drop", payload); + }, + onresize(payload) { + this.$emit("resize", payload); + }, + resetEventLoader() { + this.reset(); + }, + clearOutCalendarEventEmphasis() { + this.$refs.calendar.$el + .querySelectorAll( + ".fhc-calendar-base-grid .fhc-calendar-base-grid-line-event", + ) + .forEach((el) => { + const spinner = el.querySelector(".spinner-overlay"); + if (spinner) { + spinner.remove(); + } - }, - setup(props, context) { - const rangeInterval = Vue.ref(null); + el.classList.remove( + "updating-event", + "updated-event", + "updated-event-long", + "deemphasized-event", + "deemphasized-event-long", + ); + }); + }, + }, + setup(props, context) { + const rangeInterval = Vue.ref(null); - const { events, lv, reset } = useEventLoader(rangeInterval, props.getPromiseFunc); + const { events, lv, reset } = useEventLoader( + rangeInterval, + props.getPromiseFunc, + ); - Vue.watch(lv, newValue => { - context.emit('update:lv', newValue); - }); + Vue.watch(lv, (newValue) => { + context.emit("update:lv", newValue); + }); - return { - rangeInterval, - events, - lv, - reset - }; - }, + return { + rangeInterval, + events, + lv, + reset, + }; + }, - created() { - this.$api - .call(ApiKalender.getStunden()) - .then(res => { - return this.teachingunits = res.data.map(el => ({ - id: el.stunde, - start: el.beginn, - end: el.ende - })); - }); + created() { + this.$api.call(ApiKalender.getStunden()).then((res) => { + return (this.teachingunits = res.data.map((el) => ({ + id: el.stunde, + start: el.beginn, + end: el.ende, + }))); + }); - this.$api - .call(ApiKalender.getCalendarHours()) - .then(res => { - this.hoursplan = { - start: res.data.start, - end: res.data.end - }; - }); - }, - template: /* html */` + this.$api.call(ApiKalender.getCalendarHours()).then((res) => { + this.hoursplan = { + start: res.data.start, + end: res.data.end, + }; + }); + }, + template: /* html */ `
- ` -} + `, +}; diff --git a/public/js/components/Tempus/Tempus.js b/public/js/components/Tempus/Tempus.js index 7d9978be5..ec68b4cca 100644 --- a/public/js/components/Tempus/Tempus.js +++ b/public/js/components/Tempus/Tempus.js @@ -491,7 +491,15 @@ export default { }; }, - _updateKalenderEvent(obj, startDT, endDT, start_time, end_time, onSuccess) { + _updateKalenderEvent( + obj, + startDT, + endDT, + start_time, + end_time, + onSuccess, + onError, + ) { const origStart = luxon.DateTime.fromISO(obj.orig.isostart); const origEnd = luxon.DateTime.fromISO(obj.orig.isoend); @@ -517,6 +525,11 @@ export default { this.$refs.calendar.$refs.calendar.$refs.mode.$refs.view.$refs.grid.disableAutoScroll(); this.currentlyUpdatedEvent = obj.orig; } + }) + .catch((error) => { + if (onError) { + onError(error); + } }); }, @@ -526,14 +539,14 @@ export default { return; const { item, start, end } = payload; const obj = item[0]; - if (!obj?.orig?.kalender_id) - return alert("Kein gültiges Kalender-Event zum Resizen"); + if (!obj?.orig?.kalender_id) return; const dates = this._parseDates(start, end); if (!dates) return; if ( - luxon.DateTime.fromISO(obj.orig.isostart).toMillis() === dates.startDT.toMillis() && + luxon.DateTime.fromISO(obj.orig.isostart).toMillis() === + dates.startDT.toMillis() && luxon.DateTime.fromISO(obj.orig.isoend).toMillis() === dates.endDT.toMillis() ) @@ -554,6 +567,10 @@ export default { () => { this.$refs.calendar.resetEventLoader(); }, + () => { + this.$refs.calendar.clearOutCalendarEventEmphasis(); + this.$refs.calendar.resetEventLoader(); + }, ); }, @@ -571,7 +588,8 @@ export default { if (!dates) return; if ( - luxon.DateTime.fromISO(obj.orig.isostart).toMillis() === dates.startDT.toMillis() && + luxon.DateTime.fromISO(obj.orig.isostart).toMillis() === + dates.startDT.toMillis() && luxon.DateTime.fromISO(obj.orig.isoend).toMillis() === dates.endDT.toMillis() ) @@ -617,6 +635,16 @@ export default { this.$refs.calendar.resetEventLoader(); this.bcc.postMessage("dropped"); }, + () => { + this.$refs.calendar.clearOutCalendarEventEmphasis(); + this.updateKalenderEventElementDisplay( + obj.orig.kalender_id, + luxon.DateTime.fromISO(obj.orig.isostart), + luxon.DateTime.fromISO(obj.orig.isoend) + ); + this.$refs.calendar.clearOutCalendarEventEmphasis(); + this.$refs.calendar.resetEventLoader(); + }, ); } else { alert("Unbekannter Drop-Typ: " + obj.type); @@ -886,29 +914,7 @@ export default { scrollToAndEmphasizeUpdatedEvent() { if (!this.currentlyUpdatedEvent) return; - document - .querySelectorAll(".fhc-calendar-base-grid .part-body") - .forEach((el) => { - el.classList.remove("deemphasized-event", "deemphasized-event-long"); - }); - document - .querySelectorAll( - ".fhc-calendar-base-grid .fhc-calendar-base-grid-line-event", - ) - .forEach((el) => { - const spinner = el.querySelector(".spinner-overlay"); - if (spinner) { - spinner.remove(); - } - - el.classList.remove( - "updating-event", - "updated-event", - "updated-event-long", - "deemphasized-event", - "deemphasized-event-long", - ); - }); + this.$refs.calendar.clearOutCalendarEventEmphasis(); setTimeout(() => { const eventEl = document.querySelector( @@ -958,34 +964,32 @@ export default { el.classList.add(deemphasizedUpdateClassName); } }); - document - .querySelectorAll(".fhc-calendar-base-grid .part-body") - .forEach((el) => { - if (el !== eventEl) { - el.classList.add(deemphasizedUpdateClassName); - } - }); }, timeout); this.currentlyUpdatedEvent = null; }, 100); }, updateKalenderEventElementDisplay(calendarId, startDT, endDT) { - if (!calendarId) return alert("Kein gültiges Kalender-Event zum Resizen"); + if (!calendarId) return; - let newPotentialStart = - startDT.diff(luxon.DateTime.fromISO("2026-06-22T00:00")).toMillis() ?? - 1; - let newPotentialEnd = endDT - .diff(luxon.DateTime.fromISO("2026-06-22T00:00")) - .toMillis(); + let startOfDay = startDT.startOf("day"); + let newPotentialStart = startDT.diff(startOfDay).toMillis() ?? 1; + let newPotentialEnd = endDT.diff(startOfDay).toMillis(); let element = document.querySelector(`[data-id="event-${calendarId}"]`); - if (!element) return alert("Kein gültiges Kalender-Event zum Resizen"); + if (!element) return; + + const calendarView = element.closest(".fhc-calendar-mode-week-view"); + const targetGridLine = calendarView?.querySelector( + `.fhc-calendar-base-grid-line[data-day="${startOfDay.toISODate()}"]`, + ); + if (!targetGridLine) + return alert("Kein gültiger Kalendertag zum Verschieben gefunden"); setTimeout(() => { - element.style.gridRowEnd = "t_" + newPotentialEnd; + targetGridLine.appendChild(element); element.style.gridRowStart = "t_" + newPotentialStart; + element.style.gridRowEnd = "t_" + newPotentialEnd; }, 100); const outerDiv = document.createElement("div"); diff --git a/tests/cypress/.env.example b/tests/cypress/.env.example index afe039acb..bf3302947 100644 --- a/tests/cypress/.env.example +++ b/tests/cypress/.env.example @@ -1,4 +1,4 @@ BASE_URL=https://demo.dev.technikum-wien.at/demoanon -USER_NAME=demoadmin -USER_PASSWORD=Demo -LOGIN_AS_USER=demoadmin +USER_NAME=testUser +USER_PASSWORD=testPassword +LOGIN_AS_USER=testUser diff --git a/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.course-picker.cy.js b/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.course-picker.cy.js index 2d6bbb597..7d967d1a0 100644 --- a/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.course-picker.cy.js +++ b/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.course-picker.cy.js @@ -81,7 +81,9 @@ context("Tempus course picker tests", () => { waitForOk("@fetchPlanData"); tempusPage.waitForCalendarToFinishLoading(); - + + cy.wait(1000); + cy.get("@initialEventCount").then((initialEventCount) => { tempusPage .getCalendarEvents() diff --git a/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.events.cy.js b/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.events.cy.js index c616b3aa5..cae3fe3df 100644 --- a/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.events.cy.js +++ b/tests/cypress/cypress/e2e/specs/ui/tempus/tempus.events.cy.js @@ -55,7 +55,7 @@ context("Tempus event mutation tests", () => { .getCalendarEventById(eventId) .should("be.visible") .rightclick(); - tempusPage.getEventContextMenuOption("Raumauswahl").click(); + tempusPage.getEventContextMenuOption("Raumauswahl").click({ force: true }); waitForOk("@fetchRoomSuggestions"); tempusPage @@ -86,6 +86,8 @@ context("Tempus event mutation tests", () => { tempusPage.waitForCalendarToFinishLoading(); cy.get("@newRoom").then((newRoom) => { + console.log(eventId) + console.log(newRoom) cy.get("@updatedEventId").then((updatedEventId) => { tempusPage.expectCalendarEventRoom(updatedEventId, newRoom); @@ -131,7 +133,7 @@ context("Tempus event mutation tests", () => { .scrollIntoView() .should("be.visible") .rightclick(); - tempusPage.getEventContextMenuOption("Raumauswahl").click(); + tempusPage.getEventContextMenuOption("Raumauswahl").click({ force: true }); waitForOk("@fetchRoomSuggestions"); tempusPage diff --git a/tests/cypress/cypress/support/pages/tempus.po.js b/tests/cypress/cypress/support/pages/tempus.po.js index baf3519a0..37050f39a 100644 --- a/tests/cypress/cypress/support/pages/tempus.po.js +++ b/tests/cypress/cypress/support/pages/tempus.po.js @@ -287,7 +287,7 @@ class TempusPage { .contains(".bootstrap-modal.show .modal-title", "Raumauswahl") .closest(".bootstrap-modal.show"); getRaumauswahlRoomOptions = () => - this.getRaumauswahlModal().find(".list-group-item"); + this.getRaumauswahlModal().find(".list-group-item span"); getResourcesModal = () => cy .get("[data-cy='resourcesAssignmentModal']") @@ -378,6 +378,7 @@ class TempusPage { setCurrentSemester = () => { this.getSemesterSetterButton().click(); + waitForOk("@getCurrentSemester"); }; dropEventOnCalendarPart = (eventId, partIndex, options = {}) => {