frontend/app/CommissionsList/CommissionsList.tsx (319 lines of code) (raw):
import {
Button,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TablePagination,
TableRow,
TableSortLabel,
Grid,
Menu,
MenuItem,
} from "@material-ui/core";
import React, { useEffect, useState } from "react";
import { useHistory, useLocation } from "react-router-dom";
import { getCommissionsOnPage, getWorkingGroupNameMap } from "./helpers";
import { SortDirection } from "../utils/lists";
import { Helmet } from "react-helmet";
import ProjectFilterComponent from "../filter/ProjectFilterComponent.jsx";
import { isLoggedIn } from "../utils/api";
import { buildFilterTerms, filterTermsToQuerystring } from "../filter/terms";
import { useGuardianStyles } from "~/misc/utils";
const tableHeaderTitles: HeaderTitle<Commission>[] = [
{ label: "Title", key: "title" },
{ label: "Projects", key: "projectCount" },
{ label: "Created", key: "created" },
{ label: "Group", key: "workingGroupId" },
{ label: "Status", key: "status" },
{ label: "Owner", key: "owner" },
];
declare var deploymentRootPath: string;
const pageSizeOptions = [25, 50, 100];
const CommissionsList: React.FC = () => {
const classes = useGuardianStyles();
const [commissions, setCommissions] = useState<Commission[]>([]);
const [workingGroups, setWorkingGroups] = useState<Map<number, string>>(
new Map()
);
const [page, setPage] = useState(0);
const [pageSize, setRowsPerPage] = useState(pageSizeOptions[0]);
const [order, setOrder] = useState<SortDirection>("desc");
const [orderBy, setOrderBy] = useState<keyof Commission>("created");
const history = useHistory();
const [filterTerms, setFilterTerms] = useState<
ProjectFilterTerms | undefined
>(undefined);
const [user, setUser] = useState<PlutoUser | null>(null);
const { search } = useLocation();
const [commissionToOpen, setCommissionToOpen] = useState<number>(1);
useEffect(() => {
if (filterTerms != undefined) {
const updateCommissions = async () => {
const commissions = await getCommissionsOnPage({
page,
pageSize,
filterTerms: filterTerms,
order,
orderBy,
});
const workingGroups = await getWorkingGroupNameMap(commissions);
setCommissions(commissions);
setWorkingGroups(workingGroups);
};
updateCommissions();
}
}, [filterTerms, page, pageSize, order, orderBy]);
const handleChangePage = (
_event: React.MouseEvent<HTMLButtonElement, MouseEvent> | null,
newPage: number
) => {
setPage(newPage);
};
const handleChangeRowsPerPage = async (
event: React.ChangeEvent<HTMLInputElement>
) => {
setRowsPerPage(+event.target.value);
setPage(0);
};
const sortByColumn = (property: keyof Commission) => (
_event: React.MouseEvent<unknown>
) => {
const isAsc = orderBy === property && order === "asc";
setOrder(isAsc ? "desc" : "asc");
setOrderBy(property);
};
useEffect(() => {
const fetchWhoIsLoggedIn = async () => {
try {
let user = await isLoggedIn();
user.uid = generateUserName(user.uid);
setUser(user);
} catch (error) {
console.error("Could not login user:", error);
}
};
fetchWhoIsLoggedIn();
}, []);
useEffect(() => {
const currentURL = new URLSearchParams(search).toString();
let newFilters = buildFilterTerms(currentURL);
if (newFilters.user === "Mine" && user) {
newFilters.user = user.uid;
}
console.log("Filter terms set: ", newFilters);
setFilterTerms(newFilters);
}, [user]);
const generateUserName = (inputString: string) => {
if (inputString.includes("@")) {
const splitString = inputString.split("@", 1)[0];
const userNameConst = splitString.replace(".", "_");
return userNameConst;
}
return inputString;
};
const [contextMenu, setContextMenu] = React.useState<{
mouseX: number;
mouseY: number;
} | null>(null);
const handleContextMenu = (event: React.MouseEvent, commission: number) => {
event.preventDefault();
setCommissionToOpen(commission);
setContextMenu(
contextMenu === null
? {
mouseX: event.clientX + 2,
mouseY: event.clientY - 6,
}
: null
);
};
const handleClose = () => {
setContextMenu(null);
};
const userAllowed = (confidential: Boolean, commissionUser: string) => {
if (confidential == undefined) {
return true;
}
if (confidential == false) {
return true;
}
if (user != null) {
if (user.isAdmin) {
return true;
} else if (
commissionUser
.split("|")
.includes(generateUserName(user.uid).toLowerCase())
) {
return true;
} else {
return false;
}
} else {
return true;
}
};
return (
<>
<Helmet>
<title>All Commissions</title>
</Helmet>
<Grid container justifyContent="space-between">
{filterTerms ? (
<Grid item>
<ProjectFilterComponent
filterTerms={filterTerms}
filterDidUpdate={(newFilters: ProjectFilterTerms) => {
const updatedUrlParams = filterTermsToQuerystring(newFilters);
if (newFilters.user === "Everyone") {
newFilters.user = undefined;
}
if (newFilters.user === "Mine" && user) {
newFilters.user = user.uid;
}
setFilterTerms(newFilters);
history.push("?" + updatedUrlParams);
}}
/>
</Grid>
) : null}
<Grid className={classes.buttonGrid} style={{ marginLeft: "auto" }}>
<Button
className={classes.createButton}
variant="contained"
color="primary"
onClick={() => {
history.push("/commission/new");
}}
>
New
</Button>
</Grid>
</Grid>
<Paper elevation={3}>
<TableContainer>
<Table className={classes.table}>
<TableHead>
<TableRow>
{tableHeaderTitles.map((title) => (
<TableCell
key={title.label}
sortDirection={orderBy === title.key ? order : false}
>
{title.key ? (
<TableSortLabel
active={orderBy === title.key}
direction={orderBy === title.key ? order : "asc"}
onClick={sortByColumn(title.key)}
>
{title.label}
{orderBy === title.key && (
<span className={classes.visuallyHidden}>
{order === "desc"
? "sorted descending"
: "sorted ascending"}
</span>
)}
</TableSortLabel>
) : (
title.label
)}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{commissions.map(
({
id,
title,
projectCount,
created,
workingGroupId,
status,
owner,
confidential,
}) => {
if (userAllowed(confidential, owner)) {
return (
<TableRow
hover={true}
onClick={() => {
window.open(
`${deploymentRootPath}commission/${id}`,
"_blank"
);
}}
key={id}
onContextMenu={(e) => {
handleContextMenu(e, id);
}}
>
<TableCell>{title}</TableCell>
<TableCell>{projectCount}</TableCell>
<TableCell>
{new Date(created).toLocaleString()}
</TableCell>
<TableCell>
{workingGroups.get(workingGroupId) ?? "<Unknown>"}
</TableCell>
<TableCell>{status}</TableCell>
<TableCell>{owner.replace(/\|/g, " ")}</TableCell>
</TableRow>
);
} else {
return null;
}
}
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={pageSizeOptions}
component="div"
// FIXME: count = -1 causes the pagination component to be able to
// walk past the last page, which displays zero rows. Need an endpoint
// which returns the total, or is returned along the commissions data.
count={-1}
rowsPerPage={pageSize}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
// FIXME: remove when count is correct
labelDisplayedRows={({ from, to }) => `${from}-${to}`}
/>
</Paper>
<Menu
open={contextMenu !== null}
onClose={handleClose}
anchorReference="anchorPosition"
anchorPosition={
contextMenu !== null
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
: undefined
}
>
<MenuItem
onClick={(e) => {
window.open(
`${deploymentRootPath}commission/${commissionToOpen}`,
"_blank"
);
handleClose();
}}
>
Open in new window or tab
</MenuItem>
<MenuItem
onClick={(e) => {
history.push(`/commission/${commissionToOpen}`);
handleClose();
}}
>
Open in existing window or tab
</MenuItem>
</Menu>
</>
);
};
export default CommissionsList;