Files
ota-admin-portal/src/components/Fleets/Status/Vehicles/Table/index.jsx
Tristan Timblin 2a71d87c93 CEC-5183 add bulk actions to fleets page (#470)
* add multi-select

* select all

* add bulk-actions component

* update tests

* remove console.log
2023-10-16 10:29:21 -07:00

300 lines
8.7 KiB
JavaScript

import {
Checkbox,
Grid,
LinearProgress,
Table,
TableBody,
TableCell,
TableFooter,
TablePagination,
TableRow,
Tooltip
} from "@material-ui/core";
import AddCircleIcon from "@material-ui/icons/AddCircle";
import DeleteIcon from "@material-ui/icons/Delete";
import clsx from "clsx";
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { logger } from "../../../../../services/monitoring";
import { hasRole, Permissions } from "../../../../../utils/roles";
import {
FleetProvider,
useFleetContext
} from "../../../../Contexts/FleetContext";
import { useStatusContext } from "../../../../Contexts/StatusContext";
import { useUserContext } from "../../../../Contexts/UserContext";
import SearchField from "../../../../Controls/SearchField";
import DeleteConfirmation from "../../../../DeleteConfirmation";
import TableHeaderSortable from "../../../../Table/HeaderSortable";
import { useLocalStorage } from "../../../../useLocalStorage";
import ConnectedIcon from "../../../../Controls/ConnectedIcon";
import BulkActions from "../../../../BulkActions";
import useStyles from "../../../../useStyles";
const tableColumns = [
{
id: "vin",
label: "VIN",
},
{
id: "trex_version",
label: "T.REX Version",
},
{
id: "car_update",
label: "Car Update",
},
{
id: "car_update_status",
label: "Car Update Status",
},
{
id: "",
label: "Actions",
},
];
const PAGE_SIZE = "FLEET_STATUS_VEHICLES_TABLE_PAGE_SIZE";
const MainForm = ({ name }) => {
const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10);
const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("id");
const [order, setOrder] = useState("desc");
const [search, setSearch] = useState("");
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [selected, setSelected] = useState([]);
const classes = useStyles();
const { setMessage } = useStatusContext();
const {
fleetVehicles,
totalFleetVehicles,
watchFleetVehicles,
getFleetVehicles,
deleteFleetVehicle,
} = useFleetContext();
const {
token: {
idToken: { jwtToken: token },
},
groups,
providers,
} = useUserContext();
const handleSearch = (query) => {
setSearch(query);
};
useEffect(() => {
(async () => {
try {
if (!name || !token) return;
await getFleetVehicles(
name,
{
search,
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
},
token
);
watchFleetVehicles.start({ token });
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
})();
return () => {
watchFleetVehicles.end();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [token, pageIndex, pageSize, orderBy, order, search]);
const handleChangePageIndex = (event, newIndex) => {
setPageIndex(newIndex);
};
const handleChangePageSize = (event) => {
setPageSize(parseInt(event.target.value, 10));
setPageIndex(0);
};
const handleSort = (event, property) => {
try {
if (property === orderBy) {
if (order === "asc") {
setOrder("desc");
} else {
setOrder("asc");
}
} else {
setOrderBy(property);
setOrder("asc");
}
} catch (e) {
logger.warn(e.stack);
}
};
const handleSelect = (vin, setState) => {
setSelected(selected => setState
? [...selected, vin]
: selected.filter(select => select !== vin));
};
const handleSelectAll = (event) => {
const allSelected = !event.target.checked;
setSelected(() => allSelected
? []
: fleetVehicles.map((vehicle) => vehicle.vin));
}
const onDelete = async (vin) => {
try {
await deleteFleetVehicle(name, { vin: vin }, token);
setMessage(`Deleted ${vin}`);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
};
const Actions = (vin) => {
let actions = [];
if (hasRole(groups, Permissions.FiskerDelete, providers)) {
actions.push({
tip: `Delete "${vin}"`,
id: vin,
icon: <DeleteIcon aria-label={`Delete ${vin}`} />,
});
}
if (actions.length === 0) return ["No actions"];
return actions.map((action) => {
if (action.link != null) {
return (
<Tooltip key={action.link} title={action.tip}>
<Link to={action.link} style={{ margin: 5 }}>
{action.icon}
</Link>
</Tooltip>
);
} else {
return (
<span key={`delete-${action.id}-of-div`}>
<Tooltip key={`delete-${action.id}`} title={action.tip}>
<Link to="#" onClick={() => onDelete(action.id)}>
{action.icon}
</Link>
</Tooltip>
<DeleteConfirmation
message={action.id}
open={showDeleteModal}
close={() => setShowDeleteModal(false)}
deleteFunction={() => onDelete(action.id)}
/>
</span>
);
}
});
};
return (
<div className={clsx(classes.paper, classes.tableSize)}>
<Grid container className={classes.root} spacing={2}>
<Grid item md={1} className={classes.textJustifyAlign}>
<Link to={`/fleet/${name}/vehicle-add`}>
<AddCircleIcon fontSize="large" />
</Link>
</Grid>
<Grid item md={3}>
<BulkActions ids={selected} actions={["addTags", "deleteVehicles", "sms", "updateConfig"]} />
</Grid>
<Grid item md={8} align="right" className={classes.textCenterAlign}>
<SearchField classes={classes} onSearch={handleSearch} />
</Grid>
</Grid>
<Table>
<TableHeaderSortable
classes={classes}
orderBy={orderBy}
order={order}
columnData={tableColumns}
onSortRequest={handleSort}
multiSelect={true}
onSelectAll={handleSelectAll}
selectCount={selected.length}
rowCount={fleetVehicles.length}
/>
<TableBody>
{fleetVehicles && fleetVehicles.map((car) => {
const isSelected = selected.includes(car.vin);
return (car.vin && <TableRow key={"row" + car.vin}>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
onChange={() => handleSelect(car.vin, !isSelected)}
/>
</TableCell>
<TableCell key={"cell" + car.vin} align="center">
{(car.connected || car.connectedHMI) &&
<ConnectedIcon
key={"icon" + car.vin}
connected={car.connected}
connectedHMI={car.connectedHMI}
style={{ marginRight: 3 }}
/>
}
<Link key={"link" + car.vin} to={`/vehicle-status/${car.vin}`}>{car.vin}</Link>
</TableCell>
<TableCell key={"cell2" + car.vin} align="center">{car.trex_version}</TableCell>
<TableCell key={"cell3" + car.car_update_name}>
<Tooltip key={"cell3tooltip"} title={`${car.car_update_id} / ${car.car_update_type}`}>
<Link to={`/vehicle-status/${car.vin}/${car.car_update_id}`} className={classes.truncateCell}>
{car.car_update_name}
</Link>
</Tooltip>
</TableCell>
<TableCell key={"cell4" + car.car_update_status}>
{car.car_update_status}
{car.car_update_progress > -1 && (
<LinearProgress variant="determinate" value={car.car_update_progress} />
)}
</TableCell>
<TableCell key={"cell5" + car.vin} align="center">{Actions(car.vin)}</TableCell>
</TableRow>
)
})}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10, 25, 100]}
colSpan={8}
count={totalFleetVehicles}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onPageChange={handleChangePageIndex}
onRowsPerPageChange={handleChangePageSize}
/>
</TableRow>
</TableFooter>
</Table>
</div>
);
};
const FleetVehiclesTable = (props) => (
<FleetProvider>
<MainForm {...props} />
</FleetProvider>
);
export default FleetVehiclesTable;