Files
ota-admin-portal/src/components/Manifest/List/index.jsx
2023-10-13 17:18:36 -04:00

510 lines
14 KiB
JavaScript

import {
Checkbox,
Grid,
Table,
TableBody,
TableCell,
TableFooter,
TablePagination,
TableRow,
Tooltip,
} from "@material-ui/core";
import DeleteIcon from "@material-ui/icons/Delete";
import SendIcon from "@material-ui/icons/Send";
import VisibilityIcon from "@material-ui/icons/Visibility";
import {
ToggleButton,
ToggleButtonGroup
} from "@mui/material";
import clsx from "clsx";
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import EditIcon from "@material-ui/icons/Edit";
import { logger } from "../../../services/monitoring";
import { LocalDateTimeString } from "../../../utils/dates";
import { TYPE_MANIFEST_AFTERSALES, TYPE_MANIFEST_CONFIG, TYPE_MANIFEST_SOFTWARE } from "../../../utils/manifest_types";
import { Permissions, hasRole } from "../../../utils/roles";
import {
ManifestsProvider,
useManifestsContext
} from "../../Contexts/ManifestsContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext";
import DropDownButton from "../../Controls/DropDownButton";
import ECUList from "../../Controls/ECUList";
import { RoleWrap } from "../../Controls/RoleWrap";
import SearchField from "../../Controls/SearchField";
import DeleteConfirmation from "../../DeleteConfirmation";
import TableHeaderSortable from "../../Table/HeaderSortable";
import { useLocalStorage } from "../../useLocalStorage";
import useStyles from "../../useStyles";
import { useUpdateManifest } from "../../../hooks";
import GeneralConfirmation from "../../GeneralConfirmation";
const tableColumns = [
{
id: "id",
label: "ID",
},
{
id: "name",
label: "Name",
},
{
id: "version",
label: "Version",
},
{
id: "manifest_type",
label: "Type",
},
{
id: "sums",
label: "SUMS",
},
{
id: "type",
label: "Update",
},
{
id: "created_at",
label: "Created",
},
{
id: "updated_at",
label: "Updated",
},
{
id: "",
label: "Actions",
},
];
const formatType = (type) => {
switch (type) {
case "forced":
return "Forced";
default:
return "Standard";
}
};
const formatManifestType = (manifestType) => {
switch (manifestType) {
case 1:
return "Software";
case 2:
return "Config";
case 3:
return "Magna";
case 4:
return "Aftersales";
default:
return manifestType;
}
}
const PAGE_SIZE = "MANIFEST_LIST_PAGE_SIZE";
const MainForm = () => {
const classes = useStyles();
const [pageSize, setPageSize] = useLocalStorage(PAGE_SIZE, 10);
const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("id");
const [order, setOrder] = useState("asc");
const [search, setSearch] = useLocalStorage("DEPLOYMENT_SEARCH", "");
const [active, setActive] = useLocalStorage("DEPLOYMENT_TAB_TOGGLE", "software");
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [showArchiveModal, setShowArchiveModal] = useState(false);
const [archiveLabel, setArchiveLabel] = useState("Archive");
const { getManifests, manifests, totalManifests } =
useManifestsContext();
const { setMessage, setTitle, setSitePath } = useStatusContext();
const {
token: {
idToken: { jwtToken: token },
},
groups,
providers,
} = useUserContext();
const {
remove,
archive,
updateManifestIds,
setUpdateManifestIds,
setMakeActive,
} = useUpdateManifest(token);
const sortHandler = (event, property) => {
if (property === orderBy) {
if (order === "asc") {
setOrder("desc");
} else {
setOrder("asc");
}
} else {
setOrderBy(property);
setOrder("asc");
}
};
useEffect(() => {
setTitle("Deployments");
setSitePath([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
(async () => {
try {
handleActiveChange(null, active);
switch (active) {
case "all":
await getManifests(
{
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
search,
},
token
);
break;
case "aftersales":
await getManifests(
{
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
manifest_type: TYPE_MANIFEST_AFTERSALES,
search,
active: "true",
},
token
);
break;
case "config":
await getManifests(
{
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
manifest_type: TYPE_MANIFEST_CONFIG,
search,
active: "true",
},
token
);
break;
case "software":
await getManifests(
{
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
manifest_type: TYPE_MANIFEST_SOFTWARE,
search,
active: "true",
},
token
);
break;
case "archived":
await getManifests(
{
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
manifest_type: TYPE_MANIFEST_SOFTWARE,
search,
active: "false",
},
token
);
break;
default:
break;
}
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageIndex, pageSize, token, orderBy, order, search, active, updateManifestIds]);
useEffect(() => {
setUpdateManifestIds([]);
}, [active, setUpdateManifestIds]);
const handleChangePageIndex = (_event, newIndex) => {
setPageIndex(newIndex);
};
const handleChangePageSize = (event) => {
setPageSize(parseInt(event.target.value, 10));
setPageIndex(0);
};
const handleSearch = (query) => {
setPageIndex(0);
setSearch(query);
};
const handleActiveChange = (event, newAlignment) => {
if (newAlignment !== null) {
setActive(newAlignment);
setMakeActive(newAlignment === 'archived');
setArchiveLabel(() => {
if (newAlignment === "archived") {
return "Activate";
}
return "Archive";
});
}
}
const handleSelectAll = () => {
setUpdateManifestIds((selected) => selected.length ? [] : manifests.map((manifest) => manifest.id));
};
const handleSelect = (event, manifest) => {
setUpdateManifestIds((selected) => {
if (event.target.checked) {
return [...selected, manifest.id];
}
return selected.filter(id => id !== manifest.id);
});
};
const setDeletePopup = (row) => {
handleSelect({ target: { checked: true } }, row);
setShowDeleteModal(true);
};
const onArchive = async () => {
try {
await archive()
.then(({ message }) => {
setUpdateManifestIds([]);
setMessage(message);
});
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
};
const onDelete = async () => {
try {
await remove()
.then(({ summary }) => {
setUpdateManifestIds([]);
setMessage(summary);
});
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
};
const Actions = (row) => {
let actions = [];
if (hasRole(groups, Permissions.FiskerMagnaRead, providers)) {
actions.push({
tip: `Status "${row.name} ${row.version}"`,
link: `/package-status/${row.id}`,
icon: (
<VisibilityIcon aria-label={`Status ${row.name} ${row.version}`} />
),
});
}
if (hasRole(groups, Permissions.FiskerCreate, providers)) {
actions.push({
tip: `Update "${row.name} ${row.version}"`,
link: `/package-update/${row.id}`,
icon: <EditIcon aria-label={`Update ${row.name} ${row.version}`} />,
});
}
if (hasRole(groups, Permissions.FiskerUpdateDeploy, providers)) {
actions.push({
tip: `Deploy "${row.name} ${row.version}"`,
link: `/package-deploy/${row.id}`,
icon: <SendIcon aria-label={`Deploy ${row.name} ${row.version}`} />,
});
}
if (hasRole(groups, Permissions.FiskerDelete, providers)) {
actions.push({
tip: `Delete "${row.name} ${row.version}"`,
id: row.id,
icon: <DeleteIcon aria-label={`Delete ${row.name} ${row.version}`} />,
});
}
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-key`}>
<Tooltip key={`delete-${action.id}`} title={action.tip}>
<Link to="#" onClick={() => setDeletePopup(row)}>
{action.icon}
</Link>
</Tooltip>
</span>
);
}
});
};
return (
<div className={clsx(classes.paper, classes.tableSize)}>
<Grid container className={classes.root} spacing={2}>
<Grid item md={4} className={classes.textJustifyAlign}></Grid>
<Grid item md={4} className={classes.textCenterAlign}>
<SearchField classes={classes} onSearch={handleSearch} savedSearchValue={search} />
<RoleWrap
groups={groups}
providers={providers}
rolesPerProvider={Permissions.FiskerCreate}
>
<ToggleButtonGroup
value={active}
exclusive
aria-label="Active"
onChange={handleActiveChange}
>
<ToggleButton value={"software"}>Software</ToggleButton>
<ToggleButton value={"archived"}>Archived</ToggleButton>
<ToggleButton value={"all"}>All</ToggleButton>
</ToggleButtonGroup>
</RoleWrap>
</Grid>
<Grid item md={4} className={classes.textRightAlign}>
<DropDownButton
actions={[
{
name: archiveLabel,
trigger: () => setShowArchiveModal(true),
disabled: !updateManifestIds.length || active === "all",
}
]}
/>
</Grid>
</Grid>
<Table>
<TableHeaderSortable
classes={classes}
orderBy={orderBy}
order={order}
columnData={tableColumns}
onSortRequest={sortHandler}
multiSelect
onSelectAll={handleSelectAll}
selectCount={updateManifestIds ? updateManifestIds.length : 0}
rowCount={manifests ? manifests.length : 0}
/>
<TableBody>
{manifests.map((row) => {
const isSelected = updateManifestIds
? !!updateManifestIds.find((id) => id === row.id)
: false;
return (
<TableRow key={row.id}>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
onChange={(event) => handleSelect(event, row)}
/>
</TableCell>
<TableCell align="center">{row.id}</TableCell>
<TableCell align="center">
{row.name}
{row.ecu_list && (
<>
<br />
<ECUList
list={row.ecu_list}
search={search}
searchedOnly={true}
/>
</>
)}
</TableCell>
<TableCell align="center">{row.version}</TableCell>
<TableCell align="center">
{formatManifestType(row.manifest_type)}
</TableCell>
<TableCell align="center">
<Link to={`/sums/${row.sums}`}>
{row.sums}
</Link>
</TableCell>
<TableCell align="center">
{formatType(row.type)}
</TableCell>
<TableCell align="center">
{LocalDateTimeString(row.created)}
</TableCell>
<TableCell align="center">
{LocalDateTimeString(row.updated)}
</TableCell>
<TableCell align="center">{Actions(row)}</TableCell>
</TableRow>
);
})}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10, 25, 100]}
colSpan={tableColumns.length + 1}
count={totalManifests}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onPageChange={handleChangePageIndex}
onRowsPerPageChange={handleChangePageSize}
/>
</TableRow>
</TableFooter>
</Table>
<DeleteConfirmation
message={`${updateManifestIds.length} records.`}
open={showDeleteModal}
close={() => setShowDeleteModal(false)}
deleteFunction={() => onDelete()}
/>
<GeneralConfirmation
title={`${archiveLabel} Resource?`}
message={`${archiveLabel} ${updateManifestIds.length} records.`}
open={showArchiveModal}
close={() => setShowArchiveModal(false)}
actionFunction={() => onArchive()}
/>
</div>
);
};
const ManifestsList = () => (
<ManifestsProvider>
<MainForm />
</ManifestsProvider>
);
export default ManifestsList;