import React, { useEffect, CSSProperties } from 'react' import { useStyles } from './styled' import { useSelector } from 'react-redux' import { activeInstance, fetchAccount } from '../../../redux/clientSlice' import { useMutation, useQuery, useQueryClient } from 'react-query' import Client, { ErrorInfo, MapInfo } from '../../../classes/client' import ActionChooser, { ActionType } from '../action-chooser' import ActionDispatcher from '../action-dispatcher' import dayjs from 'dayjs' import { Filter, LabelFilter } from '..' import { FormattedMessage, useIntl } from 'react-intl' 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 Button from '@material-ui/core/Button' import InputBase from '@material-ui/core/InputBase' import Link from '@material-ui/core/Link' import LabelTwoTone from '@material-ui/icons/LabelTwoTone' import DeleteOutlined from '@material-ui/icons/DeleteOutlined' import MoreHorizIcon from '@material-ui/icons/MoreHoriz' import StarRateRoundedIcon from '@material-ui/icons/StarRateRounded' import SearchIcon from '@material-ui/icons/Search' // Load fromNow pluggin import relativeTime from 'dayjs/plugin/relativeTime' dayjs.extend(relativeTime) 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' // eslint-disable-next-line @typescript-eslint/no-explicit-any 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): React.ReactElement => { 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() // Configure locale ... const account = fetchAccount() if (account) { dayjs.locale(account.locale.code) } useEffect(() => { setSelected([]) setPage(0) setFilter(props.filter) }, [props.filter.type, (props.filter as LabelFilter).label]) const { isLoading, data } = useQuery('maps', () => { return 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) => void) => { return (event): void => { setActiveRowAction({ mapId: mapId, el: event.currentTarget, }) event.stopPropagation() } } const starredMultation = useMutation( (id: number) => { const map = mapsInfo.find((m) => m.id == id) return client.updateStarred(id, !map?.starred) }, { onSuccess: () => { queryClient.invalidateQueries('maps') }, onError: () => { // 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 = () => { 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} {dayjs( row.lastModificationTime ).fromNow()} ) }) )}
setActiveDialog(undefined)} mapsId={activeDialog ? activeDialog.mapsId : []} />
) }