2022-07-19 20:55:38 +00:00
|
|
|
<template>
|
2022-07-20 19:50:59 +00:00
|
|
|
<div class="gantt-container">
|
2022-07-20 19:53:21 +00:00
|
|
|
<g-gantt-chart
|
|
|
|
:chart-start="`${dateFrom} 00:00`"
|
|
|
|
:chart-end="`${dateTo} 23:59`"
|
|
|
|
:precision="precision"
|
|
|
|
bar-start="startDate"
|
|
|
|
bar-end="endDate"
|
|
|
|
:grid="true"
|
|
|
|
@dragend-bar="updateTask"
|
|
|
|
@dblclick-bar="openTask"
|
|
|
|
font="'Open Sans', sans-serif"
|
|
|
|
:width="ganttChartWidth + 'px'"
|
|
|
|
>
|
|
|
|
<g-gantt-row
|
|
|
|
v-for="(bar, k) in ganttBars"
|
|
|
|
:key="k"
|
|
|
|
label=""
|
|
|
|
:bars="bar"
|
|
|
|
/>
|
|
|
|
</g-gantt-chart>
|
2022-07-20 19:50:59 +00:00
|
|
|
</div>
|
2022-07-20 16:53:55 +00:00
|
|
|
<form
|
|
|
|
@submit.prevent="createTask()"
|
|
|
|
class="add-new-task"
|
|
|
|
v-if="canWrite"
|
|
|
|
>
|
|
|
|
<transition name="width">
|
|
|
|
<input
|
|
|
|
@blur="hideCreateNewTask"
|
|
|
|
@keyup.esc="newTaskFieldActive = false"
|
|
|
|
class="input"
|
|
|
|
ref="newTaskTitleField"
|
|
|
|
type="text"
|
|
|
|
v-if="newTaskFieldActive"
|
|
|
|
v-model="newTaskTitle"
|
|
|
|
/>
|
|
|
|
</transition>
|
|
|
|
<x-button @click="showCreateNewTask" :shadow="false" icon="plus">
|
2022-07-20 19:30:45 +00:00
|
|
|
{{ $t('task.new') }}
|
2022-07-20 16:53:55 +00:00
|
|
|
</x-button>
|
|
|
|
</form>
|
2022-07-19 20:55:38 +00:00
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2022-07-20 16:53:55 +00:00
|
|
|
import {computed, nextTick, ref} from 'vue'
|
2022-07-19 20:55:38 +00:00
|
|
|
import TaskCollectionService from '@/services/taskCollection'
|
2022-07-20 19:50:59 +00:00
|
|
|
import {format, parse} from 'date-fns'
|
2022-07-19 20:55:38 +00:00
|
|
|
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
|
|
|
import TaskService from '@/services/task'
|
2022-07-20 16:53:55 +00:00
|
|
|
import {useStore} from 'vuex'
|
|
|
|
import Rights from '../../models/constants/rights.json'
|
|
|
|
import TaskModel from '@/models/task'
|
2022-07-20 19:38:41 +00:00
|
|
|
import {useRouter} from 'vue-router'
|
2022-07-19 20:55:38 +00:00
|
|
|
|
|
|
|
const dateFormat = 'yyyy-LL-dd kk:mm'
|
|
|
|
|
2022-07-20 16:53:55 +00:00
|
|
|
const store = useStore()
|
2022-07-20 19:38:41 +00:00
|
|
|
const router = useRouter()
|
2022-07-20 16:53:55 +00:00
|
|
|
|
2022-07-19 20:55:38 +00:00
|
|
|
const props = defineProps({
|
|
|
|
listId: {
|
|
|
|
type: Number,
|
|
|
|
required: true,
|
|
|
|
},
|
2022-07-20 16:35:18 +00:00
|
|
|
precision: {
|
|
|
|
type: String,
|
|
|
|
default: 'day',
|
|
|
|
},
|
2022-07-19 20:55:38 +00:00
|
|
|
dateFrom: {
|
2022-07-20 16:35:18 +00:00
|
|
|
type: String,
|
|
|
|
required: true,
|
2022-07-19 20:55:38 +00:00
|
|
|
},
|
|
|
|
dateTo: {
|
2022-07-20 16:35:18 +00:00
|
|
|
type: String,
|
|
|
|
required: true,
|
2022-07-19 20:55:38 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2022-07-20 19:50:59 +00:00
|
|
|
const DAY_WIDTH_PIXELS = 30
|
|
|
|
const ganttChartWidth = computed(() => {
|
|
|
|
const from = parse(props.dateFrom, 'yyyy-LL-dd', new Date())
|
|
|
|
const to = parse(props.dateTo, 'yyyy-LL-dd', new Date())
|
|
|
|
const dateDiff = Math.floor((to - from) / (1000 * 60 * 60 * 24))
|
2022-07-20 19:53:21 +00:00
|
|
|
|
2022-07-20 19:50:59 +00:00
|
|
|
return dateDiff * DAY_WIDTH_PIXELS
|
|
|
|
})
|
|
|
|
|
2022-07-20 16:53:55 +00:00
|
|
|
const canWrite = computed(() => store.state.currentList.maxRight > Rights.READ)
|
|
|
|
|
2022-07-19 20:55:38 +00:00
|
|
|
const tasks = ref([])
|
|
|
|
const ganttBars = ref([])
|
|
|
|
|
2022-07-20 16:44:17 +00:00
|
|
|
const defaultStartDate = format(new Date(), dateFormat)
|
|
|
|
const defaultEndDate = format(new Date((new Date()).setDate((new Date()).getDate() + 7)), dateFormat)
|
|
|
|
|
2022-07-19 20:55:38 +00:00
|
|
|
// We need a "real" ref object for the gantt bars to instantly update the tasks when they are dragged on the chart.
|
|
|
|
// A computed won't work directly.
|
|
|
|
function mapGanttBars() {
|
2022-07-20 16:53:55 +00:00
|
|
|
ganttBars.value = []
|
2022-07-19 20:55:38 +00:00
|
|
|
tasks.value.forEach(t => ganttBars.value.push([{
|
|
|
|
startDate: t.startDate ? format(t.startDate, dateFormat) : defaultStartDate,
|
|
|
|
endDate: t.endDate ? format(t.endDate, dateFormat) : defaultEndDate,
|
|
|
|
ganttBarConfig: {
|
|
|
|
id: t.id,
|
|
|
|
label: t.title,
|
|
|
|
hasHandles: true,
|
|
|
|
style: {
|
|
|
|
color: colorIsDark(t.getHexColor()) ? 'black' : 'white',
|
|
|
|
backgroundColor: t.getHexColor(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}]))
|
|
|
|
}
|
|
|
|
|
|
|
|
async function loadTasks() {
|
|
|
|
tasks.value = new Map()
|
|
|
|
|
|
|
|
const params = {
|
|
|
|
sort_by: ['start_date', 'done', 'id'],
|
|
|
|
order_by: ['asc', 'asc', 'desc'],
|
2022-07-20 16:40:27 +00:00
|
|
|
filter_by: ['done', 'start_date', 'start_date'],
|
|
|
|
filter_comparator: ['equals', 'greater_equals', 'less_equals'],
|
|
|
|
filter_value: ['false', props.dateFrom, props.dateTo],
|
2022-07-19 20:55:38 +00:00
|
|
|
filter_concat: 'and',
|
2022-07-20 16:40:27 +00:00
|
|
|
filter_include_nulls: true,
|
2022-07-19 20:55:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const taskCollectionService = new TaskCollectionService()
|
|
|
|
|
|
|
|
const getAllTasks = async (page = 1) => {
|
|
|
|
const tasks = await taskCollectionService.getAll({listId: props.listId}, params, page)
|
|
|
|
if (page < taskCollectionService.totalPages) {
|
|
|
|
const nextTasks = await getAllTasks(page + 1)
|
|
|
|
return tasks.concat(nextTasks)
|
|
|
|
}
|
|
|
|
return tasks
|
|
|
|
}
|
|
|
|
|
|
|
|
const loadedTasks = await getAllTasks()
|
|
|
|
|
|
|
|
loadedTasks
|
|
|
|
.forEach(t => {
|
|
|
|
tasks.value.set(t.id, t)
|
|
|
|
})
|
|
|
|
|
|
|
|
mapGanttBars()
|
|
|
|
}
|
|
|
|
|
|
|
|
loadTasks()
|
|
|
|
|
|
|
|
async function updateTask(e) {
|
|
|
|
const task = tasks.value.get(e.bar.ganttBarConfig.id)
|
|
|
|
task.startDate = e.bar.startDate
|
|
|
|
task.endDate = e.bar.endDate
|
|
|
|
const taskService = new TaskService()
|
|
|
|
await taskService.update(task)
|
|
|
|
// TODO: Loading animation
|
|
|
|
}
|
2022-07-20 16:53:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
const newTaskFieldActive = ref(false)
|
|
|
|
const newTaskTitleField = ref()
|
|
|
|
const newTaskTitle = ref('')
|
|
|
|
|
|
|
|
function showCreateNewTask() {
|
|
|
|
if (!newTaskFieldActive.value) {
|
|
|
|
// Timeout to not send the form if the field isn't even shown
|
|
|
|
setTimeout(() => {
|
|
|
|
newTaskFieldActive.value = true
|
|
|
|
nextTick(() => newTaskTitleField.value.focus())
|
|
|
|
}, 100)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function hideCreateNewTask() {
|
|
|
|
if (newTaskTitle.value === '') {
|
|
|
|
nextTick(() => (newTaskFieldActive.value = false))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function createTask() {
|
|
|
|
if (!newTaskFieldActive.value) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let task = new TaskModel({
|
|
|
|
title: newTaskTitle.value,
|
|
|
|
listId: props.listId,
|
|
|
|
startDate: defaultStartDate,
|
|
|
|
endDate: defaultEndDate,
|
|
|
|
})
|
|
|
|
const taskService = new TaskService()
|
|
|
|
const r = await taskService.create(task)
|
|
|
|
tasks.value.set(r.id, r)
|
|
|
|
mapGanttBars()
|
|
|
|
newTaskTitle.value = ''
|
|
|
|
hideCreateNewTask()
|
|
|
|
}
|
2022-07-20 19:38:41 +00:00
|
|
|
|
|
|
|
function openTask(e) {
|
|
|
|
console.log('open', e.bar.ganttBarConfig.id)
|
|
|
|
router.push({
|
|
|
|
name: 'task.detail',
|
2022-07-20 19:50:59 +00:00
|
|
|
params: {id: e.bar.ganttBarConfig.id},
|
|
|
|
state: {backdropView: router.currentRoute.value.fullPath},
|
2022-07-20 19:38:41 +00:00
|
|
|
})
|
|
|
|
}
|
2022-07-19 20:55:38 +00:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<style>
|
|
|
|
.g-gantt-row-label {
|
|
|
|
display: none !important;
|
|
|
|
}
|
|
|
|
</style>
|
2022-07-20 19:30:45 +00:00
|
|
|
|
|
|
|
<style scoped lang="scss">
|
2022-07-20 19:50:59 +00:00
|
|
|
.gantt-container {
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
|
|
#g-gantt-chart {
|
|
|
|
width: 2000px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-20 19:30:45 +00:00
|
|
|
.add-new-task {
|
|
|
|
padding: 1rem .7rem .4rem .7rem;
|
|
|
|
display: flex;
|
|
|
|
max-width: 450px;
|
|
|
|
|
|
|
|
.input {
|
|
|
|
margin-right: .7rem;
|
|
|
|
font-size: .8rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.button {
|
|
|
|
font-size: .68rem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|