all repos — caroster @ 8a4f9e069b406ad3b9c2405dbef767b2a5304477

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