import React, { useEffect } from 'react' import { useStyles } from './styled'; 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 TablePagination from '@material-ui/core/TablePagination'; import TableRow from '@material-ui/core/TableRow'; import TableSortLabel from '@material-ui/core/TableSortLabel'; import Toolbar from '@material-ui/core/Toolbar'; import Paper from '@material-ui/core/Paper'; import Checkbox from '@material-ui/core/Checkbox'; import IconButton from '@material-ui/core/IconButton'; import Tooltip from '@material-ui/core/Tooltip'; import StarRateRoundedIcon from '@material-ui/icons/StarRateRounded'; import MoreHorizIcon from '@material-ui/icons/MoreHoriz'; import { CSSProperties } from 'react'; import { useSelector } from 'react-redux'; import { activeInstance } from '../../../redux/clientSlice'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import { ErrorInfo, MapInfo } from '../../../client'; import Client from '../../../client'; import ActionChooser, { ActionType } from '../action-chooser'; import ActionDispatcher from '../action-dispatcher'; import { Button, InputBase, Link } from '@material-ui/core'; import SearchIcon from '@material-ui/icons/Search'; import moment from 'moment' import { Filter, LabelFilter } from '..'; import { FormattedMessage, useIntl } from 'react-intl'; import { DeleteOutlined, LabelTwoTone } from '@material-ui/icons'; import Alert from '@material-ui/lab/Alert'; function descendingComparator(a: T, b: T, orderBy: keyof T) { if (b[orderBy] < a[orderBy]) { return -1; } if (b[orderBy] > a[orderBy]) { return 1; } return 0; } type Order = 'asc' | 'desc'; function getComparator( order: Order, orderBy: Key, ): (a: { [key in Key]: number | string | boolean | number[] | undefined }, b: { [key in Key]: number | string | number[] | boolean }) => number { return order === 'desc' ? (a, b) => descendingComparator(a, b, orderBy) : (a, b) => -descendingComparator(a, b, orderBy); } function stableSort(array: T[], comparator: (a: T, b: T) => number) { const stabilizedThis = array.map((el, index) => [el, index] as [T, number]); stabilizedThis.sort((a, b) => { const order = comparator(a[0], b[0]); if (order !== 0) return order; return a[1] - b[1]; }); return stabilizedThis.map((el) => el[0]); } interface HeadCell { id: keyof MapInfo; label?: string; numeric: boolean; style?: CSSProperties; } interface EnhancedTableProps { classes: ReturnType; numSelected: number; onRequestSort: (event: React.MouseEvent, property: keyof MapInfo) => void; onSelectAllClick: (event: React.ChangeEvent) => void; order: Order; orderBy: string; rowCount: number; } function EnhancedTableHead(props: EnhancedTableProps) { const intl = useIntl(); const { classes, onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort } = props; const createSortHandler = (property: keyof MapInfo) => (event: React.MouseEvent) => { onRequestSort(event, property); }; const headCells: HeadCell[] = [ { id: 'title', numeric: false, label: intl.formatMessage({ id: 'map.name', defaultMessage: 'Name' }) }, { id: 'labels', numeric: false }, { id: 'createdBy', numeric: false, label: intl.formatMessage({ id: 'map.creator', defaultMessage: 'Creator' }), style: { width: '70px', whiteSpace: 'nowrap' } }, { id: 'lastModificationTime', numeric: true, label: intl.formatMessage({ id: 'map.last-update', defaultMessage: 'Last Update' }), style: { width: '70px', whiteSpace: 'nowrap' } } ]; return ( 0 && numSelected < rowCount} checked={rowCount > 0 && numSelected === rowCount} onChange={onSelectAllClick} size='small' inputProps={{ 'aria-label': 'select all desserts' }} /> {headCells.map((headCell) => { return ( {headCell.label} {orderBy === headCell.id && ( {order === 'desc' ? 'sorted descending' : 'sorted ascending'} )} ) })} ); } type ActionPanelState = { el: HTMLElement | undefined, mapId: number } interface MapsListProps { filter: Filter } const mapsFilter = (filter: Filter, search: string): ((mapInfo: MapInfo) => boolean) => { return (mapInfo: MapInfo) => { // Check for filter condition let result = false; switch (filter.type) { case 'all': result = true; break; case 'starred': result = mapInfo.starred; break; case 'owned': result = mapInfo.role == 'owner'; break; case 'shared': result = mapInfo.role != 'owner'; break; case 'label': result = !mapInfo.labels || mapInfo.labels.includes((filter as LabelFilter).label.id) break; case 'public': result = mapInfo.isPublic; break; default: result = false; } // Does it match search filter criteria... if (search && result) { result = mapInfo.title.toLowerCase().indexOf(search.toLowerCase()) != -1; } return result; } } export const MapsList = (props: MapsListProps) => { const classes = useStyles(); const [order, setOrder] = React.useState('asc'); const [filter, setFilter] = React.useState({ type: 'all' }); const [orderBy, setOrderBy] = React.useState('lastModificationTime'); const [selected, setSelected] = React.useState([]); const [searchCondition, setSearchCondition] = React.useState(''); const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); const client: Client = useSelector(activeInstance); const intl = useIntl(); const queryClient = useQueryClient(); useEffect(() => { setSelected([]); setPage(0); setFilter(props.filter) }, [props.filter.type, (props.filter as LabelFilter).label]); const { isLoading, error, data } = useQuery('maps', async () => { return await client.fetchAllMaps(); }); const mapsInfo: MapInfo[] = data ? data.filter(mapsFilter(filter, searchCondition)) : []; const [activeRowAction, setActiveRowAction] = React.useState(undefined); type ActiveDialog = { actionType: ActionType; mapsId: number[]; }; const [activeDialog, setActiveDialog] = React.useState(undefined); const handleRequestSort = (event: React.MouseEvent, property: keyof MapInfo) => { const isAsc = orderBy === property && order === 'asc'; setOrder(isAsc ? 'desc' : 'asc'); setOrderBy(property); }; const handleSelectAllClick = (event: React.ChangeEvent): void => { if (event.target.checked) { const newSelecteds = mapsInfo.map((n) => n.id); setSelected(newSelecteds); return; } setSelected([]); }; const handleRowClick = (event: React.MouseEvent, id: number): void => { const selectedIndex = selected.indexOf(id); let newSelected: number[] = []; if (selectedIndex === -1) { newSelected = newSelected.concat(selected, id); } else if (selectedIndex === 0) { newSelected = newSelected.concat(selected.slice(1)); } else if (selectedIndex === selected.length - 1) { newSelected = newSelected.concat(selected.slice(0, -1)); } else if (selectedIndex > 0) { newSelected = newSelected.concat( selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1), ); } setSelected(newSelected); }; const handleChangePage = (event: unknown, newPage: number) => { setPage(newPage); }; const handleChangeRowsPerPage = (event: React.ChangeEvent) => { setRowsPerPage(parseInt(event.target.value, 10)); setPage(0); }; const handleActionClick = (mapId: number): ((event: any) => void) => { return (event: any): void => { setActiveRowAction( { mapId: mapId, el: event.currentTarget } ); event.stopPropagation(); }; }; const starredMultation = useMutation((id: number) => { const map = mapsInfo.find(m => m.id == id); return client.changeStarred(id, !Boolean(map?.starred)); }, { onSuccess: () => { queryClient.invalidateQueries('maps'); }, onError: (error) => { // setError(error); } } ); const handleStarred = (event: React.MouseEvent, id: number) => { event.stopPropagation(); starredMultation.mutate(id); } const handleActionMenuClose = (action: ActionType): void => { if (action) { const mapId = activeRowAction?.mapId; setActiveDialog({ actionType: action as ActionType, mapsId: [mapId] as number[] }); } setActiveRowAction(undefined); }; const handleOnSearchChange = (e: React.ChangeEvent) => { setSearchCondition(e.target.value); } const handleDeleteClick = (event: React.MouseEvent) => { setActiveDialog({ actionType: 'delete', mapsId: selected }); } const isSelected = (id: number) => selected.indexOf(id) !== -1; return (
{selected.length > 0 && } {selected.length > 0 && }
{isLoading ? ( Loading ...) : (mapsInfo.length == 0 ? () : stableSort(mapsInfo, getComparator(order, orderBy)) .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .map((row: MapInfo) => { const isItemSelected = isSelected(row.id); const labelId = row.id; return ( handleRowClick(event, row.id)} role="checkbox" aria-checked={isItemSelected} tabIndex={-1} key={row.id} selected={isItemSelected} style={{ border: "0" }} > handleStarred(e, row.id)}> e.stopPropagation()}> {row.title} {row.labels} {row.createdBy} {moment(row.lastModificationTime).fromNow()} ); }))}
setActiveDialog(undefined)} mapsId={activeDialog ? activeDialog.mapsId : []} />
); } const ErrorDialog = (props) => { return (This is an error alert — check it out!); }