Added collapse to table
This commit is contained in:
@@ -27,11 +27,14 @@ import Stack from "@mui/material/Stack/Stack";
|
|||||||
import ModeIcon from "@mui/icons-material/Mode";
|
import ModeIcon from "@mui/icons-material/Mode";
|
||||||
import ClearIcon from "@mui/icons-material/Clear";
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
import Fade from "@mui/material/Fade/Fade";
|
import Fade from "@mui/material/Fade/Fade";
|
||||||
|
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||||
|
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
|
||||||
import NodePieChart, { PieData } from "./NodePieChart";
|
import NodePieChart, { PieData } from "./NodePieChart";
|
||||||
import Grid2 from "@mui/material/Unstable_Grid2"; // Grid version 2
|
import Grid2 from "@mui/material/Unstable_Grid2"; // Grid version 2
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
Collapse,
|
||||||
Container,
|
Container,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
useTheme,
|
useTheme,
|
||||||
@@ -111,52 +114,6 @@ function stableSort<T>(
|
|||||||
return stabilizedThis.map((el) => el[0]);
|
return stabilizedThis.map((el) => el[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EnhancedTableProps {
|
|
||||||
onRequestSort: (
|
|
||||||
event: React.MouseEvent<unknown>,
|
|
||||||
property: keyof TableData,
|
|
||||||
) => void;
|
|
||||||
order: Order;
|
|
||||||
orderBy: string;
|
|
||||||
rowCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function EnhancedTableHead(props: EnhancedTableProps) {
|
|
||||||
const { order, orderBy, onRequestSort } = props;
|
|
||||||
const createSortHandler =
|
|
||||||
(property: keyof TableData) => (event: React.MouseEvent<unknown>) => {
|
|
||||||
onRequestSort(event, property);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
{headCells.map((headCell) => (
|
|
||||||
<TableCell
|
|
||||||
key={headCell.id}
|
|
||||||
align={headCell.alignRight ? "right" : "left"}
|
|
||||||
padding={headCell.disablePadding ? "none" : "normal"}
|
|
||||||
sortDirection={orderBy === headCell.id ? order : false}
|
|
||||||
>
|
|
||||||
<TableSortLabel
|
|
||||||
active={orderBy === headCell.id}
|
|
||||||
direction={orderBy === headCell.id ? order : "asc"}
|
|
||||||
onClick={createSortHandler(headCell.id)}
|
|
||||||
>
|
|
||||||
{headCell.label}
|
|
||||||
{orderBy === headCell.id ? (
|
|
||||||
<Box component="span" sx={visuallyHidden}>
|
|
||||||
{order === "desc" ? "sorted descending" : "sorted ascending"}
|
|
||||||
</Box>
|
|
||||||
) : null}
|
|
||||||
</TableSortLabel>
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EnhancedTableToolbarProps {
|
interface EnhancedTableToolbarProps {
|
||||||
selected: string | undefined;
|
selected: string | undefined;
|
||||||
tableData: TableData[];
|
tableData: TableData[];
|
||||||
@@ -337,7 +294,7 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
|||||||
{/* Pie Chart Grid */}
|
{/* Pie Chart Grid */}
|
||||||
<Grid2
|
<Grid2
|
||||||
key="PieChart"
|
key="PieChart"
|
||||||
lg={6}
|
md={6}
|
||||||
xs={12}
|
xs={12}
|
||||||
display="flex"
|
display="flex"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
@@ -353,7 +310,7 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
|||||||
key="CardStack"
|
key="CardStack"
|
||||||
lg={6}
|
lg={6}
|
||||||
display="flex"
|
display="flex"
|
||||||
sx={{ display: { lg: "flex", xs: "none" } }}
|
sx={{ display: { lg: "flex", xs: "none", md: "flex" } }}
|
||||||
>
|
>
|
||||||
{cardStack}
|
{cardStack}
|
||||||
</Grid2>
|
</Grid2>
|
||||||
@@ -366,64 +323,189 @@ function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLastSeen(last_seen: number) {
|
|
||||||
return (
|
|
||||||
<Typography component="div" align="left" variant="body1">
|
|
||||||
{last_seen} days ago
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderName(name: string, id: string) {
|
|
||||||
return (
|
|
||||||
<Stack>
|
|
||||||
<Typography component="div" align="left" variant="body1">
|
|
||||||
{name}
|
|
||||||
</Typography>
|
|
||||||
<Typography color="grey" component="div" align="left" variant="body2">
|
|
||||||
{id}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderStatus(status: NodeStatus) {
|
|
||||||
switch (status) {
|
|
||||||
case NodeStatus.Online:
|
|
||||||
return (
|
|
||||||
<Stack direction="row" alignItems="center" gap={1}>
|
|
||||||
<CircleIcon color="success" style={{ fontSize: 15 }} />
|
|
||||||
<Typography component="div" align="left" variant="body1">
|
|
||||||
Online
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
|
|
||||||
case NodeStatus.Offline:
|
|
||||||
return (
|
|
||||||
<Stack direction="row" alignItems="center" gap={1}>
|
|
||||||
<CircleIcon color="error" style={{ fontSize: 15 }} />
|
|
||||||
<Typography component="div" align="left" variant="body1">
|
|
||||||
Offline
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
case NodeStatus.Pending:
|
|
||||||
return (
|
|
||||||
<Stack direction="row" alignItems="center" gap={1}>
|
|
||||||
<CircleIcon color="warning" style={{ fontSize: 15 }} />
|
|
||||||
<Typography component="div" align="left" variant="body1">
|
|
||||||
Pending
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeTableProps {
|
export interface NodeTableProps {
|
||||||
tableData: TableData[];
|
tableData: TableData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EnhancedTableProps {
|
||||||
|
onRequestSort: (
|
||||||
|
event: React.MouseEvent<unknown>,
|
||||||
|
property: keyof TableData,
|
||||||
|
) => void;
|
||||||
|
order: Order;
|
||||||
|
orderBy: string;
|
||||||
|
rowCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EnhancedTableHead(props: EnhancedTableProps) {
|
||||||
|
const { order, orderBy, onRequestSort } = props;
|
||||||
|
const createSortHandler =
|
||||||
|
(property: keyof TableData) => (event: React.MouseEvent<unknown>) => {
|
||||||
|
onRequestSort(event, property);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell id="dropdown" colSpan={1} />
|
||||||
|
{headCells.map((headCell) => (
|
||||||
|
<TableCell
|
||||||
|
key={headCell.id}
|
||||||
|
align={headCell.alignRight ? "right" : "left"}
|
||||||
|
padding={headCell.disablePadding ? "none" : "normal"}
|
||||||
|
sortDirection={orderBy === headCell.id ? order : false}
|
||||||
|
>
|
||||||
|
<TableSortLabel
|
||||||
|
active={orderBy === headCell.id}
|
||||||
|
direction={orderBy === headCell.id ? order : "asc"}
|
||||||
|
onClick={createSortHandler(headCell.id)}
|
||||||
|
>
|
||||||
|
{headCell.label}
|
||||||
|
{orderBy === headCell.id ? (
|
||||||
|
<Box component="span" sx={visuallyHidden}>
|
||||||
|
{order === "desc" ? "sorted descending" : "sorted ascending"}
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
</TableSortLabel>
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Row(props: {
|
||||||
|
row: TableData;
|
||||||
|
selected: string | undefined;
|
||||||
|
setSelected: (a: string | undefined) => void;
|
||||||
|
}) {
|
||||||
|
function renderStatus(status: NodeStatus) {
|
||||||
|
switch (status) {
|
||||||
|
case NodeStatus.Online:
|
||||||
|
return (
|
||||||
|
<Stack direction="row" alignItems="center" gap={1}>
|
||||||
|
<CircleIcon color="success" style={{ fontSize: 15 }} />
|
||||||
|
<Typography component="div" align="left" variant="body1">
|
||||||
|
Online
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
case NodeStatus.Offline:
|
||||||
|
return (
|
||||||
|
<Stack direction="row" alignItems="center" gap={1}>
|
||||||
|
<CircleIcon color="error" style={{ fontSize: 15 }} />
|
||||||
|
<Typography component="div" align="left" variant="body1">
|
||||||
|
Offline
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
case NodeStatus.Pending:
|
||||||
|
return (
|
||||||
|
<Stack direction="row" alignItems="center" gap={1}>
|
||||||
|
<CircleIcon color="warning" style={{ fontSize: 15 }} />
|
||||||
|
<Typography component="div" align="left" variant="body1">
|
||||||
|
Pending
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { row, selected, setSelected } = props;
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
//const labelId = `enhanced-table-checkbox-${index}`;
|
||||||
|
|
||||||
|
// Speed optimization. We compare string pointers here instead of the string content.
|
||||||
|
const isSelected = selected == row.name;
|
||||||
|
|
||||||
|
const handleClick = (event: React.MouseEvent<unknown>, name: string) => {
|
||||||
|
if (isSelected) {
|
||||||
|
setSelected(undefined);
|
||||||
|
} else {
|
||||||
|
setSelected(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
|
||||||
|
{/* Rendered Row */}
|
||||||
|
<TableRow
|
||||||
|
hover
|
||||||
|
role="checkbox"
|
||||||
|
aria-checked={isSelected}
|
||||||
|
tabIndex={-1}
|
||||||
|
key={row.name}
|
||||||
|
selected={isSelected}
|
||||||
|
sx={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
<TableCell padding="none">
|
||||||
|
<IconButton
|
||||||
|
aria-label="expand row"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
>
|
||||||
|
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell component="th" scope="row" onClick={(event) => handleClick(event, row.name)}>
|
||||||
|
<Stack>
|
||||||
|
<Typography component="div" align="left" variant="body1">
|
||||||
|
{row.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
color="grey"
|
||||||
|
component="div"
|
||||||
|
align="left"
|
||||||
|
variant="body2"
|
||||||
|
>
|
||||||
|
{row.id}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right" onClick={(event) => handleClick(event, row.name)}>{renderStatus(row.status)}</TableCell>
|
||||||
|
<TableCell align="right" onClick={(event) => handleClick(event, row.name)}>
|
||||||
|
<Typography component="div" align="left" variant="body1">
|
||||||
|
{row.last_seen} days ago
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
{/* Row Expansion */}
|
||||||
|
<TableRow>
|
||||||
|
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
|
||||||
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||||
|
<Box sx={{ margin: 1 }}>
|
||||||
|
<Typography variant="h6" gutterBottom component="div">
|
||||||
|
History
|
||||||
|
</Typography>
|
||||||
|
<Table size="small" aria-label="purchases">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Date</TableCell>
|
||||||
|
<TableCell>Customer</TableCell>
|
||||||
|
<TableCell align="right">Amount</TableCell>
|
||||||
|
<TableCell align="right">Total price ($)</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow key="test1">
|
||||||
|
<TableCell>Test1</TableCell>
|
||||||
|
<TableCell>Test2</TableCell>
|
||||||
|
<TableCell align="right">Test</TableCell>
|
||||||
|
<TableCell align="right">Test</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Collapse>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function NodeTable(props: NodeTableProps) {
|
export default function NodeTable(props: NodeTableProps) {
|
||||||
let { tableData } = props;
|
let { tableData } = props;
|
||||||
|
|
||||||
@@ -446,15 +528,6 @@ export default function NodeTable(props: NodeTableProps) {
|
|||||||
setOrderBy(property);
|
setOrderBy(property);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = (event: React.MouseEvent<unknown>, name: string) => {
|
|
||||||
// Speed optimization. We compare string pointers here instead of the string content.
|
|
||||||
if (selected == name) {
|
|
||||||
setSelected(undefined);
|
|
||||||
} else {
|
|
||||||
setSelected(name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangePage = (event: unknown, newPage: number) => {
|
const handleChangePage = (event: unknown, newPage: number) => {
|
||||||
setPage(newPage);
|
setPage(newPage);
|
||||||
};
|
};
|
||||||
@@ -466,9 +539,6 @@ export default function NodeTable(props: NodeTableProps) {
|
|||||||
setPage(0);
|
setPage(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Speed optimization. We compare string pointers here instead of the string content.
|
|
||||||
const isSelected = (name: string) => name == selected;
|
|
||||||
|
|
||||||
// Avoid a layout jump when reaching the last page with empty rows.
|
// Avoid a layout jump when reaching the last page with empty rows.
|
||||||
const emptyRows =
|
const emptyRows =
|
||||||
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - tableData.length) : 0;
|
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - tableData.length) : 0;
|
||||||
@@ -483,79 +553,62 @@ export default function NodeTable(props: NodeTableProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Box sx={{ width: "100%" }}>
|
||||||
|
<Paper sx={{ width: "100%", mb: 2 }}>
|
||||||
|
<EnhancedTableToolbar
|
||||||
|
tableData={tableData}
|
||||||
|
selected={selected}
|
||||||
|
onClear={() => setSelected(undefined)}
|
||||||
|
/>
|
||||||
|
<TableContainer>
|
||||||
|
<Table
|
||||||
|
sx={{ minWidth: 750 }}
|
||||||
|
aria-labelledby="tableTitle"
|
||||||
|
size={dense ? "small" : "medium"}
|
||||||
|
>
|
||||||
|
<EnhancedTableHead
|
||||||
|
order={order}
|
||||||
|
orderBy={orderBy}
|
||||||
|
onRequestSort={handleRequestSort}
|
||||||
|
rowCount={tableData.length}
|
||||||
|
/>
|
||||||
|
<TableBody>
|
||||||
|
{visibleRows.map((row, index) => {
|
||||||
|
const labelId = `enhanced-table-checkbox-${index}`;
|
||||||
|
|
||||||
<Box sx={{ width: "100%" }}>
|
return (
|
||||||
<Paper sx={{ width: "100%", mb: 2 }}>
|
<Row
|
||||||
<EnhancedTableToolbar
|
key={row.id}
|
||||||
tableData={tableData}
|
row={row}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
onClear={() => setSelected(undefined)}
|
setSelected={setSelected}
|
||||||
/>
|
/>
|
||||||
<TableContainer>
|
);
|
||||||
<Table
|
})}
|
||||||
sx={{ minWidth: 750 }}
|
{emptyRows > 0 && (
|
||||||
aria-labelledby="tableTitle"
|
<TableRow
|
||||||
size={dense ? "small" : "medium"}
|
style={{
|
||||||
>
|
height: (dense ? 33 : 53) * emptyRows,
|
||||||
<EnhancedTableHead
|
}}
|
||||||
order={order}
|
>
|
||||||
orderBy={orderBy}
|
<TableCell colSpan={6} />
|
||||||
onRequestSort={handleRequestSort}
|
</TableRow>
|
||||||
rowCount={tableData.length}
|
)}
|
||||||
/>
|
</TableBody>
|
||||||
<TableBody>
|
</Table>
|
||||||
{visibleRows.map((row, index) => {
|
</TableContainer>
|
||||||
const isItemSelected = isSelected(row.name);
|
{/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */}
|
||||||
const labelId = `enhanced-table-checkbox-${index}`;
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[5, 10, 25]}
|
||||||
return (
|
labelRowsPerPage={is_xs ? "Rows" : "Rows per page:"}
|
||||||
<TableRow
|
component="div"
|
||||||
hover
|
count={tableData.length}
|
||||||
onClick={(event) => handleClick(event, row.name)}
|
rowsPerPage={rowsPerPage}
|
||||||
role="checkbox"
|
page={page}
|
||||||
aria-checked={isItemSelected}
|
onPageChange={handleChangePage}
|
||||||
tabIndex={-1}
|
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||||
key={row.name}
|
/>
|
||||||
selected={isItemSelected}
|
</Paper>
|
||||||
sx={{ cursor: "pointer" }}
|
</Box>
|
||||||
>
|
|
||||||
<TableCell component="th" id={labelId} scope="row">
|
|
||||||
{renderName(row.name, row.id)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">
|
|
||||||
{renderStatus(row.status)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">
|
|
||||||
{renderLastSeen(row.last_seen)}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{emptyRows > 0 && (
|
|
||||||
<TableRow
|
|
||||||
style={{
|
|
||||||
height: (dense ? 33 : 53) * emptyRows,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TableCell colSpan={6} />
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
{/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */}
|
|
||||||
<TablePagination
|
|
||||||
rowsPerPageOptions={[5, 10, 25]}
|
|
||||||
labelRowsPerPage={is_xs ? "Rows" : "Rows per page:"}
|
|
||||||
component="div"
|
|
||||||
count={tableData.length}
|
|
||||||
rowsPerPage={rowsPerPage}
|
|
||||||
page={page}
|
|
||||||
onPageChange={handleChangePage}
|
|
||||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,30 @@
|
|||||||
import { createTheme } from "@mui/material/styles";
|
import { createTheme } from "@mui/material/styles";
|
||||||
|
|
||||||
export const darkTheme = createTheme({
|
export const darkTheme = createTheme({
|
||||||
|
breakpoints: {
|
||||||
|
values: {
|
||||||
|
xs: 0,
|
||||||
|
sm: 400,
|
||||||
|
md: 900,
|
||||||
|
lg: 1200,
|
||||||
|
xl: 1536,
|
||||||
|
},
|
||||||
|
},
|
||||||
palette: {
|
palette: {
|
||||||
mode: "dark",
|
mode: "dark",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const lightTheme = createTheme({
|
export const lightTheme = createTheme({
|
||||||
|
breakpoints: {
|
||||||
|
values: {
|
||||||
|
xs: 0,
|
||||||
|
sm: 400,
|
||||||
|
md: 900,
|
||||||
|
lg: 1200,
|
||||||
|
xl: 1536,
|
||||||
|
},
|
||||||
|
},
|
||||||
palette: {
|
palette: {
|
||||||
mode: "light",
|
mode: "light",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user