all repos — caroster @ 0106eb023ccf03f53408d4a3a523b5430f51d898

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