frontend/app/WorkingGroups/WorkingGroups.tsx (236 lines of code) (raw):

import React, { useEffect, useState } from "react"; import { RouteComponentProps } from "react-router-dom"; import { Table, TableHead, TableBody, TableRow, TableCell, TableContainer, Paper, Button, TablePagination, TableSortLabel, IconButton, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Tooltip, } from "@material-ui/core"; import DeleteIcon from "@material-ui/icons/Delete"; import { getWorkingGroupsOnPage, deleteWorkingGroup } from "./helpers"; import { sortListByOrder, SortDirection } from "../utils/lists"; import { isLoggedIn } from "../utils/api"; import { SystemNotification, SystemNotifcationKind, } from "@guardian/pluto-headers"; import { Helmet } from "react-helmet"; import { Visibility, VisibilityOff } from "@material-ui/icons"; import { useGuardianStyles } from "~/misc/utils"; const tableHeaderTitles: HeaderTitle<WorkingGroup>[] = [ { label: "Name", key: "name" }, { label: "Commissioner", key: "commissioner" }, { label: "Visibility", key: "hide" }, { label: "" }, ]; const pageSizeOptions = [25, 50, 100]; const WorkingGroups: React.FC<RouteComponentProps> = (props) => { const classes = useGuardianStyles(); const [workingGroups, setWorkingGroups] = useState<WorkingGroup[]>([]); const [isAdmin, setIsAdmin] = useState<boolean>(false); const [updatingWorkingGroup, setUpdatingWorkingGroup] = useState< WorkingGroup | undefined >(undefined); const [openDialog, setOpenDialog] = useState<boolean>(false); const [page, setPage] = useState(0); const [pageSize, setRowsPerPage] = useState(pageSizeOptions[0]); const [order, setOrder] = useState<SortDirection>("asc"); const [orderBy, setOrderBy] = useState<keyof WorkingGroup>("name"); useEffect(() => { const fetchWorkingGroupsOnPage = async () => { const workingGroups = await getWorkingGroupsOnPage({ page, pageSize }); setWorkingGroups(workingGroups); }; const fetchWhoIsLoggedIn = async () => { try { const loggedIn = await isLoggedIn(); setIsAdmin(loggedIn.isAdmin); } catch { setIsAdmin(false); } }; fetchWhoIsLoggedIn(); fetchWorkingGroupsOnPage(); }, [page, pageSize]); const onNewWorkingGroup = (): void => { props.history.push("/working-group/new"); }; 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 WorkingGroup) => ( _event: React.MouseEvent<unknown> ) => { const isAsc = orderBy === property && order === "asc"; setOrder(isAsc ? "desc" : "asc"); setOrderBy(property); }; const closeDialog = () => { setOpenDialog(false); }; const onDeleteWorkingGroup = async () => { closeDialog(); try { const workingGroupId = updatingWorkingGroup?.id as number; await deleteWorkingGroup(workingGroupId); setWorkingGroups( workingGroups.filter((group) => group.id !== workingGroupId) ); SystemNotification.open( SystemNotifcationKind.Success, `Successfully deleted Working Group: "${updatingWorkingGroup?.name}"` ); } catch { SystemNotification.open( SystemNotifcationKind.Error, `Failed to delete Working Group "${updatingWorkingGroup?.name}"` ); } }; return ( <> <Helmet> <title>Working Groups - Pluto Admin</title> </Helmet> <Button className={classes.createNewWorkingGroup} variant="outlined" onClick={onNewWorkingGroup} > New </Button> <Paper elevation={3}> <TableContainer> <Table className={classes.table}> <TableHead> <TableRow> {tableHeaderTitles.map((title, index) => ( <TableCell key={title.label ? title.label : index} 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> {sortListByOrder(workingGroups, orderBy, order).map( ({ id, name, commissioner, hide }) => ( <TableRow hover={true} onClick={() => props.history.push(`/working-group/${id}`)} key={id} > <TableCell>{name}</TableCell> <TableCell>{commissioner}</TableCell> <TableCell> {hide ? ( <Tooltip title="This working group is discontinued"> <VisibilityOff className={classes.visibilityIcon} /> </Tooltip> ) : ( <Tooltip title="This working group is not discontinued"> <Visibility className={classes.visibilityIcon} /> </Tooltip> )} </TableCell> <TableCell align={"right"}> {isAdmin && ( <IconButton onClick={(event) => { event.stopPropagation(); setUpdatingWorkingGroup({ id, name, commissioner, hide, }); setOpenDialog(true); }} > <DeleteIcon /> </IconButton> )} </TableCell> </TableRow> ) )} </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> <Dialog open={openDialog} onClose={closeDialog} aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" > <DialogTitle id="alert-dialog-title">Delete Working Group</DialogTitle> <DialogContent> <DialogContentText id="alert-dialog-description"> Are you sure you want to delete Working Group " {updatingWorkingGroup?.name}"? </DialogContentText> </DialogContent> <DialogActions> <Button onClick={closeDialog}>Cancel</Button> <Button color="secondary" onClick={onDeleteWorkingGroup}> Ok </Button> </DialogActions> </Dialog> </> ); }; export default WorkingGroups;