all repos — caroster @ 5bd547765b3767f6a06f03e35145ab9328a997dc

[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 {addPassengerToTravel, removePassengerFromWaitingList} = usePassengersActions();
 43
 44  const availability = useMemo(() => {
 45    if (!travels) return;
 46    return travels.reduce((count, {vehicle, passengers = []}) => {
 47      if (!passengers) return count + vehicle.seats;
 48      return count + vehicle.seats - passengers.length;
 49    }, 0);
 50  }, [travels]);
 51
 52  const removePassengerFromWaitingListFallBack = useCallback(removePassengerFromWaitingList, [event]);
 53
 54  const selectTravel = useCallback(
 55    async travel => {
 56      const {id, ...passenger} = addingPassenger;
 57      const onError = () => addToast(t('passenger.errors.cant_select_travel'));
 58      addPassengerToTravel({
 59        travel,
 60        passenger,
 61        onError,
 62        onSucceed: () =>
 63          removePassengerFromWaitingListFallBack({
 64            passenger: addingPassenger,
 65            event: {
 66              ...event,
 67              waitingList: event.waitingList.filter(
 68                item => item.id !== addingPassenger.id
 69              ),
 70            },
 71
 72            onError,
 73            onSucceed: () => {
 74              setAddingPassenger(null);
 75              slideToTravel(travel.id);
 76              addToast(t('passenger.success.added_to_car', {name: addingPassenger.name}));
 77            },
 78          }),
 79      });
 80    },
 81    [event, addingPassenger] // eslint-disable-line
 82  );
 83
 84  const onPress = useCallback(
 85    (passengerId: string) => {
 86      const selectedPassenger = event.waitingList.find(
 87        item => item.id === passengerId
 88      );
 89      if (isEditing) setRemovingPassenger(selectedPassenger);
 90      else setAddingPassenger(selectedPassenger);
 91    },
 92    [isEditing, event]
 93  );
 94
 95  const ListButton = isEditing
 96    ? ({onClick}: {onClick: () => void}) => (
 97        <ClearButton icon="close" onClick={onClick} tabIndex={-1} />
 98      )
 99    : ({onClick}: {onClick: () => void}) => (
100        <AssignButton onClick={onClick} tabIndex={-1} />
101      );
102
103  return (
104    <>
105      <Paper className={classes.root}>
106        <div className={clsx(classes.header, 'tour_waiting_list')}>
107          <IconButton
108            size="small"
109            color="primary"
110            className={classes.editBtn}
111            disabled={!event.waitingList?.length}
112            onClick={toggleEditing}
113          >
114            {isEditing ? <Icon>check</Icon> : <Icon>edit</Icon>}
115          </IconButton>
116          <Typography variant="h5">{t('passenger.title')}</Typography>
117          <Typography variant="overline">
118            {t('passenger.availability.seats', {count: availability})}
119          </Typography>
120        </div>
121        <Divider />
122        <AddPassengerButtons
123          getOnClickFunction={getToggleNewPassengerDialogFunction}
124          canAddSelf={canAddSelf}
125          variant="waitingList"
126        />
127        <Divider />
128        <PassengersList
129          passengers={event.waitingList}
130          onPress={onPress}
131          Button={ListButton}
132          disabled={!isEditing && availability <= 0}
133        />
134      </Paper>
135      <RemoveDialog
136        text={
137          <Trans
138            i18nKey="passenger.actions.remove_alert"
139            values={{
140              name: removingPassenger?.name,
141            }}
142            components={{italic: <i />, bold: <strong />}}
143          />
144        }
145        open={!!removingPassenger}
146        onClose={() => setRemovingPassenger(null)}
147        onRemove={() =>
148          removePassengerFromWaitingListFallBack({
149            passenger: removingPassenger,
150            event,
151            onSucceed: () => addToEvent(event.id),
152            onError: () =>
153              addToast(t('passenger.errors.cant_remove_passenger')),
154          })
155        }
156      />
157      <TravelDialog
158        travels={travels}
159        open={!!addingPassenger}
160        onClose={() => setAddingPassenger(null)}
161        onSelect={selectTravel}
162      />
163    </>
164  );
165};
166
167const sortTravels = (a, b) => {
168  const dateA = new Date(a.departure).getTime();
169  const dateB = new Date(b.departure).getTime();
170  if (dateA === dateB)
171    return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
172  else return dateA - dateB;
173};
174
175const useStyles = makeStyles(theme => ({
176  root: {
177    position: 'relative',
178  },
179  header: {
180    padding: theme.spacing(2),
181  },
182  editBtn: {
183    position: 'absolute',
184    top: 0,
185    right: 0,
186    margin: theme.spacing(1),
187    zIndex: theme.zIndex.speedDial,
188  },
189}));
190
191export default WaitingList;