Files
jandl-taskmanager-app/app/src/plugins/tm@dashboard/tasks/_components/z-timeline.vue
T

421 lines
11 KiB
Vue

<template>
<div class="calendar-wrapper">
<Vue-cal
:hide-view-selector="true"
:hide-title-bar="true"
:disable-views="['years', 'year', 'month', '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="100"
:snap-to-time="15"
:min-cell-width="300"
@ready="onReady()"
@view-change="weekChanged"
@event-drop="eventMoved"
@event-focus="eventFocused"
>
<template v-slot:weekday-heading="{ heading }">
<div class="w-100 h-100 d-flex flex-column">
<span class="d-flex align-center">
<img v-if="getHeadingDate(heading.date) == 'Dnes'" :src="require('./_img/home-icon.svg')" class="mr-2">
<p>{{ getHeadingDate(heading.date) }}</p>
</span>
<div v-if="heading.unscheduledTime && heading.initials" class="unscheduled-time d-flex align-center text-left">
<div class="unscheduled-time-initials d-flex align-center justify-center me-4">
<p>{{heading.initials}}</p>
</div>
<div>
<p class="mb-0 mt-0 unscheduled-time-name">{{heading.name}}</p>
<h1 class="mb-0 mt-0">{{heading.unscheduledTime}} h</h1>
</div>
</div>
</div>
</template>
<template v-slot:time-cell="{ hours, minutes }">
<span class="d-flex align-center justify-center">
<p>{{hours}}:{{String(minutes).padStart(2, '0')}}</p>
<div class="time-column-line"></div>
</span>
</template>
<template v-slot:event="{ event }">
<div class="event-wrapper w-100 h-100 d-flex flex-column justify-space-between" :class="{'short-event' : getEventLenght(event) <= 9000000, 'mini-event' : getEventLenght(event) <= 5400000, 'micro-event' : getEventLenght(event) <= 3600000}">
<div class="event-content">
<small v-if="(event.project_code && !event.is_blocker)" class="project-code">{{ event.project_code }}</small>
<h3>{{ event.title }}</h3>
<div class="d-flex align-center event-details event-initials-wrapper">
<div class="event-initials d-flex justify-center align-center">
<small>{{ getUserInitials(event) }}</small>
</div>
<small class="event-initials-date">{{ event.start.toLocaleDateString('sk-SK') }}</small>
</div>
</div>
<div class="event-footer d-flex">
<v-btn rounded class="event-footer-button me-2" @click="this.showCompleteModal = {task: event}"><img :src="require('./_img/checkmark.svg')" alt=""></v-btn>
<v-btn rounded class="event-footer-button me-2" @click="editTask(event.id)" :style="{opacity: 0.3}"><img :src="require('./_img/pencil.svg')" alt=""></v-btn>
<v-btn rounded class="event-footer-button me-2" @click="this.showDeleteModal = {task: event}"><img :src="require('./_img/bin.svg')" alt=""></v-btn>
</div>
</div>
</template>
</Vue-cal>
<Z-complete-task-modal v-if="showCompleteModal" :visible="showCompleteModal ? true : false" :task="showCompleteModal.task" @close="showCompleteModal = null" />
<Z-delete-task-modal v-if="showDeleteModal" :visible="showDeleteModal ? true : false" :task="showDeleteModal.task" @close="showDeleteModal = null" />
</div>
</template>
<script>
import ZCompleteTaskModal from '@/plugins/tm@modal/task/z-complete-task-modal.vue'
import ZDeleteTaskModal from '@/plugins/tm@modal/task/z-delete-task-modal.vue'
export default {
components: {
ZCompleteTaskModal,
ZDeleteTaskModal
},
props: {
calData: {
type: Object,
required: true,
default() {
return {}
}
},
calConfig: {
type: Object,
required: true,
default() {
return {startHour: 8, endHour: 16, date: new Date()}
}
}
},
data() {
return {
vueCalConfig: {
editing: {
title: false,
drag: true,
resize: false,
delete: false,
create: false
}
},
focusedEventId: null,
showCompleteModal: null,
showDeleteModal: null
}
},
mounted() {
this._scrollToDefaultTime()
},
methods: {
weekChanged() {
const scrollContainer = document.getElementsByClassName('vuecal__scrollable-body')[0]
scrollContainer.scrollLeft = 0
this.$nextTick(() => {
this.scrollToYesterday()
})
},
async eventMoved(e) {
this.focusedEventId = e.event.id
this.$emit('eventMoved', e)
},
onReady() {
this.scrollToYesterday()
this.correctTimecolumnMargin()
},
scrollToYesterday() {
if (!document.getElementsByClassName('yesterday-cell').length || !document.getElementsByClassName('today-cell').length) return
const scrollContainer = document.getElementsByClassName('vuecal__scrollable-body')[0]
const yesterdayCell = document.getElementsByClassName('yesterday-cell')[0]
scrollContainer.scrollLeft = yesterdayCell.offsetLeft - scrollContainer.offsetLeft - 20
},
correctTimecolumnMargin() {
const heightOfDayHeader = document.getElementsByClassName('vuecal__heading')[0].clientHeight
document.getElementsByClassName('vuecal__time-column')[0].style.marginTop = `${heightOfDayHeader}px`
},
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))
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')
},
getUserInitials(event) {
return event.name.charAt(0).toUpperCase() + event.surname.charAt(0).toUpperCase()
},
getEventLenght(event) {
return event.end.getTime() - event.start.getTime()
},
taskClicked(task) {
console.log("task", task)
if (task.end.getTime() - task.start.getTime() <= 3600000) {
console.log("ShortTask")
}
},
editTask(id) {
console.log('edit', id)
},
eventFocused(event) {
this.focusedEventId = event.task_id
console.log(`Focused: ${event.task_id}`)
},
_scrollToDefaultTime() {
const calendarContainer = document.querySelector('.main-content')
const defaultTimeToScrollTo = document.querySelectorAll('.vuecal__time-cell')[7]
const toolbarHeight = document.querySelector('.v-toolbar').getBoundingClientRect().height
const subheaderHeight = document.querySelector('.subheader').getBoundingClientRect().height
calendarContainer.scrollTo({
top: defaultTimeToScrollTo.getBoundingClientRect().top - toolbarHeight - subheaderHeight
})
},
}
}
</script>
<style lang="sass" scoped>
.vuecal
box-shadow: none !important
.calendar-wrapper
width: 100%
padding-top: 50px
&:deep()
.vuecal__cells
-ms-overflow-style: none
scrollbar-width: none
&::-webkit-scrollbar
display: none
&.week-view
border-left: 1px solid rgba(0, 0, 0, .1)
padding-left: 20px
.vuecal__flex
flex-wrap: nowrap !important
// TIME COLUMN
.vuecal__time-column
width: 150px
box-shadow: none !important
.time-column-line
width: 40px
height: 0
border: 1px solid rgba(33, 33, 33, 0.08)
margin-left: 10px
@media (max-width: 450px)
.vuecal__time-column
width: 70px
.time-column-line
width: 10px
// HEADING
.vuecal__weekdays-headings,
.vuecal__cell:before
border: none
.vuecal__heading
height: fit-content
min-height: 100%
padding-right: 20px
.weekday-label
justify-content: start !important
font-size: 24px
font-weight: 700
.unscheduled-time
background: rgba(0, 0, 0, 0.04)
border-radius: 24px
padding: 10px 15px
margin-top: 10px
.unscheduled-time-name
font-weight: 400
font-size: 15px
.unscheduled-time-initials
width: 50px
height: 50px
background: #6200EE
border-radius: 50%
p
font-weight: 400
font-size: 20px
color: white
// BODY
.vuecal__body
-ms-overflow-style: none
scrollbar-width: none
&::-webkit-scrollbar
display: none
.vuecal__now-line
display: none !important
.vuecal__cell
padding: 10px 20px 10px 0px
.vuecal:not(.vuecal--day-view) .vuecal__cell--selected:not(.vuecal__cell--today) ,
.vuecal__cell--current:not(.vuecal__cell--today),
.vuecal__cell--today,
.vuecal__cell--highlighted,
.vuecal__cell--selected
background-color: white
// EVENT
.vuecal__event
border-radius: 16px
padding: 20px 10px
text-align: left
background: rgba(248, 248, 248, 1)
min-height: 40px
transition: min-height .4s, height .4s, box-shadow .4s, width .4s, left .4s
&.vuecal__event--focus
z-index: 100 !important
min-height: 250px !important
width: 100% !important
left: 0 !important
&:not(.vuecal__event--focus)
&:has(.short-event)
padding: 10px !important
&:has(.micro-event)
padding: 5px 10px !important
.short-event
.project-code
display: none !important
h3
font-size: 16px !important
.mini-event
.event-content
display: flex
align-items: center
flex-wrap: nowrap
justify-content: space-between
width: 100%
.event-initials-wrapper
margin-bottom: 0
.event-initials
margin-right: 0
h3
font-size: 14px !important
text-overflow: ellipsis
width: calc( 100% - 35px )
white-space: nowrap
overflow: hidden
margin: 0
.event-initials-date
display: none !important
.micro-event
.event-footer
display: none !important
&.event-blocker
background: #B00020 !important
h3,
.event-details *
color: white !important
.event-details .event-initials
border: 1px solid white
.event-footer
border-top: 1px solid rgba(255, 255, 255, 0.19)
.project-code
margin: 0 0 20px
display: block
h3
font-size: 18px
font-weight: 400
color: Black
margin: 0 0 10px
max-height: 100%
small
color: black
.event-initials-wrapper
margin-bottom: 20px
.event-initials
height: 30px
width: 30px
border: 1px solid black
border-radius: 50%
padding: 5px
margin-right: 7px
small
font-size: 11px
.event-footer
padding-top: 15px
border-top: 1px solid rgba(33, 33, 33, 0.08)
.event-footer-button
box-shadow: none !important
padding: 9px !important
min-height: fit-content
height: fit-content
min-width: fit-content
width: fit-content
img
width: 18px
</style>