185 lines
5.8 KiB
JavaScript
185 lines
5.8 KiB
JavaScript
import React, { useState } from 'react';
|
|
import Table from '@mui/material/Table';
|
|
import TableBody from '@mui/material/TableBody';
|
|
import TableCell from '@mui/material/TableCell';
|
|
import TableContainer from '@mui/material/TableContainer';
|
|
import TableHead from '@mui/material/TableHead';
|
|
import TableRow from '@mui/material/TableRow';
|
|
import TableSortLabel from '@mui/material/TableSortLabel';
|
|
import Paper from '@mui/material/Paper';
|
|
import classNames from 'classnames';
|
|
import Button from "@mui/material/Button";
|
|
import { visuallyHidden } from '@mui/utils';
|
|
import _ from 'lodash';
|
|
import Box from "@mui/material/Box";
|
|
|
|
// let columns = [
|
|
// {
|
|
// name: "ID",
|
|
// value: (row) => row._id,
|
|
// descendingComparator: (a, b) => {return value(b) < value(a) ? 1 : value(b) > value(a) ? -1 : 0}
|
|
// }
|
|
// ]
|
|
//
|
|
// let rows = [
|
|
// {
|
|
// _id: 1234,
|
|
// name: "abc",
|
|
// value: 123
|
|
// }
|
|
// ]
|
|
//
|
|
// let options = {
|
|
// key: (row) => row._id,
|
|
// editor: (row) => {return (<MyRowEditor value={row}/>)}
|
|
// }
|
|
|
|
const cssTopControls = {
|
|
display: 'flex',
|
|
flexDirection: 'row-reverse',
|
|
}
|
|
|
|
/*
|
|
* Separate the Meteor calls as much as possible to avoid them being run repeatedly unnecessarily (were being run on every selection in the table).
|
|
*/
|
|
export default ({columns, rows, options}) => {
|
|
const [selected, setSelected] = useState(undefined);
|
|
|
|
let selectRow = (e, row) => {
|
|
setSelected(row);
|
|
}
|
|
|
|
const [edited, setEdited] = useState(undefined);
|
|
const [order, setOrder] = useState('asc') //'asc' or 'desc'
|
|
const [orderBy, setOrderBy] = useState(undefined); //Column name being sorted.
|
|
|
|
let editRow = (e, row) => {
|
|
setEdited(row);
|
|
}
|
|
|
|
const closeEditor = () => {
|
|
setEdited(undefined)
|
|
}
|
|
|
|
const addRow = () => {
|
|
setEdited({});
|
|
}
|
|
|
|
let containerStyle = options.maxHeight ? {maxHeight: options.maxHeight} : {}
|
|
let keyHandler = (e) => {
|
|
!edited && options.keyHandler && options.keyHandler(e, selected)
|
|
|
|
// Close the editor if the user hits escape.
|
|
if(edited && e.key === 'Escape') {
|
|
setEdited(undefined)
|
|
e.stopPropagation()
|
|
}
|
|
|
|
if(!edited && e.key === 'Insert') {
|
|
setEdited({})
|
|
e.stopPropagation()
|
|
}
|
|
}
|
|
|
|
const sort = (e, column) => {
|
|
const isAscending = orderBy === column && order === 'asc' //Descending if this is the first click on the column or toggling from ascending.
|
|
setOrder(isAscending ? 'desc' : 'asc')
|
|
setOrderBy(column)
|
|
}
|
|
|
|
const descendingComparator = (a, b, orderBy) => {
|
|
let av = orderBy.value(a)
|
|
let bv = orderBy.value(b)
|
|
|
|
if(bv < av) return -1
|
|
else if(bv > av) return 1
|
|
else return 0
|
|
}
|
|
const getComparator = (order, orderBy) => {
|
|
if(!orderBy) return undefined
|
|
else if(orderBy.descendingComparator) return order === 'desc' ? (a,b) => orderBy.descendingComparator(a,b) : (a,b) => -orderBy.descendingComparator(a,b)
|
|
else return order === 'desc'
|
|
? (a, b) => descendingComparator(a, b, orderBy)
|
|
: (a, b) => -descendingComparator(a, b, orderBy);
|
|
}
|
|
const stableSort = (array, comparator) => {
|
|
if(comparator) {
|
|
const stabilizedThis = array.map((el, index) => [el, index]);
|
|
stabilizedThis.sort((a, b) => {
|
|
const order = comparator(a[0], b[0]);
|
|
if (order !== 0) {
|
|
return order;
|
|
}
|
|
return a[1] - b[1];
|
|
});
|
|
return stabilizedThis.map((el) => el[0]);
|
|
}
|
|
else return array
|
|
}
|
|
// console.log(rows)
|
|
return (
|
|
<div className='simpleTableContainer'>
|
|
{options.add && <div style={cssTopControls}><Button variant="text" className="button" onClick={addRow}>Add</Button></div>}
|
|
<TableContainer className="simpleTable" component={Paper} style={containerStyle}>
|
|
<Table size="small" aria-label="Table" tabIndex="0" onKeyDown={keyHandler}>
|
|
<TableHead className="sticky">
|
|
<TableRow>
|
|
{columns.map((column, i) => {return (
|
|
<TableCell key={i} className="headerCell" sortDirection={orderBy === column ? order : false}>
|
|
<TableSortLabel active={orderBy === column} direction={orderBy === column ? order : 'asc'} onClick={(e) => {sort(e, column)}}>
|
|
{column.name}
|
|
{orderBy === column.name && (
|
|
<Box style={{color: 'white'}} component="span" sx={visuallyHidden}>
|
|
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
|
</Box>
|
|
)}
|
|
</TableSortLabel>
|
|
</TableCell>
|
|
)})}
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{edited && !options.key(edited) && (
|
|
<TableRow key="NewEditor" className="tableRow">
|
|
<TableCell className="editorContainer" colSpan={columns.length}>
|
|
{options.editor(edited, closeEditor)}
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
{stableSort(rows, getComparator(order, orderBy)).map((row, i) => {
|
|
return (
|
|
<TableRow key={options.key(row)} className={classNames({tableRow: true, selected: (!edited || options.key(edited) !== options.key(row)) && selected && options.key(selected) === options.key(row)})} onDoubleClick={(e) => {editRow(e, row)}} onClick={(e) => selectRow(e, row)}>
|
|
{edited && options.key(edited) === options.key(row) ?
|
|
<TableCell className="editorContainer" colSpan={columns.length}>
|
|
{options.editor(edited, closeEditor)}
|
|
</TableCell>
|
|
:
|
|
<>
|
|
{columns.map((column, ci) => {
|
|
// console.log("Rendering Cell")
|
|
// console.log(column);
|
|
// console.log(column.value(row));
|
|
let value = column.value(row);
|
|
|
|
if(_.isObject(value)) {
|
|
console.error("Cannot have an object returned as the value in a table.")
|
|
console.log("Cell value: ")
|
|
console.log(value)
|
|
value = JSON.stringify(value)
|
|
}
|
|
|
|
return (
|
|
<TableCell key={ci} align="left">{value}</TableCell>
|
|
)
|
|
})}
|
|
</>
|
|
}
|
|
</TableRow>
|
|
)
|
|
})}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
</div>
|
|
)
|
|
} |