stundenplan day layout fixed for scroll behaviour; max-height for stundenplan events to avoid escalating the grid width;

This commit is contained in:
Johann Hoffmann
2024-12-11 16:59:35 +01:00
parent fcbb9e43a4
commit 82e7783259
6 changed files with 118 additions and 92 deletions
+2 -2
View File
@@ -49,7 +49,8 @@
.fhc-calendar-week-page > div {
/*transform: translate(-0.75em, -0.75em);*/
/*width: calc(100% + 1.5em);*/
height: calc(100% + 1.5em);
/*height: calc(100% + 1.5em);*/
max-height: 100%;
}
.fhc-calendar-week-page > div > div {
padding-left: 3em;
@@ -105,7 +106,6 @@
.fhc-calendar-day-page .events {
display: grid;
grid-template-columns: 3em 1fr;
margin-left: -3em;
/*min-height: 266.6666666667%;*/
}
.fhc-calendar-day-page .events .day {
+3 -1
View File
@@ -123,7 +123,9 @@ const app = Vue.createApp({
</span>
</template>
<template #weekPage="{event,day,isSelected}">
<div @click="showModal(event?.orig)" type="button" :class="{'selectedEvent':isSelected}" class="fhc-entry border border-secondary border d-flex flex-column align-items-center justify-content-evenly h-100">
<div @click="showModal(event?.orig)" type="button" :class="{'selectedEvent':isSelected}"
class="fhc-entry border border-secondary border d-flex flex-column align-items-center
justify-content-evenly h-100" style="max-height: 75px; overflow: auto;">
<span>{{event?.orig.topic}}</span>
<span v-for="lektor in event?.orig.lektor">{{lektor.kurzbz}}</span>
<span>{{event?.orig.ort_kurzbz}}</span>
+103 -85
View File
@@ -1,18 +1,24 @@
import CalendarDate from '../../../composables/CalendarDate.js';
import LvModal from "../../../components/Cis/Mylv/LvModal.js";
function ggt(m, n) { return n == 0 ? m : ggt(n, m % n); }
function kgv(m, n) { return (m * n) / ggt(m, n); }
function ggt(m, n) {
return n == 0 ? m : ggt(n, m % n);
}
function kgv(m, n) {
return (m * n) / ggt(m, n);
}
export default {
components:{
name: 'DayPage',
components: {
LvModal,
},
data() {
return {
hourPosition: null,
hourPositionTime: null,
lvMenu:null,
lvMenu: null,
}
},
inject: [
@@ -39,20 +45,20 @@ export default {
'page:forward',
'input'
],
watch:{
watch: {
//TODO: on first render non of the day-page components are active and the watcher on selectedEvent does not fetch the lvMenu
//TODO: workaround is to watch the active state and refetch in case the lvMenu is empty
active:{
handler(value){
if(value){
if(!this.lvMenu){
active: {
handler(value) {
if (value) {
if (!this.lvMenu) {
this.fetchLvMenu(this.selectedEvent);
}
}
},
immediate:true,
immediate: true,
},
eventsPerDayAndHour:{
eventsPerDayAndHour: {
handler(newEvents) {
// if no event is selected, select the first event of the day
if (this.selectedEvent == null && newEvents[this.day.toDateString()]?.events.length > 0) {
@@ -64,29 +70,27 @@ export default {
},
immediate: true
},
selectedEvent:{
selectedEvent: {
handler(event) {
// return early if the day-page component is not the active carousel item
if(!this.active)
{
if (!this.active) {
return;
}
this.lvMenu = null;
this.fetchLvMenu(event);
},
immediate:true,
immediate: true,
},
isSliding:{
handler(value){
if(value)
{
isSliding: {
handler(value) {
if (value) {
this.setSelectedEvent(null);
}
}
}
},
computed: {
pageHeaderStyle(){
pageHeaderStyle() {
return {
'z-index': 4,
'grid-template-columns': 'repeat(' + this.day.length + ', 1fr)',
@@ -95,13 +99,13 @@ export default {
top: 0,
}
},
dayGridStyle(){
dayGridStyle() {
return {
'grid-template-columns': '1 1fr',
'grid-template-rows': 'repeat(' + (this.hours.length * 60 / this.smallestTimeFrame) + ', 1fr)',
}
},
noLvStyle(){
noLvStyle() {
return {
top: (this.calendarScrollTop + 100) + 'px',
position: 'absolute',
@@ -111,19 +115,19 @@ export default {
'z-index': 1,
}
},
indicatorStyle(){
indicatorStyle() {
return {
'pointer-events': 'none',
'padding-left': '3.5rem',
'margin-top': '-1px',
'z-index': 2,
'border-color':'#00649C!important',
'border-color': '#00649C!important',
top: this.hourPosition + 'px',
left: 0,
right: 0,
}
}
},
noEventsCondition(){
noEventsCondition() {
return !this.isSliding && this.filteredEvents?.length === 0;
},
hours() {
@@ -138,17 +142,23 @@ export default {
if (this.isSliding) return {};
const res = {};
let key = this.day.toDateString();
let nextDay = new Date(this.day);
nextDay.setDate(nextDay.getDate() + 1);
nextDay.setMilliseconds(nextDay.getMilliseconds() - 1);
let d = { events: [], lanes: 1 };
let d = {events: [], lanes: 1};
if (this.events[key]) {
this.events[key].forEach(evt => {
let event = {
orig: evt, lane: 1, maxLane: 1, start: evt.start < this.day ? this.day : evt.start, end: evt.end > nextDay ? nextDay : evt.end, shared: [], setSharedMaxRecursive(doneItems) {
orig: evt,
lane: 1,
maxLane: 1,
start: evt.start < this.day ? this.day : evt.start,
end: evt.end > nextDay ? nextDay : evt.end,
shared: [],
setSharedMaxRecursive(doneItems) {
this.maxLane = Math.max(doneItems[0].maxLane, this.maxLane);
doneItems.push(this);
this.shared.filter(other => !doneItems.includes(other)).forEach(i => i.setSharedMaxRecursive(doneItems));
@@ -168,7 +178,7 @@ export default {
d.lanes = d.events.map(e => e.maxLane).reduce((res, i) => kgv(res, i), 1);
}
res[key] = d;
return res;
},
smallestTimeFrame() {
@@ -176,7 +186,7 @@ export default {
},
},
methods: {
fetchLvMenu(event){
fetchLvMenu(event) {
if (event && event.type == 'lehreinheit') {
this.$fhcApi.factory.stundenplan.getLehreinheitStudiensemester(event.lehreinheit_id[0]).then(
res => res.data
@@ -195,14 +205,14 @@ export default {
// this is the id attribute that is responsible to scroll the calender to the first event
return 'scroll' + hour + this.focusDate.d + this.week;
},
hourGridStyle(hour){
hourGridStyle(hour) {
return {
'pointer-events': 'none',
top: this.getAbsolutePositionForHour(hour),
left: 0,
right: 0,
'z-index': 0,
}
}
},
eventGridStyle(day, event) {
return {
@@ -237,26 +247,24 @@ export default {
// calculate minutes from float part of time
let minute = Math.round(60 * minutePercentage);
// convert minutes to 5 minute interval
if (minute % 5 != 0)
{
if (minute % 5 != 0) {
minute = Math.round(minute / 5) * 5;
}
// in case the rounding made the minutes 60, increase the hour and reset the minutes
if (minute == 60)
{
if (minute == 60) {
currentHour++;
minute = 0;
}
// ## after rounding the time to the nearest 5 Minute interval, we have to convert the time back to the relative position
// convert current time in minutes
currentMinutes = currentHour * 60 + minute;
// calculate the minutes percentage of the total minutes
timePercentage = ((currentMinutes - ( this.hours[0] * 60 ) ) / ( this.hours.length * 60 ) ) * 100;
timePercentage = ((currentMinutes - (this.hours[0] * 60)) / (this.hours.length * 60)) * 100;
// calculate the relative position of the time percentage
position = height * (timePercentage / 100);
this.hourPosition = position;
// add padding to minutes that consist of only one digit
minute.toString().length == 1 ? minute = "0" + minute : minute;
this.hourPositionTime = currentHour + ":" + minute;
@@ -275,58 +283,67 @@ export default {
dateToMinutesOfDay(day) {
return Math.floor(((day.getHours() - 7) * 60 + day.getMinutes()) / this.smallestTimeFrame) + 1;
},
},
template: /*html*/`
<div class="fhc-calendar-day-page ">
<div class="row m-0">
<div class="col-12 col-xl-6 p-0">
<div class="d-flex flex-column">
<div class="fhc-calendar-week-page-header d-grid border-2 border-bottom text-center" :style="pageHeaderStyle" >
<div type="button" class="flex-grow-1" :title="day.toLocaleString(undefined, {dateStyle:'short'})" @click.prevent="changeToMonth(day)">
<div class="fw-bold">{{day.toLocaleString(undefined, {weekday: size < 2 ? 'narrow' : (size < 3 ? 'short' : 'long')})}}</div>
<a href="#" class="small text-secondary text-decoration-none" >{{day.toLocaleString(undefined, [{day:'numeric',month:'numeric'},{day:'numeric',month:'numeric'},{day:'numeric',month:'numeric'},{dateStyle:'short'}][this.size])}}</a>
</div>
</div>
<div ref="eventcontainer" class="position-relative flex-grow-1" @mousemove="calcHourPosition" @mouseleave="hourPosition = null" >
<div :id="hourGridIdentifier(hour)" v-for="hour in hours" :key="hour" class="position-absolute box-shadow-border-top" :style="hourGridStyle(hour)"></div>
<Transition>
<div v-if="hourPosition" class="position-absolute border-top small" :style="indicatorStyle">
<span class="border border-top-0 px-2 bg-white">{{hourPositionTime}}</span>
mounted() {
const container = document.getElementById("calendarContainer")
if(container) container.style.overflow = 'hidden'
},
template: `
<div class="fhc-calendar-day-page h-100">
<div class="row m-0 h-100">
<div class="col-12 col-xl-6 p-0 h-100">
<div class="d-flex flex-column h-100">
<div ref="header" class="fhc-calendar-week-page-header d-grid border-2 border-bottom text-center" :style="pageHeaderStyle">
<div type="button" class="flex-grow-1" :title="day.toLocaleString(undefined, {dateStyle:'short'})" @click.prevent="changeToMonth(day)">
<div class="fw-bold">{{day.toLocaleString(undefined, {weekday: size < 2 ? 'narrow' : (size < 3 ? 'short' : 'long')})}}</div>
<a href="#" class="small text-secondary text-decoration-none" >{{day.toLocaleString(undefined, [{day:'numeric',month:'numeric'},{day:'numeric',month:'numeric'},{day:'numeric',month:'numeric'},{dateStyle:'short'}][this.size])}}</a>
</div>
</Transition>
<div>
<h1 v-if="noEventsCondition" class="m-0 text-secondary" ref="noEventsText" :style="noLvStyle">Keine Lehrveranstaltungen</h1>
<div :class="{'fhc-calendar-no-events-overlay':noEventsCondition, 'events':true}">
<div class="hours">
<div v-for="hour in hours" style="min-height:100px" :key="hour" class="text-muted text-end small" :ref="'hour' + hour">{{hour}}:00</div>
</div>
<div v-for="day in eventsPerDayAndHour" :key="day" class=" day border-start" :style="dayGridStyle">
<div v-for="event in day.events" :key="event" :style="eventGridStyle(day,event)" :class="{'selectedEvent':event.orig == selectedEvent}" class="mx-2 small rounded overflow-hidden " >
<!-- desktop version of the page template, parent receives slotProp mobile = false -->
<div class="d-none d-xl-block h-100 " @click.prevent="eventClick(event)">
<slot name="dayPage" :event="event" :day="day" :mobile="false">
<p>this is a slot placeholder</p>
</slot>
</div>
<div id="scroll g-0" style="height: 100%; overflow: scroll;">
<div ref="eventcontainer" class="position-relative flex-grow-1" @mousemove="calcHourPosition" @mouseleave="hourPosition = null" >
<div :id="hourGridIdentifier(hour)" v-for="hour in hours" :key="hour" class="position-absolute box-shadow-border-top" :style="hourGridStyle(hour)"></div>
<Transition>
<div v-if="hourPosition" class="position-absolute border-top small" :style="indicatorStyle">
<span class="border border-top-0 px-2 bg-white">{{hourPositionTime}}</span>
</div>
</Transition>
<div>
<h1 v-if="noEventsCondition" class="m-0 text-secondary" ref="noEventsText" :style="noLvStyle">Keine Lehrveranstaltungen</h1>
<div :class="{'fhc-calendar-no-events-overlay':noEventsCondition, 'events':true}">
<div class="hours">
<div v-for="hour in hours" style="min-height:100px" :key="hour" class="text-muted text-end small" :ref="'hour' + hour">{{hour}}:00</div>
</div>
<!-- mobile version of the page template, parent receives slotProp mobile = true -->
<div class="d-block d-xl-none h-100" @click.prevent="eventClick(event)">
<slot name="dayPage" :event="event" :day="day" :mobile="true">
<p>this is a slot placeholder</p>
</slot>
<div v-for="day in eventsPerDayAndHour" :key="day" class=" day border-start" :style="dayGridStyle">
<div v-for="event in day.events" :key="event" :style="eventGridStyle(day,event)" :class="{'selectedEvent':event.orig == selectedEvent}" class="mx-2 small rounded overflow-hidden " >
<!-- desktop version of the page template, parent receives slotProp mobile = false -->
<div class="d-none d-xl-block h-100 " @click.prevent="eventClick(event)">
<slot name="dayPage" :event="event" :day="day" :mobile="false">
<p>this is a slot placeholder</p>
</slot>
</div>
<!-- mobile version of the page template, parent receives slotProp mobile = true -->
<div class="d-block d-xl-none h-100" @click.prevent="eventClick(event)">
<slot name="dayPage" :event="event" :day="day" :mobile="true">
<p>this is a slot placeholder</p>
</slot>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="d-none d-xl-block col-xl-6 p-0">
<div style="z-index:0" class="p-5 sticky-top d-flex justify-content-center align-items-center flex-column">
<div style="max-height: calc(var(--fhc-calendar-pane-height) - 100px); overflow-y:auto;" class="w-100">
<div class="d-xl-block col-xl-6 p-0">
<div style="z-index:0" class="p-2 sticky-top d-flex justify-content-center align-items-center flex-column">
<div style="max-height: calc(var(--fhc-calendar-pane-height)); overflow-y:auto;" class="w-100">
<template v-if="selectedEvent && lvMenu">
<slot name="pageMobilContent" :lvMenu="lvMenu" >
<p>this is a slot placeholder</p>
@@ -345,7 +362,8 @@ export default {
</div>
</div>
</div>
</div>
</div>`
</div>
</div>
`
}
@@ -79,6 +79,10 @@ export default {
this.selectDay(day);
}
},
mounted() {
const container = document.getElementById("calendarContainer")
if(container) container.style.overflow = 'scroll'
},
template: /*html*/`
<div class="fhc-calendar-month-page" :class="{'show-weeks': showWeeks}">
<div v-if="showWeeks" class=" bg-light fw-bold border-top border-bottom text-center"></div>
+2 -2
View File
@@ -82,8 +82,8 @@ export default {
template: `
<div ref="carousel" class="calendar-pane carousel slide" @[\`slide.bs.carousel\`]="slide" @[\`slid.bs.carousel\`]="slid" :data-queue="queue">
<!--height calc function just for user testing purpose (has to be fixed)-->
<div @scroll="scrollCalendar" ref="calendarContainer" class="carousel-inner " style="height:var(--fhc-calendar-pane-height); overflow:scroll">
<div ref="carouselItems" v-for="i in [...Array(3).keys()]" :key="i" class="carousel-item">
<div id="calendarContainer" @scroll="scrollCalendar" ref="calendarContainer" class="carousel-inner " style="height:var(--fhc-calendar-pane-height);">
<div ref="carouselItems" v-for="i in [...Array(3).keys()]" :key="i" class="carousel-item" style="height:var(--fhc-calendar-pane-height);">
<slot :active="i == activeCarouselItemIndex" :index="i" :offset="offsets[i]" />
</div>
</div>
+4 -2
View File
@@ -201,10 +201,12 @@ export default {
mounted() {
setTimeout(() => this.$refs.eventcontainer.scrollTop = this.$refs.eventcontainer.scrollHeight / 3 + 1, 0);
const container = document.getElementById("calendarContainer")
if(container) container.style.overflow = 'scroll'
},
template: /*html*/`
<div class="fhc-calendar-week-page">
<div class="d-flex flex-column">
<div class="fhc-calendar-week-page-header d-grid border-2 border-bottom text-center" :style="pageHeaderStyle" >
<div type="button" v-for="day in days" :key="day" class="flex-grow-1" :title="day.toLocaleString(undefined, {dateStyle:'short'})" @click.prevent="changeToMonth(day)">
@@ -225,7 +227,7 @@ export default {
</div>
<div v-for="day in eventsPerDayAndHour" :key="day" class=" day border-start" :style="dayGridStyle(day)">
<div v-for="event in day.events" :key="event" @click.prevent="weekPageClick(event.orig, day)" :style="eventGridStyle(day,event)" class="mx-2 small rounded overflow-hidden " >
<slot name="weekPage" :event="event" :day="day" :isSelected="event.orig == selectedEvent" >
<slot name="weekPage" :event="event" :day="day" :isSelected="event.orig == selectedEvent">
<p>this is a placeholder which means that no template was passed to the Calendar Page slot</p>
</slot>
</div>