lamassu-server/packages/admin-ui/src/components/tables/DataTable.jsx
2025-05-13 07:30:38 +01:00

211 lines
5.5 KiB
JavaScript

import classnames from 'classnames'
import * as R from 'ramda'
import React, { useState, useEffect } from 'react'
import {
AutoSizer,
List,
CellMeasurer,
CellMeasurerCache,
} from 'react-virtualized'
import { Table, TBody, THead, Tr, Td, Th } from '../fake-table/Table'
import { H4 } from '../typography'
import ExpandClosedIcon from '../../styling/icons/action/expand/closed.svg?react'
import ExpandOpenIcon from '../../styling/icons/action/expand/open.svg?react'
import { EmptyTable } from '../table'
const Row = ({
id,
index,
elements,
data,
width,
Details,
expanded,
expandRow,
expWidth,
expandable,
onClick,
size,
...props
}) => {
const hasPointer = onClick || expandable
const trClasses = {
'cursor-pointer': hasPointer,
'border-2 border-transparent': true,
'border-2 border-zircon shadow-md': expanded,
}
return (
<div className="p-[1px]">
<div
data-cy={id}
className={classnames({ 'pt-3': expanded && index !== 0 })}>
<Tr
size={size}
className={classnames(trClasses)}
onClick={() => {
expandable && expandRow(id, data)
onClick && onClick(data)
}}
error={data.error || data.hasError || data.batchError}
shouldShowError={false}
errorMessage={data.errorMessage || data.hasError || data.batchError}>
{elements.map(({ view = it => it?.toString(), ...props }, idx) => (
<Td key={idx} {...props}>
{view(data)}
</Td>
))}
{expandable && (
<Td width={expWidth} textAlign="center">
<button
onClick={() => expandRow(id, data)}
className="outline-0 border-0 bg-transparent cursor-pointer p-1">
{expanded && <ExpandOpenIcon />}
{!expanded && <ExpandClosedIcon />}
</button>
</Td>
)}
</Tr>
</div>
{expanded && (
<div className="pb-3">
<Tr
className={classnames({
'border-2 border-zircon shadow-md': expanded,
})}>
<Td width={width}>
<Details it={data} timezone={props.timezone} />
</Td>
</Tr>
</div>
)}
</div>
)
}
const DataTable = ({
elements = [],
data = [],
Details,
className,
tableClassName,
expandable,
initialExpanded,
onClick,
loading,
maxWidth = 1200,
emptyText,
rowSize,
...props
}) => {
const [expanded, setExpanded] = useState(initialExpanded)
useEffect(() => setExpanded(initialExpanded), [initialExpanded])
const coreWidth = R.compose(R.sum, R.map(R.prop('width')))(elements)
const expWidth = maxWidth - coreWidth
const width = coreWidth + (expandable ? expWidth : 0)
const expandRow = (id, data) => {
if (data.id) {
cache.clear(data.id)
setExpanded(data.id === expanded ? null : data.id)
} else {
cache.clear(id)
setExpanded(id === expanded ? null : id)
}
}
const cache = new CellMeasurerCache({
defaultHeight: 58,
fixedWidth: true,
})
function rowRenderer({ index, key, parent, style }) {
return (
<CellMeasurer
cache={cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}>
{({ registerChild }) => (
<div ref={registerChild} style={style}>
<Row
width={width}
size={rowSize}
id={data[index].id ? data[index].id : index}
index={index}
expWidth={expWidth}
elements={elements}
data={data[index]}
Details={Details}
expanded={
data[index].id
? data[index].id === expanded
: index === expanded
}
expandRow={expandRow}
expandable={expandable}
onClick={onClick}
timezone={props.timezone}
/>
</div>
)}
</CellMeasurer>
)
}
return (
<div
className={classnames({
'flex flex-1 flex-col': true,
[className]: !!className,
})}>
<Table
className={classnames(
'mb-12 min-h-50 flex-1 flex flex-col',
tableClassName,
)}
style={{ width }}>
<THead>
{elements.map(({ width, className, textAlign, header }, idx) => (
<Th
key={idx}
width={width}
className={className}
textAlign={textAlign}>
{header}
</Th>
))}
{expandable && <Th width={expWidth}></Th>}
</THead>
<TBody className="flex-auto">
{loading && <H4>Loading...</H4>}
{!loading && R.isEmpty(data) && <EmptyTable message={emptyText} />}
{!loading && !R.isEmpty(data) && (
<AutoSizer disableWidth>
{({ height }) => (
<List
// this has to be in a style because of how the component works
style={{ outline: 'none' }}
{...props}
height={loading ? 0 : height}
width={width}
rowCount={data.length}
rowHeight={cache.rowHeight}
rowRenderer={rowRenderer}
overscanRowCount={5}
deferredMeasurementCache={cache}
/>
)}
</AutoSizer>
)}
</TBody>
</Table>
</div>
)
}
export default DataTable