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).
Related
I'm trying to create a material ui table where the status column comes as a dropdown.
Here is status column is dropdown with values 'Pending,In progress,Approved'.
code:
const columns = [
{ id: 'state', label: 'State', minWidth: 170 },
{
id: 'status ',
label: 'Status',
minWidth: 170,
align: 'right',
lookup: { 0: 'Pending', 1: 'In progress',2:'Approved' }
},
];
function createData(state,status) {
return {state,status};
}
const rows = [
createData('Create New Submission', 1),
];
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
{columns.map((column) => (
<TableCell
key={column.id}
align={column.align}
style={{ minWidth: column.minWidth }}
>
{column.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{rows
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row) => {
return (
<TableRow hover role="checkbox" tabIndex={-1} key={row.code}>
{columns.map((column) => {
const value = row[column.id];
return (
<TableCell key={column.id} align={column.align}>
{column.format && typeof value === 'number'
? column.format(value)
: value}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
But dropdown is not working.Any help would be Appreciated.
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.
Code runs and I get the table but I'm also retreiving some database data. I threw a couple console.logs in the there and my query runs and returns data. However, it doesn't show up in the collapsible table. Appreciate any advice!
import React, { useContext, useState } from "react";
import PropTypes from 'prop-types';
import { useQuery, useMutation } from "#apollo/react-hooks";
import { makeStyles } from '#material-ui/core/styles';
import Box from '#material-ui/core/Box';
import Collapse from '#material-ui/core/Collapse';
import IconButton from '#material-ui/core/IconButton';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import TableContainer from '#material-ui/core/TableContainer';
import TableHead from '#material-ui/core/TableHead';
import TableRow from '#material-ui/core/TableRow';
import Typography from '#material-ui/core/Typography';
import Paper from '#material-ui/core/Paper';
import KeyboardArrowDownIcon from '#material-ui/icons/KeyboardArrowDown';
import KeyboardArrowUpIcon from '#material-ui/icons/KeyboardArrowUp';
import CreateTicket from "views/Dashboard/CreateTicket";
import {
LIST_CREATIVE_FORM,
SUBMIT_CREATIVE_FORM,
COMPLETE_CREATIVE_FORM
} from "queries/formSubmission";
import { READ_ME } from "queries/users";
import { Context } from "redux/store";
export default function Tickets() {
const [state, dispatch] = useContext(Context);
const [selectedCreative, setSelectedCreative] = useState(null);
const customer_id = state.customers?.selected?.id;
const { data: me } = useQuery(READ_ME);
let { loading, data, refetch } = useQuery(LIST_CREATIVE_FORM, {
skip: !customer_id,
variables: { customerId: customer_id }
});
const [completeCreativeForm, { loading: completing }] = useMutation(COMPLETE_CREATIVE_FORM, {
skip: !customer_id,
onCompleted: () => {
refetch();
}
});
data = data?.listCreativeForm || [];
console.log(data);
const editable = me?.readMe?.user_level === "master" || me?.readMe?.user_level === "master";
const columns = [
{
Header: "Creative or Promotion Name",
accessor: "creative_promotion_name"
},
{
Header: "Creative Messaging",
accessor: "creative_message"
},
{
Header: "Start Date",
accessor: "creative_start"
},
{
Header: "End Date",
accessor: "creative_end"
},
{
Header: "Landing Page",
accessor: "landing_page"
},
{
Header: "Notes",
accessor: "notes"
},
{
Header: "BanerAds",
accessor: "bannerads",
Cell: ({ original }) => original?.bannerads ? "Yes" : "No"
},
{
Header: "SocialAds",
accessor: "socialads",
Cell: ({ original }) => original?.socialads ? "Yes" : "No"
},
{
Header: "OnlineVideo",
accessor: "onlinevideo",
Cell: ({ original }) => original?.onlinevideo ? "Yes" : "No"
},
{
Header: "Out of Home",
accessor: "out_of_home",
Cell: ({ original }) => original?.out_of_home ? "Yes" : "No"
},
{
Header: "Link to Asset",
accessor: "link_to_assets",
Cell: ({ original }) => (
<a href={original?.link_to_assets} target="_blank">
{original?.link_to_assets ? "View" : ""}
</a>
)
},
{
Header: "Submitted By",
accessor: "submitted_by",
Cell: ({ original }) => (
<div>{original?.user_submitted ? `${original.user_submitted?.first_name} ${original.user_submitted?.last_name}` : ""}</div>
)
},
{
Header: "Completed",
accessor: "completed",
Cell: ({ original }) => original?.completed
? <div style={{ color: "green" }}>Yes</div>
: <div style={{ color: "red" }}>No</div>
},
{
Header: "Completed By",
accessor: "completed_by",
Cell: ({ original }) => (
<>
<div>{original?.user_completed ? `${original.user_completed?.first_name} ${original.user_completed?.last_name}` : ""}</div>
{editable && !original?.completed && (
<a
href="#"
onClick={(e) => {
e.preventDefault();
completeCreativeForm({
variables: {
id: original?.id
}
});
}}
>
Complete
</a>
)}
</>
)
},
{
Header: "",
accessor: "update",
Cell: ({ original }) => (
editable && !original?.completed && (
<a
href="#"
onClick={(e) => {
e.preventDefault();
setSelectedCreative(original);
}}
>
Update
</a>
)
)
}
];
Tickets.propTypes = {
offline: PropTypes.bool
};
const useRowStyles = makeStyles({
root: {
'& > *': {
borderBottom: 'unset',
},
},
});
function Row(props) {
const { row } = props;
const [open, setOpen] = React.useState(false);
const classes = useRowStyles();
console.log("data");
console.log(data);
return (
<React.Fragment>
<TableRow className={classes.root} columns={columns} data={data}>
<TableCell>
<IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
</TableCell>
<TableCell align="right">{data.values.creative_promotion_name} lhey</TableCell>
<TableCell align="right">{data.values.creative_message}</TableCell>
<TableCell align="right">{data.values.creative_start}</TableCell>
<TableCell align="right">{data.values.creative_end}</TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box 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>
{row.history.map((historyRow) => (
<TableRow key={historyRow.date}>
<TableCell component="th" scope="row">
{historyRow.date}
</TableCell>
<TableCell>{historyRow.customerId}</TableCell>
<TableCell align="right">{historyRow.amount}</TableCell>
<TableCell align="right">
{Math.round(historyRow.amount * row.price * 100) / 100}
</TableCell>
</TableRow>
))}
</TableBody>*/}
</Table>
</Box>
</Collapse>
</TableCell>
</TableRow>
</React.Fragment>
);
}
console.log(columns)
return (
<TableContainer component={Paper}>
<Table aria-label="collapsible table" columns={columns} data={data}>
<TableBody>
{data.map((row) => (
<Row key={row.id} row={row} />
))}
</TableBody>
</Table>
</TableContainer>
);
}
Ok so here's what code worked for me to pass my database data to the collapsible table...
function Row(props) {
const { row } = props;
const [open, setOpen] = React.useState(false);
const classes = useRowStyles();
return (
<React.Fragment>
<TableRow>
<TableCell>
<IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
</TableCell>
<TableCell align="right">{row.creative_promotion_name}</TableCell>
<TableCell align="right">{row.creative_message}</TableCell>
<TableCell align="right">{row.creative_start}</TableCell>
<TableCell align="right">{row.creative_end}</TableCell>
<TableCell align="right">{row.out_of_home}</TableCell>
<TableCell align="right">{row.link_to_assets}</TableCell>
<TableCell align="right">{row.submitted_by}</TableCell>
<TableCell align="right">{row.completed}</TableCell>
<TableCell align="right">{row.completed_by}</TableCell>
<TableCell align="right">{row.update}</TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={4}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box margin={1}>
<Table size="small" aria-label="purchases">
<TableRow>
<TableCell>Notes:</TableCell>
<TableCell align="left">{row.notes}</TableCell>
</TableRow>
<TableRow>
<TableCell>Created Time:</TableCell>
<TableCell align="left">{row.created_time}
</TableCell>
</TableRow>
</Table>
</Box>
</Collapse>
</TableCell>
</TableRow>
</React.Fragment>
);
}
const classes = useRowStyles();
return (
<div>
<TableContainer component={Paper} style={{overflowY: "auto", maxWidth: "100%", maxHeight: "600px"}}>
<h2 className={classes.pageHeader}>Tickets</h2>
<Table stickyHeader aria-label="collapsible table" columns={columns} data={data}>
<TableHead>
<TableRow>
<TableCell />
{columns.map((data) => (
<TableCell>{data.Header}</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{data.map((datarow) => (
<Row data={datarow.id} row={datarow} />
))}
</TableBody>
</Table>
</TableContainer>
</div>
);
}
I am using MUI to display a dummy data from a json file onto the table.
I have an input field which onChange event would call the function to filter out data based on input and show on the table. The filter of the data works and is displayed on the table as the last row but the old data is also shown as well. I have tried different ways but still could not figure out what is the issue.
const [sdata, setsData] = useState(Data); // data from file
const [searched, setSearched] = useState(""); // maintain state of the search field
const [filteredResults, setFilterdResults] = useState([]) // maintain state of filtered data
// function to filter data...
const requestSearch = (value) => {
let filteredData = sdata.filter((val) => {
if (searched === '') {
return val
} else {
return val.student_name.toLowerCase().includes(value.toLowerCase())
}
})
setFilterdResults(filteredData)
console.log("Filtered Data", filteredData)
}
// search input filed **only one for now**
<div className="search">
<Box
sx={{ display: "flex", alignItems: "flex-end", paddingRight: "10px" }}
>
<SearchIcon sx={{ color: "action.active", mr: 1, my: 0.5 }} />
<TextField
id="student"
label="Student"
variant="standard"
value={searched}
onChange={(evt) => {
setSearched(evt.target.value)
requestSearch(evt.target.value);
// console.log(evt.target.value);
}}
/>
<SearchIcon sx={{ color: "action.active", mr: 1, my: 0.5 }} />
<TextField id="zone" label="Zone" variant="standard" />
</Box>
</div>
// displaying data based on condition (if there is a searched input or not)
<TableContainer>
<Table aria-label="studentInfo">
<TableHead>
<TableRow>
{columns.map((col) => (
<TableCell
sx={{
backgroundColor: "#662d91",
color: "white",
fontSize: matches ? "12" : "18px",
fontWeight: "bold",
}}
key={col.id}
align={col.align}
style={{
minWidth: matches ? col.minWidth : col.mobWidth,
paddingLeft: "5px",
}}
>
{col.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{searched.length = 1 ? (
<>
{
filteredResults.map((std) => (
<StyledTableRow
// style={{ backgroundColor: getZoneColor(std.zone) }}
key={std.tg}
>
<StyledTableCell>{std.tg}</StyledTableCell>
<StyledTableCell>{std.student_name}</StyledTableCell>
<StyledTableCell>{std.admin_no}</StyledTableCell>
<StyledTableCell>{std.action}</StyledTableCell>
<StyledTableCell
style={{
color:
// "black",
getZoneColor(std.zone),
}}
>
{std.zone}
</StyledTableCell>
</StyledTableRow>
))
}
</>
) : (
<>
{
sdata.map((std) => (
<StyledTableRow
// style={{ backgroundColor: getZoneColor(std.zone) }}
key={std.tg}
>
<StyledTableCell>{std.tg}</StyledTableCell>
<StyledTableCell>{std.student_name}</StyledTableCell>
<StyledTableCell>{std.admin_no}</StyledTableCell>
<StyledTableCell>{std.action}</StyledTableCell>
<StyledTableCell
style={{
color:
// "black",
getZoneColor(std.zone),
}}
>
{std.zone}
</StyledTableCell>
</StyledTableRow>
))
}
</>
)}
</TableBody>
</Table>
</TableContainer>
The weird thing is if I force reload the tab and do the search it runs flawlessly but the next time I search it causes the issue
After force reload first search
The next search
try code below
const filterData = (inputValue) => {
const filteredData = sdata.filter(i => i.student_name?.includes(inputValue))
setsetSearched(inputValue);
setFilterdResults(filteredData)
}
call the function on onChange property of your input
<input onChange={(e)=> filterData(e.target.value)} />
and change your condition to this
searched.length > 0
Currently My table view is like following:
Mui Table The table has a column name 'Action' which has a edit icon button.
Now I want to show (visibility) edit icon to edit each row only when user will hover over table row.
I have tried to override MUITable theme of Material Table but the following code didn't work. can anybody help me?
const getMuiTheme = () => createMuiTheme({
overrides: {
MUIDataTableBodyCell: {
root: {
'&:last-child': {
visibility: 'hidden'
}
}
},
MuiTableRow: {
root: {
'&$hover:hover': {
'& .MUIDataTableBodyCell-root': {
'&:last-child': {
visibility: 'visible'
}
}
}
}
}
}
});
This is an example for that purpose: https://codesandbox.io/s/material-demo-hr3te?file=/demo.js.
Basically I do something:
Add new state to store a boolean variable to determine when to show/hide a component.
Add onMouseEnter and onMouseLeave to TableRow component, for hover effect.
Then set state according to the hover event above.
That's it!
You need to add one action column in your columns array like below:
const columns = [
{
name: 'id',
label: 'id',
options: {
sort: false,
viewColumns: false,
display: false,
filter: false
}
},
{
name: 'transportationBranch',
label: 'Transportation Branch',
options: {
sort: false,
viewColumns: false
}
},
{
name: 'charge',
label: 'Charge',
options: {
filter: false,
sort: false
}
},
{
name: 'tax',
label: 'Tax',
options: {
filter: false,
sort: false
}
},
{
name: '',
label: '',
options: {
filter: false,
sort: false,
viewColumns: false,
customBodyRender: (value, tableMeta, updateValue) => {
return (
<IconButton
id={'Edit-' + tableMeta.rowIndex}
style={{display: 'none'}}
component="button"
variant="body2"
onClick={(event) => {
console.log(event);
alert(tableMeta.rowIndex);
}}
>
<EditIcon />
</IconButton>
);
}
}
}
];
add the the following option in mui datatable options:
setRowProps: (row, dataIndex, rowIndex) => {
return {
onMouseEnter: (e) => handleRowHover(e, row, rowIndex),
onMouseLeave: (e) => handleRowHoverLeave(e, row, rowIndex)
};
},
Write these two event in your component:
function handleRowHover(event, row, rowIndex) {
let control = document.getElementById('Edit-' + rowIndex);
control.style.display = 'block';
}
function handleRowHoverLeave(event, row, rowIndex) {
let control = document.getElementById('Edit-' + rowIndex);
control.style.display = 'none';
}
Note: we used id={'Edit-' + tableMeta.rowIndex} in IconButton so
that we can use getElementById in our two events.
If you can implement this properly then you will see this type ui on your browser:
I modified my original answer according to the mods comments:
I modified ShinaBR2 answer, so that it shows only a text in the current row: https://codesandbox.io/s/material-ui-table-onhover-action-zfloy?file=/demo.js
The idea is to use the row id on mouse enter (or mouse hover) and compare it with the current row, that is hovered to display the element in the row.
export default function SimpleTable() {
const classes = useStyles();
const [showActionId, setShowActionId] = useState(-1);
return (
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Dessert (100g serving)</TableCell>
<TableCell align="right">Calories</TableCell>
<TableCell align="right">Fat (g)</TableCell>
<TableCell align="right">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow
key={row.name}
onMouseEnter={() => {
setShowActionId(row.id); // set id here
}}
onMouseLeave={() => setShowActionId(-1)}
>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">{row.calories}</TableCell>
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">
{row.id === showActionId ? "Show me" : ""} // check the id here and display the message
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
One approach for this is css in js code , we make the icon hidden by default(hiddePin in makeStyles), then change the 'hidden' to 'visible' on hover using "&:hover $clearHidden".
below code was verified . my place is not good work with CodeSandBox(network issue) , so i just show the code here .
const useStyles = makeStyles({
table: {
minWidth: 350,
},
root: {
"&:hover $clearHidden": {
visibility: "visible"
}
},
clearHidden: {},
hiddePin: {
visibility: "hidden"
}
});
return (
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>name</TableCell>
<TableCell align="center">desc</TableCell>
<TableCell align="center">title2</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row.name} hover={true}>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="center">{row.desc}</TableCell>
<TableCell align="center" className={classes.root} >
<IconButton size="small" className={clsx(classes.hiddePin, classes.clearHidden)} onClick={(event) => handlePinClick(event,row.stamp)}>
<SvgPinOutLine />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
To only show the actions in the row your hovering over, I added the following useState objects
const [showAction, setShowAction] = React.useState(false);
const [showId, setShowId] = React.useState(0);
and then in the row object
onMouseEnter={() => {
setShowAction(true);
setShowId(row.id);}
}
onMouseLeave={() => setShowAction(false)}
and then in the specific cell that shows the actions:
{row.id == showId && showAction ? (
<IconButton>
<DeleteIcon/>
</IconButton>
) : (
<Box></Box>)}
I hope this helps someone!