all repos — caroster @ e70dda0ec966d182cfaf626d43aea6888cdb7634

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

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

  1import {useReducer, useState, useMemo, useCallback} from 'react';
  2import {makeStyles} from '@material-ui/core/styles';
  3import Typography from '@material-ui/core/Typography';
  4import IconButton from '@material-ui/core/IconButton';
  5import Icon from '@material-ui/core/Icon';
  6import Paper from '@material-ui/core/Paper';
  7import Divider from '@material-ui/core/Divider';
  8import clsx from 'clsx';
  9import {Trans, useTranslation} from 'react-i18next';
 10import useToastStore from '../../stores/useToastStore';
 11import useEventStore from '../../stores/useEventStore';
 12import useAddToEvents from '../../hooks/useAddToEvents';
 13import PassengersList from '../PassengersList';
 14import RemoveDialog from '../RemoveDialog';
 15import AddPassengerButtons from '../AddPassengerButtons';
 16import TravelDialog from './TravelDialog';
 17import ClearButton from '../ClearButton';
 18import AssignButton from './AssignButton';
 19import usePassengersActions from '../../hooks/usePassengersActions';
 20
 21interface Props {
 22  getToggleNewPassengerDialogFunction: (addSelf: boolean) => () => void;
 23  canAddSelf: boolean;
 24  slideToTravel: (travelId: string) => void;
 25}
 26
 27const WaitingList = ({
 28  getToggleNewPassengerDialogFunction,
 29  canAddSelf,
 30  slideToTravel,
 31}: Props) => {
 32  const classes = useStyles();
 33  const {t} = useTranslation();
 34  const event = useEventStore(s => s.event);
 35  const addToast = useToastStore(s => s.addToast);
 36  const {addToEvent} = useAddToEvents();
 37  const [isEditing, toggleEditing] = useReducer(i => !i, false);
 38  const [removingPassenger, setRemovingPassenger] = useState(null);
 39  const [addingPassenger, setAddingPassenger] = useState(null);
 40  const travels =
 41    event?.travels?.length > 0 ? event.travels.slice().sort(sortTravels) : [];
 42  const {
 43    addPassengerToTravel,
 44    removePassengerFromWaitingList,
 45  } = usePassengersActions();
 46
 47  const availability = useMemo(() => {
 48    if (!travels) return;
 49    return travels.reduce((count, {vehicle, passengers = []}) => {
 50      if (!vehicle) return 0;
 51      else if (!passengers) return count + vehicle.seats;
 52      return count + vehicle.seats - passengers.length;
 53    }, 0);
 54  }, [travels]);
 55
 56  const removePassengerFromWaitingListFallBack = useCallback(
 57    removePassengerFromWaitingList,
 58    [event]
 59  );
 60
 61  const selectTravel = useCallback(
 62    async travel => {
 63      const {id, ...passenger} = addingPassenger;
 64      const onError = () => addToast(t('passenger.errors.cant_select_travel'));
 65      addPassengerToTravel({
 66        travel,
 67        passenger,
 68        onError,
 69        onSucceed: () =>
 70          removePassengerFromWaitingListFallBack({
 71            passenger: addingPassenger,
 72            event: {
 73              ...event,
 74              waitingList: event.waitingList.filter(
 75                item => item.id !== addingPassenger.id
 76              ),
 77            },
 78
 79            onError,
 80            onSucceed: () => {
 81              setAddingPassenger(null);
 82              slideToTravel(travel.id);
 83              addToast(
 84                t('passenger.success.added_to_car', {
 85                  name: addingPassenger.name,
 86                })
 87              );
 88            },
 89          }),
 90      });
 91    },
 92    [event, addingPassenger] // eslint-disable-line
 93  );
 94
 95  const onPress = useCallback(
 96    (passengerId: string) => {
 97      const selectedPassenger = event.waitingList.find(
 98        item => item.id === passengerId
 99      );
100      if (isEditing) setRemovingPassenger(selectedPassenger);
101      else setAddingPassenger(selectedPassenger);
102    },
103    [isEditing, event]
104  );
105
106  const ListButton = isEditing
107    ? ({onClick}: {onClick: () => void}) => (
108        <ClearButton icon="close" onClick={onClick} tabIndex={-1} />
109      )
110    : ({onClick}: {onClick: () => void}) => (
111        <AssignButton onClick={onClick} tabIndex={-1} />
112      );
113
114  return (
115    <>
116      <Paper className={classes.root}>
117        <div className={clsx(classes.header, 'tour_waiting_list')}>
118          <IconButton
119            size="small"
120            color="primary"
121            className={classes.editBtn}
122            disabled={!event.waitingList?.length}
123            onClick={toggleEditing}
124          >
125            {isEditing ? <Icon>check</Icon> : <Icon>edit</Icon>}
126          </IconButton>
127          <Typography variant="h5">{t('passenger.title')}</Typography>
128          <Typography variant="overline">
129            {t('passenger.availability.seats', {count: availability})}
130          </Typography>
131        </div>
132        <Divider />
133        <AddPassengerButtons
134          getOnClickFunction={getToggleNewPassengerDialogFunction}
135          canAddSelf={canAddSelf}
136          variant="waitingList"
137        />
138        <Divider />
139        <PassengersList
140          passengers={event.waitingList}
141          onPress={onPress}
142          Button={ListButton}
143          disabled={!isEditing && availability <= 0}
144        />
145      </Paper>
146      <RemoveDialog
147        text={
148          <Trans
149            i18nKey="passenger.actions.remove_alert"
150            values={{
151              name: removingPassenger?.name,
152            }}
153            components={{italic: <i />, bold: <strong />}}
154          />
155        }
156        open={!!removingPassenger}
157        onClose={() => setRemovingPassenger(null)}
158        onRemove={() =>
159          removePassengerFromWaitingListFallBack({
160            passenger: removingPassenger,
161            event,
162            onSucceed: () => addToEvent(event.id),
163            onError: () =>
164              addToast(t('passenger.errors.cant_remove_passenger')),
165          })
166        }
167      />
168      <TravelDialog
169        travels={travels}
170        open={!!addingPassenger}
171        onClose={() => setAddingPassenger(null)}
172        onSelect={selectTravel}
173      />
174    </>
175  );
176};
177
178const sortTravels = (a, b) => {
179  const dateA = new Date(a.departure).getTime();
180  const dateB = new Date(b.departure).getTime();
181  if (dateA === dateB)
182    return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
183  else return dateA - dateB;
184};
185
186const useStyles = makeStyles(theme => ({
187  root: {
188    position: 'relative',
189  },
190  header: {
191    padding: theme.spacing(2),
192  },
193  editBtn: {
194    position: 'absolute',
195    top: 0,
196    right: 0,
197    margin: theme.spacing(1),
198    zIndex: theme.zIndex.speedDial,
199  },
200}));
201
202export default WaitingList;