@@ -144,6 +144,8 @@ export const FreightTimeline = (props: { freights: Freight[]; project: string })
144144 ] ) ;
145145
146146 const freightListStyleRef = useRef < HTMLDivElement > ( null ) ;
147+ const freightListContainerRef = useRef < HTMLDivElement > ( null ) ;
148+ const carouselButtonRef = useRef < HTMLDivElement > ( null ) ;
147149
148150 const loadMore = useCallback ( ( ) => {
149151 setVisibleCount ( ( prev ) => Math . min ( prev + PAGE_SIZE , filteredFreights . length ) ) ;
@@ -162,15 +164,21 @@ export const FreightTimeline = (props: { freights: Freight[]; project: string })
162164 } ;
163165
164166 const scrollCarouselRight = ( ) => {
165- const right = freightListStyleRef . current ?. style . right || '0px' ;
167+ const rightStr = freightListStyleRef . current ?. style . right || '0px' ;
166168
167- const nextRight = + right . slice ( 0 , - 2 ) + 160 ;
169+ const right = + rightStr . slice ( 0 , - 2 ) ;
168170
169- if ( nextRight >= ( freightListStyleRef . current ?. clientWidth || 0 ) / 2 ) {
170- // At the edge — load more items if available
171- if ( visibleCount < filteredFreights . length ) {
172- loadMore ( ) ;
173- }
171+ const actualScrollEndFromRight =
172+ ( freightListStyleRef . current ?. getBoundingClientRect ( ) . width ?? 0 ) -
173+ ( freightListContainerRef . current ?. getBoundingClientRect ( ) . width ?? 0 ) ;
174+
175+ const nextRight = right + 160 ;
176+
177+ const isFreightScrollEnd = right > actualScrollEndFromRight ;
178+
179+ if ( isFreightScrollEnd && visibleCount < filteredFreights . length ) {
180+ loadMore ( ) ;
181+ } else if ( isFreightScrollEnd ) {
174182 return ;
175183 }
176184
@@ -210,84 +218,92 @@ export const FreightTimeline = (props: { freights: Freight[]; project: string })
210218 preferredFilter = { freightTimelineControllerContext . preferredFilter }
211219 />
212220 < div
213- className = { classNames ( 'w-full flex relative px-5' , {
214- 'overflow-hidden' : ! dndContext . active
215- } ) }
221+ className = 'flex flex-1 min-w-0'
216222 onWheel = { ( e ) => {
217223 if ( e . deltaY > 0 ) {
218224 scrollCarouselRight ( ) ;
219225 } else if ( e . deltaY < 0 ) {
220226 scrollCarouselLeft ( ) ;
221227 }
222228 } }
229+ ref = { freightListContainerRef }
223230 >
224231 < div
225- className = 'flex gap-1 relative right-0 transition-[right] duration-300 ease-out'
226- ref = { freightListStyleRef }
232+ className = 'bg-gray-100 px-1 flex items-center cursor-pointer rounded-sm hover:bg-gray-200 flex-shrink-0'
233+ onClick = { ( ) => {
234+ scrollCarouselLeft ( ) ;
235+ } }
236+ ref = { carouselButtonRef }
237+ >
238+ < FontAwesomeIcon icon = { faCaretLeft } />
239+ </ div >
240+
241+ < div
242+ className = { classNames ( 'flex-1 relative min-w-0' , {
243+ 'overflow-hidden' : ! dndContext . active
244+ } ) }
227245 >
228- { filteredFreights . slice ( 0 , visibleCount ) . map ( ( freight ) => {
229- const freightSoakTime = soakTime ?. [ freight ?. metadata ?. name || '' ] ;
246+ < div
247+ className = 'w-fit flex gap-1 relative right-0 transition-[right] duration-300 ease-out'
248+ ref = { freightListStyleRef }
249+ >
250+ { filteredFreights . slice ( 0 , visibleCount ) . map ( ( freight ) => {
251+ const freightSoakTime = soakTime ?. [ freight ?. metadata ?. name || '' ] ;
252+
253+ const promotionEligible = Boolean (
254+ promotionEligibleFreight ?. find (
255+ ( f ) => f ?. metadata ?. name === freight ?. metadata ?. name
256+ )
257+ ) ;
230258
231- const promotionEligible = Boolean (
232- promotionEligibleFreight ?. find ( ( f ) => f ?. metadata ?. name === freight ?. metadata ?. name )
233- ) ;
259+ if ( freight . count && freight . count > 0 ) {
260+ return (
261+ < FreightExpandTile
262+ key = { `expand-tile-${ freight ?. metadata ?. uid } -${ freight ?. count } ` }
263+ count = { freight . count }
264+ />
265+ ) ;
266+ }
234267
235- if ( freight . count && freight . count > 0 ) {
236268 return (
237- < FreightExpandTile
238- key = { `expand-tile-${ freight ?. metadata ?. uid } -${ freight ?. count } ` }
239- count = { freight . count }
269+ < FreightCard
270+ key = { `${ freight ?. metadata ?. uid } -${ freight ?. count } ` }
271+ className = 'h-full'
272+ stagesInFreight = {
273+ dictionaryContext ?. freightInStages ?. [ freight ?. metadata ?. name || '' ] || [ ]
274+ }
275+ freight = { freight }
276+ preferredFilter = { freightTimelineControllerContext . preferredFilter }
277+ setViewingFreight = { setViewingFreight }
278+ viewingFreight = { viewingFreight }
279+ inUse = {
280+ ( dictionaryContext ?. freightInStages [ freight ?. metadata ?. name || '' ] ?. length ||
281+ 0 ) > 0
282+ }
283+ stageColorMap = { colorContext . stageColorMap }
284+ promote = { isPromotionMode }
285+ isPromotionEligibleLoading = { getPromotionEligibleFreightQuery . isFetching }
286+ promotionEligible = { promotionEligible }
287+ onReviewAndPromote = { ( ) => {
288+ const stage = actionContext ?. action ?. stage ?. metadata ?. name || '' ;
289+
290+ navigate (
291+ generatePath ( paths . promote , {
292+ name : props . project ,
293+ freight : freight ?. metadata ?. name ,
294+ stage : stage
295+ } )
296+ ) ;
297+ } }
298+ soakTime = { freightSoakTime }
240299 />
241300 ) ;
242- }
243-
244- return (
245- < FreightCard
246- key = { `${ freight ?. metadata ?. uid } -${ freight ?. count } ` }
247- className = 'h-full'
248- stagesInFreight = {
249- dictionaryContext ?. freightInStages ?. [ freight ?. metadata ?. name || '' ] || [ ]
250- }
251- freight = { freight }
252- preferredFilter = { freightTimelineControllerContext . preferredFilter }
253- setViewingFreight = { setViewingFreight }
254- viewingFreight = { viewingFreight }
255- inUse = {
256- ( dictionaryContext ?. freightInStages [ freight ?. metadata ?. name || '' ] ?. length ||
257- 0 ) > 0
258- }
259- stageColorMap = { colorContext . stageColorMap }
260- promote = { isPromotionMode }
261- isPromotionEligibleLoading = { getPromotionEligibleFreightQuery . isFetching }
262- promotionEligible = { promotionEligible }
263- onReviewAndPromote = { ( ) => {
264- const stage = actionContext ?. action ?. stage ?. metadata ?. name || '' ;
265-
266- navigate (
267- generatePath ( paths . promote , {
268- name : props . project ,
269- freight : freight ?. metadata ?. name ,
270- stage : stage
271- } )
272- ) ;
273- } }
274- soakTime = { freightSoakTime }
275- />
276- ) ;
277- } ) }
278- </ div >
279-
280- < div
281- className = 'absolute left-0 h-full bg-gray-100 px-1 flex items-center cursor-pointer rounded-sm hover:bg-gray-200'
282- onClick = { ( ) => {
283- scrollCarouselLeft ( ) ;
284- } }
285- >
286- < FontAwesomeIcon icon = { faCaretLeft } />
301+ } ) }
302+ </ div >
287303 </ div >
288304
289305 < div
290- className = 'absolute right-0 h-full bg-gray-100 px-1 flex items-center cursor-pointer rounded-sm hover:bg-gray-200'
306+ className = 'bg-gray-100 px-1 flex items-center cursor-pointer rounded-sm hover:bg-gray-200 flex-shrink-0 '
291307 onClick = { ( ) => {
292308 scrollCarouselRight ( ) ;
293309 } }
0 commit comments