I have a table which has arrow on each row. When the arrow is clicked, the row is expected to expand and show more data in a tabular form. When the arrow is clicked again, the expanded nested table should collapse. The code is in typescript.
This is what I have so far. I have created the first table and able to display the data.I am not able to find the right way to display the second nested table.
const columns = [
"First Name",
"Last Name",
];
const [openRows, setOpenRows] = useState<Record<string, boolean>>({});
const toggleRow = (dataRow: any) =>
setOpenRows((prevState) => ({
...prevState,
[dataRow.userId]: true,
}));
return (
<Paper elevation={3} variant="outlined">
<TableContainer className={classes.container}>
<Table stickyHeader classes={{ root: classes.tableRoot }}>
<colgroup>
<col width="5%" />
<col width="10%" />
<col width="15%" />
</colgroup>
<TableHead>
<TableRow>
{columns.map((column, idx) => (
<TableCell
key={column}
className={classes.cell}
role="columnheader"
colSpan={idx === 0 ? 2 : 1}
>
<Typography variant="h6">{column}</Typography>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{data.map((dataRow) => (
<TableRow
key={dataRow.userId}
title="tableRow"
className={classes.tableRow}
classes={{ hover: classes.hover }}
hover
>
<TableCell classes={{ root: classes.arrowCell }}>
<IconButton
size="small"
onClick={() => toggleRow(dataRow.userId)}
>
{openRows ? <ArrowDropDownIcon /> : <ArrowDropUpIcon />}
</IconButton>
<Collapse
in={openRows[dataRow.userId] ?? false}
timeout="auto"
unmountOnExit
>
<Box margin={1}>
<TableCell>{dataRow.jobDetails.company}</TableCell>
<TableCell>{dataRow.jobDetails.title}</TableCell>
</Box>
</Collapse>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Paper>
);
};
export interface UserData {
userId: string;
firstName: string;
lastName: string;
jobDetails: {
company: string;
title: string;
};
}
interface UserDataProps {
data: UserData[];
}
Thank you so much.
Related
I have a table with a list of teams; the last column in each row opens up a menu, and I want the menu items to open up a modal or a dialog, depending on the item. I want to keep the table, menu, and modal as separate components. So it looks like this:
Teams.tsx
const testDataTeams = [
{ name: 'Cincinatti Bengals', owner: 'John Person', created: '11/11/11', members: 4 },
{ name: 'Los Angeles Rams', owner: 'John Person', created: '11/11/11', members: 3 },
{ name: 'The A-Team', owner: 'John Person', created: '11/11/11', members: 2 },
]
export default function Teams() {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [openModal, setOpen] = React.useState(null);
const open = Boolean(anchorEl);
const moreOptions = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const clickModal = Boolean(openModal)
const closeModal = () => {
setOpen(null)
};
return (
<TableContainer component={Paper}>
<Table sx={{ display: { xs: 'none', md: 'table', lg: 'table' }, minWidth: 700 }} aria-label="customized table">
<TableHead>
<TableRow>
<StyledTableCell>Name</StyledTableCell>
<StyledTableCell align="right">Owner</StyledTableCell>
<StyledTableCell align="right">Date Created</StyledTableCell>
<StyledTableCell align="right">Members</StyledTableCell>
<StyledTableCell align="right"></StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{testDataTeams.map((item) => (
<StyledTableRow key={item.name}>
<StyledTableCell component="th" scope="row">{item.name}</StyledTableCell>
<StyledTableCell align="right">{item.owner}</StyledTableCell>
<StyledTableCell align="right">{item.created}</StyledTableCell>
<StyledTableCell align="right">{item.members}</StyledTableCell>
<StyledTableCell align="right">
<IconButton
id="basic-button"
aria-controls={open ? 'basic-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={moreOptions}>
<MoreIcon />
</IconButton>
<Menu
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
>
<TeamMenu clickModal={clickModal} closeModal={closeModal} />
</Menu>
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
TeamMenu.tsx
export default function TeamMenu(clickModal, closeModal) {
return (
<div>
<MenuItem>
<ListItemIcon>
<NotificationsIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Notification Settings</ListItemText>
</MenuItem>
<MenuItem onClick={closeModal}>
<ListItemIcon>
<EditIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Edit Team Info</ListItemText>
</MenuItem>
<TeamsModal open={clickModal} handleClose={closeModal} />
<MenuItem>
<ListItemIcon>
<GroupIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Edit Members</ListItemText>
</MenuItem>
<MenuItem>
<ListItemIcon>
<DeleteIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Delete Team</ListItemText>
</MenuItem>
</div>
);
}
TeamsModal.tsx
const TeamsModal = ({ open, handleClose }) => {
return (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box>
<Typography id="modal-modal-title" variant="h6" component="h2">
Text in a modal
</Typography>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
</Typography>
</Box>
</Modal>
)
}
export default TeamsModal;
I can get props to pass to the Team Menu, but I'm having trouble getting it down another level, and I'm worried I've made my code too convoluted. What would be the best way to execute this?
You can pass a callback function to the TeamsMenu component like you are doing here, but it doesn't look like you are returning the actual TeamsModal component in the Teams component.
You want to have the TeamsModal in the Teams component like so:
const testDataTeams = [
{ name: 'Cincinatti Bengals', owner: 'John Person', created: '11/11/11', members: 4 },
{ name: 'Los Angeles Rams', owner: 'John Person', created: '11/11/11', members: 3 },
{ name: 'The A-Team', owner: 'John Person', created: '11/11/11', members: 2 },
]
export default function Teams() {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [openModal, setOpen] = React.useState<boolean>(false);
const open = Boolean(anchorEl);
const moreOptions = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const clickModal = () => setOpen(true);
const closeModal = () => setOpen(false);
return (
<>
<TableContainer component={Paper}>
<Table sx={{ display: { xs: 'none', md: 'table', lg: 'table' }, minWidth: 700 }} aria-label="customized table">
<TableHead>
<TableRow>
<StyledTableCell>Name</StyledTableCell>
<StyledTableCell align="right">Owner</StyledTableCell>
<StyledTableCell align="right">Date Created</StyledTableCell>
<StyledTableCell align="right">Members</StyledTableCell>
<StyledTableCell align="right"></StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{testDataTeams.map((item) => (
<StyledTableRow key={item.name}>
<StyledTableCell component="th" scope="row">{item.name}</StyledTableCell>
<StyledTableCell align="right">{item.owner}</StyledTableCell>
<StyledTableCell align="right">{item.created}</StyledTableCell>
<StyledTableCell align="right">{item.members}</StyledTableCell>
<StyledTableCell align="right">
<IconButton
id="basic-button"
aria-controls={open ? 'basic-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={moreOptions}>
<MoreIcon />
</IconButton>
<Menu
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
>
<TeamMenu clickModal={clickModal} closeModal={closeModal} />
</Menu>
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
{openModal && (
<TeamsModal open={openModal} handleClose={() => setOpen(false)} />
)}
</>
);
}
Notice that I defined the state type for open as boolean instead of using null.
This ensures the modal will open when the openModal state is set to true.
Also, if you are using Typescript I recommend you define types for components and props to ensure you aren't making silly mistakes and have that sweet autocomplete capability.
I'm using React and meterial-ui. The two components from material ui I am using are Collapsible Table and a Tree view inside it.
The data structure goes like this:
Projects -> workpackages -> work packages within work packages
So I list my projects in my table, then when you click the collapsible arrow it drops down and displays the work packges within that project in a tree, now I'm trying to figure out how to display sub trees within the tree if a work package has more work packages within it.
My goal is to populate the tree with some hardcoded data, then if it has another child object of data within it, create a sub tree.
I currently have it to where the tree is displaying within the table, but I can't figure out how to display a sub tree.
Columns for my table:
const columns = [
{ field: "project", title: "Project" },
{ field: "projectManager", title: "Project Manager" },
{ field: "startDate", title: "Start Date" },
{ field: "endDate", title: "End Date" },
];
Projects data for the table:
const projects = [
{
title: "Build Backend",
projectManager: "Bruce",
startDate: "12/12/12",
endDate: "12/12/16"
},
{
title: "Build Frontend",
projectManager: "Medhat",
startDate: "11/11/22",
endDate: "12/12/25"
}
]
My Table:
export default function CollapsibleTable() {
return (
<TableContainer component={Paper}>
<Table aria-label="collapsible table">
<TableHead>
<TableRow>
<TableCell />
{columns.map((column) => (
<TableCell key={column.field}>
<Typography variant="body1">
<strong>{column.title}</strong>
</Typography>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{projects.map((row) => (
<Row key={row.title} row={row} />
))}
</TableBody>
</Table>
</TableContainer>
);
}
My work packages data, the first one has a sub work package within it 'package':
const workPackages = [
{
title: "Setup Database",
startDate: "01/12/22",
package: {
title: "Create Crud"
}
},
{
title: "Install express",
startDate: "01/11/12"
}
]
Table rows within my table that have the tree view:
function Row(props) {
const { row } = props;
const [open, setOpen] = React.useState(false);
return (
<React.Fragment>
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
</TableCell>
<TableCell component="th" scope="row">
{row.title}
</TableCell>
<TableCell >{row.projectManager}</TableCell>
<TableCell >{row.startDate}</TableCell>
<TableCell >{row.endDate}</TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
<Collapse in={open} timeout="auto" unmountOnExit>
<TreeView
aria-label="file system navigator"
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
sx={{ height: 240, flexGrow: 1, maxWidth: 400, overflowY: 'auto' }}
>
{workPackages.map((tree) => (
<TreeItem label={tree.title}>
</TreeItem>
))}
</TreeView>
</Collapse>
</TableCell>
</TableRow>
</React.Fragment>
);
}
How it looks currently:
Table
How do I check and add a subtree to the tree for that package?
Read material ui docs (Multi selection section). They show how to add a tree in another one (subtree).
I have a very peculiar case to display Selected Item Count in my Material UI - Accordion Summary:
Following Should be the Behavior:
When a User click on the Header Checkbox it should select All the Item in that Panel and then shows the Count of those Items as X Selected
Right Now I am able to do the following :
As You can see the Number of Items selected is showing on Each Panel But I only Want it to show in the Selected Panel.
Below is my Code for Above screen Shot:
{unsubscribedEmployeeData ? Object.entries(unsubscribedEmployeeData).map(([name, value]) => {
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-label="Expand"
aria-controls="additional-actions1-content"
id="additional-actions1-header"
>
<EnhancedTableToolbar numSelected={selected.length} name={name} values={value} />
</AccordionSummary>
<AccordionDetails>
<Typography color="textSecondary">
<div className={classes.root}>
<Paper className={classes.paper}>
<TableContainer>
<Table
className={classes.table}
aria-labelledby="tableTitle"
size={dense ? 'small' : 'medium'}
aria-label="enhanced table"
>
<TableBody>
{stableSort(value, getComparator(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row, index) => {
const isItemSelected = isSelectedID(row.id);
const labelId = `enhanced-table-checkbox-${index}`;
return (
<TableRow
hover
onClick={(event) => handleClick(event, row.first_name + " " + row.last_name, row.id)}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={row.id}
selected={isItemSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isItemSelected}
inputProps={{ 'aria-labelledby': labelId }}
/>
</TableCell>
<TableCell align="center" component="th" id={labelId} scope="row" >
{row.first_name + " " + row.last_name}
</TableCell>
<TableCell align="center">{row.email}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Paper>
</div>
</Typography>
</AccordionDetails>
</Accordion>
);
}) : undefined}
EnhancedTableToolbar
const EnhancedTableToolbar = (props) => {
const classes = useToolbarStyles();
const { numSelected, name, values } = props;
return (
<Toolbar
className={clsx(classes.root)}
>
{numSelected > 0 ? (
<>
<EnhancedTableHead
classes={classes}
numSelected={selected.length}
onSelectAllClick={(event) => handleSelectAllClick(event,values)}
rowCount={values.length}
label={name}
/>
<Typography component="div">
{numSelected} selected
</Typography>
</>
) : (
<EnhancedTableHead
classes={classes}
numSelected={selected.length}
onSelectAllClick={(event) => handleSelectAllClick(event,values)}
rowCount={values.length}
label={name}
/>
)}
</Toolbar>
);
};
handleSelectAllClick
const handleSelectAllClick = (event, val) => {
if (event.target.checked) {
const newSelectedsID = val.map((n) => n.id);
const newSelecteds = val.map((n) => n.first_name + " " + n.last_name);
console.log(newSelecteds);
console.log(newSelectedsID);
setSelectedID(newSelectedsID);
setSelected(newSelecteds);
return;
}
setSelected([]);
setSelectedID([]);
};
Please guide me through this. Thanks
I'm stuck on this for a few days ...
I don't found the way to be on the top of a new page when you click on next, I always stay at the bottom whatever I do.
I already check on StackOverflow and GitHub, I found this issue which seems to be close: #9186
I supposed using ref and callback is the right way, I already try to implement it. However, I'm always stuck at the having the last element and I can't scrollTop to the one of the page
I based my code on Custom pagination actions which is the table page made by material-iu
Here is an example of my code
function DisplayList(props) {
var rows = [];
const data = props.data;
const tableRef = React.createRef();
const searchData = props.searchData;
const setHoverAddress = props.setHoverAddress;
const classes = useStyles1();
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
const handleChangePage = (event, newPage) => {
setPage(newPage);
if(tableRef.current) {tableRef.current.scrollTop = 0;}
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
data.map((result, index) => { // WARNING : slice here which limits the number of results: .slice(0, 5)
const volulme = Math.round(result.volulme);
const volulme2 = Math.round(result.volulme2);
rows.push(
<div id={index}>
<ListItem
alignItems="flex-start"
onMouseEnter={e => {
console.log(index);
}}
>
<Grid container direction="row" spacing={1}>
<Grid item xs={5}>
{/* <Stage width={150} height={150}>
<Layer>
<Shape
sceneFunc={(context, shape) => {
context.beginPath();
context.moveTo(20, 10);
context.lineTo(120, 80);
context.lineTo(120, 140);
context.lineTo(22, 140);
context.closePath();
// (!) Konva specific method, it is very important
context.fillStrokeShape(shape);
}}
fill="#00D2FF"
stroke="black"
strokeWidth={2}
/>
</Layer>
</Stage> */}
</Grid>
<Grid item xs={7}>
<ListItemText
primary={
}
secondary={
<React.Fragment>
<Typography
component="span"
variant="body2"
display="inline"
color="textPrimary"
>
Solid2 : {volulme2}
</Typography>
</React.Fragment>
}
/>
<ListItemText
secondary={
<React.Fragment>
<Typography
component="span"
variant="body2"
display="inline"
color="textPrimary"
>
Solid : {volulme}
</Typography>
</React.Fragment>
}
/>
<FormControlLabel
control={
<Checkbox icon={<FavoriteBorder />}
checkedIcon={<Favorite />}
color="primary"
onClick={(e) => {
if (e.target.checked) {
addFavourite(parc_id, 1)
} else {
removeFavourite(parc_id, 1)
}
}}
name="checkedH" />
}
label="Enregistrer"
/>
</Grid>
</Grid>
</ListItem>
</div>
)
})
return (
<Table className={classes.table} aria-label="custom pagination table">
<TableBody>
{(rowsPerPage > 0
? rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
: rows
).map((row) => (
<TableRow key={index}>
<TableCell component="th" scope="row">
<div ref={tableRef}>
{row}
</div>
</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10, 25, { label: 'All', value: -1 }]}
colSpan={3}
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
SelectProps={{
inputProps: { 'aria-label': 'rows per page' },
native: true,
}}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
ActionsComponent={TablePaginationActions}
/>
</TableRow>
</TableFooter>
</Table>
)
}
Thank you for your time and your answer!
Have a nice day
You add a ref to the table and use scrollIntoView in handleChangePage
Working demo
Code snippet
...
const handleChangePage = (event, newPage) => {
ref.current.scrollIntoView(); //scroll to the beginning of the table
// window.scrollTo({ top: 0, behavior: 'smooth' }) //scroll to the top of the page
setPage(newPage);
};
...
<TableContainer ref={ref} component={Paper}>
<Table className={classes.table} aria-label="custom pagination table">
<TableBody>
...
I am trying to pass a function to child component in functional component and every time I get error saying this is not a function.
Administrator.js
const handlePagination = (event, newPage) => {
console.log("newPage", newPage);
event.preventDefault();
console.log("newPage", newPage);
props.getUserLoadSiteManagers(
props.accessToken,
menuValues,
filterText,
newPage,
20
);
};
{TableData && TableData.rows && TableData.rows.length > 0 ? (
<Table
pagination={pagination}
handlePagination={handlePagination}
_handleCheckbox={_handleCheckbox}
_handleUserCheckBox={_handleUserCheckBox}
data={TableData}
isItemSelected
numSelected={() => numSelected(numSelected)}
onClick={SelectedId(SelectedId)}
/>
) : (
<h3>No Data Available</h3>
)}
Table.js
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={props.data.rows.length}
rowsPerPage={rowsPerPage}
page={props.pagination.number}
backIconButtonProps={{
"aria-label": "Previous Page",
}}
nextIconButtonProps={{
"aria-label": "Next Page",
}}
onChangePage={() => props.handlePagination()}
onChangeRowsPerPage={handleChangeRowsPerPage}
/>
all Props
Table.js
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar numSelected={selected.length} data={props.data} />
<div className={classes.tableWrapper}>
<Table
className={classes.table}
aria-labelledby="tableTitle"
size={dense ? "small" : "medium"}
>
{/*//! Table Body Component */}
<TableBody>
<TableRow
hover
onClick={(event) =>
handleClick(event, row.name, row.userId)
}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={props.data.rows.name}
selected={isItemSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isItemSelected}
inputProps={{ "aria-labelledby": labelId }}
/>
</TableCell>
{rowData(row)}
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 49 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</div>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={props.data.rows.length}
rowsPerPage={rowsPerPage}
page={props.pagination.number}
backIconButtonProps={{
"aria-label": "Previous Page",
}}
nextIconButtonProps={{
"aria-label": "Next Page",
}}
onChangePage={() => props.handlePagination()}
onChangeRowsPerPage={handleChangeRowsPerPage}
/>
</Paper>
</div>
);
}
I really need help with this. This is frustrating.