Random bug fixes, minor mods and styling changes

This commit is contained in:
Ivymaster
2026-09-17 09:25:19 +02:00
parent 8bac1c1ebb
commit d7a5c73233
8 changed files with 252 additions and 157 deletions
+2 -1
View File
@@ -108,7 +108,8 @@
display: none;
}
.weekPageContainer .calendar-event-collisions
.weekPageContainer .calendar-event-collisions,
.rangePageContainer .calendar-event-collisions
{
display: grid;
grid-template-columns: auto 1fr;
+27 -3
View File
@@ -77,7 +77,31 @@ header,
.fhc-calendar-mode-range .main-header:not(.main-header-empty),
.fhc-calendar-mode-range .fhc-calendar-base-grid-line {
border-top: solid !important;
border-top-color: black !important;
border-top-width: 1px !important;
border-top: solid 1px black !important;
}
.fhc-calendar-mode-range .main-header:not(.main-header-empty) {
padding-top: .25rem !important;
}
.fhc-calendar-mode-range .fhc-calendar-base-grid-line {
grid-auto-rows: max-content !important;
overflow: hidden !important;
}
.fhc-calendar-mode-range .fhc-calendar-base-grid-line-event {
break-inside: avoid !important;
margin: 1px !important;
page-break-inside: avoid !important;
z-index: auto !important;
}
.fhc-calendar-mode-range .fhc-calendar-base-grid-line-event > .rangePageContainer {
background-color: var(--event-bg, var(--event-default-bg)) !important;
break-inside: avoid !important;
page-break-inside: avoid !important;
}
.fhc-calendar-mode-range .fhc-calendar-base-grid-line-event > .rangePageContainer * {
background-color: transparent !important;
}
+1 -1
View File
@@ -22,7 +22,7 @@ export default {
params: { lehreinheit_id, start_date, end_date }
};
},
getPlanLecturer(start_date, end_date)
getPlanLecturer(start_date, end_date, collisionCheck = true, maxDailyEventLimit = null)
{
return {
method: 'get',
+15 -6
View File
@@ -93,6 +93,10 @@ export default {
type: Number,
default: 1
},
waitForAllPromises: {
type: Boolean,
default: true,
},
parkedEvents: {
type: Object,
default: () => new Set(),
@@ -125,6 +129,10 @@ export default {
type: Boolean,
default: true,
},
canToggleCollisionCheck: {
type: Boolean,
default: true,
},
rangeViewPreviewLink: {
type: String,
default: null,
@@ -224,10 +232,6 @@ export default {
visibleEvents() {
let list = this.events;
// Start with the first week and then keep events overlapping a grid line
// that is visible inside the slider viewport, including a one-day buffer
// before and after the visible dates.
console.log('visibleDates', this.visibleDates);
if (
this.isRangeVirtualScrollEnabled
&& this.currentMode === 'range'
@@ -374,7 +378,9 @@ export default {
const { events, lv, reset } = useEventLoader(
rangeInterval,
props.getPromiseFunc,
() => props.cacheMultiplier
() => props.cacheMultiplier,
undefined,
props.waitForAllPromises,
);
Vue.watch(lv, (newValue) => {
@@ -482,7 +488,10 @@ export default {
></i>
<span>Reservierung</span>
</div>
<div class="d-flex align-items-center gap-2">
<div
v-if="canToggleCollisionCheck"
class="d-flex align-items-center gap-2"
>
<i :class="appConfig.ignore_kollision ? 'fa-solid fa-triangle-exclamation text-danger' : 'fa-solid fa-circle-check text-success'"></i>
<span class="form-check-label">
{{ appConfig.ignore_kollision ? 'Kollisionscheck aus' : 'Kollisionscheck an' }}
@@ -154,7 +154,11 @@ export default {
class="position-relative"
@wheel.stop
>
<div class="position-absolute bottom-0 start-0 m-1">
<div
:class="mode === 'range'
? 'position-absolute top-0 end-0 m-1'
: 'position-absolute bottom-0 start-0 m-1'"
>
{{event.verplante_stunden}}
</div>
<div
@@ -166,7 +170,7 @@ export default {
</div>
<div class="event-text" v-tooltip="tooltipString">
<div
:class="{ 'd-flex align-items-center gap-1': mode === 'range' }"
:class="{ 'd-flex align-items-center gap-2': mode === 'range' }"
:style="mode === 'range' ? null : { display: 'contents' }"
>
<span class="event-topic">{{ topicString }}</span>
+73 -74
View File
@@ -155,13 +155,16 @@ export default {
},
rangeViewPreviewLink() {
const previewUrl = `${this.tempusRoot.replace(/\/$/, '')}/preview`;
if (!this.lastRange?.start || !this.lastRange?.end)
return previewUrl;
const params = new URLSearchParams({
startDate: this.lastRange.start.toISODate(),
endDate: this.lastRange.end.toISODate(),
previewRole: this.previewRole,
});
if (!this.lastRange?.start || !this.lastRange?.end)
return `${previewUrl}?${params}`;
params.set('startDate', this.lastRange.start.toISODate());
params.set('endDate', this.lastRange.end.toISODate());
for (const [key, value] of Object.entries(this.getPlanFilter()))
params.set(key, JSON.stringify(value));
return `${previewUrl}?${params}`;
},
@@ -450,18 +453,12 @@ export default {
searchfunction(params) {
return this.$api.call(ApiSearchbar.search(params));
},
getPromiseFunc(start, end) {
const isMonth = this.currentMode?.toLowerCase() === "month";
const collisionCheck = !isMonth;
const maxDailyEventLimit = isMonth ? this.maxDailyEventLimitForMonthView : null;
const hasRooms = this.rooms.length > 0;
const hasLektoren = this.lecturers.length > 0;
const hasStg = this.studiengaenge.length > 0;
getPlanFilter() {
const filter = {};
if (hasRooms) filter.ort = this.rooms.map((room) => room.ort_kurzbz);
if (hasStg) {
if (this.rooms.length)
filter.ort = this.rooms.map((room) => room.ort_kurzbz);
if (this.studiengaenge.length) {
filter.stg = this.studiengaenge.map(
({ stg_kz, semester, orgform_kurzbz }) => ({
stg_kz,
@@ -470,11 +467,19 @@ export default {
}),
);
}
if (hasLektoren)
if (this.lecturers.length)
filter.uid = this.lecturers.map((lecture) => lecture.uid);
return filter;
},
getPromiseFunc(start, end) {
const isMonth = this.currentMode?.toLowerCase() === "month";
const collisionCheck = !isMonth;
const maxDailyEventLimit = isMonth ? this.maxDailyEventLimitForMonthView : null;
const filter = this.getPlanFilter();
let response = null;
if (this.previewRole === 'lektor')
if (this.previewRole === 'lektor') {
response = [
this.$api.call(
ApiKalender.getPlanLecturer(
@@ -485,8 +490,7 @@ export default {
),
),
];
if (this.previewRole === 'student')
} else if (this.previewRole === 'student') {
response = [
this.$api.call(
ApiKalender.getPlanStudent(
@@ -497,18 +501,19 @@ export default {
),
),
];
response = [
this.$api.call(
ApiKalender.getPlan(
filter,
start.toISODate(),
end.toISODate(),
collisionCheck,
maxDailyEventLimit,
} else {
response = [
this.$api.call(
ApiKalender.getPlan(
filter,
start.toISODate(),
end.toISODate(),
collisionCheck,
maxDailyEventLimit,
),
),
),
];
];
}
if (response) {
response[0].then((result) => {
@@ -957,22 +962,22 @@ export default {
scrollToAndEmphasizeUpdatedEvent() {
if (!this.currentlyUpdatedEvent) return;
document
.querySelectorAll(
'.fhc-calendar-base-grid .fhc-calendar-base-grid-line-event',
)
.forEach((el) => {
const spinner = el.querySelector('.spinner-overlay');
if (spinner) {
spinner.remove();
}
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',
);
});
el.classList.remove(
'updating-event',
'updated-event',
'updated-event-long',
);
});
setTimeout(() => {
const eventEl = document.querySelector(
@@ -1000,18 +1005,18 @@ export default {
});
}
let timeout = 0;
let emphasizeUpdateClassName = isInsideScrolledView
? 'updated-event'
: 'updated-event-long';
let timeout = 0;
let emphasizeUpdateClassName = isInsideScrolledView
? 'updated-event'
: 'updated-event-long';
if (!isInsideScrolledView) timeout = 300;
if (!isInsideScrolledView) timeout = 300;
setTimeout(() => {
eventEl.classList.add(emphasizeUpdateClassName);
}, timeout);
setTimeout(() => {
eventEl.classList.add(emphasizeUpdateClassName);
}, timeout);
this.currentlyUpdatedEvent = null;
this.currentlyUpdatedEvent = null;
}, 100);
},
updateKalenderEventElementDisplay(calendarGruppenId, startDT, endDT) {
@@ -1052,8 +1057,19 @@ export default {
targetGridLine.insertBefore(element, null);
element.classList.add('tempus-temporary-calendar-event');
}
element.style.gridRowEnd = 't_' + newPotentialEnd;
element.style.gridRowStart = 't_' + newPotentialStart;
if (this.currentMode === 'range') {
element.style.gridColumnStart = 't_' + newPotentialStart;
element.style.gridColumnEnd = 't_' + newPotentialEnd;
} else {
element.style.gridRowStart = 't_' + newPotentialStart;
element.style.gridRowEnd = 't_' + newPotentialEnd;
}
element.scrollIntoView({
behavior: 'smooth',
inline: 'center',
block: 'nearest',
});
}, 100);
const outerDiv = document.createElement('div');
@@ -1066,24 +1082,6 @@ export default {
element.appendChild(outerDiv);
const eventRect = element.getBoundingClientRect();
const offset = 300;
const isInsideScrolledView =
element.offsetLeft < calendar.scrollLeft + calendar.clientWidth &&
element.offsetLeft + element.offsetWidth > calendar.scrollLeft &&
element.offsetTop < calendar.scrollTop + calendar.clientHeight &&
element.offsetTop + element.offsetHeight > calendar.scrollTop;
const rect = element.getBoundingClientRect();
if (!isInsideScrolledView) {
element.scrollIntoView({
behavior: 'smooth',
inline: 'center',
block: 'nearest',
});
}
},
clearTemporaryEvents() {
const calendar = this.$refs.calendar?.$el;
@@ -1249,6 +1247,7 @@ export default {
@events-reloaded="clearTemporaryEvents"
class="responsive-calendar"
:cache-multiplier="currentMode === 'week' ? 1 : 0"
:wait-for-all-promises="false"
/>
</template>
</horizontal-split>
@@ -39,6 +39,22 @@ function getRangeFromQuery(query, timezone) {
return luxon.Interval.fromDateTimes(start, end);
}
function getPlanFilterFromQuery(query) {
const filter = {};
for (const key of ['ort', 'stg', 'uid']) {
const value = Array.isArray(query?.[key]) ? query[key][0] : query?.[key];
if (typeof value !== 'string') continue;
try {
filter[key] = JSON.parse(value);
} catch {
// Ignore malformed filter parameters.
}
}
return filter;
}
export default {
name: 'TempusCalendarPreview',
components: {
@@ -85,6 +101,7 @@ export default {
visibleStatus: ['all'],
selectedStudiensemester: this.defaultSemester,
urlRange: null,
planFilter: {},
calendarDatesByMode: {
week: today,
month: today,
@@ -147,28 +164,11 @@ export default {
this.currentMode = newMode;
},
getPromiseFunc(start, end) {
const collisionCheck = false;
const hasRooms = this.rooms.length > 0;
const hasLektoren = this.lecturers.length > 0;
const hasStg = this.studiengaenge.length > 0;
const filter = {};
if (hasRooms) filter.ort = this.rooms.map((room) => room.ort_kurzbz);
if (hasStg) {
filter.stg = this.studiengaenge.map(
({ stg_kz, semester, orgform_kurzbz }) => ({
stg_kz,
semester,
orgform_kurzbz,
}),
);
}
if (hasLektoren)
filter.uid = this.lecturers.map((lecture) => lecture.uid);
const collisionCheck = true;
const filter = this.planFilter;
let response = null;
if (this.previewRole === 'lektor')
if (this.previewRole === 'lektor') {
response = [
this.$api.call(
ApiKalender.getPlanLecturer(
@@ -179,8 +179,7 @@ export default {
),
),
];
if (this.previewRole === 'student')
} else if (this.previewRole === 'student') {
response = [
this.$api.call(
ApiKalender.getPlanStudent(
@@ -191,8 +190,8 @@ export default {
),
),
];
response = [
} else {
response = [
this.$api.call(
ApiKalender.getPlan(
filter,
@@ -203,6 +202,7 @@ export default {
),
),
];
}
if (response) {
response[0].then((result) => {
@@ -397,25 +397,27 @@ export default {
this.hoveredEvent = null;
}
},
scrollToAndEmphasizeUpdatedEvent() {
scrollToAndEmphasizeUpdatedEvent(shouldEmphasize = true) {
if (!this.currentlyUpdatedEvent) return;
document
.querySelectorAll(
'.fhc-calendar-base-grid .fhc-calendar-base-grid-line-event',
)
.forEach((el) => {
const spinner = el.querySelector('.spinner-overlay');
if (spinner) {
spinner.remove();
}
if (shouldEmphasize) {
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',
);
});
el.classList.remove(
'updating-event',
'updated-event',
'updated-event-long',
);
});
}
setTimeout(() => {
const eventEl = document.querySelector(
@@ -443,18 +445,20 @@ export default {
});
}
let timeout = 0;
let emphasizeUpdateClassName = isInsideScrolledView
? 'updated-event'
: 'updated-event-long';
if (shouldEmphasize) {
let timeout = 0;
let emphasizeUpdateClassName = isInsideScrolledView
? 'updated-event'
: 'updated-event-long';
if (!isInsideScrolledView) timeout = 300;
if (!isInsideScrolledView) timeout = 300;
setTimeout(() => {
eventEl.classList.add(emphasizeUpdateClassName);
}, timeout);
setTimeout(() => {
eventEl.classList.add(emphasizeUpdateClassName);
}, timeout);
this.currentlyUpdatedEvent = null;
this.currentlyUpdatedEvent = null;
}
}, 100);
},
updateKalenderEventElementDisplay(calendarGruppenId, startDT, endDT) {
@@ -495,8 +499,19 @@ export default {
targetGridLine.insertBefore(element, null);
element.classList.add('tempus-temporary-calendar-event');
}
element.style.gridRowEnd = 't_' + newPotentialEnd;
element.style.gridRowStart = 't_' + newPotentialStart;
if (this.currentMode === 'range') {
element.style.gridColumnStart = 't_' + newPotentialStart;
element.style.gridColumnEnd = 't_' + newPotentialEnd;
} else {
element.style.gridRowStart = 't_' + newPotentialStart;
element.style.gridRowEnd = 't_' + newPotentialEnd;
}
element.scrollIntoView({
behavior: 'smooth',
inline: 'center',
block: 'nearest',
});
}, 100);
const outerDiv = document.createElement('div');
@@ -509,24 +524,6 @@ export default {
element.appendChild(outerDiv);
const eventRect = element.getBoundingClientRect();
const offset = 300;
const isInsideScrolledView =
element.offsetLeft < calendar.scrollLeft + calendar.clientWidth &&
element.offsetLeft + element.offsetWidth > calendar.scrollLeft &&
element.offsetTop < calendar.scrollTop + calendar.clientHeight &&
element.offsetTop + element.offsetHeight > calendar.scrollTop;
const rect = element.getBoundingClientRect();
if (!isInsideScrolledView) {
element.scrollIntoView({
behavior: 'smooth',
inline: 'center',
block: 'nearest',
});
}
},
clearTemporaryEvents() {
const calendar = this.$refs.calendar?.$el;
@@ -544,6 +541,10 @@ export default {
'$route.query': {
immediate: true,
handler(query) {
this.planFilter = getPlanFilterFromQuery(query);
this.previewRole = Array.isArray(query?.previewRole)
? query.previewRole[0]
: query?.previewRole ?? 'planer';
this.urlRange = getRangeFromQuery(query, this.config.timezone);
const rangeStart = this.urlRange?.start.toISODate()
?? luxon.DateTime.now().setZone(this.config.timezone).toISODate();
@@ -580,7 +581,6 @@ export default {
.call(ApiRenderers.loadTempusRenderers())
.then((res) => res.data)
.then((data) => {
console.log(Object.keys(data))
for (let rendertype of Object.keys(data)) {
let modalTitle = null;
let modalContent = null;
@@ -648,6 +648,7 @@ export default {
:is-event-dragging-enabled="false"
:is-event-resizing-enabled="false"
:isRangeVirtualScrollEnabled="false"
:can-toggle-collision-check="false"
@update:date="handleChangeDate"
@update:mode="handleChangeMode"
@event-hover="onEventHover"
@@ -5,6 +5,7 @@ export function useEventLoader(
getPromiseFunc,
cacheMultiplier = 1,
requestIntervalDays = PLAN_REQUEST_INTERVAL_DAYS,
waitForAllPromises = true,
) {
let hasFirstLoadOccurred = false;
@@ -85,6 +86,62 @@ export function useEventLoader(
addVisualForEventsLoading();
if (waitForAllPromises) {
Promise.allSettled(promises).then((results) => {
let newlyLoadedEvents = [];
let hasSuccessfullyLoadedEvents = false;
results.forEach((res) => {
if (
!(res.status === "fulfilled" && res.value.meta.status === "success")
)
return;
hasSuccessfullyLoadedEvents = true;
if (res.value.meta.lv) lv.value = res.value.meta.lv;
newlyLoadedEvents = newlyLoadedEvents.concat(res.value.data);
});
let tempAllEvents = Array.from(
new Map(
[...allEvents.value, ...newlyLoadedEvents].map((event) => [
event.eindeutige_kalender_gruppen_id,
event,
]),
).values(),
);
if (!isCacheEnabled && hasSuccessfullyLoadedEvents) {
const newlyLoadedEventIds = new Set(
newlyLoadedEvents.map(
(event) => event.eindeutige_kalender_gruppen_id,
),
);
tempAllEvents = tempAllEvents.filter((event) => {
const eventStart = getTimestampFromISODate(event.isostart);
const eventEnd = getTimestampFromISODate(event.isoend);
const overlapsDisplayedDateRange =
eventStart < currentlyDisplayedDateRange.end.ts &&
eventEnd > currentlyDisplayedDateRange.start.ts;
return (
!overlapsDisplayedDateRange ||
newlyLoadedEventIds.has(event.eindeutige_kalender_gruppen_id)
);
});
}
allEvents.value = removeVisualForEventsLoading(
ensureEventsAreInValidCacheRange(tempAllEvents),
);
hasFirstLoadOccurred = true;
});
return;
}
let newlyLoadedEvents = [];
let pendingPromiseCount = promises.length;
let hasSuccessfullyLoadedEvents = false;