@@ -36,20 +36,37 @@ export const GanttGroup: React.FC<GanttGroupProps> = (props) => {
3636 const [ activeGroup , setActiveGroup ] = React . useState < string | undefined > ( undefined )
3737
3838 // Parse stepWidth to pixels
39- const stepWidthPx = parseInt ( stepWidth as string )
39+ const stepWidthPx = React . useMemo ( ( ) => parseInt ( stepWidth as string ) , [ stepWidth ] )
4040
4141 // Timeline calculations
4242 const timeRange = end - start
4343 const timelineColumns = Math . ceil ( timeRange / step )
4444 const totalTimelineWidth = timelineColumns * stepWidthPx
4545
4646 // Item statistics
47- const nonGroupItems = items ?. filter ( ( item : any ) => item . type !== "group" ) ?? [ ]
48- const avgDuration = nonGroupItems . length === 0
49- ? step
50- : nonGroupItems . reduce ( ( sum : number , item : any ) => sum + ( item . end - item . start ) , 0 ) / nonGroupItems . length
51- const itemMinStart = items && items . length > 0 ? Math . min ( ...items . map ( item => item . start ) ) : start
52- const itemMaxEnd = items && items . length > 0 ? Math . max ( ...items . map ( item => item . end ) ) : end
47+ const { avgDuration, itemMinStart, itemMaxEnd} = React . useMemo ( ( ) => {
48+ if ( ! items || items . length === 0 ) {
49+ return { avgDuration : step , itemMinStart : start , itemMaxEnd : end }
50+ }
51+ let sumDuration = 0
52+ let nonGroupCount = 0
53+ let minStart = Infinity
54+ let maxEnd = - Infinity
55+ for ( let i = 0 ; i < items . length ; i ++ ) {
56+ const item = items [ i ]
57+ if ( item . start < minStart ) minStart = item . start
58+ if ( item . end > maxEnd ) maxEnd = item . end
59+ if ( ( item as any ) . type !== "group" ) {
60+ sumDuration += item . end - item . start
61+ nonGroupCount ++
62+ }
63+ }
64+ return {
65+ avgDuration : nonGroupCount === 0 ? step : sumDuration / nonGroupCount ,
66+ itemMinStart : minStart ,
67+ itemMaxEnd : maxEnd ,
68+ }
69+ } , [ items , start , end , step ] )
5370
5471 // Column rendering calculations
5572 const columnsNeeded = items && items . length > 0 ? Math . ceil ( ( itemMaxEnd - start ) / step ) : timelineColumns
@@ -62,13 +79,14 @@ export const GanttGroup: React.FC<GanttGroupProps> = (props) => {
6279 }
6380
6481 handleResize ( )
65- viewportRef ?. current ?. addEventListener ( "resize" , handleResize )
82+ const viewport = viewportRef . current
83+ viewport ?. addEventListener ( "resize" , handleResize )
6684 window . addEventListener ( "resize" , handleResize )
6785 return ( ) => {
6886 window . removeEventListener ( "resize" , handleResize )
69- viewportRef . current ?. removeEventListener ( "resize" , handleResize )
87+ viewport ?. removeEventListener ( "resize" , handleResize )
7088 }
71- } )
89+ } , [ ] )
7290
7391 // Calculate row assignments (non-overlapping rows)
7492 const itemRows = items ?. length ? items
@@ -82,12 +100,24 @@ export const GanttGroup: React.FC<GanttGroupProps> = (props) => {
82100 : [ ]
83101
84102 // Style configurations
85- const containerStyles : CSSProperties = {
103+ const containerStyles : CSSProperties = React . useMemo ( ( ) => ( {
86104 display : "grid" ,
87105 gridTemplateColumns : `repeat(${ columnsToRender } , ${ stepWidth } )` ,
88106 minWidth : "100%" ,
89107 gridColumn : "1 / -1" ,
90- }
108+ } ) , [ columnsToRender , stepWidth ] )
109+
110+ const rowStyle : CSSProperties = React . useMemo ( ( ) => ( {
111+ gridColumn : "1 / -1" ,
112+ minHeight : rowHeight ,
113+ position : "relative" ,
114+ backgroundColor : "transparent"
115+ } ) , [ rowHeight ] )
116+
117+ const gridLineColumns = React . useMemo (
118+ ( ) => Array . from ( { length : Math . max ( 0 , columnsToRender - 1 ) } ) ,
119+ [ columnsToRender ]
120+ )
91121
92122 return (
93123 < div data-gantt-id = { props . id } id = { props . id } ref = { viewportRef } style = { containerStyles } >
@@ -98,14 +128,9 @@ export const GanttGroup: React.FC<GanttGroupProps> = (props) => {
98128 avgDuration = { avgDuration }
99129 stepWidth = { stepWidth } /> }
100130 { itemRows . map ( ( row , rowIndex ) => (
101- < >
102- < div key = { `row-${ rowIndex } ` } style = { {
103- gridColumn : "1 / -1" ,
104- minHeight : rowHeight ,
105- position : "relative" ,
106- backgroundColor : "transparent"
107- } } >
108- { Array . from ( { length : columnsToRender - 1 } ) . map ( ( _ , columnIndex ) => (
131+ < React . Fragment key = { `row-frag-${ rowIndex } ` } >
132+ < div key = { `row-${ rowIndex } ` } style = { rowStyle } >
133+ { gridLineColumns . map ( ( _ , columnIndex ) => (
109134 < div key = { `grid-${ columnIndex } ` } style = { {
110135 position : "absolute" ,
111136 left : ( columnIndex + 1 ) * stepWidthPx ,
@@ -158,20 +183,18 @@ export const GanttGroup: React.FC<GanttGroupProps> = (props) => {
158183 const hasVisibleWidth = itemPosition . width > 0
159184
160185 return hasVisibleWidth && (
161- < >
162- < GanttItem
163- key = { item . id }
164- id = { item . id }
165- w = { `${ itemPosition . width } px` }
166- left = { `${ itemPosition . left } px` }
167- onClick = { ( ) => {
168- if ( item . type != "group" ) return
169- setActiveGroup ( prevState => item . id === prevState ? undefined : item . id )
170- } }
171- >
172- { children ?.( item , itemIndex ) }
173- </ GanttItem >
174- </ >
186+ < GanttItem
187+ key = { item . id }
188+ id = { item . id }
189+ w = { `${ itemPosition . width } px` }
190+ left = { `${ itemPosition . left } px` }
191+ onClick = { ( ) => {
192+ if ( item . type != "group" ) return
193+ setActiveGroup ( prevState => item . id === prevState ? undefined : item . id )
194+ } }
195+ >
196+ { children ?.( item , itemIndex ) }
197+ </ GanttItem >
175198 )
176199 } ) }
177200 </ div >
@@ -183,7 +206,7 @@ export const GanttGroup: React.FC<GanttGroupProps> = (props) => {
183206 stepWidth = { stepWidth } rowHeight = { rowHeight } items = { item . data . items }
184207 key = { `group-target-${ itemIndex } ` } />
185208 } ) }
186- </ >
209+ </ React . Fragment >
187210 ) ) }
188211
189212 </ div >
0 commit comments