feature(Stundenplan): adds Tagesansicht for the Stundenplan

This commit is contained in:
SimonGschnell
2024-11-08 15:18:10 +01:00
parent 68042c1c77
commit f304e79ee0
7 changed files with 244 additions and 13 deletions
@@ -423,7 +423,7 @@ class LvMenu extends FHCAPI_Controller
{
if($row->gruppe_kurzbz != '')
{
$bngrp_uids = $this->Benutzergrupp_model->getUids($row->gruppe_kurzbz, $angezeigtes_stsem);
$bngrp_uids = $this->Benutzergruppe_model->getUids($row->gruppe_kurzbz, $angezeigtes_stsem);
if(count($bngrp_uids) > 0)
{
if(!$row->mailgrp)
+23 -9
View File
@@ -11,6 +11,8 @@ const app = Vue.createApp({
events: null,
calendarDate: new CalendarDate(new Date()),
currentlySelectedEvent: null,
currentDay: new Date(),
minimized: false,
}
},
components: {
@@ -32,17 +34,23 @@ const app = Vue.createApp({
},
},
methods:{
selectDay: function(day){
this.currentDay = day;
},
showModal: function(event){
this.currentlySelectedEvent = event;
Vue.nextTick(() => {
this.$refs.lvmodal.show();
});
},
updateRange: function (data) {
let tmp_date = new CalendarDate(data.start);
updateRange: function ({start,end}) {
let checkDate = (date) => {
return date.m != this.calendarDate.m || date.y != this.calendarDate.y;
}
// only load month data if the month or year has changed
if(tmp_date.m != this.calendarDate.m || tmp_date.y != this.calendarDate.y){
if (checkDate(new CalendarDate(start)) && checkDate(new CalendarDate(end))){
// reset the events before querying the new events to activate the loading spinner
this.events = null;
this.calendarDate = tmp_date;
@@ -51,15 +59,12 @@ const app = Vue.createApp({
});
}
},
calendarDateToString: function (calendarDate) {
return calendarDate instanceof CalendarDate ?
[calendarDate.y, calendarDate.m + 1, calendarDate.d].join('-') :
null;
},
loadEvents: function(){
Promise.allSettled([
this.$fhcApi.factory.stundenplan.getStundenplan(this.monthFirstDay, this.monthLastDay),
@@ -96,11 +101,12 @@ const app = Vue.createApp({
created(){
this.loadEvents();
},
//TODO: Stundenplan phrase
template:/*html*/`
<h2>Stundenplan</h2>
<hr>
<lv-modal v-if="currentlySelectedEvent" :event="currentlySelectedEvent" ref="lvmodal" />
<fhc-calendar @change:range="updateRange" :events="events" initial-mode="week" show-weeks >
<lv-modal v-if="currentlySelectedEvent" :event="currentlySelectedEvent" ref="lvmodal" />
<fhc-calendar :initial-date="currentDay" @change:range="updateRange" :events="events" initial-mode="week" show-weeks @select:day="selectDay" v-model:minimized="minimized">
<template #weekPage="{event,day}">
<div @click="showModal(event?.orig)" type="button" class="d-flex flex-column align-items-center justify-content-evenly h-100">
<span>{{event?.orig.topic}}</span>
@@ -108,6 +114,14 @@ const app = Vue.createApp({
<span>{{event?.orig.ort_kurzbz}}</span>
</div>
</template>
<template #dayPage="{event,day}">
<div @click="showModal(event?.orig)" type="button" class="d-flex flex-column align-items-center justify-content-evenly h-100">
<span>{{event?.orig.topic}}</span>
<span v-for="lektor in event?.orig.lektor">{{lektor.kurzbz}}</span>
<span>{{event?.orig.ort_kurzbz}}</span>
</div>
</template>
<template
</fhc-calendar>
`
});
+5 -1
View File
@@ -3,6 +3,7 @@ import CalendarMonths from './Months.js';
import CalendarYears from './Years.js';
import CalendarWeek from './Week.js';
import CalendarWeeks from './Weeks.js';
import CalendarDay from './Day.js';
import CalendarMinimized from './Minimized.js';
import CalendarDate from '../../composables/CalendarDate.js';
@@ -16,6 +17,7 @@ export default {
CalendarYears,
CalendarWeek,
CalendarWeeks,
CalendarDay,
CalendarMinimized,
},
provide() {
@@ -104,7 +106,6 @@ export default {
},
methods: {
handleInput(day) {
console.log(day[0], "first",day[1],"second")
this.$emit(day[0], day[1]);
}
},
@@ -142,6 +143,9 @@ export default {
<template #weekPage="{event,day}">
<slot name="weekPage" :event="event" :day="day"></slot>
</template>
<template #dayPage="{event,day}">
<slot name="dayPage" :event="event" :day="day"></slot>
</template>
</component>
</div>`
}
+52
View File
@@ -0,0 +1,52 @@
import CalendarAbstract from './Abstract.js';
import CalendarPane from './Pane.js';
import CalendarDayPage from './Day/Page.js';
import CalendarDate from '../../composables/CalendarDate.js';
export default {
mixins: [
CalendarAbstract
],
components: {
CalendarDayPage,
CalendarPane
},
computed: {
title() {
return this.focusDate.format({ year: 'numeric' }) + ' KW ' + this.focusDate.w;
}
},
methods: {
paneChanged(dir) {
let previousDate = new CalendarDate(this.focusDate);
this.focusDate.d += dir;
this.emitRangeChanged(previousDate);
},
emitRangeChanged(previousDate) {
this.$emit('change:range', { start: previousDate, end:this.focusDate });
},
prev() {
this.$refs.pane.prev();
},
next() {
this.$refs.pane.next();
},
selectEvent(event) {
this.$emit('input', ['select:event', event]);
}
},
created() {
this.emitRangeChanged();
},
template: /*html*/`
<div class="fhc-calendar-day">
<calendar-header :title="title" @prev="prev" @next="next" @updateMode="$emit('updateMode', $event)" @click="$emit('updateMode', 'week')"/>
<calendar-pane ref="pane" v-slot="slot" @slid="paneChanged">
<calendar-day-page :year="focusDate.y" :week="focusDate.w+slot.offset" @updateMode="$emit('updateMode', $event)" @page:back="prev" @page:forward="next" @input="selectEvent" >
<template #dayPage="{event,day}">
<slot name="dayPage" :event="event" :day="day" ></slot>
</template>
</calendar-day-page>
</calendar-pane>
</div>`
}
+154
View File
@@ -0,0 +1,154 @@
import CalendarDate from '../../../composables/CalendarDate.js';
function ggt(m, n) { return n == 0 ? m : ggt(n, m % n); }
function kgv(m, n) { return (m * n) / ggt(m, n); }
export default {
data() {
return {
hourPosition: null,
hourPositionTime: null,
}
},
inject: [
'date',
'focusDate',
'size',
'events',
'noMonthView'
],
props: {
year: Number,
week: Number
},
emits: [
'updateMode',
'page:back',
'page:forward',
'input'
],
computed: {
hours() {
// returns an array with elements starting at 7 and ending at 24
return [...Array(24).keys()].filter(hour => hour >= 7 && hour <= 24);
},
days() {
let startDay = this.focusDate;
let result = [];
result.push(new Date(startDay.y, startDay.m, startDay.d));
return result;
},
eventsPerDayAndHour() {
const res = {};
this.days.forEach(day => {
let key = day.toDateString();
let nextDay = new Date(day);
nextDay.setDate(nextDay.getDate() + 1);
nextDay.setMilliseconds(nextDay.getMilliseconds() - 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 < day ? 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));
}
};
event.shared = d.events.filter(other => other.start < event.end && other.end > event.start);
event.shared.forEach(other => other.shared.push(event));
let occupiedLanes = event.shared.map(other => other.lane);
while (occupiedLanes.includes(event.lane))
event.lane++;
event.maxLane = Math.max(...[event.lane], ...occupiedLanes);
if (event.maxLane > 1) {
event.setSharedMaxRecursive([event]);
}
d.events.push(event);
});
d.lanes = d.events.map(e => e.maxLane).reduce((res, i) => kgv(res, i), 1);
}
res[key] = d;
});
return res;
},
smallestTimeFrame() {
return [30, 15, 10, 5][this.size];
}
},
methods: {
calcHourPosition(event) {
let top = this.$refs.eventcontainer.getBoundingClientRect().top;
let position = event.clientY - top;
this.hourPosition = position;
// position percentage of total height
let timePercentage = (position / this.$refs.eventcontainer.offsetHeight) * 100;
// minute percentage of total minutes
let result = (this.hours.length * 60) * (timePercentage / 100);
// calculate time in float
let currentMinutes = ((result + (this.hours[0] * 60)) / 60);
// get hour part of time
let currentHour = Math.floor(currentMinutes);
// get float part of time
let minutePercentage = currentMinutes % currentHour;
// calculate minutes from float part of time
let minute = Math.round(60 * minutePercentage);
// in case the rounding made the minutes 60, increase the hour and reset the minutes
if (minute == 60) {
currentHour++;
minute = 0;
}
// add padding to minutes that consist of only one digit
minute.toString().length == 1 ? minute = "0" + minute : minute;
this.hourPositionTime = currentHour + ":" + minute;
},
getAbsolutePositionForHour(hour) {
// used for the absolute positioning of the gutters of hours
return (100 / this.hours.length) * (hour - (24 - this.hours.length)) + '%';
},
changeToMonth(day) {
if (!this.noMonthView) {
this.date.set(day);
this.focusDate.set(day);
this.$emit('updateMode', 'month');
}
},
dateToMinutesOfDay(day) {
return Math.floor(((day.getHours() - 7) * 60 + day.getMinutes()) / this.smallestTimeFrame) + 1;
}
},
mounted() {
setTimeout(() => this.$refs.eventcontainer.scrollTop = this.$refs.eventcontainer.scrollHeight / 3 + 1, 0);
},
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="{'z-index':4,'grid-template-columns': 'repeat(' + days.length + ', 1fr)', 'grid-template-rows':1}" style="position:sticky; top:0; " >
<div type="button" v-for="day in days" :key="day" 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="" >
<div v-for="hour in hours" :key="hour" class="position-absolute border-top" style="pointer-events: none;" :style="{top:getAbsolutePositionForHour(hour),left:0,right:0,'z-index':0}"></div>
<div v-if="hourPosition" class="position-absolute border-top small" style="pointer-events: none; padding-left:3.5rem; margin-top:-1px;z-index:2;border-color:#00649C !important" :style="{top:hourPosition+'px',left:0,right:0}">
<span class="border border-top-0 px-2 bg-white">{{hourPositionTime}}</span>
</div>
<div class="events">
<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="{'grid-template-columns': 'repeat(' + day.lanes + ', 1fr)', 'grid-template-rows': 'repeat(' + (hours.length * 60 / smallestTimeFrame) + ', 1fr)'}">
<div :style="{'background-color':event.orig.color}" class="mx-2 border border-dark border-2 small rounded overflow-hidden " @click.prevent="$emit('input', event.orig)" :style="{'z-index':1,'grid-column-start': 1+(event.lane-1)*day.lanes/event.maxLane, 'grid-column-end': 1+event.lane*day.lanes/event.maxLane, 'grid-row-start': dateToMinutesOfDay(event.start), 'grid-row-end': dateToMinutesOfDay(event.end) ,'--test': dateToMinutesOfDay(event.end)}" v-for="event in day.events" :key="event">
<slot name="dayPage" :event="event" :day="day">
<p>this is a placeholder which means that no template was passed to the Calendar Page slot</p>
</slot>
</div>
</div>
</div>
</div>
</div>
</div>`
}
+3 -1
View File
@@ -4,7 +4,8 @@ export default {
selected: this.mode,
modes:{
week:"Woche",
month:"Monat",
month:"Monat",
day: "Tag",
},
}
},
@@ -52,6 +53,7 @@ export default {
<button type="button" :class="{'active':mode_kurzbz == mode}" style="margin-right: 4px;" @click.prevent="$emit('updateMode',mode_kurzbz)" class="btn btn-outline-secondary" v-for="(mode_bezeichnung,mode_kurzbz) in modes">
<i v-if="!noWeekView && mode_kurzbz == 'week'" class="fa fa-calendar-week"></i>
<i v-else-if="!noMonthView && mode_kurzbz == 'month'" class="fa fa-calendar-days"></i>
<i v-else-if="mode_kurzbz == 'day'" class="fa-solid fa-sun"></i>
</button>
</div>
</div>
+6 -1
View File
@@ -61,6 +61,10 @@ export default {
this.$emit('updateMode', 'week');
}
},
changeToDay(day) {
this.focusDate.set(day);
this.$emit('updateMode', 'day');
},
highlight(week, day){
this.highlightedWeek = week.no;
this.highlightedDay = day;
@@ -71,7 +75,8 @@ export default {
clickEvent(day,week) {
if(!this.noWeekView)
{
this.changeToWeek(week);
//this.changeToWeek(week);
this.changeToDay(day);
}
this.selectDay(day);
}