Allow variable precision on the Gantt graph #3759
@ -5,10 +5,9 @@
|
||||
/>
|
||||
<div ref="ganttContainer" class="gantt-container" v-else>
|
||||
<GGanttChart
|
||||
:date-format="DAYJS_ISO_DATE_FORMAT"
|
||||
:chart-start="isoToKebabDate(filters.dateFrom)"
|
||||
:chart-end="isoToKebabDate(filters.dateTo)"
|
||||
precision="day"
|
||||
:chart-start="dateFromDate"
|
||||
:chart-end="dateToDate"
|
||||
:precision="ganttChartPrecision"
|
||||
bar-start="startDate"
|
||||
bar-end="endDate"
|
||||
:grid="true"
|
||||
@ -40,15 +39,9 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, ref, watch, toRefs, onActivated} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
import {getHexColor} from '@/models/task'
|
||||
|
||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||
import {isoToKebabDate} from '@/helpers/time/isoToKebabDate'
|
||||
import {parseKebabDate} from '@/helpers/time/parseKebabDate'
|
||||
|
||||
import type {ITask, ITaskPartialWithId} from '@/modelTypes/ITask'
|
||||
import type {DateISO} from '@/types/DateISO'
|
||||
import type {GanttFilters} from '@/views/project/helpers/useGanttFilters'
|
||||
|
||||
import {
|
||||
@ -66,12 +59,10 @@ export interface GanttChartProps {
|
||||
isLoading: boolean,
|
||||
filters: GanttFilters,
|
||||
tasks: Map<ITask['id'], ITask>,
|
||||
defaultTaskStartDate: DateISO
|
||||
defaultTaskEndDate: DateISO
|
||||
defaultTaskStartDate: Date
|
||||
defaultTaskEndDate: Date
|
||||
}
|
||||
|
||||
const DAYJS_ISO_DATE_FORMAT = 'YYYY-MM-DD'
|
||||
|
||||
const props = defineProps<GanttChartProps>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@ -93,16 +84,28 @@ const dateFromDate = computed(() => new Date(new Date(filters.value.dateFrom).se
|
||||
const dateToDate = computed(() => new Date(new Date(filters.value.dateTo).setHours(23,59,0,0)))
|
||||
|
||||
const DAY_WIDTH_PIXELS = 30
|
||||
const DAY_HOUR_PRECISION_THRESHOLD = 5
|
||||
|
||||
const ganttChartWidth = computed(() => {
|
||||
|
||||
const ganttContainerReference = ganttContainer?.value
|
||||
const ganttContainerWidth = ganttContainerReference ? (ganttContainerReference['clientWidth'] ?? 0) : 0
|
||||
|
||||
const dateDiff = Math.floor((dateToDate.value.valueOf() - dateFromDate.value.valueOf()) / MILLISECONDS_A_DAY)
|
||||
const calculatedWidth = dateDiff * DAY_WIDTH_PIXELS
|
||||
|
||||
let calculatedWidth = dateDiff * DAY_WIDTH_PIXELS
|
||||
|
||||
if(dateDiff < DAY_HOUR_PRECISION_THRESHOLD) {
|
||||
calculatedWidth *= 24
|
||||
}
|
||||
|
||||
return (calculatedWidth > ganttContainerWidth) ? calculatedWidth + 'px' : '100%'
|
||||
})
|
||||
|
||||
const ganttChartPrecision = computed(() => {
|
||||
const dateDiff = Math.floor((dateToDate.value.valueOf() - dateFromDate.value.valueOf()) / MILLISECONDS_A_DAY)
|
||||
|
||||
return dateDiff < DAY_HOUR_PRECISION_THRESHOLD ? 'hour' : 'day'
|
||||
})
|
||||
|
||||
const ganttBars = ref<GanttBarObject[][]>([])
|
||||
@ -121,9 +124,10 @@ watch(
|
||||
|
||||
function transformTaskToGanttBar(t: ITask) {
|
||||
const black = 'var(--grey-800)'
|
||||
|
||||
return [{
|
||||
startDate: isoToKebabDate(t.startDate ? t.startDate.toISOString() : props.defaultTaskStartDate),
|
||||
endDate: isoToKebabDate(t.endDate ? t.endDate.toISOString() : props.defaultTaskEndDate),
|
||||
startDate: t.startDate ?? props.defaultTaskStartDate,
|
||||
endDate: t.endDate ?? props.defaultTaskEndDate,
|
||||
ganttBarConfig: {
|
||||
id: String(t.id),
|
||||
label: t.title,
|
||||
@ -143,10 +147,32 @@ async function updateGanttTask(e: {
|
||||
e: MouseEvent;
|
||||
datetime?: string | undefined;
|
||||
}) {
|
||||
|
||||
const taskId = Number(e.bar.ganttBarConfig.id)
|
||||
const task = tasks.value.get(taskId)
|
||||
|
||||
const startDate: Date = new Date(e.bar.startDate)
|
||||
const endDate: Date = new Date(e.bar.endDate)
|
||||
|
||||
if(task && ganttChartPrecision.value == 'day') {
|
||||
|
||||
|
||||
if(task.startDate) {
|
||||
startDate.setHours(task.startDate.getHours(), task.startDate.getMinutes(), task.startDate.getSeconds())
|
||||
} else {
|
||||
startDate.setHours(0,0,0)
|
||||
}
|
||||
|
||||
if(task.endDate) {
|
||||
endDate.setHours(task.endDate.getHours(), task.endDate.getMinutes(), task.endDate.getSeconds())
|
||||
} else {
|
||||
endDate.setHours(23,59,59)
|
||||
}
|
||||
}
|
||||
|
||||
emit('update:task', {
|
||||
id: Number(e.bar.ganttBarConfig.id),
|
||||
startDate: new Date(parseKebabDate(e.bar.startDate).setHours(0,0,0,0)),
|
||||
endDate: new Date(parseKebabDate(e.bar.endDate).setHours(23,59,0,0)),
|
||||
id: Number(taskId),
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
export const DATEFNS_DATE_FORMAT_KEBAB = 'yyyy-LL-dd'
|
||||
export const DATEFNS_DATETIME_FORMAT_KEBAB = 'yyyy-LL-dd HH:mm'
|
||||
|
||||
export const SECONDS_A_MINUTE = 60
|
||||
export const SECONDS_A_HOUR = SECONDS_A_MINUTE * 60
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type {DateISO} from '@/types/DateISO'
|
||||
import type {DateKebab} from '@/types/DateKebab'
|
||||
import type {DateKebab, TimeKebab} from '@/types/DateKebab'
|
||||
|
||||
// ✅ Format a date to YYYY-MM-DD (or any other format)
|
||||
function padTo2Digits(num: number) {
|
||||
@ -13,4 +13,16 @@ export function isoToKebabDate(isoDate: DateISO) {
|
||||
padTo2Digits(date.getMonth() + 1), // January is 0, but we want it to be 1
|
||||
padTo2Digits(date.getDate()),
|
||||
].join('-') as DateKebab
|
||||
}
|
||||
|
||||
export function isoToKebabTime(isoDate: DateISO) {
|
||||
const date = new Date(isoDate)
|
||||
return [
|
||||
date.getFullYear(),
|
||||
padTo2Digits(date.getMonth() + 1), // January is 0, but we want it to be 1
|
||||
padTo2Digits(date.getDate()),
|
||||
].join('-') + ' ' + [
|
||||
padTo2Digits(date.getHours()), // January is 0, but we want it to be 1
|
||||
padTo2Digits(date.getMinutes()),
|
||||
].join(':') as TimeKebab
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
import {parse} from 'date-fns'
|
||||
import {DATEFNS_DATE_FORMAT_KEBAB} from '@/constants/date'
|
||||
import type {DateKebab} from '@/types/DateKebab'
|
||||
import {DATEFNS_DATE_FORMAT_KEBAB, DATEFNS_DATETIME_FORMAT_KEBAB} from '@/constants/date'
|
||||
import type {DateKebab, TimeKebab} from '@/types/DateKebab'
|
||||
|
||||
export function parseKebabDate(date: DateKebab): Date {
|
||||
return parse(date, DATEFNS_DATE_FORMAT_KEBAB, new Date())
|
||||
}
|
||||
|
||||
export function parseKebabDateTime(date: TimeKebab): Date {
|
||||
return parse(date, DATEFNS_DATETIME_FORMAT_KEBAB, new Date())
|
||||
}
|
@ -2,3 +2,8 @@
|
||||
* Date in Format 2022-12-10
|
||||
*/
|
||||
export type DateKebab = `${string}-${string}-${string}`
|
||||
|
||||
/**
|
||||
* Date in Format 2022-12-10 00:00
|
||||
*/
|
||||
export type TimeKebab = `${string}-${string}-${string} ${string}:${string}`
|
||||
|
@ -65,7 +65,6 @@ import {createAsyncComponent} from '@/helpers/createAsyncComponent'
|
||||
import {useGanttFilters} from './helpers/useGanttFilters'
|
||||
import {RIGHTS} from '@/constants/rights'
|
||||
|
||||
import type {DateISO} from '@/types/DateISO'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
|
||||
type Options = Flatpickr.Options.Options
|
||||
@ -91,12 +90,12 @@ const {
|
||||
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(
|
||||
const defaultTaskStartDate: Date = new Date(today.setHours(0, 0, 0, 0))
|
||||
const defaultTaskEndDate: Date = new Date(new Date(
|
||||
today.getFullYear(),
|
||||
today.getMonth(),
|
||||
today.getDate() + DEFAULT_DATE_RANGE_DAYS,
|
||||
).setHours(23, 59, 0, 0)).toISOString()
|
||||
).setHours(23, 59, 0, 0))
|
||||
|
||||
async function addGanttTask(title: ITask['title']) {
|
||||
return await addTask({
|
||||
|
Reference in New Issue
Block a user
Can we make
'day'
aconst
? And please use strict comparison.