all repos — caroster @ v8.0

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