mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-06-12 17:49:28 +00:00
feature(Stundenplan): adds Tagesansicht for the Stundenplan
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
`
|
||||
});
|
||||
|
||||
@@ -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>`
|
||||
}
|
||||
|
||||
@@ -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>`
|
||||
}
|
||||
@@ -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>`
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user