mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-12 19:03:39 +03:00
[Ooditable] Dom optimizations and cleanup
This commit is contained in:
@@ -5,7 +5,6 @@ import TableContainer from '@mui/material/TableContainer'
|
||||
import TableHead from '@mui/material/TableHead'
|
||||
|
||||
import { type Table as TableType } from '@tanstack/react-table'
|
||||
import { OodiTableAggregationRow } from './components/AggregationRow'
|
||||
import { OodiTableDataRow } from './components/Cell'
|
||||
import { OodiTableHeaderGroup } from './components/Header'
|
||||
import { OodiTablePagination } from './components/Pagination'
|
||||
@@ -57,8 +56,8 @@ export const OodiTableContainer = <OTData,>({
|
||||
},
|
||||
}}
|
||||
>
|
||||
{table.getAggregationRowModel().rows.map(OodiTableAggregationRow)}
|
||||
{table.getRowModel().rows.map(OodiTableDataRow)}
|
||||
{table.getAggregationRowModel().rows.map(row => OodiTableDataRow(row, true))}
|
||||
{table.getRowModel().rows.map(row => OodiTableDataRow(row))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import TableCell, { type TableCellProps } from '@mui/material/TableCell'
|
||||
import TableRow from '@mui/material/TableRow'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { flexRender } from '@tanstack/react-table'
|
||||
|
||||
import type { FC } from 'react'
|
||||
|
||||
import { AggregationRow } from '../features/aggregationRows'
|
||||
import { getCommonPinningStyles } from '../styles'
|
||||
|
||||
const OodiTableAggregationCell: FC<TableCellProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<TableCell
|
||||
{...props}
|
||||
sx={{
|
||||
borderWidth: '0 1px 1px 0',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'grey.300',
|
||||
padding: '0 1em',
|
||||
height: '3em',
|
||||
...props?.sx,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
)
|
||||
}
|
||||
|
||||
export const OodiTableAggregationRow = <OTData,>(row: AggregationRow<OTData>) => (
|
||||
<TableRow key={row.id}>
|
||||
{row.getVisibleCells().map(cell => {
|
||||
const { maxSize } = cell.column.columnDef
|
||||
return (
|
||||
<OodiTableAggregationCell
|
||||
key={cell.id}
|
||||
sx={{
|
||||
...getCommonPinningStyles(cell.column),
|
||||
maxWidth: maxSize !== Number.MAX_SAFE_INTEGER ? maxSize : '20em',
|
||||
}}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.aggregatedCell, cell.getContext())}
|
||||
</OodiTableAggregationCell>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
)
|
||||
@@ -1,54 +1,27 @@
|
||||
import Box from '@mui/material/Box'
|
||||
import TableCell, { type TableCellProps } from '@mui/material/TableCell'
|
||||
import './styles.css'
|
||||
|
||||
import TableCell from '@mui/material/TableCell'
|
||||
import TableRow from '@mui/material/TableRow'
|
||||
import { flexRender } from '@tanstack/react-table'
|
||||
import type { Row } from '@tanstack/react-table'
|
||||
import type { FC } from 'react'
|
||||
|
||||
import { getCommonPinningStyles } from '../styles'
|
||||
import { flexRender } from './util'
|
||||
|
||||
const OodiTableCell: FC<TableCellProps> = ({ children, ...props }) => {
|
||||
return (
|
||||
<TableCell
|
||||
{...props}
|
||||
sx={{
|
||||
borderWidth: '0 1px 1px 0',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'grey.300',
|
||||
padding: '0 1em',
|
||||
height: '3em',
|
||||
...props?.sx,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</TableCell>
|
||||
)
|
||||
}
|
||||
|
||||
export const OodiTableDataRow = <OTData,>(row: Row<OTData>) => (
|
||||
export const OodiTableDataRow = <OTData,>(row: Row<OTData>, aggregate = false) => (
|
||||
<TableRow key={row.id}>
|
||||
{row.getVisibleCells().map(cell => {
|
||||
const { maxSize } = cell.column.columnDef
|
||||
return (
|
||||
<OodiTableCell
|
||||
<TableCell
|
||||
className="ot-data-cell"
|
||||
key={cell.id}
|
||||
sx={{
|
||||
...getCommonPinningStyles(cell.column),
|
||||
maxWidth: maxSize !== Number.MAX_SAFE_INTEGER ? maxSize : '20em',
|
||||
}}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</OodiTableCell>
|
||||
{flexRender(aggregate ? cell.column.columnDef.aggregatedCell : cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import Box from '@mui/material/Box'
|
||||
import { SxProps, Theme } from '@mui/material/styles'
|
||||
import TableCell, { type TableCellProps } from '@mui/material/TableCell'
|
||||
import { styled, SxProps, Theme } from '@mui/material/styles'
|
||||
import TableCell from '@mui/material/TableCell'
|
||||
import TableRow from '@mui/material/TableRow'
|
||||
import { flexRender } from '@tanstack/react-table'
|
||||
import type { HeaderGroup } from '@tanstack/react-table'
|
||||
import type { FC, ReactNode } from 'react'
|
||||
|
||||
import { getCommonPinningStyles, verticalStyles } from '../styles'
|
||||
import { OodiTableSortIcons } from './SortIcons'
|
||||
import { flexRender } from './util'
|
||||
|
||||
const OodiTableHeader: FC<TableCellProps & { children?: ReactNode }> = ({ children, ...props }) => {
|
||||
return (
|
||||
<TableCell
|
||||
{...props}
|
||||
sx={{
|
||||
const OtHeaderCell = styled(TableCell)(({ theme }) => ({
|
||||
position: 'relative',
|
||||
padding: '1.5em',
|
||||
borderWidth: '0 1px 1px 0',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'grey.300',
|
||||
borderColor: theme.palette.grey[300],
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: 'white',
|
||||
...props.sx,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TableCell>
|
||||
)
|
||||
}
|
||||
backgroundColor: theme.palette.common.white,
|
||||
}))
|
||||
|
||||
const OtVerticalContentWrapper = styled('div')({
|
||||
writingMode: 'vertical-rl',
|
||||
textOrientation: 'mixed',
|
||||
whiteSpace: 'normal',
|
||||
overflow: 'visible',
|
||||
height: '100%',
|
||||
maxWidth: '15em',
|
||||
minHeight: '160px',
|
||||
maxHeight: '240px',
|
||||
margin: '0 auto',
|
||||
})
|
||||
|
||||
export const OodiTableHeaderGroup = <OTData,>(headerGroup: HeaderGroup<OTData>, verticalHeaders: string[]) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
@@ -41,15 +41,16 @@ export const OodiTableHeaderGroup = <OTData,>(headerGroup: HeaderGroup<OTData>,
|
||||
}
|
||||
|
||||
const isVertical = verticalHeaders.includes(header.id)
|
||||
const canSort = header.column.getCanSort()
|
||||
|
||||
const sx = {
|
||||
cursor: header.column.getCanSort() ? 'pointer' : 'inherit',
|
||||
cursor: canSort ? 'pointer' : 'inherit',
|
||||
...(isVertical && verticalStyles),
|
||||
...getCommonPinningStyles(header.column),
|
||||
}
|
||||
|
||||
return (
|
||||
<OodiTableHeader
|
||||
<OtHeaderCell
|
||||
colSpan={header.colSpan}
|
||||
key={header.id}
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
@@ -57,33 +58,14 @@ export const OodiTableHeaderGroup = <OTData,>(headerGroup: HeaderGroup<OTData>,
|
||||
sx={sx as SxProps<Theme>}
|
||||
>
|
||||
{isVertical ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
writingMode: 'vertical-rl',
|
||||
textOrientation: 'mixed',
|
||||
whiteSpace: 'normal',
|
||||
overflow: 'visible',
|
||||
height: '100%',
|
||||
maxWidth: '15em',
|
||||
minHeight: '160px',
|
||||
maxHeight: '240px',
|
||||
margin: '0 auto',
|
||||
}}
|
||||
>
|
||||
<OtVerticalContentWrapper>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</Box>
|
||||
</Box>
|
||||
</OtVerticalContentWrapper>
|
||||
) : (
|
||||
<Box>{flexRender(header.column.columnDef.header, header.getContext())}</Box>
|
||||
<>{flexRender(header.column.columnDef.header, header.getContext())}</>
|
||||
)}
|
||||
<OodiTableSortIcons canSort={header.column.getCanSort()} isSorted={header.column.getIsSorted()} />
|
||||
</OodiTableHeader>
|
||||
<OodiTableSortIcons canSort={canSort} isSorted={header.column.getIsSorted()} />
|
||||
</OtHeaderCell>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
|
||||
@@ -1,33 +1,43 @@
|
||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'
|
||||
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'
|
||||
import SwapVertIcon from '@mui/icons-material/SwapVert'
|
||||
import Box from '@mui/material/Box'
|
||||
import styled from '@mui/material/styles/styled'
|
||||
import type { SortDirection } from '@tanstack/react-table'
|
||||
|
||||
export const OodiTableSortIcons = ({ canSort, isSorted }: { canSort: boolean; isSorted: SortDirection | false }) => (
|
||||
<Box
|
||||
sx={{
|
||||
const OtSortIconWrapper = styled('div')(() => ({
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
right: '0',
|
||||
bottom: '0',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'flex-end',
|
||||
height: 'fit-content',
|
||||
width: 'fit-content',
|
||||
}))
|
||||
|
||||
'& > svg': {
|
||||
width: '1em',
|
||||
height: '1em',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{(canSort &&
|
||||
{
|
||||
asc: <ArrowUpwardIcon fontSize="small" sx={{ color: theme => theme.palette.primary.main }} />,
|
||||
desc: <ArrowDownwardIcon fontSize="small" sx={{ color: theme => theme.palette.primary.main }} />,
|
||||
false: <SwapVertIcon fontSize="small" sx={{ color: 'grey.300' }} />,
|
||||
}[isSorted as string]) ??
|
||||
null}
|
||||
</Box>
|
||||
const OtSortAscIcon = styled(ArrowUpwardIcon)(({ theme }) => ({
|
||||
fontSize: '1.25rem',
|
||||
color: theme.palette.primary.main,
|
||||
}))
|
||||
|
||||
const OtSortDescIcon = styled(ArrowDownwardIcon)(({ theme }) => ({
|
||||
fontSize: '1.25rem',
|
||||
color: theme.palette.primary.main,
|
||||
}))
|
||||
|
||||
const OtSortUnsetIcon = styled(SwapVertIcon)(({ theme }) => ({
|
||||
fontSize: '1.25rem',
|
||||
color: theme.palette.grey[300],
|
||||
}))
|
||||
|
||||
export const OodiTableSortIcons = ({ canSort, isSorted }: { canSort: boolean; isSorted: SortDirection | false }) => (
|
||||
<OtSortIconWrapper>
|
||||
{canSort
|
||||
? {
|
||||
asc: <OtSortAscIcon />,
|
||||
desc: <OtSortDescIcon />,
|
||||
false: <OtSortUnsetIcon />,
|
||||
}[isSorted as string]
|
||||
: null}
|
||||
</OtSortIconWrapper>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Using css classes is faster than Mui style/sx/styled components
|
||||
and in large tables it can add up especially without virtualization
|
||||
*/
|
||||
|
||||
.ot-data-cell.MuiTableCell-root {
|
||||
border-width: 0 1px 1px 0;
|
||||
border-style: solid;
|
||||
border-color: #e0e0e0; /* grey.300 */
|
||||
padding: 0 1em;
|
||||
height: 3em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ot-data-cell > div:not(.MuiStack-root, .MuiBox-root) {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Zebra stripe layout for the whole table */
|
||||
.ot-table-body > tr:nth-of-type(odd) > td {
|
||||
background-color: #f5f5f5; /* grey.100 */
|
||||
}
|
||||
|
||||
.ot-table-body > tr:nth-of-type(even) > td {
|
||||
background-color: white;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
export const flexRender = <TProps extends object>(
|
||||
Comp: ((props: TProps) => ReactNode) | string | undefined,
|
||||
props: TProps
|
||||
): ReactNode => {
|
||||
if (typeof Comp === 'string') return Comp
|
||||
return Comp ? Comp(props) : null
|
||||
}
|
||||
@@ -27,6 +27,9 @@ declare module '@tanstack/react-table' {
|
||||
}
|
||||
|
||||
interface Table<TData> {
|
||||
/**
|
||||
* Aggregation rows are not included in pagination/sorting.
|
||||
*/
|
||||
getAggregationRowModel: () => AggregationRowModel<TData>
|
||||
getAllAggregationRowIds: () => string[]
|
||||
hasAggregationRows: () => boolean
|
||||
|
||||
@@ -22,8 +22,5 @@ export const getCommonPinningStyles = <TData>(column: Column<TData>): SxProps<Th
|
||||
|
||||
export const verticalStyles: SxProps<Theme> = {
|
||||
overflow: 'visible',
|
||||
borderWidth: '1px 1px 1px 0',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'grey.300',
|
||||
padding: '0.5em',
|
||||
padding: '1em 0.5em',
|
||||
}
|
||||
|
||||
+2
-6
@@ -162,14 +162,13 @@ export const useGetColumnDefinitions = (modules: Map<string, CourseTabModule>):
|
||||
substitutePrefix +
|
||||
`Grade: ${correctCourse.grade}\nCompleted on: ${formatDate(correctCourse.completionDate, dateFormat)}`
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', margin: 'auto' }} title={title}>
|
||||
<div title={title}>
|
||||
<CheckIcon sx={{ color: sub ? theme.palette.ooditable.hops : theme.palette.ooditable.success }} />
|
||||
</div>
|
||||
)
|
||||
} else if (correctCourse.enrollmentDate) {
|
||||
return (
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'center', margin: 'auto' }}
|
||||
title={
|
||||
substitutePrefix + `Last enrollment ${formatDate(correctCourse.enrollmentDate, dateFormat)}`
|
||||
}
|
||||
@@ -185,10 +184,7 @@ export const useGetColumnDefinitions = (modules: Map<string, CourseTabModule>):
|
||||
)
|
||||
} else if (correctCourse.inHops) {
|
||||
return (
|
||||
<div
|
||||
style={{ display: 'flex', justifyContent: 'center', margin: 'auto' }}
|
||||
title={substitutePrefix + 'In primary studyplan'}
|
||||
>
|
||||
<div title={substitutePrefix + 'In primary studyplan'}>
|
||||
<CropSquareIcon sx={{ color: theme.palette.ooditable.hops }} />
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user