221 lines
4.9 KiB
Vue
221 lines
4.9 KiB
Vue
<template>
|
|
<ProjectWrapper
|
|
class="project-gantt"
|
|
:project-id="filters.projectId"
|
|
:view-id
|
|
>
|
|
<template #default>
|
|
<card :has-content="false">
|
|
<div class="gantt-options">
|
|
<div class="field">
|
|
<label
|
|
class="label"
|
|
for="range"
|
|
>{{ $t('project.gantt.range') }}</label>
|
|
<div class="control">
|
|
<Foo
|
|
id="range"
|
|
ref="flatPickerEl"
|
|
v-model="flatPickerDateRange"
|
|
:config="flatPickerConfig"
|
|
class="input"
|
|
:placeholder="$t('project.gantt.range')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="!hasDefaultFilters"
|
|
class="field"
|
|
>
|
|
<label
|
|
class="label"
|
|
for="range"
|
|
>Reset</label>
|
|
<div class="control">
|
|
<x-button @click="setDefaultFilters">
|
|
Reset
|
|
</x-button>
|
|
</div>
|
|
</div>
|
|
<FancyCheckbox
|
|
v-model="filters.showTasksWithoutDates"
|
|
is-block
|
|
>
|
|
{{ $t('project.gantt.showTasksWithoutDates') }}
|
|
</FancyCheckbox>
|
|
</div>
|
|
</card>
|
|
|
|
<div class="gantt-chart-container">
|
|
<card
|
|
:has-content="false"
|
|
:padding="false"
|
|
class="has-overflow"
|
|
>
|
|
<GanttChart
|
|
:filters="filters"
|
|
:tasks="tasks"
|
|
:is-loading="isLoading"
|
|
:default-task-start-date="defaultTaskStartDate"
|
|
:default-task-end-date="defaultTaskEndDate"
|
|
@update:task="updateTask"
|
|
/>
|
|
<TaskForm
|
|
v-if="canWrite"
|
|
@createTask="addGanttTask"
|
|
/>
|
|
</card>
|
|
</div>
|
|
</template>
|
|
</ProjectWrapper>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {computed, ref, toRefs} from 'vue'
|
|
import type Flatpickr from 'flatpickr'
|
|
import {useI18n} from 'vue-i18n'
|
|
import type {RouteLocationNormalized} from 'vue-router'
|
|
|
|
import {useBaseStore} from '@/stores/base'
|
|
import {getFlatpickrLanguage} from '@/helpers/flatpickrLanguage'
|
|
|
|
import Foo from '@/components/misc/flatpickr/Flatpickr.vue'
|
|
import ProjectWrapper from '@/components/project/ProjectWrapper.vue'
|
|
import FancyCheckbox from '@/components/input/FancyCheckbox.vue'
|
|
import TaskForm from '@/components/tasks/TaskForm.vue'
|
|
|
|
import {createAsyncComponent} from '@/helpers/createAsyncComponent'
|
|
import {useGanttFilters} from '../../../views/project/helpers/useGanttFilters'
|
|
import {RIGHTS} from '@/constants/rights'
|
|
|
|
import type {DateISO} from '@/types/DateISO'
|
|
import type {ITask} from '@/modelTypes/ITask'
|
|
import type {IProjectView} from '@/modelTypes/IProjectView'
|
|
|
|
type Options = Flatpickr.Options.Options
|
|
|
|
const props = defineProps<{
|
|
route: RouteLocationNormalized
|
|
viewId: IProjectView['id']
|
|
}>()
|
|
|
|
const GanttChart = createAsyncComponent(() => import('@/components/tasks/GanttChart.vue'))
|
|
|
|
const baseStore = useBaseStore()
|
|
const canWrite = computed(() => baseStore.currentProject?.maxRight > RIGHTS.READ)
|
|
|
|
const {route} = toRefs(props)
|
|
const {
|
|
filters,
|
|
hasDefaultFilters,
|
|
setDefaultFilters,
|
|
tasks,
|
|
isLoading,
|
|
addTask,
|
|
updateTask,
|
|
} = useGanttFilters(route, props.viewId)
|
|
|
|
const DEFAULT_DATE_RANGE_DAYS = 7
|
|
|
|
const today = new Date()
|
|
const defaultTaskStartDate: DateISO = new Date(today.setHours(0, 0, 0, 0)).toISOString()
|
|
const defaultTaskEndDate: DateISO = new Date(new Date(
|
|
today.getFullYear(),
|
|
today.getMonth(),
|
|
today.getDate() + DEFAULT_DATE_RANGE_DAYS,
|
|
).setHours(23, 59, 0, 0)).toISOString()
|
|
|
|
async function addGanttTask(title: ITask['title']) {
|
|
return await addTask({
|
|
title,
|
|
projectId: filters.value.projectId,
|
|
startDate: defaultTaskStartDate,
|
|
endDate: defaultTaskEndDate,
|
|
})
|
|
}
|
|
|
|
const flatPickerEl = ref<typeof Foo | null>(null)
|
|
const flatPickerDateRange = computed<Date[]>({
|
|
get: () => ([
|
|
new Date(filters.value.dateFrom),
|
|
new Date(filters.value.dateTo),
|
|
]),
|
|
set(newVal) {
|
|
const [dateFrom, dateTo] = newVal.map((date) => date?.toISOString())
|
|
|
|
// only set after whole range has been selected
|
|
if (!dateTo) return
|
|
|
|
Object.assign(filters.value, {dateFrom, dateTo})
|
|
},
|
|
})
|
|
|
|
const initialDateRange = [filters.value.dateFrom, filters.value.dateTo]
|
|
|
|
const {t} = useI18n({useScope: 'global'})
|
|
const flatPickerConfig = computed<Options>(() => ({
|
|
altFormat: t('date.altFormatShort'),
|
|
altInput: true,
|
|
defaultDate: initialDateRange,
|
|
enableTime: false,
|
|
mode: 'range',
|
|
locale: getFlatpickrLanguage(),
|
|
}))
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.gantt-chart-container {
|
|
padding-bottom: 1rem;
|
|
}
|
|
|
|
.gantt-options {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1rem;
|
|
|
|
@media screen and (max-width: $tablet) {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
|
|
:global(.link-share-view:not(.has-background)) .gantt-options {
|
|
border: none;
|
|
box-shadow: none;
|
|
|
|
.card-content {
|
|
padding: .5rem;
|
|
}
|
|
}
|
|
|
|
.field {
|
|
margin-bottom: 0;
|
|
width: 33%;
|
|
|
|
&:not(:last-child) {
|
|
padding-right: .5rem;
|
|
}
|
|
|
|
@media screen and (max-width: $tablet) {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
margin-top: .5rem;
|
|
padding-right: 0 !important;
|
|
}
|
|
|
|
&, .input {
|
|
font-size: .8rem;
|
|
}
|
|
|
|
.select,
|
|
.select select {
|
|
height: auto;
|
|
width: 100%;
|
|
font-size: .8rem;
|
|
}
|
|
|
|
.label {
|
|
font-size: .9rem;
|
|
}
|
|
}
|
|
</style> |