all repos — caroster @ 1469af7c7664fa1069a420382188dbca6499f78d

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

frontend/containers/WaitingList/index.js (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 = ({car}) => {
 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 [removing, setRemoving] = useState(null);
 28  const [adding, setAdding] = 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 saveWaitingList = useCallback(
 42    async (waitingList, i18nError) => {
 43      try {
 44        await updateEvent({
 45          variables: {id: event.id, eventUpdate: {waiting_list: waitingList}},
 46        });
 47        addToEvent(event.id);
 48      } catch (error) {
 49        console.error(error);
 50        addToast(t(i18nError));
 51      }
 52    },
 53    [event, addToEvent] // eslint-disable-line
 54  );
 55
 56  const addPassenger = useCallback(
 57    async passenger =>
 58      saveWaitingList(
 59        [...(event.waiting_list || []), passenger],
 60        'passenger.errors.cant_add_passenger'
 61      ),
 62    [event, saveWaitingList] // eslint-disable-line
 63  );
 64
 65  const removePassenger = useCallback(
 66    async index => {
 67      return saveWaitingList(
 68        event.waiting_list.filter((_, i) => i !== index),
 69        'passenger.errors.cant_remove_passenger'
 70      );
 71    },
 72    [event, saveWaitingList] // eslint-disable-line
 73  );
 74
 75  const selectCar = useCallback(
 76    async car => {
 77      try {
 78        await updateCar({
 79          variables: {
 80            id: car.id,
 81            carUpdate: {
 82              passengers: [
 83                ...(car.passengers || []),
 84                event.waiting_list[adding],
 85              ],
 86            },
 87          },
 88        });
 89        await updateEvent({
 90          variables: {
 91            id: event.id,
 92            eventUpdate: {
 93              waiting_list: event.waiting_list.filter((_, i) => i !== adding),
 94            },
 95          },
 96        });
 97      } catch (error) {
 98        console.error(error);
 99        addToast(t('passenger.errors.cant_select_car'));
100      }
101      setAdding(null);
102    },
103    [event, adding] // eslint-disable-line
104  );
105
106  const onPress = useCallback(
107    index => {
108      if (isEditing) setRemoving(index);
109      else setAdding(index);
110    },
111    [isEditing]
112  );
113
114  return (
115    <>
116      <Paper className={classes.root}>
117        <div className={classes.header}>
118          <IconButton
119            size="small"
120            color="primary"
121            className={classes.editBtn}
122            disabled={!event.waiting_list || !event.waiting_list.length}
123            onClick={toggleEditing}
124          >
125            {isEditing ? <Icon>check</Icon> : <Icon>edit</Icon>}
126          </IconButton>
127          <Typography variant="h5">{t('passenger.title')}</Typography>
128          <Typography variant="overline">
129            {t('passenger.availability.seats', {count: availability})}
130          </Typography>
131        </div>
132        <Divider />
133        <PassengersList
134          passengers={event.waiting_list}
135          addPassenger={addPassenger}
136          onPress={onPress}
137          icon={isEditing ? 'close' : 'chevron_right'}
138          disabled={!isEditing && availability <= 0}
139        />
140      </Paper>
141      <RemoveDialog
142        text={
143          <Trans
144            i18nKey="passenger.actions.remove_alert"
145            values={{
146              name: event.waiting_list ? event.waiting_list[removing] : null,
147            }}
148            components={{italic: <i />, bold: <strong />}}
149          />
150        }
151        open={removing !== null}
152        onClose={() => setRemoving(null)}
153        onRemove={() => removePassenger(removing)}
154      />
155      <CarDialog
156        cars={cars}
157        open={adding !== null}
158        onClose={() => setAdding(null)}
159        onSelect={selectCar}
160      />
161    </>
162  );
163};
164
165const sortCars = (a, b) => {
166  const dateA = new Date(a.departure).getTime();
167  const dateB = new Date(b.departure).getTime();
168  if (dateA === dateB) return new Date(a.createdAt) - new Date(b.createdAt);
169  else return dateA - dateB;
170};
171
172const useStyles = makeStyles(theme => ({
173  root: {
174    position: 'relative',
175  },
176  header: {
177    padding: theme.spacing(2),
178  },
179  editBtn: {
180    position: 'absolute',
181    top: 0,
182    right: 0,
183    margin: theme.spacing(1),
184    zIndex: theme.zIndex.speedDial,
185  },
186}));
187
188export default WaitingList;