all repos — caroster @ v0.9.1

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