Initial commit

This commit is contained in:
Matej Glasnák committed 2022-11-21 05:42:38 +01:00
commit d76f4258f7
231 files changed
+27803

No files matched your search

Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.33329 13.3334V13.8334C5.4659 13.8334 5.59308 13.7807 5.68685 13.6869L5.33329 13.3334ZM2.66663 13.3334H2.16663C2.16663 13.6095 2.39048 13.8334 2.66663 13.8334V13.3334ZM2.66663 10.6667L2.31307 10.3132C2.2193 10.4069 2.16663 10.5341 2.16663 10.6667H2.66663ZM10.1952 3.13813L10.5488 3.49169V3.49169L10.1952 3.13813ZM11.138 3.13813L10.7845 3.49169V3.49169L11.138 3.13813ZM12.8619 4.86199L13.2154 4.50844V4.50844L12.8619 4.86199ZM12.8619 5.8048L12.5083 5.45125L12.8619 5.8048ZM5.33329 12.8334H2.66663V13.8334H5.33329V12.8334ZM3.16663 13.3334V10.6667H2.16663V13.3334H3.16663ZM3.02018 11.0203L10.5488 3.49169L9.84167 2.78458L2.31307 10.3132L3.02018 11.0203ZM10.7845 3.49169L12.5083 5.21554L13.2154 4.50844L11.4916 2.78458L10.7845 3.49169ZM12.5083 5.45125L4.97974 12.9798L5.68685 13.6869L13.2154 6.15835L12.5083 5.45125ZM12.5083 5.21554C12.5734 5.28063 12.5734 5.38616 12.5083 5.45125L13.2154 6.15835C13.6711 5.70274 13.6711 4.96405 13.2154 4.50844L12.5083 5.21554ZM10.5488 3.49169C10.6139 3.4266 10.7194 3.4266 10.7845 3.49169L11.4916 2.78458C11.036 2.32897 10.2973 2.32897 9.84167 2.78458L10.5488 3.49169Z" fill="#14181F"/>
<path d="M8 5.33325L10.6667 7.99992" stroke="#14181F" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.33337 6.66675L9.33337 11.3334" stroke="#14181F" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.66663 6.66675L6.66663 11.3334" stroke="#14181F" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 4V3.5C3.72386 3.5 3.5 3.72386 3.5 4H4ZM12 4H12.5C12.5 3.72386 12.2761 3.5 12 3.5V4ZM4 4.5H12V3.5H4V4.5ZM11.5 4V13.3333H12.5V4H11.5ZM11.3333 13.5H4.66667V14.5H11.3333V13.5ZM4.5 13.3333V4H3.5V13.3333H4.5ZM4.66667 13.5C4.57462 13.5 4.5 13.4254 4.5 13.3333H3.5C3.5 13.9777 4.02233 14.5 4.66667 14.5V13.5ZM11.5 13.3333C11.5 13.4254 11.4254 13.5 11.3333 13.5V14.5C11.9777 14.5 12.5 13.9777 12.5 13.3333H11.5Z" fill="#14181F"/>
<path d="M2.66663 4H13.3333" stroke="#14181F" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.6667 4V4.5C10.9429 4.5 11.1667 4.27614 11.1667 4H10.6667ZM5.33337 4H4.83337C4.83337 4.27614 5.05723 4.5 5.33337 4.5V4ZM6.00004 2.5H10V1.5H6.00004V2.5ZM10.1667 2.66667V4H11.1667V2.66667H10.1667ZM10.6667 3.5H5.33337V4.5H10.6667V3.5ZM5.83337 4V2.66667H4.83337V4H5.83337ZM10 2.5C10.0921 2.5 10.1667 2.57462 10.1667 2.66667H11.1667C11.1667 2.02233 10.6444 1.5 10 1.5V2.5ZM6.00004 1.5C5.35571 1.5 4.83337 2.02233 4.83337 2.66667H5.83337C5.83337 2.57462 5.90799 2.5 6.00004 2.5V1.5Z" fill="#14181F"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,68 @@
<template>
<v-table>
<thead>
<tr>
<th class="text-left">Kód projektu</th>
<th class="text-left">Názov</th>
<th class="text-left">Naplánované</th>
<th class="text-left">Zostávajúce</th>
<th class="text-left">Akcia</th>
</tr>
</thead>
<tbody>
<tr v-for="task in tasks" :key="task.id">
<td>{{ task.project_code }}</td>
<td>{{ task.title }}</td>
<td>{{ task.duration / 1000 / 60 / 60 }} h</td>
<td>{{ (task.remaining_time / 1000 / 60 / 60).toFixed(2) }} h</td>
<td class="task-action">
<div class="d-flex justify-start">
<img class="pencil-icon" :src="require('@/plugins/tm@dashboard/_img/pencil-icon.svg')" alt="pencil-icon" />
<img class="trash-icon" :src="require('@/plugins/tm@dashboard/_img/trash-icon.svg')" alt="trash-icon" />
</div>
</td>
</tr>
</tbody>
</v-table>
</template>
<script>
export default {
props: {
tasks: {
type: Array,
required: true
}
}
}
</script>
<style lang="sass" scoped>
.task-action
.trash-icon
height: 25px
background: rgba(0, 0, 0, 0.04)
margin-left: 12px
border-radius: 50%
cursor: pointer
.pencil-icon
height: 25px
background: rgba(0, 0, 0, 0.04)
border-radius: 50%
cursor: pointer
v-table, th, td
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity))
@media screen and (max-width: 496px)
.task-action
div
display: flex
flex-direction: column
.pencil-icon, .trash-icon
border-radius: 50%
margin: 6px
</style>
@@ -0,0 +1,30 @@
<template>
<W-loadable :content="ongoingTasks">
<A-ongoing-tasks :tasks="ongoingTasks" />
</W-loadable>
</template>
<script>
import AOngoingTasks from './_components/a-ongoing-tasks-overview.vue'
export default {
components: {
AOngoingTasks
},
data() {
return {
ongoingTasks: null
}
},
async created() {
try {
this.ongoingTasks = await this.$axios.get_auth_data('v1/overview/ongoing-tasks')
} catch (error) {
this.unscheduledTasks = {error: error.data.error}
this.$toast.error(error.data.error)
}
}
}
</script>
Whitespace-only changes.
@@ -0,0 +1,81 @@
<template>
<v-row class="px-10 py-8 subheader">
<v-select
v-model="selectedEmployee"
class="subheader-input"
:items="employees"
label="Zamestnanec"
variant="outlined"
></v-select>
<div class="d-flex date-wrapper">
<v-btn class="subheader-icon-button " variant="outlined"><img :src="require('./assets/icon-prev.svg')" alt=""></v-btn>
<v-text-field
class="subheader-input disabled"
:items="items"
label="Dátum"
v-model="selectedDate"
variant="outlined"
append-inner-icon="mdi-calendar-blank-outline"
></v-text-field>
<v-btn class="subheader-icon-button " variant="outlined"><img :src="require('./assets/icon-next.svg')" alt=""></v-btn>
</div>
<v-text-field
class="subheader-input"
:items="items"
label="Nenaplánované hodiny"
v-model="plannedTime"
variant="outlined"
></v-text-field>
</v-row>
</template>
<script>
export default {
data() {
return {
employees: ['Foo', 'Bar', 'Fizz', 'Buzz'],
selectedDate: "13.11.2022 - 17.11.2022",
plannedTime: "35 h",
selectedEmployee: 'Bar'
}
},
}
</script>
<style lang="sass" scoped>
.subheader
max-width: 100%
margin: 0
border-bottom: 1px solid rgba(33, 33, 33, 0.08)
border-top: 1px solid rgba(33, 33, 33, 0.08)
gap: 30px
flex-wrap: wrap
.subheader-icon-button
border: 1px solid #ABABAB
height: 56px
width: 56px
min-width: auto !important
padding: 0
margin: 0 10px
&:deep(.v-btn__append)
margin: 0
.subheader-input
max-width: 250px !important
min-width: 180px
&:deep(.v-input__details)
display: none
&.disabled
pointer-events: none !important
.date-wrapper
flex: 0 1 402px
</style>
@@ -0,0 +1,3 @@
<svg width="9" height="16" viewBox="0 0 9 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.70711 0.292893C1.31658 -0.0976311 0.683418 -0.0976311 0.292893 0.292893C-0.0976311 0.683418 -0.0976311 1.31658 0.292893 1.70711L1.70711 0.292893ZM8 8L8.70711 8.70711C9.09763 8.31658 9.09763 7.68342 8.70711 7.29289L8 8ZM0.292893 14.2929C-0.0976311 14.6834 -0.0976311 15.3166 0.292893 15.7071C0.683418 16.0976 1.31658 16.0976 1.70711 15.7071L0.292893 14.2929ZM0.292893 1.70711L7.29289 8.70711L8.70711 7.29289L1.70711 0.292893L0.292893 1.70711ZM7.29289 7.29289L0.292893 14.2929L1.70711 15.7071L8.70711 8.70711L7.29289 7.29289Z" fill="#14181F"/>
</svg>

After

Width:  |  Height:  |  Size: 655 B

@@ -0,0 +1,3 @@
<svg width="10" height="17" viewBox="0 0 10 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.79289 16.2071C8.18342 16.5976 8.81658 16.5976 9.20711 16.2071C9.59763 15.8166 9.59763 15.1834 9.20711 14.7929L7.79289 16.2071ZM1.5 8.5L0.792893 7.79289C0.402369 8.18342 0.402369 8.81658 0.792893 9.20711L1.5 8.5ZM9.20711 2.20711C9.59763 1.81658 9.59763 1.18342 9.20711 0.792893C8.81658 0.402369 8.18342 0.402369 7.79289 0.792893L9.20711 2.20711ZM9.20711 14.7929L2.20711 7.79289L0.792893 9.20711L7.79289 16.2071L9.20711 14.7929ZM2.20711 9.20711L9.20711 2.20711L7.79289 0.792893L0.792893 7.79289L2.20711 9.20711Z" fill="#14181F"/>
</svg>

After

Width:  |  Height:  |  Size: 643 B

@@ -0,0 +1,326 @@
<template>
<div>
<v-sheet tile height="54" color="grey lighten-3" class="d-flex">
<v-btn icon class="ma-2" @click="$refs.calendar.prev()">
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-select v-model="type" :items="types" dense outlined hide-details class="ma-2" label="type"></v-select>
<v-select v-model="mode" :items="modes" dense outlined hide-details label="event-overlap-mode" class="ma-2"></v-select>
<v-select v-model="weekday" :items="weekdays" dense outlined hide-details label="weekdays" class="ma-2"></v-select>
<v-spacer></v-spacer>
<v-btn icon class="ma-2" @click="$refs.calendar.next()">
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</v-sheet>
<v-sheet height="600">
<v-calendar ref="calendar" v-model="value" :weekdays="weekday" :type="type" :events="events" :event-overlap-mode="mode" :event-overlap-threshold="60" :event-color="getEventColor" :event-ripple="false" @change="getEvents" @mousedown:event="startDrag" @mousedown:time="startTime" @mousemove:time="mouseMove" @mouseup:time="endDrag" @mouseleave="cancelDrag">
<template #event="{ event, timed }">
<div class="pl-1" v-html="getEventHTML(event, timed)"></div>
<div v-if="timed" class="v-event-drag-bottom" @mousedown.stop="extendBottom(event)"></div>
</template>
<template #day-body="{ date, week }">
<div class="v-current-time" :class="{ first: date === week[0].date }" :style="{ top: nowY }">
<div class="v-current-time-time">
{{ nowTime }}
</div>
</div>
</template>
&nbsp;
</v-calendar>
<!-- {{ lastEvent }} -->
</v-sheet>
</div>
</template>
<script>
export default {
data() {
return {
type: 'day',
types: ['month', 'week', 'day', '4day'],
mode: 'stack',
modes: ['stack', 'column'],
weekday: [0, 1, 2, 3, 4, 5, 6],
weekdays: [
{ text: 'Sun - Sat', value: [0, 1, 2, 3, 4, 5, 6] },
{ text: 'Mon - Sun', value: [1, 2, 3, 4, 5, 6, 0] },
{ text: 'Mon - Fri', value: [1, 2, 3, 4, 5] },
{ text: 'Mon, Wed, Fri', value: [1, 3, 5] }
],
value: '',
events: [],
colors: ['#2196F3', '#3F51B5', '#673AB7', '#00BCD4', '#4CAF50', '#FF9800', '#757575'],
names: ['Meeting', 'Holiday', 'PTO', 'Travel', 'Event', 'Birthday', 'Conference', 'Party'],
dragEvent: null,
dragStart: null,
lastEvent: '',
createEvent: null,
createStart: null,
extendOriginal: null,
isMounted: false
}
},
computed: {
nowY() {
const cal = this.$refs.calendar
if (!cal && !this.isMounted) {
return -1
}
return cal.timeToY(cal.times.now) + 'px'
},
nowTime() {
const cal = this.$refs.calendar
if (!cal && !this.isMounted) {
return -1
}
return cal.formatTime(cal.times.now)
}
},
mounted() {
const cal = this.$refs.calendar
this.isMounted = true
// scroll to the current time
const minutes = cal.times.now.hour * 60 + cal.times.now.minute
const firstTime = Math.max(0, minutes - (minutes % 30) - 30)
cal.scrollToTime(firstTime)
// every minute update the current time bar
setInterval(() => cal.updateTimes(), 60 * 1000)
},
methods: {
getEvents({ start, end }) {
const events = []
const min = new Date(`${start.date}T00:00:00`)
const max = new Date(`${end.date}T23:59:59`)
const days = (max.getTime() - min.getTime()) / 86400000
const eventCount = this.rnd(days, days + 20)
for (let i = 0; i < eventCount; i++) {
const allDay = this.rnd(0, 3) === 0
const firstTimestamp = this.rnd(min.getTime(), max.getTime())
const first = new Date(firstTimestamp - (firstTimestamp % 900000))
const secondTimestamp = this.rnd(2, allDay ? 288 : 8) * 900000
const second = new Date(first.getTime() + secondTimestamp)
events.push({
name: this.names[this.rnd(0, this.names.length - 1)],
start: this.formatDate(first, !allDay),
end: this.formatDate(second, !allDay),
color: this.colors[this.rnd(0, this.colors.length - 1)]
})
}
this.events = events
},
getEventColor(event) {
const rgb = parseInt(event.color.substring(1), 16)
const r = (rgb >> 16) & 0xff
const g = (rgb >> 8) & 0xff
const b = (rgb >> 0) & 0xff
return event === this.dragEvent ? `rgba(${r}, ${g}, ${b}, 0.5)` : event === this.createEvent ? `rgba(${r}, ${g}, ${b}, 0.5)` : event.color
},
getEventHTML(event, timed) {
const cal = this.$refs.calendar
let name = event.name
if (event.start.hasTime) {
if (timed) {
const showStart = event.start.hour < 12 && event.end.hour >= 12
const start = cal.formatTime(event.start, showStart)
const end = cal.formatTime(event.end, true)
// const singline = diffMinutes(event.start, event.end) <= this.parsedEventOverlapThreshold
const singline = event.end - event.start <= this.parsedEventOverlapThreshold
const separator = singline ? ', ' : '<br>'
return `<strong>${name}</strong>${separator}${start} - ${end}`
} else {
const time = this.formatTime(event.start, true)
return `<strong>${time}</strong> ${name}`
}
}
return name
},
rnd(a, b) {
return Math.floor((b - a + 1) * Math.random()) + a
},
formatDate(a, withTime) {
return withTime ? `${a.getFullYear()}-${a.getMonth() + 1}-${a.getDate()} ${a.getHours()}:${a.getMinutes()}` : `${a.getFullYear()}-${a.getMonth() + 1}-${a.getDate()}`
},
eventMove(e) {
console.log('eventMove', e)
},
startDrag(e) {
if (e.event && e.timed) {
this.dragEvent = e.event
this.dragTime = null
this.extendOriginal = null
}
this.lastEvent = 'startDrag'
},
startTime(e) {
const mouse = this.toDate(e)
if (this.dragEvent && this.dragTime === null) {
const start = this.toDate(this.dragEvent.start)
this.dragTime = mouse.getTime() - start.getTime()
} else {
this.createStart = this.roundTime(mouse.getTime())
this.createEvent = {
name: '(no title)',
start: this.toTimestamp(new Date(this.createStart)),
end: this.toTimestamp(new Date(this.createStart)),
color: this.colors[this.rnd(0, this.colors.length - 1)]
}
this.events.push(this.createEvent)
}
this.lastEvent = 'startTime'
},
extendBottom(event) {
this.createEvent = event
this.createStart = this.toDate(event.start).getTime()
this.extendOriginal = event.end
},
mouseMoveEvent(e) {
console.log('mouseMoveEvent', e)
},
mouseMove(e) {
if (this.dragEvent && this.dragTime !== null) {
const start = this.toDate(this.dragEvent.start)
const end = this.toDate(this.dragEvent.end)
const duration = end.getTime() - start.getTime()
const mouse = this.toDate(e)
const newStartTime = mouse.getTime() - this.dragTime
const newStart = new Date(this.roundTime(newStartTime))
const newEnd = new Date(newStart.getTime() + duration)
this.dragEvent.start = this.toTimestamp(newStart)
this.dragEvent.end = this.toTimestamp(newEnd)
} else if (this.createEvent && this.createStart !== null) {
const mouse = this.toDate(e).getTime()
const mouseRounded = this.roundTime(mouse, false)
const min = Math.min(mouseRounded, this.createStart)
const max = Math.max(mouseRounded, this.createStart)
this.createEvent.start = this.toTimestamp(new Date(min))
this.createEvent.end = this.toTimestamp(new Date(max))
}
},
endDrag(e) {
this.dragTime = null
this.dragEvent = null
this.createEvent = null
this.createStart = null
this.extendOriginal = null
this.lastEvent = 'endDrag'
},
cancelDrag(e) {
if (this.createEvent) {
if (this.extendOriginal) {
this.createEvent.end = this.extendOriginal
} else {
const i = this.events.indexOf(this.createEvent)
if (i !== -1) {
this.events.splice(i, 1)
}
}
}
this.createEvent = null
this.createStart = null
this.dragTime = null
this.dragEvent = null
this.lastEvent = 'cancelDrag'
},
roundTime(time, down = true) {
const roundDownTime = 15 * 60 * 1000 // 15 minutes
return down ? time - (time % roundDownTime) : time + (roundDownTime - (time % roundDownTime))
},
toDate(tms) {
return typeof tms === 'string' ? new Date(tms) : new Date(tms.year, tms.month - 1, tms.day, tms.hour, tms.minute)
},
toTimestamp(date) {
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`
}
}
}
</script>
<style lang="sass" scoped>
.v-calendar
user-select: none
-webkit-user-select: none
.v-event-drag-bottom
position: absolute
left: 0
right: 0
bottom: 4px
height: 4px
cursor: ns-resize
.v-event-drag-bottom::after
display: none
position: absolute
left: 50%
height: 4px
border-top: 1px solid white
border-bottom: 1px solid white
width: 20px
margin-left: -10px
opacity: 0.8
content: ''
.v-event-timed:hover .v-event-drag-bottom::after
display: block
.v-current-time
height: 1px
background-color: red
position: absolute
left: -1px
right: 0
pointer-events: none
&.first::after
content: ''
position: absolute
background-color: red
width: 7px
height: 7px
border-radius: 3.5px
margin-top: -3px
margin-left: -3px
.v-current-time-time
display: none
&.first .v-current-time-time
display: block
position: absolute
left: -60px
color: red
font-size: 10px
padding: 1px
background-color: white
width: 50px
height: 20px
text-align: right
top: -9px
</style>
@@ -0,0 +1,18 @@
<template>
<div>
<A-task-filter />
<Z-timeline />
</div>
</template>
<script>
import ATaskFilter from './_components/a-tasks-filter.vue'
import ZTimeline from './_components/z-timeline.vue'
export default {
components: {
ATaskFilter,
ZTimeline,
},
}
</script>
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,66 @@
<template>
<v-table>
<thead>
<tr>
<th class="text-left">Kód projektu</th>
<th class="text-left">Názov</th>
<th class="text-left">Trvanie</th>
<th class="text-left">Akcia</th>
</tr>
</thead>
<tbody>
<tr v-for="task in tasks" :key="task.id">
<td>{{ task.project_code }}</td>
<td>{{ task.title }}</td>
<td>{{ task.duration / 1000 / 60 / 60 }} h</td>
<td class="task-action">
<div class="d-flex justify-start">
<img class="pencil-icon" :src="require('@/plugins/tm@dashboard/_img/pencil-icon.svg')" alt="pencil-icon">
<img class="trash-icon" :src="require('@/plugins/tm@dashboard/_img/trash-icon.svg')" alt="trash-icon">
</div>
</td>
</tr>
</tbody>
</v-table>
</template>
<script>
export default {
props: {
tasks: {
type: Array,
required: true
}
}
}
</script>
<style lang="sass" scoped>
.task-action
.trash-icon
height: 25px
background: rgba(0, 0, 0, 0.04)
margin-left: 12px
border-radius: 50%
cursor: pointer
.pencil-icon
height: 25px
background: rgba(0, 0, 0, 0.04)
border-radius: 50%
cursor: pointer
v-table, th, td
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity))
@media screen and (max-width: 496px)
.task-action
div
display: flex
flex-direction: column
.pencil-icon, .trash-icon
border-radius: 50%
margin: 6px
</style>
@@ -0,0 +1,30 @@
<template>
<W-loadable :content="unscheduledTasks">
<A-unscheduled-tasks-table :tasks="unscheduledTasks" />
</W-loadable>
</template>
<script>
import AUnscheduledTasksTable from './_components/a-unscheduled-tasks-table.vue'
export default {
components: {
AUnscheduledTasksTable
},
data() {
return {
unscheduledTasks: null
}
},
async created() {
try {
this.unscheduledTasks = await this.$axios.get_auth_data('v1/overview/unscheduled-tasks')
} catch (error) {
this.unscheduledTasks = {error: error.data.error}
this.$toast.error(error.data.error)
}
}
}
</script>