11import { Box , Chip , Typography } from '@mui/material' ;
22import { useTheme } from '@mui/system' ;
3- import { CSSProperties , DragEvent , MouseEvent , useEffect , useState } from 'react' ;
3+ import { CSSProperties , DragEvent , MouseEvent , useCallback , useEffect , useRef , useState } from 'react' ;
44import useMeasure from 'react-use-measure' ;
55import { addDaysToDate } from 'shared' ;
66import { GanttChange , GanttTask , GANTT_CHART_CELL_SIZE } from '../../../../../utils/gantt.utils' ;
@@ -15,6 +15,10 @@ import {
1515import { ArcherElement } from 'react-archer' ;
1616import { v4 as uuidv4 } from 'uuid' ;
1717
18+ const CELL_SIZE_PX = 38 + 2 ; // 38px cell + 2px for borders (1px each side)
19+ const GAP_SIZE_PX = 10 ; // empirically determined, see note above
20+ const WIDTH_PER_DAY = 7.2 ; //width per day to use for resizing calculations, kind of arbitrary,
21+
1822interface GanttTaskBarEditProps < T > {
1923 days : Date [ ] ;
2024 task : GanttTask < T > ;
@@ -33,18 +37,19 @@ export const GanttTaskBarEditView = <T,>({
3337 onAddTaskPressed
3438} : GanttTaskBarEditProps < T > ) => {
3539 const theme = useTheme ( ) ;
36- const [ startX , setStartX ] = useState < number | null > ( null ) ;
3740 const [ showDropPoints , setShowDropPoints ] = useState ( false ) ;
3841 const [ isResizing , setIsResizing ] = useState ( false ) ;
39- const [ width , setWidth ] = useState ( 0 ) ; // current width of component, will change on resize
42+ const [ width , setWidth ] = useState ( 0 ) ;
43+ const [ correctWidth , setCorrectWidth ] = useState ( 0 ) ;
4044 const [ measureRef , bounds ] = useMeasure ( ) ;
41- const widthPerDay = 7.2 ; //width per day to use for resizing calculations, kind of arbitrary,
45+ const hasMeasuredRef = useRef ( false ) ;
46+ const boxRef = useRef < HTMLDivElement | null > ( null ) ;
4247
4348 const taskBarDisplayStyles : CSSProperties = {
4449 gridColumnStart : getStartCol ( task . start ) ,
4550 gridColumnEnd : getEndCol ( task . end ) ,
4651 height : '2rem' ,
47- width : task . root ? 'unset' : width === 0 ? `unset` : ` ${ width } px`,
52+ width : task . root ? 'unset' : correctWidth > 0 ? `${ correctWidth } px` : 'auto' ,
4853 border : `1px solid ${ isResizing ? theme . palette . text . primary : theme . palette . divider } ` ,
4954 borderRadius : '0.25rem' ,
5055 backgroundColor : task . styles ? task . styles . backgroundColor : theme . palette . background . paper ,
@@ -67,48 +72,65 @@ export const GanttTaskBarEditView = <T,>({
6772 right : '-10'
6873 } ;
6974
75+ const getCorrectWidth = useCallback ( ( rawWidth : number ) => {
76+ const newEventLengthInDays = floorToMultipleOf7 ( rawWidth / WIDTH_PER_DAY ) ;
77+ const displayWeeks = newEventLengthInDays / 7 + 1 ;
78+ return displayWeeks * CELL_SIZE_PX + ( displayWeeks - 1 ) * GAP_SIZE_PX ;
79+ } , [ ] ) ;
80+
7081 useEffect ( ( ) => {
71- if ( bounds . width !== 0 && width === 0 ) {
82+ if ( ! hasMeasuredRef . current && bounds . width > 0 ) {
7283 setWidth ( bounds . width ) ;
84+ setCorrectWidth ( getCorrectWidth ( bounds . width ) ) ;
85+ hasMeasuredRef . current = true ;
7386 }
74- } , [ bounds , width ] ) ;
87+ } , [ bounds . width , getCorrectWidth ] ) ;
7588
7689 // used to make sure that any changes to the start and end dates are made in multiples of 7
77- const roundToMultipleOf7 = ( num : number ) => {
78- return Math . round ( num / 7 ) * 7 ;
90+ const floorToMultipleOf7 = ( num : number ) => {
91+ return Math . floor ( num / 7 ) * 7 ;
92+ } ;
93+
94+ const ceilToMultipleOf7 = ( num : number ) => {
95+ return Math . ceil ( num / 7 ) * 7 ;
96+ } ;
97+
98+ const getDistanceFromLeft = ( clientX : number ) => {
99+ if ( ! boxRef . current ) return 0 ;
100+ const rect = boxRef . current . getBoundingClientRect ( ) ;
101+ return clientX - rect . left ;
79102 } ;
80103
81104 const handleMouseDown = ( e : MouseEvent < HTMLElement > ) => {
105+ const bar = ( e . currentTarget as HTMLElement ) . closest ( '[data-gantt-bar]' ) ;
106+ if ( ! bar ) return ;
107+
108+ boxRef . current = ( e . currentTarget as HTMLElement ) . closest ( '[data-gantt-bar]' ) as HTMLDivElement ;
82109 setIsResizing ( true ) ;
83- setStartX ( e . clientX ) ;
84110 } ;
85111
86112 const handleMouseMove = ( e : MouseEvent < HTMLElement > ) => {
87- if ( isResizing ) {
88- const currentX = e . clientX ;
89- const deltaX = currentX - startX ! ;
90- setWidth ( Math . max ( 100 , width + deltaX ) ) ;
91- setStartX ( currentX ) ;
92- }
113+ if ( ! isResizing ) return ;
114+
115+ const newWidth = Math . max ( 100 , getDistanceFromLeft ( e . clientX ) ) ;
116+
117+ setWidth ( newWidth ) ; // sync render
118+ setCorrectWidth ( getCorrectWidth ( newWidth ) ) ;
93119 } ;
94120
95121 const handleMouseUp = ( ) => {
96122 if ( isResizing ) {
97123 setIsResizing ( false ) ;
98- // Use change in width to calculate new length
99- const newEventLengthInDays = roundToMultipleOf7 ( width / widthPerDay ) ;
100- // The gantt chart tasks are inclusive (their width includes the full width of their start and end date)
124+ const newEventLengthInDays = floorToMultipleOf7 ( width / WIDTH_PER_DAY ) ;
101125 const displayWeeks = newEventLengthInDays / 7 + 1 ;
102- // We need these magic pixel numbers to dynamically calculate the correct width of the task to keep it in sync with the stored end date
103- const correctWidth = displayWeeks * 38 + ( displayWeeks - 1 ) * 10 ;
104- const newEndDate = addDaysToDate ( task . start , newEventLengthInDays ) ;
126+ const correctWidth = displayWeeks * 40 + ( displayWeeks - 1 ) * 10 ;
105127 setWidth ( correctWidth ) ;
106128 createChange ( {
107129 id : uuidv4 ( ) ,
108130 element : task . element ,
109131 type : 'change-end-date' ,
110132 originalEnd : task . end ,
111- newEnd : newEndDate
133+ newEnd : addDaysToDate ( task . start , newEventLengthInDays )
112134 } ) ;
113135 }
114136 } ;
@@ -124,7 +146,7 @@ export const GanttTaskBarEditView = <T,>({
124146 e . preventDefault ( ) ;
125147 } ;
126148 const onDrop = ( day : Date ) => {
127- const days = roundToMultipleOf7 ( differenceInDays ( day , task . start ) ) ;
149+ const days = ceilToMultipleOf7 ( differenceInDays ( day , task . start ) ) ;
128150 createChange ( { id : uuidv4 ( ) , element : task . element , type : 'shift-by-days' , days } ) ;
129151 } ;
130152
@@ -156,7 +178,7 @@ export const GanttTaskBarEditView = <T,>({
156178 } ;
157179 } ) }
158180 >
159- < div ref = { measureRef } style = { taskBarDisplayStyles } >
181+ < div data-gantt-bar ref = { measureRef } style = { taskBarDisplayStyles } >
160182 < Box sx = { webKitBoxContainerStyles ( ) } >
161183 < Box draggable = { ! task . root } onDrag = { onDragStart } onDragEnd = { onDragEnd } sx = { webKitBoxStyles ( ) } >
162184 < Box sx = { { display : 'flex' , flexDirection : 'row' } } >
0 commit comments