Skip to content

Commit

Permalink
[Tech] refacto table component
Browse files Browse the repository at this point in the history
  • Loading branch information
claire2212 committed Oct 14, 2024
1 parent 6562326 commit 617ff1a
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 231 deletions.
120 changes: 120 additions & 0 deletions frontend/src/components/Table/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { ChevronIcon } from '@features/commonStyles/icons/ChevronIcon.style'
import { Icon, SimpleTable } from '@mtes-mct/monitor-ui'
import { flexRender, getCoreRowModel, getSortedRowModel, useReactTable, type SortingState } from '@tanstack/react-table'
import { useVirtualizer } from '@tanstack/react-virtual'
import { forwardRef, useCallback, useState } from 'react'
import styled from 'styled-components'

export function TableWithRef({ columns, data }, ref) {
const { current: currentRef } = ref

const [sorting, setSorting] = useState<SortingState>([{ desc: true, id: 'updatedAt' }])

const table = useReactTable({
columns,
data,
enableSortingRemoval: false,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
onSortingChange: setSorting,
state: {
sorting
}
})

const { rows } = table.getRowModel()
const rowVirtualizer = useVirtualizer({
count: rows.length,
estimateSize: () => 40,
getItemKey: useCallback((index: number) => `${rows[index]?.id}`, [rows]),
getScrollElement: () => currentRef,
overscan: 10
})

const virtualRows = rowVirtualizer.getVirtualItems()
const [paddingTop, paddingBottom] =
virtualRows.length > 0
? [
Math.max(0, virtualRows[0]?.start ?? 0),
Math.max(0, rowVirtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0))
]
: [0, 0]

return (
<StyledDasboardsContainer ref={ref}>
<SimpleTable.Table>
<SimpleTable.Head>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<SimpleTable.Th key={header.id} $width={header.column.getSize()}>
{header.isPlaceholder ? undefined : (
<SimpleTable.SortContainer
className={header.column.getCanSort() ? 'cursor-pointer select-none' : ''}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}

{header.column.getCanSort() &&
({
asc: <StyledChevronIcon $isOpen={false} $right={false} />,
desc: <StyledChevronIcon $isOpen $right={false} />
}[header.column.getIsSorted() as string] ?? <Icon.SortingArrows size={14} />)}
</SimpleTable.SortContainer>
)}
</SimpleTable.Th>
))}
</tr>
))}
</SimpleTable.Head>
<tbody>
{paddingTop > 0 && (
<tr>
<td aria-label="empty-line-for-scroll" style={{ height: `${paddingTop}px` }} />
</tr>
)}
{virtualRows.map(virtualRow => {
const row = rows[virtualRow.index]

return (
<SimpleTable.BodyTr key={virtualRow.key}>
{row?.getVisibleCells().map(cell => (
<SimpleTable.Td
key={cell.id}
$isCenter={cell.column.id === 'geom' || cell.column.id === 'edit'}
style={{
maxWidth: cell.column.getSize(),
minWidth: cell.column.getSize(),
padding: cell.column.id === 'geom' || cell.column.id === 'edit' ? '0px' : '10px 12px',
width: cell.column.getSize()
}}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</SimpleTable.Td>
))}
</SimpleTable.BodyTr>
)
})}
{paddingBottom > 0 && (
<tr>
<td aria-label="empty-line-for-scroll" style={{ height: `${paddingBottom}px` }} />
</tr>
)}
</tbody>
</SimpleTable.Table>
</StyledDasboardsContainer>
)
}

export const Table = forwardRef(TableWithRef)

const StyledDasboardsContainer = styled.div`
overflow: auto;
width: fit-content;
// scroll width (~15px) + 4px
padding-right: 19px;
`
const StyledChevronIcon = styled(ChevronIcon)`
margin-top: 0px;
margin-right: 0px;
`
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import type { Dashboard } from '@features/Dashboard/types'

export const Columns = (regulatoryAreas, controlUnits, legacyFirefoxOffset: number = 0) => [
{
accessorFn: row => row.seaFont,
accessorFn: row => row.seaFront,
cell: info => info.getValue(),
enableSorting: true,
header: () => 'Façade',
id: 'seaFont',
id: 'seaFront',
size: 104 + legacyFirefoxOffset
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { useGetControlUnitsQuery } from '@api/controlUnitsAPI'
import { useGetRegulatoryLayersQuery } from '@api/regulatoryLayersAPI'
import { Table } from '@components/Table'
import { StyledSkeletonRow } from '@features/commonComponents/Skeleton'
import { ChevronIcon } from '@features/commonStyles/icons/ChevronIcon.style'
import { Icon, SimpleTable } from '@mtes-mct/monitor-ui'
import { flexRender, getCoreRowModel, getSortedRowModel, useReactTable, type SortingState } from '@tanstack/react-table'
import { useVirtualizer } from '@tanstack/react-virtual'
import { isLegacyFirefox } from '@utils/isLegacyFirefox'
import { paths } from 'paths'
import { useCallback, useMemo, useRef, useState } from 'react'
import { useMemo, useRef } from 'react'
import { useLocation } from 'react-router'
import styled from 'styled-components'

import { Columns } from './Columns'

Expand All @@ -28,8 +24,6 @@ export function DashboardsTable({ dashboards, isLoading }: DashboardsTableProps)

const legacyFirefoxOffset = pathname !== paths.sidewindow && isLegacyFirefox() ? -25 : 0

const [sorting, setSorting] = useState<SortingState>([{ desc: true, id: 'updatedAt' }])

const tableData = useMemo(() => (isLoading ? Array(5).fill({}) : dashboards), [isLoading, dashboards])

const columns = useMemo(
Expand All @@ -43,111 +37,7 @@ export function DashboardsTable({ dashboards, isLoading }: DashboardsTableProps)
[isLoading, controlUnits, regulatoryAreas, legacyFirefoxOffset]
)

const table = useReactTable({
columns,
data: tableData,
enableSortingRemoval: false,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
onSortingChange: setSorting,
state: {
sorting
}
})

const tableContainerRef = useRef<HTMLDivElement>(null)

const { rows } = table.getRowModel()
const rowVirtualizer = useVirtualizer({
count: rows.length,
estimateSize: () => 40,
getItemKey: useCallback((index: number) => `${rows[index]?.id}`, [rows]),
getScrollElement: () => tableContainerRef.current,
overscan: 10
})

const virtualRows = rowVirtualizer.getVirtualItems()
const [paddingTop, paddingBottom] =
virtualRows.length > 0
? [
Math.max(0, virtualRows[0]?.start ?? 0),
Math.max(0, rowVirtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0))
]
: [0, 0]

return (
<StyledDasboardsContainer ref={tableContainerRef}>
<SimpleTable.Table>
<SimpleTable.Head>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => (
<SimpleTable.Th key={header.id} $width={header.column.getSize()}>
{header.isPlaceholder ? undefined : (
<SimpleTable.SortContainer
className={header.column.getCanSort() ? 'cursor-pointer select-none' : ''}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}

{header.column.getCanSort() &&
({
asc: <StyledChevronIcon $isOpen={false} $right={false} />,
desc: <StyledChevronIcon $isOpen $right={false} />
}[header.column.getIsSorted() as string] ?? <Icon.SortingArrows size={14} />)}
</SimpleTable.SortContainer>
)}
</SimpleTable.Th>
))}
</tr>
))}
</SimpleTable.Head>
<tbody>
{paddingTop > 0 && (
<tr>
<td aria-label="empty-line-for-scroll" style={{ height: `${paddingTop}px` }} />
</tr>
)}
{virtualRows.map(virtualRow => {
const row = rows[virtualRow.index]

return (
<SimpleTable.BodyTr key={virtualRow.key}>
{row?.getVisibleCells().map(cell => (
<SimpleTable.Td
key={cell.id}
$isCenter={cell.column.id === 'geom' || cell.column.id === 'edit'}
style={{
maxWidth: cell.column.getSize(),
minWidth: cell.column.getSize(),
padding: cell.column.id === 'geom' || cell.column.id === 'edit' ? '0px' : '10px 12px',
width: cell.column.getSize()
}}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</SimpleTable.Td>
))}
</SimpleTable.BodyTr>
)
})}
{paddingBottom > 0 && (
<tr>
<td aria-label="empty-line-for-scroll" style={{ height: `${paddingBottom}px` }} />
</tr>
)}
</tbody>
</SimpleTable.Table>
</StyledDasboardsContainer>
)
return <Table ref={tableContainerRef} columns={columns} data={tableData} />
}

const StyledDasboardsContainer = styled.div`
overflow: auto;
width: fit-content;
// scroll width (~15px) + 4px
padding-right: 19px;
`
const StyledChevronIcon = styled(ChevronIcon)`
margin-top: 0px;
margin-right: 0px;
`
Loading

0 comments on commit 617ff1a

Please sign in to comment.