Compare commits
6 commits
main
...
feature/ga
Author | SHA1 | Date | |
---|---|---|---|
|
1817a1f958 | ||
|
affaa95e3f | ||
|
7e6d5956ba | ||
|
a1ee90df33 | ||
|
1604f5b4aa | ||
|
6a0427b216 |
4 changed files with 167 additions and 4 deletions
|
@ -22,6 +22,7 @@
|
||||||
"highlight.js": "10.7.2",
|
"highlight.js": "10.7.2",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"marked": "2.0.3",
|
"marked": "2.0.3",
|
||||||
|
"moment": "^2.29.1",
|
||||||
"register-service-worker": "1.7.2",
|
"register-service-worker": "1.7.2",
|
||||||
"sass": "1.32.13",
|
"sass": "1.32.13",
|
||||||
"snake-case": "3.0.4",
|
"snake-case": "3.0.4",
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
"vue-advanced-cropper": "1.5.2",
|
"vue-advanced-cropper": "1.5.2",
|
||||||
"vue-drag-resize": "1.5.4",
|
"vue-drag-resize": "1.5.4",
|
||||||
"vue-easymde": "1.4.0",
|
"vue-easymde": "1.4.0",
|
||||||
|
"vue-ganttastic": "^0.9.32",
|
||||||
"vue-shortkey": "3.1.7",
|
"vue-shortkey": "3.1.7",
|
||||||
"vue-smooth-dnd": "0.8.1",
|
"vue-smooth-dnd": "0.8.1",
|
||||||
"vuex": "3.6.2"
|
"vuex": "3.6.2"
|
||||||
|
|
|
@ -16,6 +16,40 @@
|
||||||
v-model="params"
|
v-model="params"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<g-gantt-chart
|
||||||
|
:chart-start="dateFrom.toString()"
|
||||||
|
:chart-end="dateTo.toString()"
|
||||||
|
:push-on-overlap="true"
|
||||||
|
row-label-width="0"
|
||||||
|
:row-height="37"
|
||||||
|
:grid="false"
|
||||||
|
@dragend-bar="dragged($event)"
|
||||||
|
:style="{'--day-width': dayWidthPercent + 'px'}"
|
||||||
|
ref="g-gantt-chart-container"
|
||||||
|
>
|
||||||
|
<g-gantt-row
|
||||||
|
v-for="(t, k) in ganttBars"
|
||||||
|
:key="t ? t.id : 'k'+k"
|
||||||
|
label=""
|
||||||
|
bar-start="start"
|
||||||
|
bar-end="end"
|
||||||
|
:bars="t.bars"
|
||||||
|
:highlight-on-hover="true"
|
||||||
|
>
|
||||||
|
<template v-slot:bar-label>
|
||||||
|
<span>
|
||||||
|
{{ t.title }}
|
||||||
|
</span>
|
||||||
|
<router-link :to="{name: 'task.detail', params: {id: t.id}}" class="edit-task-link">
|
||||||
|
<icon icon="pen"/>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
</g-gantt-row>
|
||||||
|
</g-gantt-chart>
|
||||||
|
|
||||||
|
|
||||||
<div class="dates">
|
<div class="dates">
|
||||||
<template v-for="(y, yk) in days">
|
<template v-for="(y, yk) in days">
|
||||||
<div :key="yk + 'year'" class="months">
|
<div :key="yk + 'year'" class="months">
|
||||||
|
@ -191,16 +225,30 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import VueDragResize from 'vue-drag-resize'
|
import VueDragResize from 'vue-drag-resize'
|
||||||
|
import {GGanttChart, GGanttRow} from 'vue-ganttastic'
|
||||||
import EditTask from './edit-task'
|
import EditTask from './edit-task'
|
||||||
|
import {mapState} from 'vuex'
|
||||||
|
|
||||||
import TaskService from '../../services/task'
|
import TaskService from '../../services/task'
|
||||||
import TaskModel from '../../models/task'
|
import TaskModel from '../../models/task'
|
||||||
import priorities from '../../models/priorities'
|
import priorities from '../../models/priorities'
|
||||||
import PriorityLabel from './partials/priorityLabel'
|
import PriorityLabel from './partials/priorityLabel'
|
||||||
import TaskCollectionService from '../../services/taskCollection'
|
import TaskCollectionService from '../../services/taskCollection'
|
||||||
import {mapState} from 'vuex'
|
|
||||||
import Rights from '../../models/rights.json'
|
import Rights from '../../models/rights.json'
|
||||||
import FilterPopup from '@/components/list/partials/filter-popup'
|
import FilterPopup from '@/components/list/partials/filter-popup'
|
||||||
|
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||||
|
|
||||||
|
// Source: https://stackoverflow.com/a/11252167/10924593
|
||||||
|
const treatAsUTC = date => {
|
||||||
|
const result = new Date(date)
|
||||||
|
result.setMinutes(result.getMinutes() - result.getTimezoneOffset())
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const daysBetween = (startDate, endDate) => {
|
||||||
|
const millisecondsPerDay = 24 * 60 * 60 * 1000;
|
||||||
|
return (treatAsUTC(endDate) - treatAsUTC(startDate)) / millisecondsPerDay;
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GanttChart',
|
name: 'GanttChart',
|
||||||
|
@ -209,6 +257,8 @@ export default {
|
||||||
PriorityLabel,
|
PriorityLabel,
|
||||||
EditTask,
|
EditTask,
|
||||||
VueDragResize,
|
VueDragResize,
|
||||||
|
GGanttChart,
|
||||||
|
GGanttRow,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
listId: {
|
listId: {
|
||||||
|
@ -251,6 +301,8 @@ export default {
|
||||||
taskCollectionService: TaskCollectionService,
|
taskCollectionService: TaskCollectionService,
|
||||||
showTaskFilter: false,
|
showTaskFilter: false,
|
||||||
|
|
||||||
|
ganttBars: [],
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
sort_by: ['done', 'id'],
|
sort_by: ['done', 'id'],
|
||||||
order_by: ['asc', 'desc'],
|
order_by: ['asc', 'desc'],
|
||||||
|
@ -275,9 +327,21 @@ export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.buildTheGanttChart()
|
this.buildTheGanttChart()
|
||||||
},
|
},
|
||||||
computed: mapState({
|
computed: {
|
||||||
canWrite: (state) => state.currentList.maxRight > Rights.READ,
|
...mapState({
|
||||||
}),
|
canWrite: (state) => state.currentList.maxRight > Rights.READ,
|
||||||
|
}),
|
||||||
|
dayWidthPercent() {
|
||||||
|
const days = daysBetween(this.startDate, this.endDate)
|
||||||
|
|
||||||
|
let containerWidth = 1
|
||||||
|
if(this.$refs['g-gantt-chart-container']) {
|
||||||
|
containerWidth = this.$refs['g-gantt-chart-container'].$el.clientWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(containerWidth / days)
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
buildTheGanttChart() {
|
buildTheGanttChart() {
|
||||||
this.setDates()
|
this.setDates()
|
||||||
|
@ -360,6 +424,25 @@ export default {
|
||||||
if (a.startDate > b.startDate) return 1
|
if (a.startDate > b.startDate) return 1
|
||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.ganttBars = this.theTasks.map(t => {
|
||||||
|
return {
|
||||||
|
id: t.id,
|
||||||
|
title: t.title,
|
||||||
|
bars: [{
|
||||||
|
id: t.id,
|
||||||
|
start: t.startDate.toString(),
|
||||||
|
end: t.endDate.toString(),
|
||||||
|
label: t.title,
|
||||||
|
ganttBarConfig: {
|
||||||
|
color: colorIsDark(t.getHexColor()) ? 'black' : 'white',
|
||||||
|
backgroundColor: t.getHexColor(),
|
||||||
|
handles: true,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
this.error(e, this)
|
this.error(e, this)
|
||||||
|
@ -487,6 +570,14 @@ export default {
|
||||||
this.error(e, this)
|
this.error(e, this)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
dragged(e) {
|
||||||
|
const bar = e.movedBars.entries().next().value[0]
|
||||||
|
const index = this.theTasks.findIndex(t => t.id === bar.id)
|
||||||
|
const task = this.theTasks[index]
|
||||||
|
task.startDate = new Date(bar.start)
|
||||||
|
task.endDate = new Date(bar.end)
|
||||||
|
this.$set(this.theTasks, index, task)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -234,4 +234,64 @@ $gantt-vertical-border-color: $grey-100;
|
||||||
|
|
||||||
.vdr.active::before {
|
.vdr.active::before {
|
||||||
display: none;
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--day-width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#g-gantt-chart {
|
||||||
|
* {
|
||||||
|
font-family: $family-sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.g-gantt-row {
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: rgba(0, 0, 0, 0) repeating-linear-gradient(90deg, #ededed, #ededed 1px, #fff 1px, #fff var(--day-width)) repeat scroll 0% 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background: rgba(0, 0, 0, 0) repeating-linear-gradient(90deg, #ededed, #ededed 1px, #fafafa 1px, #fafafa var(--day-width)) repeat scroll 0% 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .g-gantt-row-bars-container {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-gantt-bar {
|
||||||
|
border-radius: 6px !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
cursor: grab;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-gantt-bar-label {
|
||||||
|
font-size: .85rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 .5rem !important;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.edit-task-link {
|
||||||
|
color: inherit;
|
||||||
|
padding: 0 .25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-gantt-bar-handle-left, .g-gantt-bar-handle-right {
|
||||||
|
width: 8px !important;
|
||||||
|
height: 8px !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
margin-left: -2px !important;
|
||||||
|
border: 1px solid $grey-700 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.g-gantt-bar-handle-right {
|
||||||
|
margin-right: -2px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
10
yarn.lock
10
yarn.lock
|
@ -9543,6 +9543,11 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.5"
|
minimist "^1.2.5"
|
||||||
|
|
||||||
|
moment@^2.29.1:
|
||||||
|
version "2.29.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||||
|
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||||
|
|
||||||
move-concurrently@^1.0.1:
|
move-concurrently@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
||||||
|
@ -13340,6 +13345,11 @@ vue-flatpickr-component@8.1.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
flatpickr "^4.6.6"
|
flatpickr "^4.6.6"
|
||||||
|
|
||||||
|
vue-ganttastic@^0.9.32:
|
||||||
|
version "0.9.32"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-ganttastic/-/vue-ganttastic-0.9.32.tgz#7dc37db63fddd39413444a6674c85d3300f552af"
|
||||||
|
integrity sha512-xVWOFXwBFJ/1piQyQ842RZtpEIRZWOjfhSZeQUTX5oGWskRe1P7yOdZ6SI30x+S/kNGVICcyfawbnMHNix/TYg==
|
||||||
|
|
||||||
vue-hot-reload-api@^2.3.0:
|
vue-hot-reload-api@^2.3.0:
|
||||||
version "2.3.4"
|
version "2.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
|
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
|
||||||
|
|
Loading…
Reference in a new issue