all repos — caroster @ a60257e204f1cc253e9d375f87bc6d7ea661c1d8

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