stage(bugfix) fixed calendar events height

This commit is contained in:
Matej Glasnák committed 2025-06-23 00:40:28 +02:00
commit b24fefc8d4
23 files changed
+426 -329

No files matched your search

@@ -1,332 +1,396 @@
<template>
<div class="calendar-wrapper">
<Vue-cal
:hide-view-selector="true"
:hide-title-bar="true"
:disable-views="['years', 'year', 'month', 'week']"
active-view="day"
:selected-date="calConfig.date"
:onEventClick="taskClicked"
:editable-events="vueCalConfig.editing"
:events="calData.tasks"
:unscheduledTime="calData.unscheduled_times"
:overlaps-per-time-step="false"
:showTimeInCells="false"
:time-cell-height="timeCellHeight"
:snap-to-time="15"
:scroll-to-hour="calConfig.scrollHour"
:split-days="splits"
:sticky-split-labels="true"
:min-split-width="140"
@ready="onReady()"
@view-change="weekChanged"
@event-drop="eventMoved"
@event-focus="eventFocused"
>
<template v-slot:split-label="{ split }">
<div class="w-[120px] h-100 d-flex flex-column">
<span v-if="split.date" class="d-flex align-center">
<img v-if="split.isHomeoffice" :src="require('@/plugins/app/_img/icons/home.svg')" class="mr-2">
<p class="weekday-label">{{ getHeadingDate(split.date) }}</p>
</span>
<span v-else class="d-flex align-center">
<img v-if="split.isHomeoffice" :src="require('@/plugins/app/_img/icons/home.svg')" class="mr-2">
<p class="weekday-label">{{ split.name }}</p>
</span>
<div class="calendar-wrapper">
<Vue-cal
:hide-view-selector="true"
:hide-title-bar="true"
:disable-views="['years', 'year', 'month', 'week']"
active-view="day"
:selected-date="calConfig.date"
:onEventClick="taskClicked"
:editable-events="vueCalConfig.editing"
:events="calData.tasks"
:unscheduledTime="calData.unscheduled_times"
:overlaps-per-time-step="false"
:showTimeInCells="false"
:time-cell-height="timeCellHeight"
:snap-to-time="15"
:scroll-to-hour="calConfig.scrollHour"
:split-days="splits"
:sticky-split-labels="true"
:min-split-width="140"
@ready="onReady()"
@view-change="weekChanged"
@event-drop="eventMoved"
@event-focus="eventFocused"
>
<template v-slot:split-label="{ split }">
<div class="w-[120px] h-100 d-flex flex-column">
<span v-if="split.date" class="d-flex align-center">
<img
v-if="split.isHomeoffice"
:src="require('@/plugins/app/_img/icons/home.svg')"
class="mr-2"
/>
<p class="weekday-label">{{ getHeadingDate(split.date) }}</p>
</span>
<span v-else class="d-flex align-center">
<img
v-if="split.isHomeoffice"
:src="require('@/plugins/app/_img/icons/home.svg')"
class="mr-2"
/>
<p class="weekday-label">{{ split.name }}</p>
</span>
<span v-if="split.date">
<p class="day-name" >{{ split.date.toLocaleDateString('en-EN', { weekday: 'long' }) }}</p>
</span>
<span v-if="split.date">
<p class="day-name">
{{ split.date.toLocaleDateString('en-EN', { weekday: 'long' }) }}
</p>
</span>
<div class="unscheduled-time d-flex align-center text-left" :class="{'vuecal__cell-split--weekend': split.isWeekend}">
<div class="unscheduled-time-initials d-flex align-center justify-center">
<p>{{ split.initials }}</p>
</div>
<div class="text-wrap me-4">
<p class="mb-0 mt-0 unscheduled-time-name">{{split.fullname}}</p>
<h1 class="mb-0 mt-0">{{split.unscheduledTime}} h</h1>
<h1 class="mb-0 mt-0" style="font-weight:400;">{{split.scheduledTime}} h</h1>
</div>
</div>
</div>
</template>
<div
class="unscheduled-time d-flex align-center text-left"
:class="{ 'vuecal__cell-split--weekend': split.isWeekend }"
>
<div
class="unscheduled-time-initials d-flex align-center justify-center"
>
<p>{{ split.initials }}</p>
</div>
<div class="text-wrap me-4">
<p class="mb-0 mt-0 unscheduled-time-name">
{{ split.fullname }}
</p>
<h1 class="mb-0 mt-0">{{ split.unscheduledTime }} h</h1>
<h1 class="mb-0 mt-0" style="font-weight: 400">
{{ split.scheduledTime }} h
</h1>
</div>
</div>
</div>
</template>
<template v-slot:time-cell="{ hours, minutes }">
<span class="d-flex justify-center">
<span class="d-flex justify-center">
<p style="margin-top: -9px">{{hours}}:{{String(minutes).padStart(2, '0')}}</p>
<div class="time-column-line"></div>
<span class="vuecal__time-cell-line hour"></span>
</span>
<template v-slot:time-cell="{ hours, minutes }">
<span class="d-flex justify-center">
<span class="d-flex justify-center">
<p style="margin-top: -9px">
{{ hours }}:{{ String(minutes).padStart(2, '0') }}
</p>
<div class="time-column-line"></div>
<span class="vuecal__time-cell-line hour"></span>
</span>
<span class="vuecal__time-cell-line" style="margin-top: 25px"></span>
<span class="vuecal__time-cell-line" style="margin-top: 50px"></span>
<span class="vuecal__time-cell-line" style="margin-top: 75px"></span>
</span>
</template>
<span class="vuecal__time-cell-line" style="margin-top: 25px"></span>
<span class="vuecal__time-cell-line" style="margin-top: 50px"></span>
<span class="vuecal__time-cell-line" style="margin-top: 75px"></span>
</span>
</template>
<template v-slot:event="{ event }">
<A-timeline-event :event="event"
@edit-blocker="editBlocker"
@delete-blocker="deleteBlocker"
@complete-task="completeTask"
@toggle-timeblock-is-finished="toggleTimeblockIsFinished"
@edit-task="editTask"
@delete-timeblock-in-task="deleteTimeBlockInTask"
/>
</template>
</Vue-cal>
</div>
<template v-slot:event="{ event }">
<A-timeline-event
:event="event"
@edit-blocker="editBlocker"
@delete-blocker="deleteBlocker"
@complete-task="completeTask"
@toggle-timeblock-is-finished="toggleTimeblockIsFinished"
@edit-task="editTask"
@delete-timeblock-in-task="deleteTimeBlockInTask"
/>
</template>
</Vue-cal>
</div>
</template>
<script>
import { addDays } from 'date-fns'
import tmUtils from '@/plugins/tm@_utils/tm.utils'
import { mapGetters } from 'vuex'
import moment from 'moment'
import ATimelineEvent from './a-timeline-event.vue'
import { addDays } from 'date-fns';
import tmUtils from '@/plugins/tm@_utils/tm.utils';
import { mapGetters } from 'vuex';
import moment from 'moment';
import ATimelineEvent from './a-timeline-event.vue';
export default {
components: {
ATimelineEvent,
},
props: {
calData: {
type: Object,
required: true,
default() {
return {}
}
},
calConfig: {
type: Object,
required: true,
default() {
return {date: new Date(), scrollHour: 7}
}
},
splits: {
type: Array,
required: false,
default() {
return []
}
}
},
components: {
ATimelineEvent,
},
props: {
calData: {
type: Object,
required: true,
default() {
return {};
},
},
calConfig: {
type: Object,
required: true,
default() {
return { date: new Date(), scrollHour: 7 };
},
},
splits: {
type: Array,
required: false,
default() {
return [];
},
},
},
data() {
return {
vueCalConfig: {
editing: {
title: false,
drag: true,
resize: false,
delete: false,
create: false
}
},
focusedEventId: null,
timeCellHeight: 100,
tmUtils,
visible: false
}
},
data() {
return {
vueCalConfig: {
editing: {
title: false,
drag: true,
resize: false,
delete: false,
create: false,
},
},
focusedEventId: null,
timeCellHeight: 100,
tmUtils,
visible: false,
};
},
computed: {
...mapGetters('tm', ['mastertasks'])
},
computed: {
...mapGetters('tm', ['mastertasks']),
},
methods: {
isAfterDeadline(event) {
const deadline = this.getDeadline(event.subtask)
methods: {
isAfterDeadline(event) {
const deadline = this.getDeadline(event.subtask);
if (!deadline)
return
if (!deadline) return;
const mDate = moment(event.date)
const mDeadline = moment(deadline)
const hours = event.end.getHours()
const minutes = event.end.getMinutes()
const mDate = moment(event.date);
const mDeadline = moment(deadline);
const hours = event.end.getHours();
const minutes = event.end.getMinutes();
mDate.hours(hours)
mDate.minutes(minutes)
mDate.hours(hours);
mDate.minutes(minutes);
return mDate.isAfter(mDeadline)
},
return mDate.isAfter(mDeadline);
},
getDeadline(subtask) {
if (subtask?.deadline)
return subtask.deadline
getDeadline(subtask) {
if (subtask?.deadline) return subtask.deadline;
return subtask.master_task_deadline
},
return subtask.master_task_deadline;
},
weekChanged() {
const scrollContainer = this.$el.getElementsByClassName('vuecal__scrollable-body')[0]
scrollContainer.scrollLeft = 0
},
weekChanged() {
const scrollContainer = this.$el.getElementsByClassName(
'vuecal__scrollable-body',
)[0];
scrollContainer.scrollLeft = 0;
},
async eventMoved({ event, newSplit }) {
const date = addDays(this.calConfig.date, newSplit - 1)
const start = date.setHours(Math.floor(event.startTimeMinutes / 60), Math.round(event.startTimeMinutes % 60), 0)
const end = date.setHours(Math.floor(event.endTimeMinutes / 60), Math.round(event.endTimeMinutes % 60), 0)
const split = this.splits.find(split => split.id == newSplit )
async eventMoved({ event, newSplit }) {
const date = addDays(this.calConfig.date, newSplit - 1);
const start = date.setHours(
Math.floor(event.startTimeMinutes / 60),
Math.round(event.startTimeMinutes % 60),
0,
);
const end = date.setHours(
Math.floor(event.endTimeMinutes / 60),
Math.round(event.endTimeMinutes % 60),
0,
);
const split = this.splits.find((split) => split.id == newSplit);
if(this.$route.name == 'Dashboard - Tasks') {
const editedSubtask = {
...event.subtask,
timeblocks: [...event.subtask.timeblocks, {
id: event._timeblock_id,
is_finished: 0,
start: tmUtils.formatDateTimeForBackend(start),
end: tmUtils.formatDateTimeForBackend(end)
}],
timesheets: event.subtask.timesheets.map((timesheet) => {
delete timesheet.employee_name
return timesheet
})
}
if (this.$route.name == 'Dashboard - Tasks') {
const editedSubtask = {
...event.subtask,
timeblocks: [
...event.subtask.timeblocks,
{
id: event._timeblock_id,
is_finished: 0,
start: tmUtils.formatDateTimeForBackend(start),
end: tmUtils.formatDateTimeForBackend(end),
},
],
timesheets: event.subtask.timesheets.map((timesheet) => {
delete timesheet.employee_name;
return timesheet;
}),
};
try {
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${editedSubtask.id}`, editedSubtask)
this.$events.emit('on-subtask-edited', subtask)
try {
const { subtask } = await this.$axios.put_auth(
`v1/subtasks/${editedSubtask.id}`,
editedSubtask,
);
this.$events.emit('on-subtask-edited', subtask);
if (subtask.employee_id == this.$store.getters['auth/user'].id)
this.$events.emit('show-task-add-time-modal', { subtask })
} catch (error) {
this.$toast.error(error.message || error)
if (subtask.employee_id == this.$store.getters['auth/user'].id)
this.$events.emit('show-task-add-time-modal', { subtask });
} catch (error) {
this.$toast.error(error.message || error);
if (process.env.NODE_ENV === 'development')
console.log('[z-timeline.vue] Error: ', error)
}
if (process.env.NODE_ENV === 'development')
console.log('[z-timeline.vue] Error: ', error);
}
const isAfterDeadline = this.isAfterDeadline(event)
const isAfterDeadline = this.isAfterDeadline(event);
if(isAfterDeadline) this.$toast.error('Timeblock, ktorý ste presunuli je po deadline !')
} else if(this.$route.name == 'Dashboard - Teams') {
const editedSubtask = {
...event.subtask,
employee_id: split.employee_id,
timeblocks: [...event.subtask.timeblocks, {
id: event._timeblock_id,
is_finished: 0,
start: tmUtils.formatDateTimeForBackend(event.start),
end: tmUtils.formatDateTimeForBackend(event.end)
}],
timesheets: event.subtask.timesheets.map((timesheet) => {
delete timesheet.employee_name
return timesheet
})
}
try {
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${editedSubtask.id}`, editedSubtask)
this.$events.emit('on-subtask-edited', subtask)
} catch (error) {
this.$toast.error(error.message || error)
if (isAfterDeadline)
this.$toast.error('Timeblock, ktorý ste presunuli je po deadline !');
} else if (this.$route.name == 'Dashboard - Teams') {
const editedSubtask = {
...event.subtask,
employee_id: split.employee_id,
timeblocks: [
...event.subtask.timeblocks,
{
id: event._timeblock_id,
is_finished: 0,
start: tmUtils.formatDateTimeForBackend(event.start),
end: tmUtils.formatDateTimeForBackend(event.end),
},
],
timesheets: event.subtask.timesheets.map((timesheet) => {
delete timesheet.employee_name;
return timesheet;
}),
};
try {
const { subtask } = await this.$axios.put_auth(
`v1/subtasks/${editedSubtask.id}`,
editedSubtask,
);
this.$events.emit('on-subtask-edited', subtask);
} catch (error) {
this.$toast.error(error.message || error);
if (process.env.NODE_ENV === 'development')
console.log('[z-timeline.vue] Error: ', error)
}
}
},
if (process.env.NODE_ENV === 'development')
console.log('[z-timeline.vue] Error: ', error);
}
}
},
onReady() {
this.correctMargins()
},
onReady() {
this.correctMargins();
},
correctMargins() {
if (!this.$el.getElementsByClassName('vuecal__split-days-headers').length)
return
correctMargins() {
if (!this.$el.getElementsByClassName('vuecal__split-days-headers').length)
return;
const heightOfDayHeader = this.$el.getElementsByClassName('vuecal__split-days-headers')[0].clientHeight
this.$el.getElementsByClassName('vuecal__time-column')[0].style.marginTop = `${heightOfDayHeader}px`
this.$el.getElementsByClassName('vuecal__cells-body')[0].style.marginTop = `${heightOfDayHeader}px`
},
const heightOfDayHeader = this.$el.getElementsByClassName(
'vuecal__split-days-headers',
)[0].clientHeight;
this.$el.getElementsByClassName(
'vuecal__time-column',
)[0].style.marginTop = `${heightOfDayHeader}px`;
this.$el.getElementsByClassName(
'vuecal__cells-body',
)[0].style.marginTop = `${heightOfDayHeader}px`;
},
getHeadingDate(value) {
const date = new Date(value)
getHeadingDate(value) {
const date = new Date(value);
const today = new Date()
const yesterday = new Date((new Date()).setDate(today.getDate() - 1))
const tomorrow = new Date((new Date()).setDate(today.getDate() + 1))
const today = new Date();
const yesterday = new Date(new Date().setDate(today.getDate() - 1));
const tomorrow = new Date(new Date().setDate(today.getDate() + 1));
if (today.toLocaleDateString('sk-SK') === date.toLocaleDateString('sk-SK')) return "Dnes"
if (yesterday.toLocaleDateString('sk-SK') === date.toLocaleDateString('sk-SK')) return "Včera"
if (tomorrow.toLocaleDateString('sk-SK') === date.toLocaleDateString('sk-SK')) return "Zajtra"
if (
today.toLocaleDateString('sk-SK') === date.toLocaleDateString('sk-SK')
)
return 'Dnes';
if (
yesterday.toLocaleDateString('sk-SK') ===
date.toLocaleDateString('sk-SK')
)
return 'Včera';
if (
tomorrow.toLocaleDateString('sk-SK') ===
date.toLocaleDateString('sk-SK')
)
return 'Zajtra';
return date.toLocaleDateString('sk-SK')
},
return date.toLocaleDateString('sk-SK');
},
taskClicked(subtask) {
// console.log('task clicked')
},
taskClicked(subtask) {
// console.log('task clicked')
},
eventFocused(event) {
this.focusedEventId = event.task_id
// console.log(`Focused: ${event.task_id}`)
},
eventFocused(event) {
this.focusedEventId = event.task_id;
// console.log(`Focused: ${event.task_id}`)
},
completeTask({ subtask }) {
this.$events.emit('show-complete-task-modal', { subtask })
},
completeTask({ subtask }) {
this.$events.emit('show-complete-task-modal', { subtask });
},
// async toggleTimeblockIsFinished(event) {
// if (!event.timeblock.is_finished)
// this.$events.emit('show-task-add-time-modal', { subtask: event.subtask })
// async toggleTimeblockIsFinished(event) {
// if (!event.timeblock.is_finished)
// this.$events.emit('show-task-add-time-modal', { subtask: event.subtask })
// try {
// await this.$axios.put_auth(`v1/timeblocks/${event._timeblock_id}`, {is_finished: !event.timeblock.is_finished})
// // TODO: Co mi to vrati? A updatenut
// } catch (error) {
// this.$toast.error(error.message || error)
// }
// },
// try {
// await this.$axios.put_auth(`v1/timeblocks/${event._timeblock_id}`, {is_finished: !event.timeblock.is_finished})
// // TODO: Co mi to vrati? A updatenut
// } catch (error) {
// this.$toast.error(error.message || error)
// }
// },
async toggleTimeblockIsFinished(event) {
const subtask = event.subtask
const timeblock = subtask.timeblocks.find(originalTimeblock => originalTimeblock.id == event._timeblock_id)
async toggleTimeblockIsFinished(event) {
const subtask = event.subtask;
const timeblock = subtask.timeblocks.find(
(originalTimeblock) => originalTimeblock.id == event._timeblock_id,
);
timeblock.is_finished = timeblock?.is_finished ? 0 : 1
timeblock.is_finished = timeblock?.is_finished ? 0 : 1;
try {
const {data: newTimeblock} = await this.$axios.put_auth(`v1/timeblocks/${timeblock.id}`, timeblock)
try {
const { data: newTimeblock } = await this.$axios.put_auth(
`v1/timeblocks/${timeblock.id}`,
timeblock,
);
subtask.timeblocks = subtask.timeblocks.map(originalTimeblock => {
if (originalTimeblock.id == newTimeblock.id)
return newTimeblock
subtask.timeblocks = subtask.timeblocks.map((originalTimeblock) => {
if (originalTimeblock.id == newTimeblock.id) return newTimeblock;
return originalTimeblock
})
return originalTimeblock;
});
this.$events.emit('on-subtask-edited', subtask)
this.$events.emit('on-subtask-edited', subtask);
if (timeblock.is_finished)
this.$events.emit('show-task-add-time-modal', { subtask })
} catch (error) {
this.$toast.error(error.message || error)
if (timeblock.is_finished)
this.$events.emit('show-task-add-time-modal', { subtask });
} catch (error) {
this.$toast.error(error.message || error);
if (process.env.NODE_ENV === 'development')
console.log('[z-timeline.vue] Error: ', error)
}
},
if (process.env.NODE_ENV === 'development')
console.log('[z-timeline.vue] Error: ', error);
}
},
editTask({subtask}) {
this.$events.emit('show-subtask-modal', {subtask})
},
editTask({ subtask }) {
this.$events.emit('show-subtask-modal', { subtask });
},
deleteTimeBlockInTask({ subtask }) {
this.$events.emit('show-delete-timeblock-modal', {subtask})
},
deleteTimeBlockInTask({ subtask }) {
this.$events.emit('show-delete-timeblock-modal', { subtask });
},
editBlocker({ blocker }) {
this.$events.emit('show-blocker-modal', {blocker})
},
editBlocker({ blocker }) {
this.$events.emit('show-blocker-modal', { blocker });
},
deleteBlocker({ blocker }) {
this.$events.emit('show-delete-blocker-modal', {blocker})
},
}
}
deleteBlocker({ blocker }) {
this.$events.emit('show-delete-blocker-modal', { blocker });
},
},
};
</script>
<style lang="sass" scoped>
@@ -472,6 +536,7 @@ export default {
text-align: left
background: rgba(248, 248, 248, 1)
transition: min-height .4s, height .4s, box-shadow .4s, width .4s, left .4s
// border: 1px solid rgba(33, 33, 33, 0.1)
&:has(.completed)
background: rgb(251, 251, 251) !important
@@ -483,10 +548,17 @@ export default {
&.vuecal__event--focus
z-index: 100 !important
min-height: 350px !important
// min-height: 350px !important
height: fit-content !important
width: 100% !important
left: 0 !important
.event__footer-wrapper
position: relative
&__top
padding: 10px 0 0 0
&:not(.vuecal__event--focus)
&:has(.micro-event) .event__body
padding: 5px !important
@@ -522,7 +594,7 @@ export default {
.micro-event
.event__footer
.event__footer-wrapper
display: none !important
.project-code