mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-24 10:22:18 +00:00
Conflict resolved
This commit is contained in:
@@ -164,7 +164,7 @@ export default {
|
||||
res.start = luxon.DateTime.fromMillis(res.start, { zone: this.timezone, locale: this.locale });
|
||||
else if (res.start instanceof Date)
|
||||
res.start = luxon.DateTime.fromJSDate(res.start, { zone: this.timezone, locale: this.locale });
|
||||
else if (typeof res.start ===
|
||||
else if (typeof res.start ===
|
||||
'string' || res.start instanceof String)
|
||||
res.start = luxon.DateTime.fromISO(res.start, { zone: this.timezone, locale: this.locale });
|
||||
}
|
||||
@@ -173,7 +173,7 @@ export default {
|
||||
res.end = luxon.DateTime.fromMillis(res.end, { zone: this.timezone, locale: this.locale });
|
||||
else if (res.end instanceof Date)
|
||||
res.end = luxon.DateTime.fromJSDate(res.end, { zone: this.timezone, locale: this.locale });
|
||||
else if (typeof res.end ===
|
||||
else if (typeof res.end ===
|
||||
'string' || res.end instanceof String)
|
||||
res.end = luxon.DateTime.fromISO(res.end, { zone: this.timezone, locale: this.locale });
|
||||
}
|
||||
@@ -199,7 +199,10 @@ export default {
|
||||
// choose default mode
|
||||
let mode = this.mode;
|
||||
if (mode)
|
||||
mode = mode.toLowerCase();
|
||||
{
|
||||
const lower = mode.toLowerCase();
|
||||
mode = Object.keys(this.modes).find(key => key.toLowerCase() === lower) || null;
|
||||
}
|
||||
if (!mode || !this.modes[mode])
|
||||
mode = Object.keys(this.modes).find(Boolean); // start with first entry as active mode
|
||||
return mode || '';
|
||||
@@ -332,4 +335,4 @@ export default {
|
||||
</bs-modal>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
@@ -398,7 +398,8 @@ export default {
|
||||
this.onDrop?.({
|
||||
item: [obj],
|
||||
start: dropStart.toISO(),
|
||||
end: dropEnd.toISO()
|
||||
end: dropEnd.toISO(),
|
||||
ctrlKey: !!(evt?.ctrlKey || evt?.metaKey)
|
||||
});
|
||||
},
|
||||
|
||||
@@ -437,7 +438,8 @@ export default {
|
||||
this.onDrop?.({
|
||||
item: [obj],
|
||||
start: dropStart.toISO(),
|
||||
end: dropEnd.toISO()
|
||||
end: dropEnd.toISO(),
|
||||
ctrlKey: !!(evt?.ctrlKey || evt?.metaKey)
|
||||
});
|
||||
},
|
||||
handleResizeStart({ edge, evt, el, event })
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import draggable from '../../../../../directives/draggable.js';
|
||||
import drop from '../../../../../directives/drop.js';
|
||||
import CalClick from '../../../../../directives/Calendar/Click.js';
|
||||
|
||||
export default {
|
||||
name: "GridLineEvent",
|
||||
directives: {
|
||||
draggable,
|
||||
drop,
|
||||
CalClick
|
||||
},
|
||||
emits: [
|
||||
@@ -29,6 +31,10 @@ export default {
|
||||
contextMenuActions: {
|
||||
from: "contextMenuActions",
|
||||
default: () => ({})
|
||||
},
|
||||
onDrop: {
|
||||
from: "onDrop",
|
||||
default: () => null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@@ -60,6 +66,8 @@ export default {
|
||||
if (this.event.endsHere)
|
||||
classes.push('event-end');
|
||||
}
|
||||
|
||||
classes.push(`calender_id-${this.event.orig.kalender_id}`);
|
||||
return classes;
|
||||
},
|
||||
dragKalenderCollection() {
|
||||
@@ -102,18 +110,45 @@ export default {
|
||||
evt.dataTransfer.setData('fhc-grab-offset-y', evt.clientY - rect.top);
|
||||
evt.dataTransfer.setData('fhc-grab-offset-x', evt.clientX - rect.left);
|
||||
},
|
||||
onDropOnCard(evt, items) {
|
||||
if (this.isHeaderOrFooter || !this.onDrop)
|
||||
return;
|
||||
|
||||
const list = Array.isArray(items) ? items : [items];
|
||||
const obj = list[0];
|
||||
if (!obj)
|
||||
return;
|
||||
|
||||
if ((evt.ctrlKey || evt.metaKey) && obj.type === 'lehreinheit')
|
||||
{
|
||||
return this.onDrop({
|
||||
item: [obj],
|
||||
ctrlKey: true,
|
||||
targetKalenderId: this.event.orig?.kalender_id ?? null
|
||||
});
|
||||
}
|
||||
|
||||
return this.onDrop({
|
||||
item: [obj],
|
||||
start: this.event.start.toISO(),
|
||||
end: this.event.end.toISO(),
|
||||
ctrlKey: false,
|
||||
targetKalenderId: null
|
||||
});
|
||||
},
|
||||
},
|
||||
template:`
|
||||
<div
|
||||
class="fhc-calendar-base-grid-line-event event"
|
||||
:class="classes"
|
||||
style="z-index: 2"
|
||||
style="z-index: 11"
|
||||
:draggable="draggable"
|
||||
:data-id="'event-' + event.orig.kalender_id"
|
||||
:data-group-id="'event-group-' + event.orig.eindeutige_gruppen_id"
|
||||
ref="eventEl"
|
||||
@dragstart="onDragStart"
|
||||
v-draggable:move.noimage="draggable ? dragKalenderCollection : {}"
|
||||
v-drop:move.lehreinheit.kalender.reservierung="onDropOnCard"
|
||||
v-cal-click:event="isHeaderOrFooter ? event : event.orig"
|
||||
@contextmenu.prevent="onRightClick"
|
||||
data-cy="calendar-event"
|
||||
|
||||
@@ -13,6 +13,9 @@ export default {
|
||||
dragKalenderCollection() {
|
||||
return this.event
|
||||
},
|
||||
topicString() {
|
||||
return Array.isArray(this.event.orig.topic) ? this.event.orig.topic.join(', ') : this.event.orig.topic;
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
@@ -22,7 +25,7 @@ export default {
|
||||
data-cy="calendar-event"
|
||||
>
|
||||
<div class="title">
|
||||
{{ event.orig.topic || event.orig.titel || event.orig.lehrfach }}
|
||||
{{ topicString || event.orig.titel || event.orig.lehrfach }}
|
||||
</div>
|
||||
<div>
|
||||
{{ event.orig.datum }} {{ event.orig.beginn }}–{{ event.orig.ende }}
|
||||
|
||||
@@ -8,7 +8,8 @@ export default {
|
||||
name: "TableView",
|
||||
inject: {
|
||||
events: "events",
|
||||
timezone: "timezone"
|
||||
timezone: "timezone",
|
||||
tableActions: "tableActions",
|
||||
},
|
||||
components: {
|
||||
CoreFilterCmpt,
|
||||
@@ -91,6 +92,19 @@ export default {
|
||||
{
|
||||
openModal() {
|
||||
this.$refs.raumModal.show();
|
||||
},
|
||||
async deleteSelected() {
|
||||
let selected = this.$refs.tableViewTable?.tabulator?.getSelectedData() ?? [];
|
||||
|
||||
if (!selected.length) return;
|
||||
|
||||
let isConfirmed = await this.$fhcAlert.confirmDelete();
|
||||
|
||||
if (!isConfirmed) return;
|
||||
|
||||
await this.tableActions?.deleteEntries(selected)
|
||||
|
||||
this.$refs.tableViewTable.tabulator.deselectRow();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -98,7 +112,7 @@ export default {
|
||||
this.$refs.tableViewTable?.tabulator?.setData(newData);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
/* mounted() {
|
||||
|
||||
this.$api.call(ApiDetails.getRaumtyp())
|
||||
.then(result => {
|
||||
@@ -106,7 +120,7 @@ export default {
|
||||
})
|
||||
.catch(this.$fhcAlert.handleSystemError);
|
||||
|
||||
},
|
||||
},*/
|
||||
template: /* html */`
|
||||
<div class="fhc-calendar-mode-table-view h-100 overflow-auto">
|
||||
<core-filter-cmpt
|
||||
@@ -117,12 +131,13 @@ export default {
|
||||
:download="true"
|
||||
>
|
||||
<template #actions>
|
||||
<button @click="deleteSelected" class="btn btn-outline-danger btn-sm">Löschen</button>
|
||||
<button class="btn btn-outline-secondary btn-sm">Verschieben</button>
|
||||
<button @click="openModal" class="btn btn-outline-secondary btn-sm">Raum wechsel</button>
|
||||
</template>
|
||||
</core-filter-cmpt>
|
||||
|
||||
<bs-modal ref="raumModal" class="bootstrap-prompt" dialogClass="modal-lg">
|
||||
<!--<bs-modal ref="raumModal" class="bootstrap-prompt" dialogClass="modal-lg">
|
||||
<template #title>Raum verschiebung</template>
|
||||
<form-input
|
||||
:label="$p.t('lehre', 'raumtyp')"
|
||||
@@ -141,7 +156,7 @@ export default {
|
||||
<template #footer>
|
||||
<button type="button" class="btn btn-primary">{{ $p.t('ui', 'speichern') }}</button>
|
||||
</template>
|
||||
</bs-modal>
|
||||
</bs-modal>-->
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -9,57 +9,65 @@ import ApiKalender from "../../api/factory/tempus/kalender.js";
|
||||
import draggable from "../../directives/draggable.js";
|
||||
|
||||
export default {
|
||||
name: "CalendarTempus",
|
||||
components: {
|
||||
FhcCalendar,
|
||||
},
|
||||
inject: {
|
||||
renderers: { from: "renderers" },
|
||||
appConfig: {
|
||||
from: "appConfig",
|
||||
default: {
|
||||
visible_status: "all",
|
||||
},
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
draggable,
|
||||
},
|
||||
props: {
|
||||
timezone: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
date: {
|
||||
type: [Date, String, Number, luxon.DateTime],
|
||||
default: luxon.DateTime.local(),
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: "Week",
|
||||
},
|
||||
getPromiseFunc: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
parkedEvents: {
|
||||
type: Object,
|
||||
default: () => new Set(),
|
||||
},
|
||||
visibleLecturers: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
extraBackgrounds: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
visibleStatus: {
|
||||
type: Array,
|
||||
default: () => ["all"],
|
||||
},
|
||||
},
|
||||
emits: ["update:date", "update:mode", "update:range", "drop", "resize"],
|
||||
name: "CalendarTempus",
|
||||
components: {
|
||||
FhcCalendar
|
||||
},
|
||||
inject: {
|
||||
renderers: {from: 'renderers'},
|
||||
appConfig: {
|
||||
from: 'appConfig',
|
||||
default: {
|
||||
visible_status: 'all'
|
||||
}
|
||||
}
|
||||
},
|
||||
directives: {
|
||||
draggable,
|
||||
},
|
||||
props: {
|
||||
timezone: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
date: {
|
||||
type: [Date, String, Number, luxon.DateTime],
|
||||
default: luxon.DateTime.local()
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'Week'
|
||||
},
|
||||
getPromiseFunc: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
parkedEvents: {
|
||||
type: Object,
|
||||
default: () => new Set()
|
||||
},
|
||||
visibleLecturers: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
extraBackgrounds: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
visibleStatus: {
|
||||
type: Array,
|
||||
default: () => ['all']
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
"update:date",
|
||||
"update:mode",
|
||||
"update:range",
|
||||
"drop",
|
||||
"resize",
|
||||
"event-hover",
|
||||
"event-unhover"
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
@@ -149,6 +157,12 @@ export default {
|
||||
resetEventLoader() {
|
||||
this.reset();
|
||||
},
|
||||
navigatePrev() {
|
||||
this.$refs.calendar.clickPrev();
|
||||
},
|
||||
navigateNext() {
|
||||
this.$refs.calendar.clickNext();
|
||||
},
|
||||
clearOutCalendarEventEmphasis() {
|
||||
this.$refs.calendar.$el
|
||||
.querySelectorAll(
|
||||
@@ -234,6 +248,8 @@ export default {
|
||||
:class="['event-type-' + event.type + ' ' + mode + 'PageContainer', { 'event--parked': parkedEvents.has(String(event.kalender_id)) }]"
|
||||
:type="mode == 'day' ? 'button' : undefined"
|
||||
:style="eventStyle(event)"
|
||||
@mouseenter="$emit('event-hover', event)"
|
||||
@mouseleave="$emit('event-unhover', event)"
|
||||
>
|
||||
<component
|
||||
v-if="mode == 'event'"
|
||||
@@ -276,5 +292,5 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</fhc-calendar>`,
|
||||
};
|
||||
</fhc-calendar>`
|
||||
}
|
||||
|
||||
@@ -24,21 +24,56 @@ export default {
|
||||
return {
|
||||
searchparam: '',
|
||||
allCourses: [],
|
||||
sortBy: null,
|
||||
multiWeekIds: new Set()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
courses() {
|
||||
const query = (this.searchparam ?? '').trim().toLowerCase();
|
||||
if (!query)
|
||||
return this.allCourses;
|
||||
|
||||
return this.allCourses.filter(course =>
|
||||
course.showname.toLowerCase().includes(query) ||
|
||||
course.lektoren?.some(l =>
|
||||
l.name.toLowerCase().includes(query) ||
|
||||
l.kurzbz.toLowerCase().includes(query)
|
||||
)
|
||||
);
|
||||
let result;
|
||||
if (query)
|
||||
{
|
||||
result = this.allCourses.filter(course =>
|
||||
course.showname.toLowerCase().includes(query) ||
|
||||
course.lektoren?.some(lektor =>
|
||||
lektor.name.toLowerCase().includes(query) ||
|
||||
lektor.kurzbz.toLowerCase().includes(query)
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
result = this.allCourses;
|
||||
|
||||
if (this.sortBy === 'lektor-asc' || this.sortBy === 'lektor-desc')
|
||||
{
|
||||
let dir = this.sortBy === 'lektor-asc' ? 1 : -1;
|
||||
|
||||
return result.sort((a, b) => {
|
||||
let an = a.lektoren?.[0]?.kurzbz ?? '';
|
||||
let bn = b.lektoren?.[0]?.kurzbz ?? '';
|
||||
return an.localeCompare(bn) * dir;
|
||||
});
|
||||
}
|
||||
else if (this.sortBy === 'kw-asc')
|
||||
{
|
||||
return result.sort((a, b) => (a.start_kw ?? 0) - (b.start_kw ?? 0));
|
||||
}
|
||||
else if (this.sortBy === 'kw-desc')
|
||||
{
|
||||
return result.sort((a, b) => (b.start_kw ?? 0) - (a.start_kw ?? 0));
|
||||
}
|
||||
else if (this.sortBy === 'stunden-asc')
|
||||
{
|
||||
return result.sort((a, b) => (a.offenestunden ?? 0) - (b.offenestunden ?? 0));
|
||||
}
|
||||
else if (this.sortBy === 'stunden-desc')
|
||||
{
|
||||
return result.sort((a, b) => (b.offenestunden ?? 0) - (a.offenestunden ?? 0));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -94,6 +129,7 @@ export default {
|
||||
id: orig.lehreinheit_id,
|
||||
orig: orig,
|
||||
stundenblockung: course.stundenblockung,
|
||||
multiweek: this.isMultiWeek(course),
|
||||
};
|
||||
},
|
||||
courseStyle(course) {
|
||||
@@ -104,7 +140,44 @@ export default {
|
||||
},
|
||||
selectLecturer(lektor) {
|
||||
this.$emit('select-lecturer', lektor);
|
||||
}
|
||||
},
|
||||
setSort(field) {
|
||||
if (this.sortBy === `${field}-asc`)
|
||||
this.sortBy = `${field}-desc`;
|
||||
else if (this.sortBy === `${field}-desc`)
|
||||
this.sortBy = null;
|
||||
else
|
||||
this.sortBy = `${field}-asc`;
|
||||
},
|
||||
sortIcon(field) {
|
||||
if (this.sortBy === `${field}-asc`)
|
||||
return 'fa-arrow-up';
|
||||
if (this.sortBy === `${field}-desc`)
|
||||
return 'fa-arrow-down';
|
||||
return 'fa-sort';
|
||||
},
|
||||
isSortActive(field) {
|
||||
return this.sortBy === `${field}-asc` || this.sortBy === `${field}-desc`;
|
||||
},
|
||||
reload()
|
||||
{
|
||||
this.loadCoursesByStg(this.stg);
|
||||
},
|
||||
toggleMultiWeek(course)
|
||||
{
|
||||
let id = course.lehreinheit_id[0];
|
||||
let weekIds = new Set(this.multiWeekIds);
|
||||
if (weekIds.has(id))
|
||||
weekIds.delete(id);
|
||||
else
|
||||
weekIds.add(id);
|
||||
|
||||
this.multiWeekIds = weekIds;
|
||||
},
|
||||
isMultiWeek(course)
|
||||
{
|
||||
return this.multiWeekIds.has(course.lehreinheit_id[0]);
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="course-picker d-flex flex-column h-100">
|
||||
@@ -114,6 +187,36 @@ export default {
|
||||
type="text"
|
||||
v-model="searchparam"
|
||||
/>
|
||||
<div class="d-flex gap-1 mt-2">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
:disabled="!stg"
|
||||
@click="reload">
|
||||
<i class="fa fa-rotate-right"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="isSortActive('lektor') ? 'btn-primary' : 'btn-outline-secondary'"
|
||||
@click="setSort('lektor')">
|
||||
Lkt <i class="fa" :class="sortIcon('lektor')"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="isSortActive('kw') ? 'btn-primary' : 'btn-outline-secondary'"
|
||||
@click="setSort('kw')">
|
||||
KW <i class="fa" :class="sortIcon('kw')"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="isSortActive('stunden') ? 'btn-primary' : 'btn-outline-secondary'"
|
||||
@click="setSort('stunden')">
|
||||
Std <i class="fa" :class="sortIcon('stunden')"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!stg" class="d-flex flex-column align-items-center justify-content-center text-center text-muted py-5 px-3 h-100">
|
||||
<span class="small fw-semibold mb-1">Keine Lehreinheiten</span>
|
||||
@@ -123,6 +226,7 @@ export default {
|
||||
<div
|
||||
v-for="course in courses"
|
||||
:key="course.lehreinheit_id"
|
||||
style="cursor:grab"
|
||||
:style="courseStyle(course)"
|
||||
class="course-picker-row"
|
||||
v-draggable:move.noimage="dragLehreinheitCollection(course)"
|
||||
@@ -131,6 +235,13 @@ export default {
|
||||
<div class="d-flex gap-1">
|
||||
<span class="fw-semibold small w-50" v-tooltip="course.lehrfach_bez">{{ course.lehrfach }} {{ course.lehrform }}</span>
|
||||
<span class="fw-semibold small w-50" v-tooltip="course.raumtypalternativ">{{ course.raumtyp }}</span>
|
||||
<i
|
||||
class="fa fa-calendar-week"
|
||||
:class="isMultiWeek(course) ? 'text-primary' : 'text-muted'"
|
||||
style="cursor:pointer"
|
||||
v-tooltip="isMultiWeek(course) ? 'MultiWeek aktiv' : 'MultiWeek Verplanung aktivieren'"
|
||||
@click.stop="toggleMultiWeek(course)"
|
||||
></i>
|
||||
</div>
|
||||
|
||||
<!--TODO(david) entfernen, dient nur für das mappen mit der lvverwaltung-->
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
export default {
|
||||
name: "KeyboardShortcuts",
|
||||
props: {
|
||||
shortcuts: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isInputFocused(target)
|
||||
{
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
return (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target.isContentEditable)
|
||||
},
|
||||
shortcutMatches(shortcut, e)
|
||||
{
|
||||
let shortcutWantsCtrl = shortcut.ctrl === true;
|
||||
let ctrlIsPressed = e.ctrlKey || e.metaKey;
|
||||
|
||||
if (shortcutWantsCtrl !== ctrlIsPressed)
|
||||
return false;
|
||||
|
||||
if (e.key !== shortcut.key)
|
||||
return false;
|
||||
|
||||
let shouldSkipWhenTyping = shortcut.skipWhenTyping !== false;
|
||||
return !(shouldSkipWhenTyping && this.isInputFocused(e.target));
|
||||
},
|
||||
handleKeydown(e)
|
||||
{
|
||||
for (let shortcut of this.shortcuts)
|
||||
{
|
||||
if (!this.shortcutMatches(shortcut, e))
|
||||
continue;
|
||||
|
||||
let shouldPreventDefault = shortcut.preventDefault !== false;
|
||||
if (shouldPreventDefault)
|
||||
e.preventDefault();
|
||||
|
||||
shortcut.handler(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('keydown', this.handleKeydown);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener('keydown', this.handleKeydown);
|
||||
},
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,191 @@
|
||||
import ApiKalender from '../../api/factory/tempus/kalender.js';
|
||||
import BsModal from '../../../js/components/Bootstrap/Modal.js';
|
||||
export default {
|
||||
components: {
|
||||
BsModal,
|
||||
},
|
||||
props: {
|
||||
show: { type: Boolean, default: false },
|
||||
lehreinheitId: { type: [String, Number], default: null },
|
||||
ortKurzbz: { type: String, default: null },
|
||||
startTime: { type: String, default: null },
|
||||
endTime: { type: String, default: null },
|
||||
},
|
||||
emits: ['close', 'confirmed'],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
plan: [],
|
||||
skippedWeeks: [],
|
||||
errors: [],
|
||||
restOffen: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
show(val) {
|
||||
if (val) {
|
||||
this.loadPlan();
|
||||
this.$refs.bsModal.show();
|
||||
}
|
||||
else {
|
||||
this.$refs.bsModal.hide();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
countTermin() {
|
||||
return (this.plan.filter(termin => termin.planen).length);
|
||||
},
|
||||
formattedSkippedWeeks() {
|
||||
return this.skippedWeeks.map(week => luxon.DateTime.fromISO(week).toFormat('dd.MM.yyyy')).join('\n');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadPlan() {
|
||||
this.loading = true;
|
||||
this.plan = [];
|
||||
this.errors = [];
|
||||
this.skippedWeeks = [];
|
||||
this.restOffen = 0;
|
||||
|
||||
await this.$api.call(
|
||||
ApiKalender.calculateMultiWeekPlan(
|
||||
this.lehreinheitId,
|
||||
this.ortKurzbz,
|
||||
this.startTime,
|
||||
this.endTime
|
||||
))
|
||||
.then(result => result.data)
|
||||
.then(result => {
|
||||
this.plan = (result.plan || []).map(termin => ({
|
||||
...termin,
|
||||
selected_ort_kurzbz: termin.raum_vorschlaege?.[0]?.ort_kurzbz ?? termin.ort_kurzbz,
|
||||
planen: (termin.collisions && termin.collisions.length > 0) ? false : true,
|
||||
}));
|
||||
|
||||
this.skippedWeeks = result.skipped_weeks || [];
|
||||
this.errors = result.errors || [];
|
||||
this.restOffen = result.rest_offen || 0;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.$fhcAlert.handleSystemError(error);
|
||||
this.close();
|
||||
});
|
||||
},
|
||||
|
||||
async confirm() {
|
||||
this.loading = true;
|
||||
|
||||
await this.$api.call(
|
||||
ApiKalender.confirmMultiWeekPlan(this.plan.filter(termin => termin.planen), this.lehreinheitId)
|
||||
);
|
||||
|
||||
this.$emit('confirmed');
|
||||
this.close();
|
||||
},
|
||||
|
||||
close() {
|
||||
this.loading = false;
|
||||
this.$emit('close');
|
||||
},
|
||||
hasCollision(termin) {
|
||||
return termin.collisions && termin.collisions.length > 0;
|
||||
},
|
||||
roomOptions(termin) {
|
||||
return termin.raum_vorschlaege || [];
|
||||
},
|
||||
formatDate(date)
|
||||
{
|
||||
return luxon.DateTime.fromISO(date).toFormat('dd.MM.yyyy');
|
||||
}
|
||||
|
||||
},
|
||||
template: `
|
||||
<bs-modal
|
||||
ref="bsModal"
|
||||
class="bootstrap-prompt"
|
||||
dialog-class="modal-xl"
|
||||
@hidden-bs-modal="close"
|
||||
>
|
||||
<template v-slot:title>Multi-Week Verplanung Vorschau</template>
|
||||
|
||||
<div v-if="loading" class="text-center py-4">
|
||||
<i class="fa fa-spinner fa-spin"></i> Wird berechnet...
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div v-if="restOffen > 0" class="alert alert-warning small">
|
||||
Achtung: {{ restOffen }} Stunden konnten nicht verplant werden (Semesterende erreicht).
|
||||
</div>
|
||||
|
||||
<div v-if="skippedWeeks.length" class="alert alert-warning small">
|
||||
Übersprungende Ferienwochen: <div style="white-space: pre-line;">{{ formattedSkippedWeeks }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="errors.length" class="alert alert-danger small">
|
||||
<div v-for="(err, i) in errors" :key="i">{{ err.message }}</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datum</th>
|
||||
<th>Zeit</th>
|
||||
<th>Collisions</th>
|
||||
<th>Raum</th>
|
||||
<th>Planen?</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(termin, i) in plan" :key="i" :class="hasCollision(termin) ? 'table-danger' : 'table-success'">
|
||||
<td>{{ formatDate(termin.datum) }}</td>
|
||||
<td>{{ termin.start.split(' ')[1] }} - {{ termin.ende.split(' ')[1] }}</td>
|
||||
<td>
|
||||
<span v-if="hasCollision(termin)" class="text-danger small">
|
||||
<div style="white-space: pre-line;">{{ termin.collisions.map(c => c.message).join('\\n') }}</div>
|
||||
</span>
|
||||
<span v-else class="text-success small">
|
||||
---
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
v-if="roomOptions(termin).length"
|
||||
class="form-select form-select-sm"
|
||||
v-model="termin.selected_ort_kurzbz"
|
||||
>
|
||||
<option
|
||||
v-for="raum in roomOptions(termin)"
|
||||
:key="raum.ort_kurzbz"
|
||||
:value="raum.ort_kurzbz"
|
||||
>
|
||||
{{ raum.ort_kurzbz }} (Score: {{ raum.score }})
|
||||
</option>
|
||||
</select>
|
||||
<span v-else class="text-muted small">Kein Raum verfügbar</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
v-model="termin.planen"
|
||||
:id="'override-' + i"
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<button class="btn btn-outline-secondary" @click="close">Abbrechen</button>
|
||||
<button class="btn btn-primary" :disabled="loading || !plan.length" @click="confirm">
|
||||
{{ countTermin }} Termine anlegen
|
||||
</button>
|
||||
</template>
|
||||
</bs-modal>
|
||||
`
|
||||
}
|
||||
@@ -15,6 +15,17 @@ export default {
|
||||
|
||||
return classes;
|
||||
},
|
||||
topicString() {
|
||||
return Array.isArray(this.event.topic) ? this.event.topic.join(', ') : this.event.topic;
|
||||
},
|
||||
ortString() {
|
||||
return Array.isArray(this.event.ort_kurzbz) ? this.event.ort_kurzbz.join(', ') : this.event.ort_kurzbz;
|
||||
},
|
||||
gruppeString() {
|
||||
return Array.isArray(this.event.gruppe)
|
||||
? this.event.gruppe.map(gruppe => gruppe.bezeichnung).join(', ')
|
||||
: this.event.gruppe;
|
||||
},
|
||||
tooltipString() {
|
||||
const tooltipArray = [];
|
||||
|
||||
@@ -25,14 +36,21 @@ export default {
|
||||
|
||||
tooltipArray.push([
|
||||
this.$p.t('profilUpdate/topic'),
|
||||
this.event.topic
|
||||
this.topicString
|
||||
].join(": "));
|
||||
|
||||
tooltipArray.push([
|
||||
this.$p.t('person/ort'),
|
||||
this.event.ort_kurzbz
|
||||
this.ortString
|
||||
].join(": "));
|
||||
|
||||
if (this.gruppeString) {
|
||||
tooltipArray.push([
|
||||
this.$p.t('lehre/gruppe'),
|
||||
this.gruppeString
|
||||
].join(": "));
|
||||
}
|
||||
|
||||
if (Array.isArray(this.event.lektor) && this.event.lektor.length > 0) {
|
||||
if (this.event.lektor.length > 3) {
|
||||
tooltipArray.push([
|
||||
@@ -80,8 +98,9 @@ export default {
|
||||
<span>{{ end }}</span>
|
||||
</div>
|
||||
<div class="event-text" v-tooltip="tooltipString">
|
||||
<span class="event-topic">{{ event.topic }}</span>
|
||||
<span data-cy="calendar-event-room" class="event-place">{{ event.ort_kurzbz }}</span>
|
||||
<span class="event-topic">{{ topicString }}</span>
|
||||
<span class="event-place" data-cy="calendar-event-room">{{ ortString }}</span>
|
||||
<span v-if="gruppeString" class="event-gruppe">{{ gruppeString }}</span>
|
||||
<span
|
||||
v-for="(lektor,index) in event.lektor.slice(0, 3)"
|
||||
class="event-lectors"
|
||||
|
||||
@@ -49,7 +49,10 @@ export default {
|
||||
return this.event.ende;
|
||||
}
|
||||
return numberPadding(this.event.ende.getHours()) + ":" + numberPadding(this.event.ende.getMinutes());
|
||||
}
|
||||
},
|
||||
ortString() {
|
||||
return Array.isArray(this.event.ort_kurzbz) ? this.event.ort_kurzbz.join(', ') : this.event.ort_kurzbz;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
mehtodNumberPadding: function (number) {
|
||||
@@ -106,7 +109,7 @@ export default {
|
||||
}}</th>
|
||||
<td>
|
||||
<a v-if="event.ort_content_id" :aria-label="$p.t('global','raum')" :title="$p.t('global','raum')" :href="getOrtContentLink"><i class="fa fa-arrow-up-right-from-square me-1" aria-hidden="true" style="color:#00649C"></i></a>
|
||||
{{event.ort_kurzbz}}
|
||||
{{ortString}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -22,17 +22,24 @@ export default {
|
||||
this.$p.t('global/uhrzeit'),
|
||||
[this.start, this.end].join(' - ')
|
||||
].join(": "));
|
||||
|
||||
|
||||
tooltipArray.push([
|
||||
this.$p.t('profilUpdate/topic'),
|
||||
this.event.topic
|
||||
this.topicString
|
||||
].join(": "));
|
||||
|
||||
|
||||
tooltipArray.push([
|
||||
this.$p.t('person/ort'),
|
||||
this.event.ort_kurzbz
|
||||
this.ortString
|
||||
].join(": "));
|
||||
|
||||
|
||||
if (this.gruppeString) {
|
||||
tooltipArray.push([
|
||||
this.$p.t('lehre/gruppe'),
|
||||
this.gruppeString
|
||||
].join(": "));
|
||||
}
|
||||
|
||||
if (Array.isArray(this.event.lektor) && this.event.lektor.length > 0) {
|
||||
if (this.event.lektor.length > 3) {
|
||||
tooltipArray.push([
|
||||
@@ -47,7 +54,22 @@ export default {
|
||||
].join(": "));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Array.isArray(this.event.teilnehmer_person) && this.event.teilnehmer_person.length > 0) {
|
||||
if (this.event.teilnehmer_person.length > 3) {
|
||||
tooltipArray.push([
|
||||
this.$p.t('lehre/teilnehmer'),
|
||||
this.event.teilnehmer_person.slice(0, 3).map(person => `${person.vorname} ${person.nachname}`).join("\n")
|
||||
+ "\n" + this.$p.t('lehre/weitereTeilnehmer', [this.event.teilnehmer_person.length - 3])
|
||||
].join(": "));
|
||||
} else {
|
||||
tooltipArray.push([
|
||||
this.$p.t('lehre/teilnehmer'),
|
||||
this.event.teilnehmer_person.map(person => `${person.vorname} ${person.nachname}`).join("\n")
|
||||
].join(": "));
|
||||
}
|
||||
}
|
||||
|
||||
return tooltipArray.join("\n");
|
||||
},
|
||||
start() {
|
||||
@@ -59,7 +81,18 @@ export default {
|
||||
return luxon.Duration
|
||||
.fromISOTime(this.event.ende)
|
||||
.toISOTime({ suppressSeconds: true });
|
||||
}
|
||||
},
|
||||
ortString() {
|
||||
return Array.isArray(this.event.ort_kurzbz) ? this.event.ort_kurzbz.join(', ') : this.event.ort_kurzbz;
|
||||
},
|
||||
topicString() {
|
||||
return Array.isArray(this.event.topic) ? this.event.topic.join(', ') : this.event.topic;
|
||||
},
|
||||
gruppeString() {
|
||||
return Array.isArray(this.event.teilnehmer_gruppe)
|
||||
? this.event.teilnehmer_gruppe.map(gruppe => gruppe.gruppe_kurzbz).join(', ')
|
||||
: this.event.teilnehmer_gruppe;
|
||||
},
|
||||
},
|
||||
template: /* html */`
|
||||
<div
|
||||
@@ -73,7 +106,7 @@ export default {
|
||||
<span>{{ end }}</span>
|
||||
</div>
|
||||
<div class="event-text" v-tooltip="tooltipString">
|
||||
<span class="event-topic">{{ event.topic }}</span>
|
||||
<span class="event-topic">{{ topicString }}</span>
|
||||
<span
|
||||
v-for="lektor in event.lektor.slice(0, 3)"
|
||||
class="event-lectors"
|
||||
@@ -86,8 +119,21 @@ export default {
|
||||
>
|
||||
... +{{ event.lektor.length - 3 }}
|
||||
</span>
|
||||
<span data-cy="calendar-event-room" class="event-place">{{ event.ort_kurzbz }}</span>
|
||||
<span
|
||||
v-for="person in (event.teilnehmer_person || []).slice(0, 3)"
|
||||
class="event-teilnehmer"
|
||||
>
|
||||
{{ person.vorname }} {{ person.nachname }}
|
||||
</span>
|
||||
<span
|
||||
v-if="event.teilnehmer_person && event.teilnehmer_person.length > 3"
|
||||
class="event-teilnehmer-plus"
|
||||
>
|
||||
... +{{ event.teilnehmer_person.length - 3 }}
|
||||
</span>
|
||||
<span class="event-place" data-cy="calendar-event-room">{{ ortString }}</span>
|
||||
<span v-if="gruppeString" class="event-gruppe">{{ gruppeString }}</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,10 @@ export default {
|
||||
return this.event.ende;
|
||||
}
|
||||
return numberPadding(this.event.ende.getHours()) + ":" + numberPadding(this.event.ende.getMinutes());
|
||||
}
|
||||
},
|
||||
ortString() {
|
||||
return Array.isArray(this.event.ort_kurzbz) ? this.event.ort_kurzbz.join(', ') : this.event.ort_kurzbz;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
mehtodNumberPadding: function (number) {
|
||||
@@ -93,7 +96,7 @@ export default {
|
||||
}}</th>
|
||||
<td>
|
||||
<a v-if="event.ort_content_id" :aria-label="$p.t('global','raum')" :title="$p.t('global','raum')" :href="getOrtContentLink"><i class="fa fa-arrow-up-right-from-square me-1" style="color:#00649C" aria-hidden="true"></i></a>
|
||||
{{event.ort_kurzbz}}
|
||||
{{ortString}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -37,6 +37,10 @@ import ApiStudiengangTree from "../../api/lehrveranstaltung/studiengangtree.js";
|
||||
import StvStudiensemester from "../Stv/Studentenverwaltung/Studiensemester.js";
|
||||
import FormInput from "../../../js/components/Form/Input.js";
|
||||
import Reservierung from "./Reservierung.js";
|
||||
import { getTempusShortcuts } from "./shortcuts.js";
|
||||
import KeyboardShortcuts from "./KeyboardShortcuts.js";
|
||||
import { useContextMenuActions } from "../../composables/Tempus/ContextMenuActions.js";
|
||||
import MultiWeekPlanModal from "./MultiWeekPlanModal.js";
|
||||
|
||||
export default {
|
||||
name: "Tempus",
|
||||
@@ -56,6 +60,8 @@ export default {
|
||||
Multiselect: primevue.multiselect,
|
||||
FormInput,
|
||||
Reservierung,
|
||||
KeyboardShortcuts,
|
||||
MultiWeekPlanModal
|
||||
},
|
||||
props: {
|
||||
defaultSemester: String,
|
||||
@@ -78,12 +84,24 @@ export default {
|
||||
currentSemester: this.defaultSemester,
|
||||
renderers: Vue.computed(() => this.renderers),
|
||||
appConfig: Vue.computed(() => this.appconfig),
|
||||
contextMenuActions: Vue.computed(() => this.contextMenuActions),
|
||||
contextMenuActions: useContextMenuActions({
|
||||
openRaumauswahl: (orig) => this.openRaumauswahl(orig),
|
||||
openResourcesAssignmentModal: (orig) => this.openResourcesAssignmentModal(orig),
|
||||
openHistory: (orig) => this.openHistory(orig),
|
||||
deleteEntry: (orig) => this.deleteEntry(orig),
|
||||
syncToLecturer: (orig) => this.syncToLecturer(orig),
|
||||
syncToStudent: (orig) => this.syncToStudent(orig),
|
||||
}),
|
||||
tableActions: {
|
||||
deleteEntries: (origList) => this.deleteEntries(origList),
|
||||
openRaumauswahl: (orig) => this.openRaumauswahl(orig),
|
||||
},
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
appconfig: {},
|
||||
currentMode: 'week',
|
||||
configEndpoints: ApiTempusConfig,
|
||||
endpoint: ApiStudiengangTree,
|
||||
raumVorschlaege: [],
|
||||
@@ -121,6 +139,7 @@ export default {
|
||||
events: null,
|
||||
minimized: false,
|
||||
currentlySelectedEvent: null,
|
||||
hoveredEvent: null,
|
||||
//currentDay: new Date(),
|
||||
studiensemesterKurzbz: this.defaultSemester,
|
||||
lists: {
|
||||
@@ -164,64 +183,19 @@ export default {
|
||||
areFormButtonsDisplayed: false,
|
||||
},
|
||||
currentlyUpdatedEvent: null,
|
||||
multiWeekModal: {
|
||||
show: false,
|
||||
lehreinheitId: null,
|
||||
ortKurzbz: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
contextMenuActions() {
|
||||
return {
|
||||
lehreinheit: [
|
||||
{
|
||||
label: "Raumauswahl",
|
||||
icon: "fa-solid fa-door-open",
|
||||
action: this.openRaumauswahl,
|
||||
},
|
||||
{
|
||||
label: "Ressourcen zuordnen",
|
||||
icon: "fa-solid fa-list",
|
||||
action: this.openResourcesAssignmentModal,
|
||||
},
|
||||
{
|
||||
label: "Freischalten für Voransicht",
|
||||
icon: "fa-solid fa-chalkboard-user",
|
||||
action: (orig) =>
|
||||
this.$api
|
||||
.call(ApiKalender.syncToLecturer(orig.kalender_id))
|
||||
.then(() => this.$refs.calendar.resetEventLoader()),
|
||||
},
|
||||
{
|
||||
label: "Freischalten für Live",
|
||||
icon: "fa-solid fa-user-graduate",
|
||||
action: (orig) =>
|
||||
this.$api
|
||||
.call(ApiKalender.syncToStudent(orig.kalender_id))
|
||||
.then(() => this.$refs.calendar.resetEventLoader()),
|
||||
},
|
||||
{
|
||||
label: "History",
|
||||
icon: "fa-solid fa-clock-rotate-left",
|
||||
action: this.openHistory,
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
icon: "fa-solid fa-calendar-xmark",
|
||||
action: this.deleteEntry,
|
||||
},
|
||||
],
|
||||
reservierung: [
|
||||
{
|
||||
label: "Delete",
|
||||
icon: "fa-solid fa-calendar-xmark",
|
||||
action: this.deleteEntry,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
currentDay() {
|
||||
return luxon.DateTime.now().setZone(this.config.timezone).toISODate();
|
||||
},
|
||||
currentMode() {
|
||||
return "week";
|
||||
},
|
||||
visibleLecturerUids() {
|
||||
if (!this.lecturers.length) return null;
|
||||
return this.lecturers
|
||||
@@ -244,6 +218,9 @@ export default {
|
||||
label: this.visibleStatusArray[status],
|
||||
}));
|
||||
},
|
||||
keyboardShortcuts() {
|
||||
return getTempusShortcuts(this);
|
||||
},
|
||||
dropdownParsedAvailableResources() {
|
||||
return this.resourcesAssignmentModal.availableResources
|
||||
.map((unit) => {
|
||||
@@ -283,15 +260,33 @@ export default {
|
||||
|
||||
this.$refs.resourcesAssignmentModal.show();
|
||||
},
|
||||
async deleteEntry(orig) {
|
||||
if (!orig?.kalender_id) return;
|
||||
async deleteEntry(orig)
|
||||
{
|
||||
if (!orig?.kalender_id)
|
||||
return;
|
||||
|
||||
await this.$api
|
||||
.call(ApiKalender.deleteEntry(orig?.kalender_id))
|
||||
.then((result) => {
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
});
|
||||
},
|
||||
await this.deleteEntryCall(orig);
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
},
|
||||
async deleteEntries(origList)
|
||||
{
|
||||
let validList = (origList ?? []).filter(orig => orig?.kalender_id);
|
||||
if (!validList.length)
|
||||
return;
|
||||
|
||||
await Promise.allSettled(validList.map(orig => this.deleteEntryCall(orig)));
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
},
|
||||
async deleteEntryCall(orig)
|
||||
{
|
||||
await this.$api.call(ApiKalender.deleteEntry(
|
||||
orig.kalender_id
|
||||
)).then(() => {
|
||||
this.$refs.parking.unpark({ type: orig.type, id: orig.kalender_id });
|
||||
});
|
||||
},
|
||||
async openHistory(orig) {
|
||||
if (!orig?.kalender_id) return;
|
||||
await this.$api
|
||||
@@ -301,6 +296,18 @@ export default {
|
||||
this.$refs.historyModel.show();
|
||||
});
|
||||
},
|
||||
syncToLecturer(orig) {
|
||||
if (!orig?.kalender_id)
|
||||
return;
|
||||
return this.$api.call(ApiKalender.syncToLecturer(orig.kalender_id))
|
||||
.then(() => this.$refs.calendar.resetEventLoader());
|
||||
},
|
||||
syncToStudent(orig) {
|
||||
if (!orig?.kalender_id)
|
||||
return;
|
||||
return this.$api.call(ApiKalender.syncToStudent(orig.kalender_id))
|
||||
.then(() => this.$refs.calendar.resetEventLoader());
|
||||
},
|
||||
async selectRaum(ort_kurzbz) {
|
||||
const orig = this.raumModal;
|
||||
await this.$api
|
||||
@@ -368,9 +375,13 @@ export default {
|
||||
if (newDate && luxon.DateTime.isDateTime(newDate) && newDate.isValid)
|
||||
this.calendarDate = newDate.toISODate();
|
||||
},
|
||||
handleChangeMode() {
|
||||
console.log("handleChangeMode");
|
||||
},
|
||||
handleChangeMode(newMode, newDate) {
|
||||
if (!newMode)
|
||||
return;
|
||||
this.currentMode = newMode;
|
||||
if (newDate && luxon.DateTime.isDateTime(newDate) && newDate.isValid)
|
||||
this.calendarDate = newDate.toISODate();
|
||||
},
|
||||
toggleStatus(selected) {
|
||||
if (!selected || selected.length === 0) {
|
||||
this.visibleStatus = ["all"];
|
||||
@@ -567,10 +578,12 @@ export default {
|
||||
dates.end_time,
|
||||
() => {
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
},
|
||||
() => {
|
||||
this.$refs.calendar.clearOutCalendarEventEmphasis();
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -585,6 +598,19 @@ export default {
|
||||
const obj = item[0];
|
||||
if (!obj?.type) return alert("Unbekannter Drop-Typ");
|
||||
|
||||
if (payload.targetKalenderId)
|
||||
{
|
||||
if (obj.type !== 'lehreinheit' || !obj.orig?.lehreinheit_id)
|
||||
return alert("Nur Lehreinheiten können einem Termin hinzugefügt werden");
|
||||
|
||||
return this.$api.call(
|
||||
ApiKalender.addToKalenderEvent(payload.targetKalenderId, obj.orig.lehreinheit_id)
|
||||
).then(() => {
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.bcc.postMessage('dropped');
|
||||
});
|
||||
}
|
||||
|
||||
const dates = this._parseDates(start, end);
|
||||
if (!dates) return;
|
||||
|
||||
@@ -608,6 +634,18 @@ export default {
|
||||
this.reservierungPending = true;
|
||||
this.$refs.reservierung.show(start_time, end_time);
|
||||
} else if (obj.type === "lehreinheit") {
|
||||
|
||||
if (obj.multiweek)
|
||||
{
|
||||
this.openMultiWeekPreview(
|
||||
obj.orig.lehreinheit_id,
|
||||
this.ort_kurzbz ? this.ort_kurzbz : obj.orig.ort_kurzbz,
|
||||
start_time,
|
||||
end_time
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
return this.$api
|
||||
.call(
|
||||
ApiKalender.addKalenderEvent(
|
||||
@@ -617,8 +655,10 @@ export default {
|
||||
end_time,
|
||||
),
|
||||
)
|
||||
.then(result => result.data)
|
||||
.then(() => {
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
this.bcc.postMessage("dropped");
|
||||
});
|
||||
} else if (obj.type === "kalender") {
|
||||
@@ -634,6 +674,7 @@ export default {
|
||||
id: obj.orig.kalender_id,
|
||||
});
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
this.bcc.postMessage("dropped");
|
||||
},
|
||||
() => {
|
||||
@@ -651,6 +692,21 @@ export default {
|
||||
alert("Unbekannter Drop-Typ: " + obj.type);
|
||||
}
|
||||
},
|
||||
openMultiWeekPreview(lehreinheit_id, ort_kurzbz, start_time, end_time) {
|
||||
this.multiWeekModal.lehreinheitId = lehreinheit_id;
|
||||
this.multiWeekModal.ortKurzbz = ort_kurzbz;
|
||||
this.multiWeekModal.startTime = start_time;
|
||||
this.multiWeekModal.endTime = end_time;
|
||||
this.multiWeekModal.show = true;
|
||||
},
|
||||
closeMultiWeekModal() {
|
||||
this.multiWeekModal.show = false;
|
||||
},
|
||||
onMultiWeekConfirmed() {
|
||||
this.$refs.calendar.resetEventLoader();
|
||||
this.$refs.coursepicker.reload();
|
||||
this.bcc.postMessage('dropped');
|
||||
},
|
||||
handleRange(range) {
|
||||
if (!range?.start || !range?.end) return;
|
||||
|
||||
@@ -1023,6 +1079,47 @@ export default {
|
||||
});
|
||||
}
|
||||
},
|
||||
onEventHover(event) {
|
||||
this.hoveredEvent = event;
|
||||
},
|
||||
onEventUnhover(event)
|
||||
{
|
||||
if (this.hoveredEvent?.kalender_id === event?.kalender_id) {
|
||||
this.hoveredEvent = null;
|
||||
}
|
||||
},
|
||||
parkHoveredEvent()
|
||||
{
|
||||
const event = this.hoveredEvent;
|
||||
if (!event?.kalender_id)
|
||||
return;
|
||||
|
||||
if (this.$refs.parking.isParked(event.kalender_id))
|
||||
{
|
||||
this.$refs.parking.unpark({ type: event.type, id: event.kalender_id });
|
||||
}
|
||||
else
|
||||
{
|
||||
this.$refs.parking.park(null, {
|
||||
type: 'kalender',
|
||||
id: event.kalender_id,
|
||||
orig: event
|
||||
});
|
||||
}
|
||||
},
|
||||
deleteHoveredEvent() {
|
||||
const event = this.hoveredEvent;
|
||||
if (!event?.kalender_id)
|
||||
return;
|
||||
|
||||
this.deleteEntry(event);
|
||||
},
|
||||
clearHoveredEvent() {
|
||||
this.hoveredEvent = null;
|
||||
},
|
||||
focusSearchbar() {
|
||||
this.$refs.searchbar?.$refs?.input.focus()
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
lecturers: {
|
||||
@@ -1118,6 +1215,7 @@ export default {
|
||||
},
|
||||
template: /*html*/ `
|
||||
<div data-cy="tempus" class="tempus">
|
||||
<keyboard-shortcuts :shortcuts="keyboardShortcuts" />
|
||||
<header class="navbar navbar-expand-lg navbar-dark bg-dark flex-md-nowrap p-0 shadow">
|
||||
<div class="col-md-4 col-lg-3 col-xl-2 d-flex align-items-center">
|
||||
<button
|
||||
@@ -1313,7 +1411,7 @@ export default {
|
||||
v-model:parked-keys="parkedKeys"
|
||||
></parking-slot>
|
||||
|
||||
<fhc-coursepicker :stg="stg" @select-lecturer="setEmp" @select-kw="jumpToKw" :studiensemester="selectedStudiensemester"></fhc-coursepicker>
|
||||
<fhc-coursepicker ref="coursepicker" :stg="stg" @select-lecturer="setEmp" @select-kw="jumpToKw" :studiensemester="selectedStudiensemester"></fhc-coursepicker>
|
||||
|
||||
</div>
|
||||
<stv-studiensemester v-model:studiensemester-kurzbz="selectedStudiensemester"></stv-studiensemester>
|
||||
@@ -1333,6 +1431,8 @@ export default {
|
||||
@resize="resizeHandler"
|
||||
@update:date="handleChangeDate"
|
||||
@update:mode="handleChangeMode"
|
||||
@event-hover="onEventHover"
|
||||
@event-unhover="onEventUnhover"
|
||||
:extra-backgrounds="extraBackgrounds"
|
||||
@update:range="handleRange"
|
||||
class="responsive-calendar"
|
||||
@@ -1464,5 +1564,14 @@ export default {
|
||||
:ort-kurzbz="ort_kurzbz"
|
||||
@saved="reservierungPending = false; $refs.calendar.resetEventLoader()"
|
||||
></reservierung>
|
||||
</div>`,
|
||||
<multi-week-plan-modal
|
||||
:show="multiWeekModal.show"
|
||||
:lehreinheit-id="multiWeekModal.lehreinheitId"
|
||||
:ort-kurzbz="multiWeekModal.ortKurzbz"
|
||||
:start-time="multiWeekModal.startTime"
|
||||
:end-time="multiWeekModal.endTime"
|
||||
@close="closeMultiWeekModal"
|
||||
@confirmed="onMultiWeekConfirmed"
|
||||
/>
|
||||
</div>`
|
||||
};
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
export function getTempusShortcuts(self) {
|
||||
return [
|
||||
{
|
||||
key: 'F',
|
||||
ctrl: true,
|
||||
skipWhenTyping: false,
|
||||
handler: () => self.focusSearchbar()
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
ctrl: true,
|
||||
skipWhenTyping: false,
|
||||
handler: () => self.handleChangeMode('month')
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
ctrl: true,
|
||||
skipWhenTyping: false,
|
||||
handler: () => self.handleChangeMode('week')
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
ctrl: true,
|
||||
skipWhenTyping: false,
|
||||
handler: () => self.handleChangeMode('tableList')
|
||||
},
|
||||
{
|
||||
key: 'ArrowLeft',
|
||||
ctrl: true,
|
||||
skipWhenTyping: false,
|
||||
handler: () => self.$refs.calendar.navigatePrev()
|
||||
},
|
||||
{
|
||||
key: 'ArrowRight',
|
||||
ctrl: true,
|
||||
skipWhenTyping: false,
|
||||
handler: () => self.$refs.calendar.navigateNext()
|
||||
},
|
||||
{
|
||||
key: 'p',
|
||||
skipWhenTyping: true,
|
||||
handler: () => self.parkHoveredEvent()
|
||||
},
|
||||
{
|
||||
key: 'P',
|
||||
skipWhenTyping: true,
|
||||
handler: () => self.parkHoveredEvent()
|
||||
},
|
||||
{
|
||||
key: 'Delete',
|
||||
skipWhenTyping: true,
|
||||
handler: () => self.deleteHoveredEvent()
|
||||
},
|
||||
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user