all repos — caroster @ 5fa52dfbd467d93ed40542222227bea53bc4dee0

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