all repos — caroster @ 313dd5f41e70cb7c57988fe4e0dd361d444e9ed7

[Octree] Group carpool to your event https://caroster.io

frontend/containers/WaitingList/index.tsx (view raw)

  1import {useReducer, useState, useMemo, useCallback} from 'react';
  2import router from 'next/dist/client/router';
  3import useMediaQuery from '@mui/material/useMediaQuery';
  4import Container from '@mui/material/Container';
  5import TuneIcon from '@mui/icons-material/Tune';
  6import Box from '@mui/material/Box';
  7import Typography from '@mui/material/Typography';
  8import IconButton from '@mui/material/IconButton';
  9import Icon from '@mui/material/Icon';
 10import Paper from '@mui/material/Paper';
 11import Divider from '@mui/material/Divider';
 12import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
 13import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';
 14import {useTheme} from '@mui/material/styles';
 15import {Trans, useTranslation} from 'react-i18next';
 16import useToastStore from '../../stores/useToastStore';
 17import useEventStore from '../../stores/useEventStore';
 18import usePassengersActions from '../../hooks/usePassengersActions';
 19import RemoveDialog from '../RemoveDialog';
 20import AddPassengerButtons from '../AddPassengerButtons';
 21import AssignButton from './AssignButton';
 22import PassengersList, { PassengerButton } from '../PassengersList';
 23
 24interface Props {
 25  canAddSelf: boolean;
 26  registered: boolean;
 27  onAddSelf: () => void;
 28  onAddOther: () => void;
 29}
 30
 31const WaitingList = (props: Props) => {
 32  const {canAddSelf, registered} = props;
 33  const {t} = useTranslation();
 34  const event = useEventStore(s => s.event);
 35  const theme = useTheme();
 36  const mobile = useMediaQuery(theme.breakpoints.down('md'));
 37  const addToast = useToastStore(s => s.addToast);
 38  const [isEditing, toggleEditing] = useReducer(i => !i, false);
 39  const [removingPassenger, setRemovingPassenger] = useState(null);
 40  const travels =
 41    event?.travels?.data?.length > 0
 42      ? event?.travels?.data.slice().sort(sortTravels)
 43      : [];
 44  const {removePassenger} = usePassengersActions();
 45
 46  const availability = useMemo(() => {
 47    if (!travels) return;
 48    return travels.reduce((count, {attributes: {seats, passengers}}) => {
 49      if (!seats) return 0;
 50      else if (!passengers) return count + seats;
 51      return count + seats - passengers?.data?.length;
 52    }, 0);
 53  }, [travels]);
 54
 55  const removePassengerCallback = useCallback(removePassenger, [event]);
 56
 57  const onPress = useCallback(
 58    (passengerId: string) => {
 59      const selectedPassenger = event?.waitingPassengers?.data.find(
 60        item => item.id === passengerId
 61      );
 62      if (isEditing) setRemovingPassenger(selectedPassenger);
 63      else router.push(`/e/${event.uuid}/assign/${selectedPassenger.id}`);
 64    },
 65    [isEditing, event]
 66  );
 67
 68  const onRemove = async () => {
 69    try {
 70      await removePassengerCallback(removingPassenger.id);
 71      addToast(t('passenger.deleted'));
 72    } catch (error) {
 73      console.error(error);
 74      addToast(t('passenger.errors.cant_remove_passenger'));
 75    }
 76  };
 77
 78  const ListButton: PassengerButton = isEditing
 79    ? ({onClick}) => (
 80        <ListItemSecondaryAction>
 81          <IconButton size="small" color="primary" onClick={onClick}>
 82            <CancelOutlinedIcon />
 83          </IconButton>
 84        </ListItemSecondaryAction>
 85      )
 86    : ({onClick, disabled}) => (
 87        <AssignButton onClick={onClick} tabIndex={-1} disabled={disabled} />
 88      );
 89
 90  return (
 91    <Container maxWidth="sm" sx={{mt: 11, mx: 0, px: mobile ? 2 : 4}}>
 92      <Paper sx={{width: '480px', maxWidth: '100%', position: 'relative'}}>
 93        <Box p={2}>
 94          <IconButton
 95            size="small"
 96            color="primary"
 97            sx={{
 98              position: 'absolute',
 99              top: 0,
100              right: 0,
101              margin: 1,
102            }}
103            disabled={!event?.waitingPassengers?.data?.length}
104            onClick={toggleEditing}
105          >
106            {isEditing ? <Icon>check</Icon> : <TuneIcon />}
107          </IconButton>
108          <Typography variant="h5">{t('passenger.title')}</Typography>
109          <Typography variant="overline">
110            {t('passenger.availability.seats', {count: availability})}
111          </Typography>
112        </Box>
113        <Divider />
114        <AddPassengerButtons
115          onAddOther={props.onAddOther}
116          onAddSelf={props.onAddSelf}
117          canAddSelf={canAddSelf}
118          registered={registered}
119          variant="waitingList"
120        />
121        <Divider />
122        {event?.waitingPassengers?.data?.length > 0 && (
123          <PassengersList
124            passengers={event.waitingPassengers.data}
125            onPress={onPress}
126            Button={ListButton}
127          />
128        )}
129      </Paper>
130      <RemoveDialog
131        text={
132          <Trans
133            i18nKey="passenger.actions.remove_alert"
134            values={{
135              name: removingPassenger?.name,
136            }}
137            components={{italic: <i />, bold: <strong />}}
138          />
139        }
140        open={!!removingPassenger}
141        onClose={() => setRemovingPassenger(null)}
142        onRemove={onRemove}
143      />
144    </Container>
145  );
146};
147
148const sortTravels = (a, b) => {
149  const dateA = new Date(a.departure).getTime();
150  const dateB = new Date(b.departure).getTime();
151  if (dateA === dateB)
152    return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
153  else return dateA - dateB;
154};
155
156export default WaitingList;