Merge branch 'main' of bitbucket.org:studiotem/jandl-taskmanager-app into production

This commit is contained in:
Matej Glasnák committed 2025-10-31 13:55:24 +01:00
commit fea06566e6
1 file changed
+277 -172
+277 -172
View File
@@ -62,10 +62,10 @@ transition-group.vuecal__cell(
</template> </template>
<script> <script>
import Event from './event.vue' import Event from './event.vue';
import tmUtils from '@/plugins/tm@_utils/tm.utils' import tmUtils from '@/plugins/tm@_utils/tm.utils';
import moment from 'moment' import moment from 'moment';
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex';
export default { export default {
inject: ['vuecal', 'utils', 'modules', 'view', 'domEvents'], inject: ['vuecal', 'utils', 'modules', 'view', 'domEvents'],
@@ -79,7 +79,7 @@ export default {
minTimestamp: { type: [Number, null], default: null }, minTimestamp: { type: [Number, null], default: null },
maxTimestamp: { type: [Number, null], default: null }, maxTimestamp: { type: [Number, null], default: null },
cellWidth: { type: [Number, Boolean], default: false }, cellWidth: { type: [Number, Boolean], default: false },
allDay: { type: Boolean, default: false } allDay: { type: Boolean, default: false },
}, },
data: () => ({ data: () => ({
@@ -90,20 +90,23 @@ export default {
// where there is no cursor coords. // where there is no cursor coords.
timeAtCursor: null, timeAtCursor: null,
highlighted: false, // On event drag over. highlighted: false, // On event drag over.
highlightedSplit: null highlightedSplit: null,
}), }),
methods: { methods: {
getSplitAtCursor({ target }) { getSplitAtCursor({ target }) {
const targetIsSplit = target.classList.contains('vuecal__cell-split') const targetIsSplit = target.classList.contains('vuecal__cell-split');
let split = targetIsSplit ? target : this.vuecal.findAncestor(target, 'vuecal__cell-split') let split = targetIsSplit
? target
: this.vuecal.findAncestor(target, 'vuecal__cell-split');
if (split) { if (split) {
split = split.attributes['data-split'].value split = split.attributes['data-split'].value;
// Convert to a numeric value if split id is a number. // Convert to a numeric value if split id is a number.
if (parseInt(split).toString() === split.toString()) split = parseInt(split) if (parseInt(split).toString() === split.toString())
split = parseInt(split);
} }
return split || null return split || null;
}, },
splitClasses(split) { splitClasses(split) {
@@ -111,24 +114,29 @@ export default {
'vuecal__cell-split': true, 'vuecal__cell-split': true,
'vuecal__cell-split--highlighted': this.highlightedSplit === split.id, 'vuecal__cell-split--highlighted': this.highlightedSplit === split.id,
'vuecal__cell-split--weekend': split.isWeekend, 'vuecal__cell-split--weekend': split.isWeekend,
[split.class]: !!split.class [split.class]: !!split.class,
} };
}, },
checkCellOverlappingEvents() { checkCellOverlappingEvents() {
// If splits, checkCellOverlappingEvents() is called from within computed splits. // If splits, checkCellOverlappingEvents() is called from within computed splits.
if (this.options.time && this.eventsCount && !this.splitsCount) { if (this.options.time && this.eventsCount && !this.splitsCount) {
if (this.eventsCount === 1) { if (this.eventsCount === 1) {
this.cellOverlaps = [] this.cellOverlaps = [];
this.cellOverlapsStreak = 1 this.cellOverlapsStreak = 1;
} }
// If only 1 event remains re-init the overlaps. // If only 1 event remains re-init the overlaps.
else [this.cellOverlaps, this.cellOverlapsStreak] = this.utils.event.checkCellOverlappingEvents(this.events, this.options) else
[this.cellOverlaps, this.cellOverlapsStreak] =
this.utils.event.checkCellOverlappingEvents(
this.events,
this.options,
);
} }
}, },
isDOMElementAnEvent(el) { isDOMElementAnEvent(el) {
return this.vuecal.isDOMElementAnEvent(el) return this.vuecal.isDOMElementAnEvent(el);
}, },
/** /**
@@ -136,20 +144,20 @@ export default {
*/ */
selectCell(DOMEvent, force = false) { selectCell(DOMEvent, force = false) {
// If splitting days, also return the clicked split on cell click when emitting event. // If splitting days, also return the clicked split on cell click when emitting event.
const split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null const split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null;
this.utils.cell.selectCell(force, this.timeAtCursor, split) this.utils.cell.selectCell(force, this.timeAtCursor, split);
this.timeAtCursor = null this.timeAtCursor = null;
}, },
onCellkeyPressEnter(DOMEvent) { onCellkeyPressEnter(DOMEvent) {
if (!this.isSelected) this.onCellFocus(DOMEvent) if (!this.isSelected) this.onCellFocus(DOMEvent);
// If splitting days, also return the clicked split on cell keypress when emitting event. // If splitting days, also return the clicked split on cell keypress when emitting event.
const split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null const split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null;
this.utils.cell.keyPressEnterCell(this.timeAtCursor, split) this.utils.cell.keyPressEnterCell(this.timeAtCursor, split);
this.timeAtCursor = null this.timeAtCursor = null;
}, },
/** /**
@@ -159,284 +167,346 @@ export default {
*/ */
onCellFocus(DOMEvent) { onCellFocus(DOMEvent) {
if (!this.isSelected && !this.isDisabled) { if (!this.isSelected && !this.isDisabled) {
this.isSelected = this.data.startDate // Highlight the cell. this.isSelected = this.data.startDate; // Highlight the cell.
// If splitting days, also return the clicked split on cell focus when emitting event. // If splitting days, also return the clicked split on cell focus when emitting event.
const split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null const split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null;
// Cell-focus event returns the cell start date (at midnight) if triggered from tab key, // Cell-focus event returns the cell start date (at midnight) if triggered from tab key,
// or cursor coords time if clicked. // or cursor coords time if clicked.
const date = this.timeAtCursor || this.data.startDate const date = this.timeAtCursor || this.data.startDate;
this.vuecal.$emit('cell-focus', split ? { date, split } : date) this.vuecal.$emit('cell-focus', split ? { date, split } : date);
} }
}, },
onCellMouseDown(DOMEvent, split = null, touch = false) { onCellMouseDown(DOMEvent, split = null, touch = false) {
// Prevent a double mouse down on touch devices. // Prevent a double mouse down on touch devices.
if ('ontouchstart' in window && !touch) return false if ('ontouchstart' in window && !touch) return false;
if (!this.isSelected) this.onCellFocus(DOMEvent) if (!this.isSelected) this.onCellFocus(DOMEvent);
const { clickHoldACell, focusAnEvent } = this.domEvents const { clickHoldACell, focusAnEvent } = this.domEvents;
// Reinit the click trigger on each mousedown. // Reinit the click trigger on each mousedown.
// In some cases we explicitly set this flag to prevent the click event to trigger, // In some cases we explicitly set this flag to prevent the click event to trigger,
// and cancel event creation. // and cancel event creation.
this.domEvents.cancelClickEventCreation = false this.domEvents.cancelClickEventCreation = false;
// Also reinit this var on each mousedown. // Also reinit this var on each mousedown.
clickHoldACell.eventCreated = false clickHoldACell.eventCreated = false;
this.timeAtCursor = new Date(this.data.startDate) this.timeAtCursor = new Date(this.data.startDate);
const { minutes, cursorCoords: { y } } = this.vuecal.minutesAtCursor(DOMEvent) const {
this.timeAtCursor.setMinutes(minutes) minutes,
cursorCoords: { y },
} = this.vuecal.minutesAtCursor(DOMEvent);
this.timeAtCursor.setMinutes(minutes);
const mouseDownOnEvent = this.isDOMElementAnEvent(DOMEvent.target) const mouseDownOnEvent = this.isDOMElementAnEvent(DOMEvent.target);
// Unfocus an event if any is focused and clicking on cell outside of an event. // Unfocus an event if any is focused and clicking on cell outside of an event.
if (!mouseDownOnEvent && focusAnEvent._eid) { if (!mouseDownOnEvent && focusAnEvent._eid) {
(this.view.events.find(e => e._eid === focusAnEvent._eid) || {}).focused = false (
this.view.events.find((e) => e._eid === focusAnEvent._eid) || {}
).focused = false;
} }
// Only if event creation is allowed and mousedown is on a cell (not on event). // Only if event creation is allowed and mousedown is on a cell (not on event).
if (this.editEvents.create && !mouseDownOnEvent) this.setUpEventCreation(DOMEvent, y) if (this.editEvents.create && !mouseDownOnEvent)
this.setUpEventCreation(DOMEvent, y);
}, },
setUpEventCreation(DOMEvent, startCursorY) { setUpEventCreation(DOMEvent, startCursorY) {
// If dragToCreateEvent is true, start the event creation from dragging // If dragToCreateEvent is true, start the event creation from dragging
// only on week and day views (doesn't make sense on month view). // only on week and day views (doesn't make sense on month view).
if (this.options.dragToCreateEvent && ['week', 'day'].includes(this.view.id)) { if (
const { dragCreateAnEvent } = this.domEvents this.options.dragToCreateEvent &&
dragCreateAnEvent.startCursorY = startCursorY ['week', 'day'].includes(this.view.id)
) {
const { dragCreateAnEvent } = this.domEvents;
dragCreateAnEvent.startCursorY = startCursorY;
// If splitting days, store the clicked split to create an event in it from the global // If splitting days, store the clicked split to create an event in it from the global
// mousemove handler in index.vue. // mousemove handler in index.vue.
dragCreateAnEvent.split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null dragCreateAnEvent.split = this.splitsCount
? this.getSplitAtCursor(DOMEvent)
: null;
// Save the time at cursor on initial mousedown. // Save the time at cursor on initial mousedown.
dragCreateAnEvent.start = this.timeAtCursor dragCreateAnEvent.start = this.timeAtCursor;
// If snapToTime, set the start to the closest intervaled number. // If snapToTime, set the start to the closest intervaled number.
if (this.options.snapToTime) { if (this.options.snapToTime) {
let timeMinutes = this.timeAtCursor.getHours() * 60 + this.timeAtCursor.getMinutes() let timeMinutes =
const plusHalfSnapTime = timeMinutes + this.options.snapToTime / 2 this.timeAtCursor.getHours() * 60 + this.timeAtCursor.getMinutes();
timeMinutes = plusHalfSnapTime - (plusHalfSnapTime % this.options.snapToTime) const plusHalfSnapTime = timeMinutes + this.options.snapToTime / 2;
timeMinutes =
plusHalfSnapTime - (plusHalfSnapTime % this.options.snapToTime);
dragCreateAnEvent.start.setHours(0, timeMinutes, 0, 0) dragCreateAnEvent.start.setHours(0, timeMinutes, 0, 0);
} }
} }
// If the cellClickHold option is true and not mousedown on an event, click & hold to create an event. // If the cellClickHold option is true and not mousedown on an event, click & hold to create an event.
else if (this.options.cellClickHold && ['month', 'week', 'day'].includes(this.view.id)) { else if (
this.setUpCellHoldTimer(DOMEvent) this.options.cellClickHold &&
['month', 'week', 'day'].includes(this.view.id)
) {
this.setUpCellHoldTimer(DOMEvent);
} }
}, },
// When click & holding a cell, and if allowed, set a timeout to create an event (can be cancelled). // When click & holding a cell, and if allowed, set a timeout to create an event (can be cancelled).
setUpCellHoldTimer(DOMEvent) { setUpCellHoldTimer(DOMEvent) {
const { clickHoldACell } = this.domEvents const { clickHoldACell } = this.domEvents;
clickHoldACell.cellId = `${this.vuecal._.uid}_${this.data.formattedDate}` clickHoldACell.cellId = `${this.vuecal._.uid}_${this.data.formattedDate}`;
// If splitting days, store the clicked split to create an event in it from the global // If splitting days, store the clicked split to create an event in it from the global
// mousemove handler in index.vue. // mousemove handler in index.vue.
clickHoldACell.split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null clickHoldACell.split = this.splitsCount
? this.getSplitAtCursor(DOMEvent)
: null;
clickHoldACell.timeoutId = setTimeout(() => { clickHoldACell.timeoutId = setTimeout(() => {
if (clickHoldACell.cellId && !this.domEvents.cancelClickEventCreation) { if (clickHoldACell.cellId && !this.domEvents.cancelClickEventCreation) {
const { _eid } = this.utils.event.createAnEvent( const { _eid } = this.utils.event.createAnEvent(
this.timeAtCursor, this.timeAtCursor,
null, null,
clickHoldACell.split ? { split: clickHoldACell.split } : {} clickHoldACell.split ? { split: clickHoldACell.split } : {},
) );
clickHoldACell.eventCreated = _eid clickHoldACell.eventCreated = _eid;
} }
}, clickHoldACell.timeout) }, clickHoldACell.timeout);
}, },
onCellTouchStart(DOMEvent, split = null) { onCellTouchStart(DOMEvent, split = null) {
// If not mousedown on an event. // If not mousedown on an event.
this.onCellMouseDown(DOMEvent, split, true) this.onCellMouseDown(DOMEvent, split, true);
}, },
onCellClick(DOMEvent, split) { onCellClick(DOMEvent, split) {
if (DOMEvent.offsetY > 950 && DOMEvent.offsetY < 1700) { const dateFrom = split.date;
const dateFrom = split.date dateFrom.setHours(Math.floor(DOMEvent.offsetY / 100), 0, 0, 0);
dateFrom.setHours(Math.floor(DOMEvent.offsetY / 100), 0, 0, 0) const dateTo = new Date(dateFrom);
const dateTo = new Date(dateFrom) dateTo.setHours(dateFrom.getHours() + 1);
dateTo.setHours(dateFrom.getHours() + 1)
const time = moment(dateFrom).format('HH:mm') const time = moment(dateFrom).format('HH:mm');
const isTeamCalendar = this.$route.name === 'Dashboard - Teams' const isTeamCalendar = this.$route.name === 'Dashboard - Teams';
const employee = isTeamCalendar ? split.employee_id : Number(localStorage.getItem('selectedEmployee') || this.user.id) const employee = isTeamCalendar
? split.employee_id
: Number(localStorage.getItem('selectedEmployee') || this.user.id);
this.$events.emit('show-menu-modal', { this.$events.emit('show-menu-modal', {
time, time,
date: tmUtils.formatDateTimeForBackend(dateFrom), date: tmUtils.formatDateTimeForBackend(dateFrom),
employee employee,
}) });
setTimeout(() => { setTimeout(() => {
this.$events.emit('add-timeblock', { this.$events.emit('add-timeblock', {
id: null, id: null,
start: tmUtils.formatDateTimeForBackend(dateFrom), start: tmUtils.formatDateTimeForBackend(dateFrom),
end: tmUtils.formatDateTimeForBackend(dateTo) end: tmUtils.formatDateTimeForBackend(dateTo),
}) });
}, 150) }, 150);
}
if (!this.isDOMElementAnEvent(DOMEvent.target)) this.selectCell(DOMEvent) if (!this.isDOMElementAnEvent(DOMEvent.target)) this.selectCell(DOMEvent);
}, },
onCellDblClick(DOMEvent) { onCellDblClick(DOMEvent) {
const date = new Date(this.data.startDate) const date = new Date(this.data.startDate);
date.setMinutes(this.vuecal.minutesAtCursor(DOMEvent).minutes) date.setMinutes(this.vuecal.minutesAtCursor(DOMEvent).minutes);
// If splitting days, also return the clicked split on cell dblclick when emitting event. // If splitting days, also return the clicked split on cell dblclick when emitting event.
const split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null const split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null;
this.vuecal.$emit('cell-dblclick', split ? { date, split } : date) this.vuecal.$emit('cell-dblclick', split ? { date, split } : date);
if (this.options.dblclickToNavigate) this.vuecal.switchToNarrowerView() if (this.options.dblclickToNavigate) this.vuecal.switchToNarrowerView();
}, },
onCellContextMenu(DOMEvent) { onCellContextMenu(DOMEvent) {
DOMEvent.stopPropagation() DOMEvent.stopPropagation();
DOMEvent.preventDefault() DOMEvent.preventDefault();
const date = new Date(this.data.startDate) const date = new Date(this.data.startDate);
const { cursorCoords, minutes } = this.vuecal.minutesAtCursor(DOMEvent) const { cursorCoords, minutes } = this.vuecal.minutesAtCursor(DOMEvent);
date.setMinutes(minutes) date.setMinutes(minutes);
// If splitting days, also return the clicked split on cell contextmenu when emitting event. // If splitting days, also return the clicked split on cell contextmenu when emitting event.
const split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null const split = this.splitsCount ? this.getSplitAtCursor(DOMEvent) : null;
this.vuecal.$emit('cell-contextmenu', { date, ...cursorCoords, ...(split || {}), e: DOMEvent }) this.vuecal.$emit('cell-contextmenu', {
} date,
...cursorCoords,
...(split || {}),
e: DOMEvent,
});
},
}, },
computed: { computed: {
...mapGetters("auth", ["user"]), ...mapGetters('auth', ['user']),
// Drag & drop module. // Drag & drop module.
dnd() { dnd() {
return this.modules.dnd return this.modules.dnd;
}, },
nowInMinutes() { nowInMinutes() {
return this.utils.date.dateToMinutes(this.vuecal.now) return this.utils.date.dateToMinutes(this.vuecal.now);
}, },
isBeforeMinDate() { isBeforeMinDate() {
return this.minTimestamp !== null && this.minTimestamp > this.data.endDate.getTime() return (
this.minTimestamp !== null &&
this.minTimestamp > this.data.endDate.getTime()
);
}, },
isAfterMaxDate() { isAfterMaxDate() {
return this.maxTimestamp && this.maxTimestamp < this.data.startDate.getTime() return (
this.maxTimestamp && this.maxTimestamp < this.data.startDate.getTime()
);
}, },
// Is the current cell disabled or not. // Is the current cell disabled or not.
isDisabled() { isDisabled() {
const { disableDays } = this.options const { disableDays } = this.options;
const { isYearsOrYearView } = this.vuecal const { isYearsOrYearView } = this.vuecal;
if (disableDays.length && disableDays.includes(this.data.formattedDate) && !isYearsOrYearView) return true if (
return this.isBeforeMinDate || this.isAfterMaxDate disableDays.length &&
disableDays.includes(this.data.formattedDate) &&
!isYearsOrYearView
)
return true;
return this.isBeforeMinDate || this.isAfterMaxDate;
}, },
// Is the current cell selected or not. // Is the current cell selected or not.
isSelected: { isSelected: {
get() { get() {
let selected = false let selected = false;
const { selectedDate } = this.view const { selectedDate } = this.view;
if (this.view.id === 'years') { if (this.view.id === 'years') {
selected = selectedDate.getFullYear() === this.data.startDate.getFullYear() selected =
} selectedDate.getFullYear() === this.data.startDate.getFullYear();
else if (this.view.id === 'year') { } else if (this.view.id === 'year') {
selected = (selectedDate.getFullYear() === this.data.startDate.getFullYear() && selected =
selectedDate.getMonth() === this.data.startDate.getMonth()) selectedDate.getFullYear() === this.data.startDate.getFullYear() &&
} selectedDate.getMonth() === this.data.startDate.getMonth();
else selected = selectedDate.getTime() === this.data.startDate.getTime() } else
selected = selectedDate.getTime() === this.data.startDate.getTime();
return selected return selected;
}, },
set(date) { set(date) {
this.view.selectedDate = date this.view.selectedDate = date;
this.vuecal.$emit('update:selected-date', this.view.selectedDate) this.vuecal.$emit('update:selected-date', this.view.selectedDate);
} },
}, },
// Cache result for performance. // Cache result for performance.
isWeekOrDayView() { isWeekOrDayView() {
return ['week', 'day'].includes(this.view.id) return ['week', 'day'].includes(this.view.id);
}, },
transitionDirection() { transitionDirection() {
return this.vuecal.transitionDirection return this.vuecal.transitionDirection;
}, },
specialHours() { specialHours() {
// this.data.specialHours is always an array, but may be empty. // this.data.specialHours is always an array, but may be empty.
return this.data.specialHours.map(block => { return this.data.specialHours.map((block) => {
let { from, to } = block let { from, to } = block;
from = Math.max(from, this.options.timeFrom) from = Math.max(from, this.options.timeFrom);
to = Math.min(to, this.options.timeTo) to = Math.min(to, this.options.timeTo);
return { return {
...block, ...block,
height: (to - from) * this.timeScale, height: (to - from) * this.timeScale,
top: (from - this.options.timeFrom) * this.timeScale top: (from - this.options.timeFrom) * this.timeScale,
} };
}) });
}, },
events() { events() {
const { startDate: cellStart, endDate: cellEnd } = this.data const { startDate: cellStart, endDate: cellEnd } = this.data;
let events = [] let events = [];
// Calculate events on month/week/day views or years/year if eventsCountOnYearView. // Calculate events on month/week/day views or years/year if eventsCountOnYearView.
if (!(['years', 'year'].includes(this.view.id) && !this.options.eventsCountOnYearView)) { if (
!(
['years', 'year'].includes(this.view.id) &&
!this.options.eventsCountOnYearView
)
) {
// Means that when vuecal.view.events changes all the cells will be looking up new value. :/ // Means that when vuecal.view.events changes all the cells will be looking up new value. :/
// Also clone array to prevent modifying original. // Also clone array to prevent modifying original.
events = this.view.events.slice(0) events = this.view.events.slice(0);
if (this.view.id === 'month') { if (this.view.id === 'month') {
events.push(...this.view.outOfScopeEvents) events.push(...this.view.outOfScopeEvents);
} }
// Only keep events in cell time range. // Only keep events in cell time range.
events = events.filter(e => this.utils.event.eventInRange(e, cellStart, cellEnd)) events = events.filter((e) =>
this.utils.event.eventInRange(e, cellStart, cellEnd),
);
if (this.options.showAllDayEvents && this.view.id !== 'month') events = events.filter(e => !!e.allDay === this.allDay) if (this.options.showAllDayEvents && this.view.id !== 'month')
events = events.filter((e) => !!e.allDay === this.allDay);
// From events in view, filter the ones that are out of `time-from`-`time-to` range in this cell. // From events in view, filter the ones that are out of `time-from`-`time-to` range in this cell.
if (this.options.time && this.isWeekOrDayView && !this.allDay) { if (this.options.time && this.isWeekOrDayView && !this.allDay) {
const { timeFrom, timeTo } = this.options const { timeFrom, timeTo } = this.options;
events = events.filter(e => { events = events.filter((e) => {
const segment = (e.daysCount > 1 && e.segments[this.data.formattedDate]) || {} const segment =
const singleDayInRange = e.daysCount === 1 && e.startTimeMinutes < timeTo && e.endTimeMinutes > timeFrom (e.daysCount > 1 && e.segments[this.data.formattedDate]) || {};
const multipleDayInRange = e.daysCount > 1 && (segment.startTimeMinutes < timeTo && segment.endTimeMinutes > timeFrom) const singleDayInRange =
const recurrMultDayInRange = false // e.daysCount > 1 && e.repeat && recurringEventInRange(e, cellStart, cellEnd) e.daysCount === 1 &&
return (e.allDay || singleDayInRange || multipleDayInRange || recurrMultDayInRange) e.startTimeMinutes < timeTo &&
}) e.endTimeMinutes > timeFrom;
const multipleDayInRange =
e.daysCount > 1 &&
segment.startTimeMinutes < timeTo &&
segment.endTimeMinutes > timeFrom;
const recurrMultDayInRange = false; // e.daysCount > 1 && e.repeat && recurringEventInRange(e, cellStart, cellEnd)
return (
e.allDay ||
singleDayInRange ||
multipleDayInRange ||
recurrMultDayInRange
);
});
} }
// Position events with time in the timeline when there is a timeline and not in allDay slot. // Position events with time in the timeline when there is a timeline and not in allDay slot.
if (this.options.time && this.isWeekOrDayView && !(this.options.showAllDayEvents && this.allDay)) { if (
this.options.time &&
this.isWeekOrDayView &&
!(this.options.showAllDayEvents && this.allDay)
) {
// Sort events in chronological order. // Sort events in chronological order.
events.sort((a, b) => a.start < b.start ? -1 : 1) events.sort((a, b) => (a.start < b.start ? -1 : 1));
} }
// If splits, checkCellOverlappingEvents() is called from within computed splits. // If splits, checkCellOverlappingEvents() is called from within computed splits.
if (!this.cellSplits.length) this.$nextTick(this.checkCellOverlappingEvents) if (!this.cellSplits.length)
this.$nextTick(this.checkCellOverlappingEvents);
} }
return events return events;
}, },
eventsCount() { eventsCount() {
return this.events.length return this.events.length;
}, },
splits() { splits() {
return this.cellSplits.map((item, i) => { return this.cellSplits.map((item, i) => {
const events = this.events.filter(e => e.split === item.id) const events = this.events.filter((e) => e.split === item.id);
const [overlaps, streak] = this.utils.event.checkCellOverlappingEvents(events.filter(e => !e.background && !e.allDay), this.options) const [overlaps, streak] = this.utils.event.checkCellOverlappingEvents(
events.filter((e) => !e.background && !e.allDay),
this.options,
);
return { return {
...item, ...item,
overlaps, overlaps,
overlapsStreak: streak, overlapsStreak: streak,
events events,
} };
}) });
}, },
splitsCount() { splitsCount() {
return this.splits.length return this.splits.length;
}, },
cellClasses() { cellClasses() {
return { return {
@@ -450,31 +520,37 @@ export default {
'vuecal__cell--selected': this.isSelected, 'vuecal__cell--selected': this.isSelected,
'vuecal__cell--highlighted': this.highlighted, 'vuecal__cell--highlighted': this.highlighted,
'vuecal__cell--has-splits': this.splitsCount, 'vuecal__cell--has-splits': this.splitsCount,
'vuecal__cell--has-events': this.eventsCount 'vuecal__cell--has-events': this.eventsCount,
} };
}, },
cellStyles() { cellStyles() {
return { return {
// cellWidth is only applied when hiding weekdays on month and week views. // cellWidth is only applied when hiding weekdays on month and week views.
...(this.cellWidth ? { width: `${this.cellWidth}%` } : {}) ...(this.cellWidth ? { width: `${this.cellWidth}%` } : {}),
} };
}, },
timelineVisible() { timelineVisible() {
const { time, timeTo } = this.options const { time, timeTo } = this.options;
return this.data.today && this.isWeekOrDayView && time && !this.allDay && this.nowInMinutes <= timeTo return (
this.data.today &&
this.isWeekOrDayView &&
time &&
!this.allDay &&
this.nowInMinutes <= timeTo
);
}, },
todaysTimePosition() { todaysTimePosition() {
// Skip the Maths if not relevant. // Skip the Maths if not relevant.
if (!this.data.today || !this.options.time) return if (!this.data.today || !this.options.time) return;
const minutesFromTop = this.nowInMinutes - this.options.timeFrom const minutesFromTop = this.nowInMinutes - this.options.timeFrom;
return Math.round(minutesFromTop * this.timeScale) return Math.round(minutesFromTop * this.timeScale);
}, },
timeScale() { timeScale() {
return this.options.timeCellHeight / this.options.timeStep return this.options.timeCellHeight / this.options.timeStep;
} },
} },
} };
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -499,15 +575,27 @@ export default {
width: 20%; width: 20%;
} }
.vuecal__cells.years-view & {width: 20%;} .vuecal__cells.years-view & {
.vuecal__cells.year-view & {width: 33.33%;} width: 20%;
.vuecal__cells.day-view & {flex: 1;} }
.vuecal--overflow-x.vuecal--day-view & {width: auto;} .vuecal__cells.year-view & {
width: 33.33%;
}
.vuecal__cells.day-view & {
flex: 1;
}
.vuecal--overflow-x.vuecal--day-view & {
width: auto;
}
.vuecal--click-to-navigate &:not(&--disabled) {cursor: pointer;} .vuecal--click-to-navigate &:not(&--disabled) {
cursor: pointer;
}
.vuecal--view-with-time &, .vuecal--view-with-time &,
.vuecal--week-view.vuecal--no-time &:not(.vuecal__cell--has-splits), .vuecal--week-view.vuecal--no-time &:not(.vuecal__cell--has-splits),
.vuecal--day-view.vuecal--no-time &:not(.vuecal__cell--has-splits) {display: block;} .vuecal--day-view.vuecal--no-time &:not(.vuecal__cell--has-splits) {
display: block;
}
&.vuecal__cell--has-splits { &.vuecal__cell--has-splits {
flex-direction: row; flex-direction: row;
@@ -524,7 +612,9 @@ export default {
bottom: -1px; bottom: -1px;
border: 1px solid rgba(196, 196, 196, 0.25); border: 1px solid rgba(196, 196, 196, 0.25);
} }
.vuecal--overflow-x.vuecal--day-view &:before {bottom: 0;} .vuecal--overflow-x.vuecal--day-view &:before {
bottom: 0;
}
&--today, &--today,
&--current { &--current {
@@ -536,14 +626,22 @@ export default {
background-color: rgba(235, 255, 245, 0.4); background-color: rgba(235, 255, 245, 0.4);
z-index: 2; z-index: 2;
.vuecal--day-view & {background: none;} .vuecal--day-view & {
background: none;
}
} }
&--out-of-scope {color: rgba(0, 0, 0, 0.25);} &--out-of-scope {
&--disabled {color: rgba(0, 0, 0, 0.25);cursor: not-allowed;} color: rgba(0, 0, 0, 0.25);
}
&--disabled {
color: rgba(0, 0, 0, 0.25);
cursor: not-allowed;
}
// Cells/splits get highlighted when dragging an event over it. // Cells/splits get highlighted when dragging an event over it.
&--highlighted:not(.vuecal__cell--has-splits), &-split.vuecal__cell-split--highlighted { &--highlighted:not(.vuecal__cell--has-splits),
&-split.vuecal__cell-split--highlighted {
background-color: rgba(0, 0, 0, 0.04); background-color: rgba(0, 0, 0, 0.04);
// Drag over feedback must be fast. Then it can fade away with longer duration. // Drag over feedback must be fast. Then it can fade away with longer duration.
transition-duration: 5ms; transition-duration: 5ms;
@@ -558,7 +656,9 @@ export default {
.vuecal--years-view &, .vuecal--years-view &,
.vuecal--year-view &, .vuecal--year-view &,
.vuecal--month-view & {justify-content: center;} .vuecal--month-view & {
justify-content: center;
}
} }
.cell-time-labels { .cell-time-labels {
@@ -587,7 +687,9 @@ export default {
transition: 0.15s ease-in-out background-color; transition: 0.15s ease-in-out background-color;
} }
&-events {width: 100%;} &-events {
width: 100%;
}
&-events-count { &-events-count {
position: absolute; position: absolute;
@@ -613,7 +715,8 @@ export default {
} }
} }
.vuecal--overflow-x.vuecal--week-view .vuecal__cell, .vuecal__cell-split { .vuecal--overflow-x.vuecal--week-view .vuecal__cell,
.vuecal__cell-split {
overflow: hidden; overflow: hidden;
} }
@@ -624,7 +727,9 @@ export default {
margin-bottom: auto; // Vertical align top within flex column and align center. margin-bottom: auto; // Vertical align top within flex column and align center.
} }
.vuecal__all-day .vuecal__no-event {display: none;} .vuecal__all-day .vuecal__no-event {
display: none;
}
.vuecal__now-line { .vuecal__now-line {
position: absolute; position: absolute;
@@ -637,7 +742,7 @@ export default {
z-index: 1; z-index: 1;
&:before { &:before {
content: ""; content: '';
position: absolute; position: absolute;
top: -6px; top: -6px;
left: 0; left: 0;