Merge branch 'merge_C4_25999_61235_61730' into rc1_FHC4_C4

This commit is contained in:
Harald Bamberger
2025-08-05 16:32:50 +02:00
14 changed files with 942 additions and 402 deletions
+3
View File
@@ -258,6 +258,9 @@ export default {
this.modalEvent.closeFn();
}
},
beforeUnmount() {
this.hideEventModal();
},
template: /* html */`
<div class="fhc-calendar-base h-100">
<base-draganddrop
@@ -0,0 +1,107 @@
import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from '../../../api/factory/lvPlan.js';
import ApiAuthinfo from '../../../api/factory/authinfo.js';
export const DEFAULT_MODE_LVPLAN = 'Week'
export default {
name: 'LvPlanLehrveranstaltung',
components: {
FhcCalendar
},
props: {
viewData: Object, // NOTE(chris): this is inherited from router-view
propsViewData: Object
},
data() {
return {
lv: null
};
},
computed:{
currentDay() {
if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date)))
return luxon.DateTime.now().setZone(this.viewData.timezone).toISODate();
return this.propsViewData?.focus_date;
},
currentMode() {
if (!this.propsViewData?.mode || !['day', 'week', 'month'].includes(this.propsViewData?.mode.toLowerCase()))
return DEFAULT_MODE_LVPLAN;
return this.propsViewData?.mode;
},
currentLv() {
if (isNaN(parseInt(this.propsViewData?.lv_id)))
return null;
return this.propsViewData.lv_id;
},
lvTitle() {
if (this.currentLv === null)
return '';
if (!this.lv)
return '';
if (this.$p.user_language.value === 'English')
return this.lv.bezeichnung_english;
return this.lv.bezeichnung;
}
},
methods: {
handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day);
},
handleChangeMode(newMode, day) {
const mode = newMode[0].toUpperCase() + newMode.slice(1)
const focus_date = day.toISODate();
this.$router.push({
name: "LvPlan",
params: {
mode,
focus_date,
lv_id: this.currentLv
}
});
},
getPromiseFunc(start, end) {
return [
this.$api.call(ApiLvPlan.eventsLv(this.propsViewData.lv_id, start.toISODate(), end.toISODate())),
this.$api.call(ApiLvPlan.getLvPlanReservierungen(start.toISODate(), end.toISODate()))
];
}
},
created() {
if (this.currentLv === null)
return;
this.$api
.call(ApiLvPlan.getLv(this.propsViewData?.lv_id))
.then(res => {
this.lv = res.data;
});
},
template: /*html*/`
<div class="cis-lvplan-personal d-flex flex-column h-100">
<h2>
{{ $p.t('lehre/stundenplan') }}
<span v-if="lvTitle" class="ps-3">
{{ lvTitle }}
</span>
</h2>
<hr>
<div v-if="currentLv === null || lv === false">
{{ $p.t('lehre/noLvFound') }}
</div>
<fhc-calendar
v-else-if="lv"
ref="calendar"
:timezone="viewData.timezone"
:get-promise-func="getPromiseFunc"
:date="currentDay"
:mode="currentMode"
@update:date="handleChangeDate"
@update:mode="handleChangeMode"
class="responsive-calendar"
/>
</div>`
};
+143
View File
@@ -0,0 +1,143 @@
import FhcCalendar from "../../Calendar/LvPlan.js";
import ApiLvPlan from '../../../api/factory/lvPlan.js';
import ApiAuthinfo from '../../../api/factory/authinfo.js';
export const DEFAULT_MODE_LVPLAN = 'Week'
export default {
name: 'LvPlanPersonal',
components: {
FhcCalendar
},
props: {
viewData: Object, // NOTE(chris): this is inherited from router-view
propsViewData: Object
},
data() {
return {
studiensemester_kurzbz: null,
studiensemester_start: null,
studiensemester_ende: null,
uid: null
};
},
computed:{
currentDay() {
if (!this.propsViewData?.focus_date || isNaN(new Date(this.propsViewData?.focus_date)))
return luxon.DateTime.now().setZone(this.viewData.timezone).toISODate();
return this.propsViewData?.focus_date;
},
currentMode() {
if (!this.propsViewData?.mode || !['day', 'week', 'month'].includes(this.propsViewData?.mode.toLowerCase()))
return DEFAULT_MODE_LVPLAN;
return this.propsViewData?.mode;
},
downloadLinks() {
if (!this.studiensemester_start || !this.studiensemester_ende || !this.uid)
return false;
const opts = { zone: this.viewData.timezone };
const start = luxon.DateTime
.fromISO(this.studiensemester_start, opts)
.toUnixInteger();
const ende = luxon.DateTime
.fromISO(this.studiensemester_ende, opts)
.toUnixInteger();
const download_link = FHC_JS_DATA_STORAGE_OBJECT.app_root
+ 'cis/private/lvplan/stpl_kalender.php'
+ '?type=student'
+ '&pers_uid=' + this.uid
+ '&begin=' + start
+ '&ende=' + ende;
return [
{ title: "excel", icon: 'fa-solid fa-file-excel', link: download_link + '&format=excel' },
{ title: "csv", icon: 'fa-solid fa-file-csv', link: download_link + '&format=csv' },
{ title: "ical1", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=1&target=ical' },
{ title: "ical2", icon: 'fa-regular fa-calendar', link: download_link + '&format=ical&version=2&target=ical' }
];
}
},
methods: {
handleChangeDate(day, newMode) {
return this.handleChangeMode(newMode, day);
},
handleChangeMode(newMode, day) {
const mode = newMode[0].toUpperCase() + newMode.slice(1)
const focus_date = day.toISODate();
this.$router.push({
name: "MyLvPlan",
params: {
mode,
focus_date
}
});
},
updateRange(rangeInterval) {
this.$api
.call(ApiLvPlan.studiensemesterDateInterval(
rangeInterval.end.startOf('week').toISODate()
))
.then(res => {
this.studiensemester_kurzbz = res.data.studiensemester_kurzbz;
this.studiensemester_start = res.data.start;
this.studiensemester_ende = res.data.ende;
});
},
getPromiseFunc(start, end) {
return [
this.$api.call(ApiLvPlan.eventsPersonal(start.toISODate(), end.toISODate())),
this.$api.call(ApiLvPlan.getLvPlanReservierungen(start.toISODate(), end.toISODate()))
];
}
},
created() {
this.$api
.call(ApiAuthinfo.getAuthUID())
.then(res => {
this.uid = res.data.uid;
});
},
template: /*html*/`
<div class="cis-lvplan-personal d-flex flex-column h-100">
<h2>
{{ $p.t('lehre/stundenplan') }}
<span v-if="studiensemester_kurzbz" class="ps-3">
{{ studiensemester_kurzbz }}
</span>
</h2>
<hr>
<fhc-calendar
ref="calendar"
:timezone="viewData.timezone"
:get-promise-func="getPromiseFunc"
:date="currentDay"
:mode="currentMode"
@update:date="handleChangeDate"
@update:mode="handleChangeMode"
@update:range="updateRange"
class="responsive-calendar"
>
<div
v-if="downloadLinks"
class="d-flex gap-1 justify-items-start"
>
<div v-for="{ title, icon, link } in downloadLinks">
<a
:href="link"
:aria-label="title"
class="py-1 btn btn-outline-secondary"
>
<div class="d-flex flex-column">
<i aria-hidden="true" :class="icon"></i>
<span style="font-size:.5rem">{{ title }}</span>
</div>
</a>
</div>
</div>
</fhc-calendar>
</div>`
};
@@ -7,8 +7,8 @@ export default {
},
},
template: `
<div class="feiertagEventContent " >
<i id="ferienEventIcon" class="fa-regular fa-calendar"></i>
<span id="ferienEventTitle" class="fw-bold text-center">{{event.titel}}</span>
</div>`,
<div class="cis-renderer-feiertage-calendar-event">
<i class="event-icon" class="fa-regular fa-calendar"></i>
<span class="event-title fw-bold text-center">{{ event.titel }}</span>
</div>`,
};
@@ -1,59 +1,92 @@
export default {
methods:{
convertTime: function ([hour, minute]) {
let date = new Date();
date.setHours(hour);
date.setMinutes(minute);
// returns date string as hh:mm
return date.toLocaleTimeString(this.$p.user_locale, { hour: '2-digit', minute: '2-digit', hour12: false });
},
},
computed:{
calendarEventTooltip: function(){
let lektorenEmpty = true;
let tooltipString = `${this.$p.t('global','uhrzeit')}: ${this.convertTime(this.event.beginn.split(":"))} / ${this.convertTime(this.event.ende.split(":")) }`;
tooltipString += `\n${this.$p.t('profilUpdate', 'topic')}: ${this.event.topic}`;
tooltipString += `${this.$p.t('person', 'ort')}: ${this.event.ort_kurzbz}`;
if(Array.isArray(this.event.lektor) && this.event.lektor.length > 0){
lektorenEmpty = false;
tooltipString += `\n${this.$p.t('lehre','lektor')}: `;
this.event.lektor.slice(0,3).forEach(lektor => {
tooltipString += `${lektor.kurzbz}\n`;
})
if(this.event.lektor.length > 3){
tooltipString += `${this.$p.t('lehre', 'weitereLektoren', [(this.event.lektor.length - 3)])}\n`;
}
}
if(lektorenEmpty){
tooltipString += "\n";
}
return tooltipString;
},
},
props:{
event: {
type:Object,
required:true,
type: Object,
required: true
}
},
computed:{
tooltipString() {
const tooltipArray = [];
tooltipArray.push([
this.$p.t('global/uhrzeit'),
[this.start, this.end].join(' - ')
].join(": "));
tooltipArray.push([
this.$p.t('profilUpdate/topic'),
this.event.topic
].join(": "));
tooltipArray.push([
this.$p.t('person/ort'),
this.event.ort_kurzbz
].join(": "));
this.event.lektor = [
this.event.lektor[0],
this.event.lektor[0],
this.event.lektor[0],
this.event.lektor[0],
this.event.lektor[0],
];
if (Array.isArray(this.event.lektor) && this.event.lektor.length > 0) {
if (this.event.lektor.length > 3) {
tooltipArray.push([
this.$p.t('lehre/lektor'),
this.event.lektor.slice(0, 3).map(lektor => lektor.kurzbz).join("\n")
+ "\n" + this.$p.t('lehre/weitereLektoren', [this.event.lektor.length - 3])
].join(": "));
} else {console.log(this.event.lektor);
tooltipArray.push([
this.$p.t('lehre/lektor'),
this.event.lektor.map(lektor => lektor.kurzbz).join("\n")
].join(": "));
}
}
return tooltipArray.join("\n");
},
start() {
return luxon.Duration
.fromISOTime(this.event.beginn)
.toISOTime({ suppressSeconds: true });
},
end() {
return luxon.Duration
.fromISOTime(this.event.ende)
.toISOTime({ suppressSeconds: true });
}
},
template: /*html*/`
<div class="lehreinheitEventContent h-100 w-100 p-1" @wheel.stop >
<div id="lehreinheitEventHeader" class="d-none d-xl-grid h-100 " v-if="!event.allDayEvent && event?.beginn && event?.ende" >
<span >{{convertTime(event.beginn.split(":"))}}</span>
<span >{{convertTime(event.ende.split(":"))}}</span>
<div
class="cis-renderer-lehreinheit-calendar-event calendar-event-default h-100 w-100 p-1"
@wheel.stop
>
<div
v-if="!event.allDayEvent && event?.beginn && event?.ende"
class="event-time d-none d-xl-grid h-100"
>
<span>{{ start }}</span>
<span>{{ end }}</span>
</div>
<div id="lehreinheitEventText" v-tooltip="calendarEventTooltip">
<span id="lehreinheitTopic">{{event.topic}}</span>
<span id="lehreinheitOrt">{{event.ort_kurzbz}}</span>
<span id="lehreinheitLektoren" v-for="(lektor,index) in event.lektor.slice(0,3)">
{{lektor.kurzbz}}
<div class="event-text" v-tooltip="tooltipString">
<span class="event-topic">{{ event.topic }}</span>
<span class="event-place">{{ event.ort_kurzbz }}</span>
<span
v-for="(lektor,index) in event.lektor.slice(0, 3)"
class="event-lectors"
>
{{ lektor.kurzbz }}
</span>
<span
v-if="event.lektor.length > 3"
class="event-lectors-plus"
>
... +{{ event.lektor.length - 3 }}
</span>
<span id="lektorEllipsis" class="fw-bold" v-if="event.lektor.length > 3">...+
{{event.lektor.length-3}}</span>
</div>
</div>
`,
@@ -1,30 +1,83 @@
export default {
methods: {
convertTime: function ([hour, minute]) {
let date = new Date();
date.setHours(hour);
date.setMinutes(minute);
// returns date string as hh:mm
return date.toLocaleTimeString(this.$p.user_locale, { hour: '2-digit', minute: '2-digit', hour12: false });
},
},
props: {
event: {
type: Object,
required: true,
},
required: true
}
},
template: `
<div class="lehreinheitEventContent h-100 w-100 p-1" >
<div id="lehreinheitEventHeader" class="h-100 " v-if="!event.allDayEvent && event?.beginn && event?.ende" >
<span class="small">{{convertTime(event.beginn.split(":"))}}</span>
<span class="small">{{convertTime(event.ende.split(":"))}}</span>
computed: {
tooltipString() {
const tooltipArray = [];
tooltipArray.push([
this.$p.t('global/uhrzeit'),
[this.start, this.end].join(' - ')
].join(": "));
tooltipArray.push([
this.$p.t('profilUpdate/topic'),
this.event.topic
].join(": "));
tooltipArray.push([
this.$p.t('person/ort'),
this.event.ort_kurzbz
].join(": "));
if (Array.isArray(this.event.lektor) && this.event.lektor.length > 0) {
if (this.event.lektor.length > 3) {
tooltipArray.push([
this.$p.t('lehre/lektor'),
this.event.lektor.slice(0, 3).map(lektor => lektor.kurzbz).join("\n")
+ "\n" + this.$p.t('lehre/weitereLektoren', [this.event.lektor.length - 3])
].join(": "));
} else {
tooltipArray.push([
this.$p.t('lehre/lektor'),
this.event.lektor.map(lektor => lektor.kurzbz).join("\n")
].join(": "));
}
}
return tooltipArray.join("\n");
},
start() {
return luxon.Duration
.fromISOTime(this.event.beginn)
.toISOTime({ suppressSeconds: true });
},
end() {
return luxon.Duration
.fromISOTime(this.event.ende)
.toISOTime({ suppressSeconds: true });
}
},
template: /* html */`
<div
class="cis-renderer-reservierungen-calendar-event calendar-event-default h-100 w-100 p-1"
>
<div
v-if="!event.allDayEvent && event?.beginn && event?.ende"
class="event-time d-grid h-100"
>
<span>{{ start }}</span>
<span>{{ end }}</span>
</div>
<div id="lehreinheitEventText">
<span>{{event.topic}}</span>
<span v-for="lektor in event.lektor">{{lektor.kurzbz}}</span>
<span>{{event.ort_kurzbz}}</span>
<div class="event-text" v-tooltip="tooltipString">
<span class="event-topic">{{ event.topic }}</span>
<span
v-for="lektor in event.lektor.slice(0, 3)"
class="event-lectors"
>
{{ lektor.kurzbz }}
</span>
<span
v-if="event.lektor.length > 3"
class="event-lectors-plus"
>
... +{{ event.lektor.length - 3 }}
</span>
<span class="event-place">{{ event.ort_kurzbz }}</span>
</div>
</div>
`,
@@ -34,7 +34,7 @@ export default {
start_time: function () {
if (!this.event.beginn)
return 'N/A';
if (!this.event.beginn instanceof Date) {
if (!(this.event.beginn instanceof Date)) {
return this.event.beginn;
}
return numberPadding(this.event.beginn.getHours()) + ":" + numberPadding(this.event.beginn.getMinutes());
@@ -42,7 +42,7 @@ export default {
end_time: function () {
if (!this.event.ende)
return 'N/A';
if (!this.event.ende instanceof Date) {
if (!(this.event.ende instanceof Date)) {
return this.event.ende;
}
return numberPadding(this.event.ende.getHours()) + ":" + numberPadding(this.event.ende.getMinutes());