From 9b912af8731b9feb4aa5483e8500ca99f100c628 Mon Sep 17 00:00:00 2001 From: Giacomo Rossetto Date: Tue, 3 Oct 2023 22:18:25 +0000 Subject: [PATCH 1/2] Initial work --- src/components/tasks/GanttChart.vue | 35 ++++++++++++++++++++--------- src/constants/date.ts | 1 + src/helpers/time/isoToKebabDate.ts | 14 +++++++++++- src/helpers/time/parseKebabDate.ts | 8 +++++-- src/types/DateKebab.ts | 5 +++++ 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/components/tasks/GanttChart.vue b/src/components/tasks/GanttChart.vue index d536c6467..296dc1cc8 100644 --- a/src/components/tasks/GanttChart.vue +++ b/src/components/tasks/GanttChart.vue @@ -6,9 +6,9 @@
() @@ -93,16 +93,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([]) @@ -121,9 +133,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: isoToKebabTime(t.startDate ? t.startDate.toISOString() : props.defaultTaskStartDate), + endDate: isoToKebabTime(t.endDate ? t.endDate.toISOString() : props.defaultTaskEndDate), ganttBarConfig: { id: String(t.id), label: t.title, @@ -145,8 +158,8 @@ async function updateGanttTask(e: { }) { 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)), + startDate: new Date(parseKebabDateTime(e.bar.startDate)), + endDate: new Date(parseKebabDateTime(e.bar.endDate)), }) } diff --git a/src/constants/date.ts b/src/constants/date.ts index 835f2c4da..b0224f26f 100644 --- a/src/constants/date.ts +++ b/src/constants/date.ts @@ -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 diff --git a/src/helpers/time/isoToKebabDate.ts b/src/helpers/time/isoToKebabDate.ts index 54290a035..532c7c581 100644 --- a/src/helpers/time/isoToKebabDate.ts +++ b/src/helpers/time/isoToKebabDate.ts @@ -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 } \ No newline at end of file diff --git a/src/helpers/time/parseKebabDate.ts b/src/helpers/time/parseKebabDate.ts index f1643aa48..68bcc3b6f 100644 --- a/src/helpers/time/parseKebabDate.ts +++ b/src/helpers/time/parseKebabDate.ts @@ -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()) } \ No newline at end of file diff --git a/src/types/DateKebab.ts b/src/types/DateKebab.ts index bdc1808df..2c5f15909 100644 --- a/src/types/DateKebab.ts +++ b/src/types/DateKebab.ts @@ -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}` -- 2.45.2 From ffa6ce1714e31f646eb96a98823f730197d4b221 Mon Sep 17 00:00:00 2001 From: Giacomo Rossetto Date: Wed, 4 Oct 2023 18:30:12 +0000 Subject: [PATCH 2/2] Remove date-to-string handling Restored snappy behavior on day precision mode --- src/components/tasks/GanttChart.vue | 49 ++++++++++++++++++----------- src/views/project/ProjectGantt.vue | 7 ++--- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/components/tasks/GanttChart.vue b/src/components/tasks/GanttChart.vue index 296dc1cc8..a49eaf788 100644 --- a/src/components/tasks/GanttChart.vue +++ b/src/components/tasks/GanttChart.vue @@ -5,9 +5,8 @@ />
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, isoToKebabTime} from '@/helpers/time/isoToKebabDate' -import {parseKebabDate, parseKebabDateTime} 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, - defaultTaskStartDate: DateISO - defaultTaskEndDate: DateISO + defaultTaskStartDate: Date + defaultTaskEndDate: Date } -const DAYJS_ISO_DATE_FORMAT = 'YYYY-MM-DD HH:mm' - const props = defineProps() const emit = defineEmits<{ @@ -135,8 +126,8 @@ function transformTaskToGanttBar(t: ITask) { const black = 'var(--grey-800)' return [{ - startDate: isoToKebabTime(t.startDate ? t.startDate.toISOString() : props.defaultTaskStartDate), - endDate: isoToKebabTime(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, @@ -156,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(parseKebabDateTime(e.bar.startDate)), - endDate: new Date(parseKebabDateTime(e.bar.endDate)), + id: Number(taskId), + startDate: startDate, + endDate: endDate, }) } diff --git a/src/views/project/ProjectGantt.vue b/src/views/project/ProjectGantt.vue index ef270a6ed..026510419 100644 --- a/src/views/project/ProjectGantt.vue +++ b/src/views/project/ProjectGantt.vue @@ -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({ -- 2.45.2