all repos — caroster @ f27c94bbe69c23f12e6cfb1f28f9c1bc2ef9514c

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

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

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