all repos — caroster @ 9e700046837e1356f38ebf38303313c053368149

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