Files
FHC-Core/public/js/components/Calendar/Base.js
T
ma0048 ab5294de2f stylingaenderungen
coursepicker umgebaut, keine backend suche mehr
hinzugefuegt:
- resizeHanlder funktion
- broadcastchannel postMessage "dropped"
- tbl_kalender_status column bezeichnung_mehrsprachig
- tbl_kalender_status column sort
2026-04-01 10:53:31 +02:00

334 lines
8.7 KiB
JavaScript

import BaseHeader from './Base/Header.js';
import BaseSlider from './Base/Slider.js';
import BsModal from '../Bootstrap/Modal.js';
import CalClick from '../../directives/Calendar/Click.js';
import DragClick from '../../directives/dragClick.js';
import Draggable from '../../directives/draggable.js';
import Drop from '../../directives/drop.js';
export default {
name: "CalendarBase",
components: {
BaseHeader,
BaseSlider,
BsModal
},
directives: {
CalClick,
DragClick,
Draggable,
Drop
},
provide() {
return {
events: Vue.computed(() => this.convertedEvents),
backgrounds: Vue.computed(() => this.convertedBackgrounds),
dropAllowed: Vue.computed(() => !!this.onDrop),
onDrop: Vue.computed(() => this.onDrop || null),
locale: Vue.computed(() => this.locale),
timezone: Vue.computed(() => this.timezone),
timeGrid: Vue.computed(() => this.timeGrid),
draggableEvents: Vue.computed(() => {
if (!this.draggableEvents)
return () => false;
if (Array.isArray(this.draggableEvents))
return event => this.draggableEvents.includes(event.type);
if (this.draggableEvents instanceof Function)
return this.draggableEvents;
return () => true;
}),
dropableEvents: Vue.computed(() => {
if (!this.onDrop)
return () => false;
if (Array.isArray(this.dropableEvents))
return item => this.dropableEvents.includes(item.type);
if (this.dropableEvents instanceof Function)
return this.dropableEvents;
return () => true;
}),
resizableEvents: Vue.computed(() => {
if (!this.resizableEvents)
return () => false;
if (Array.isArray(this.resizableEvents))
return event => this.resizableEvents.includes(event.type);
if (this.resizableEvents instanceof Function)
return this.resizableEvents;
return () => true;
}),
hasDragoverFunc: Vue.computed(() => this.onDragover),
mode: Vue.computed(() => this.mode),
onResize: Vue.computed(() => this.onResize || null),
};
},
props: {
locale: {
type: String,
default: 'de'
},
timezone: {
type: String,
required: true
},
date: {
type: [Date, String, Number, luxon.DateTime],
default: props => luxon.DateTime.now().setZone(props.timezone).startOf('day')
},
modes: {
type: Object,
required: true,
default: {}
// TODO(chris): verfication functions
},
mode: String,
modeOptions: Object,
events: {
type: Array,
default: []
},
backgrounds: {
type: Array,
default: []
},
showBtns: Boolean,
btnMonth: {
type: Boolean,
default: undefined
},
btnWeek: {
type: Boolean,
default: undefined
},
btnDay: {
type: Boolean,
default: undefined
},
btnList: {
type: Boolean,
default: undefined
},
btnTableList: {
type: Boolean,
default: undefined
},
timeGrid: Array,
draggableEvents: [Boolean, Array, Function],
dropableEvents: [Boolean, Array, Function],
resizableEvents: [Boolean, Array, Function],
onDragover: Function,
onDrop: Function,
onResize: Function
},
emits: [
"click:next",
"click:prev",
"click:mode",
"click:event",
"click:day",
"click:week",
"update:date",
"update:mode",
"update:range",
"drop"
],
data() {
return {
internalView: null,
internalDate: null,
modalEvent: null
};
},
computed: {
convertedEvents() {
return this.events.map(orig => ({
id: orig.type + orig[orig.type + '_id'],
type: orig.type,
start: luxon.DateTime.fromISO(orig.isostart).setZone(this.timezone),
end: luxon.DateTime.fromISO(orig.isoend).setZone(this.timezone),
orig
}));
},
convertedBackgrounds() {
return this.backgrounds.map(bg => {
const res = { ...bg };
if (res.start) {
if (Number.isInteger(res.start))
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 ===
'string' || res.start instanceof String)
res.start = luxon.DateTime.fromISO(res.start, { zone: this.timezone, locale: this.locale });
}
if (res.end) {
if (Number.isInteger(res.end))
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 ===
'string' || res.end instanceof String)
res.end = luxon.DateTime.fromISO(res.end, { zone: this.timezone, locale: this.locale });
}
return res;
});
},
sDate() {
if (this.date instanceof luxon.DateTime)
return this.date;
return luxon.DateTime.fromJSDate(new Date(this.date)).setZone(this.timezone);
},
cDate: {
get() {
const date = this.internalDate ? this.internalDate : this.sDate;
return date.setLocale(this.locale);
},
set(value) {
this.internalDate = value;
this.$emit('update:date', value, this.cMode);
}
},
sMode() {
// choose default mode
let mode = this.mode;
if (mode)
mode = mode.toLowerCase();
if (!mode || !this.modes[mode])
mode = Object.keys(this.modes).find(Boolean); // start with first entry as active mode
return mode || '';
},
cMode: {
get() {
return this.internalView ? this.internalView : this.sMode;
},
set(value) {
this.internalView = value;
this.$emit('update:mode', value, this.cDate);
}
}
},
watch: {
sDate(n, o) {
if (this.sDate.isValid && (!this.internalDate || !this.sDate.hasSame(this.internalDate, 'day')))
this.internalDate = this.sDate;
},
sMode() {
if (this.sMode)
this.internalView = this.sMode;
}
},
methods: {
clickPrev() {
const evt = new Event('click:prev', {cancelable: true});
this.$emit('click:prev', evt);
if (evt.defaultPrevented)
return;
// default: switch page
this.$refs.mode.prevPage();
},
clickNext() {
const evt = new Event('click:next', {cancelable: true});
this.$emit('click:next', evt);
if (evt.defaultPrevented)
return;
// default: switch page
this.$refs.mode.nextPage();
},
handleClickDefaults(evt) {
// TODO(chris): implement
switch (evt.detail.source) {
case 'day':
if (this.cMode != 'day' && this.modes['day']) {
evt.stopPropagation();
this.cDate = evt.detail.value;
this.cMode = 'day';
}
break;
case 'week':
if (this.cMode != 'week' && this.modes['week']) {
evt.stopPropagation();
this.cDate = luxon.DateTime.fromObject({
localWeekNumber: evt.detail.value.number,
localWeekYear: evt.detail.value.year
}, {
zone: this.cDate.zoneName,
locale: this.cDate.locale
});
this.cMode = 'week';
}
break;
}
},
showEventModal(eventObj) {
this.modalEvent = eventObj;
this.$refs.modal.show();
},
hideEventModal() {
if (this.modalEvent)
this.modalEvent.closeFn = undefined;
this.$refs.modal.hide();
this.modalEvent = null;
},
onModalHidden() {
if (this.modalEvent.closeFn)
this.modalEvent.closeFn();
}
},
beforeUnmount() {
this.hideEventModal();
},
template: /* html */`
<div class="fhc-calendar-base h-100">
<div class="card h-100"
v-cal-click:container
@cal-click-default.capture="handleClickDefaults"
>
<base-header
class="card-header"
v-model:date="cDate"
v-model:mode="cMode"
@prev="clickPrev"
@next="clickNext"
@click:mode="$emit('click:mode', $event)"
:btn-day="!!modes['day'] && (btnDay || (showBtns && btnDay !== false))"
:btn-week="!!modes['week'] && (btnWeek || (showBtns && btnWeek !== false))"
:btn-month="!!modes['month'] && (btnMonth || (showBtns && btnMonth !== false))"
:btn-list="!!modes['list'] && (btnList || (showBtns && btnList !== false))"
:btn-table-list="!!modes['tableList'] && (btnTableList || (showBtns && btnTableList !== false))"
:mode-options="modeOptions ? modeOptions[cMode] : undefined"
>
<slot name="actions" />
</base-header>
<component
:is="modes ? modes[cMode] : null || 'div'"
ref="mode"
v-model:current-date="cDate"
@update:range="$emit('update:range', $event)"
@request-modal-open="showEventModal"
@request-modal-close="hideEventModal"
@drop="$emit('drop', $event)"
v-bind="modeOptions ? modeOptions[cMode] : null || {}"
>
<template v-slot="slot"><slot v-bind="slot" /></template>
</component>
</div>
<bs-modal ref="modal" dialog-class="modal-lg" body-class="" @hidden-bs-modal="onModalHidden">
<template #title>
<slot v-if="modalEvent" v-bind="{mode: 'eventheader', event: modalEvent.event}" />
</template>
<template #default>
<slot v-if="modalEvent" v-bind="{mode: 'event', event: modalEvent.event}" />
</template>
</bs-modal>
</div>
`
}