@@ -15,7 +15,6 @@ import { NoFilteredTasksState } from '@/components/layouts/EmptyState/NoFiltered
1515import { FilterBar } from '@/components/layouts/FilterBar'
1616import { Header } from '@/components/layouts/Header'
1717import { CustomLink } from '@/hoc/CustomLink'
18- import CustomScrollBar from '@/hoc/CustomScrollBar'
1918import { DragDropHandler } from '@/hoc/DragDropHandler'
2019import { useFilter } from '@/hooks/useFilter'
2120import { selectTaskBoard , updateWorkflowStateIdByTaskId } from '@/redux/features/taskBoardSlice'
@@ -30,9 +29,11 @@ import { sortTaskByDescendingOrder } from '@/utils/sortTask'
3029import { prioritizeStartedStates } from '@/utils/workflowStates'
3130import { UserRole } from '@api/core/types/user'
3231import { Box , Stack } from '@mui/material'
33- import { useCallback , useEffect , useState } from 'react'
32+ import { useCallback , useEffect , useRef , useState } from 'react'
3433import { useSelector } from 'react-redux'
3534import { z } from 'zod'
35+ import { useVirtualizer } from '@tanstack/react-virtual'
36+ import { WorkflowStateResponse } from '@/types/dto/workflowStates.dto'
3637
3738interface TaskBoardProps {
3839 mode : UserRole
@@ -172,35 +173,11 @@ export const TaskBoard = ({ mode, workspace }: TaskBoardProps) => {
172173 showAddBtn = { mode === UserRole . IU || ! ! previewMode }
173174 showHeader = { showHeader }
174175 >
175- < CustomScrollBar >
176- < Stack direction = "column" rowGap = "6px" sx = { { overflowX : 'auto' } } >
177- { sortTaskByDescendingOrder < TaskResponse > ( filterTaskWithWorkflowStateId ( list . id ) ) . map ( ( task , index ) => {
178- return (
179- < CustomLink
180- key = { task . id }
181- href = { { pathname : getCardHref ( task , mode ) , query : { token } } }
182- style = { { width : 'fit-content' } }
183- >
184- < DragDropHandler
185- key = { task . id }
186- accept = { 'taskCard' }
187- index = { index }
188- task = { task }
189- draggable // Make TaskCard draggable
190- >
191- < Box key = { task . id } >
192- < TaskCard
193- task = { task }
194- key = { task . id }
195- href = { { pathname : getCardHref ( task , mode ) , query : { token } } }
196- />
197- </ Box >
198- </ DragDropHandler >
199- </ CustomLink >
200- )
201- } ) }
202- </ Stack >
203- </ CustomScrollBar >
176+ < TasksRowVirtualizer
177+ rows = { sortTaskByDescendingOrder < TaskResponse > ( filterTaskWithWorkflowStateId ( list . id ) ) }
178+ mode = { mode }
179+ token = { token ?? null }
180+ />
204181 </ TaskColumn >
205182 </ DragDropHandler >
206183 ) ) }
@@ -216,42 +193,33 @@ export const TaskBoard = ({ mode, workspace }: TaskBoardProps) => {
216193 margin : '0 auto' ,
217194 } }
218195 >
219- < CustomScrollBar >
220- { prioritizeStartedStates ( workflowStates ) . map ( ( list , index ) => (
221- < DragDropHandler
196+ { prioritizeStartedStates ( workflowStates ) . map ( ( list , index ) => (
197+ < DragDropHandler
198+ key = { list . id }
199+ accept = { 'taskCard' }
200+ index = { index }
201+ id = { list . id }
202+ onDropItem = { onDropItem }
203+ droppable // Make TaskRow droppable
204+ >
205+ < TaskRow
206+ mode = { mode }
207+ workflowStateId = { list . id }
222208 key = { list . id }
223- accept = { 'taskCard' }
224- index = { index }
225- id = { list . id }
226- onDropItem = { onDropItem }
227- droppable // Make TaskRow droppable
209+ columnName = { list . name }
210+ taskCount = { taskCountForWorkflowStateId ( list . id ) }
211+ display = { ! ! filterTaskWithWorkflowStateId ( list . id ) . length }
212+ showAddBtn = { mode === UserRole . IU || ! ! previewMode }
228213 >
229- < TaskRow
214+ < TasksColumnVirtualizer
215+ rows = { sortTaskByDescendingOrder < TaskResponse > ( filterTaskWithWorkflowStateId ( list . id ) ) }
216+ list = { list }
230217 mode = { mode }
231- workflowStateId = { list . id }
232- key = { list . id }
233- columnName = { list . name }
234- taskCount = { taskCountForWorkflowStateId ( list . id ) }
235- display = { ! ! filterTaskWithWorkflowStateId ( list . id ) . length }
236- showAddBtn = { mode === UserRole . IU || ! ! previewMode }
237- >
238- { sortTaskByDescendingOrder < TaskResponse > ( filterTaskWithWorkflowStateId ( list . id ) ) . map ( ( task , index ) => {
239- return (
240- < DragDropHandler
241- key = { task . id }
242- accept = { 'taskCard' }
243- index = { index }
244- task = { task }
245- draggable // Make ListViewTaskCard draggable
246- >
247- < TaskCardList task = { task } variant = "task" key = { task . id } workflowState = { list } mode = { mode } />
248- </ DragDropHandler >
249- )
250- } ) }
251- </ TaskRow >
252- </ DragDropHandler >
253- ) ) }
254- </ CustomScrollBar >
218+ token = { token ?? null }
219+ />
220+ </ TaskRow >
221+ </ DragDropHandler >
222+ ) ) }
255223 </ Stack >
256224 ) }
257225 < CustomDragLayer >
@@ -260,3 +228,160 @@ export const TaskBoard = ({ mode, workspace }: TaskBoardProps) => {
260228 </ >
261229 )
262230}
231+
232+ interface TasksVirtualizerProps {
233+ rows : TaskResponse [ ]
234+ mode : UserRole
235+ token : string | null
236+ }
237+
238+ // virtualization component used in board view
239+ function TasksRowVirtualizer ( { rows, mode, token } : TasksVirtualizerProps ) {
240+ const parentRef = useRef < HTMLDivElement > ( null )
241+
242+ const rowVirtualizer = useVirtualizer ( {
243+ count : rows . length ,
244+ getScrollElement : ( ) => parentRef . current ,
245+ estimateSize : useCallback (
246+ ( index : number ) => {
247+ const task = rows [ index ]
248+ let estimate = 70
249+ if ( task . isArchived ) estimate += 24
250+ if ( task . dueDate ) estimate += 24
251+ if ( task . title && task . title . length > 50 ) estimate += 20
252+ return estimate
253+ } ,
254+ [ rows ] ,
255+ ) ,
256+ measureElement : ( element ) => element . getBoundingClientRect ( ) . height ,
257+ overscan : 20 ,
258+ } )
259+
260+ return (
261+ < div
262+ ref = { parentRef }
263+ className = "List"
264+ style = { {
265+ height : `100vh` ,
266+ width : '100%' ,
267+ overflow : 'auto' ,
268+ columnGap : '6px' ,
269+ } }
270+ >
271+ < div
272+ style = { {
273+ height : `${ rowVirtualizer . getTotalSize ( ) } px` ,
274+ width : '100%' ,
275+ position : 'relative' ,
276+ } }
277+ >
278+ { rowVirtualizer . getVirtualItems ( ) . map ( ( virtualRow ) => (
279+ < div
280+ key = { virtualRow . key }
281+ data-index = { virtualRow . index }
282+ ref = { ( node ) => rowVirtualizer . measureElement ( node ) }
283+ style = { {
284+ display : 'flex' ,
285+ position : 'absolute' ,
286+ transform : `translateY(${ virtualRow . start } px)` ,
287+ width : '100%' ,
288+ } }
289+ >
290+ < div style = { { padding : '3px 0' } } >
291+ < CustomLink
292+ key = { rows [ virtualRow . index ] . id }
293+ href = { {
294+ pathname : getCardHref ( rows [ virtualRow . index ] , mode ) ,
295+ query : { token } ,
296+ } }
297+ style = { { width : 'fit-content' } }
298+ >
299+ < DragDropHandler
300+ key = { rows [ virtualRow . index ] . id }
301+ accept = { 'taskCard' }
302+ index = { virtualRow . index }
303+ task = { rows [ virtualRow . index ] }
304+ draggable
305+ >
306+ < Box >
307+ < TaskCard
308+ task = { rows [ virtualRow . index ] }
309+ key = { rows [ virtualRow . index ] . id }
310+ href = { {
311+ pathname : getCardHref ( rows [ virtualRow . index ] , mode ) ,
312+ query : { token } ,
313+ } }
314+ />
315+ </ Box >
316+ </ DragDropHandler >
317+ </ CustomLink >
318+ </ div >
319+ </ div >
320+ ) ) }
321+ </ div >
322+ </ div >
323+ )
324+ }
325+
326+ function TasksColumnVirtualizer ( { rows, mode, token, list } : TasksVirtualizerProps & { list : WorkflowStateResponse } ) {
327+ const parentRef = useRef < HTMLDivElement > ( null )
328+ const columnVirtualizer = useVirtualizer ( {
329+ count : rows . length ,
330+ getScrollElement : ( ) => parentRef . current ,
331+ estimateSize : ( ) => 44 ,
332+ measureElement : ( element ) => element . getBoundingClientRect ( ) . height ,
333+ overscan : 100 ,
334+ } )
335+ return (
336+ < div
337+ ref = { parentRef }
338+ className = "List"
339+ style = { {
340+ height : `100vh` ,
341+ width : '100%' ,
342+ overflow : 'auto' ,
343+ columnGap : '6px' ,
344+ } }
345+ >
346+ < div
347+ style = { {
348+ height : `${ columnVirtualizer . getTotalSize ( ) } px` ,
349+ width : '100%' ,
350+ position : 'relative' ,
351+ } }
352+ >
353+ { columnVirtualizer . getVirtualItems ( ) . map ( ( virtualRow ) => (
354+ < div
355+ key = { virtualRow . key }
356+ data-index = { virtualRow . index }
357+ ref = { ( node ) => columnVirtualizer . measureElement ( node ) }
358+ style = { {
359+ display : 'flex' ,
360+ position : 'absolute' ,
361+ transform : `translateY(${ virtualRow . start } px)` ,
362+ width : '100%' ,
363+ } }
364+ >
365+ < div style = { { padding : '3px 0' , width : '100%' } } >
366+ < DragDropHandler
367+ key = { rows [ virtualRow . index ] . id }
368+ accept = { 'taskCard' }
369+ index = { virtualRow . index }
370+ task = { rows [ virtualRow . index ] }
371+ draggable
372+ >
373+ < TaskCardList
374+ task = { rows [ virtualRow . index ] }
375+ variant = "task"
376+ key = { rows [ virtualRow . index ] . id }
377+ workflowState = { list }
378+ mode = { mode }
379+ />
380+ </ DragDropHandler >
381+ </ div >
382+ </ div >
383+ ) ) }
384+ </ div >
385+ </ div >
386+ )
387+ }
0 commit comments