all repos — caroster @ a39f5083b3b6277f4a1af238d79728739172f597

[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 useToastStore from '../../stores/useToastStore';
 11import useEventStore from '../../stores/useEventStore';
 12import useAddToEvents from '../../hooks/useAddToEvents';
 13import PassengersList from '../PassengersList';
 14import RemoveDialog from '../RemoveDialog';
 15import AddPassengerButtons from '../AddPassengerButtons';
 16import TravelDialog from './TravelDialog';
 17import ClearButton from '../ClearButton';
 18import AssignButton from './AssignButton';
 19import usePassengersActions from '../../hooks/usePassengersActions';
 20
 21interface Props {
 22  getToggleNewPassengerDialogFunction: (addSelf: boolean) => () => void;
 23  canAddSelf: boolean;
 24  slideToTravel: (travelId: string) => void;
 25}
 26
 27const WaitingList = ({
 28  getToggleNewPassengerDialogFunction,
 29  canAddSelf,
 30  slideToTravel,
 31}: Props) => {
 32  const classes = useStyles();
 33  const {t} = useTranslation();
 34  const event = useEventStore(s => s.event);
 35  const addToast = useToastStore(s => s.addToast);
 36  const {addToEvent} = useAddToEvents();
 37  const [isEditing, toggleEditing] = useReducer(i => !i, false);
 38  const [removingPassenger, setRemovingPassenger] = useState(null);
 39  const [addingPassenger, setAddingPassenger] = useState(null);
 40  const travels =
 41    event?.travels?.length > 0 ? event.travels.slice().sort(sortTravels) : [];
 42  const {addPassengerToTravel, removePassengerFromWaitingList} =
 43    usePassengersActions();
 44
 45  const availability = useMemo(() => {
 46    if (!travels) return;
 47    return travels.reduce((count, {vehicle, passengers = []}) => {
 48      if (!vehicle) return 0;
 49      else if (!passengers) return count + vehicle.seats;
 50      return count + vehicle.seats - passengers.length;
 51    }, 0);
 52  }, [travels]);
 53
 54  const removePassengerFromWaitingListFallBack = useCallback(
 55    removePassengerFromWaitingList,
 56    [event]
 57  );
 58
 59  const selectTravel = useCallback(
 60    async travel => {
 61      const {id, ...passenger} = addingPassenger;
 62
 63      try {
 64        await addPassengerToTravel({
 65          travel,
 66          passenger,
 67        });
 68        await removePassengerFromWaitingListFallBack({
 69          passenger: addingPassenger,
 70          event: {
 71            ...event,
 72            waitingList: event.waitingList.filter(
 73              item => item.id !== addingPassenger.id
 74            ),
 75          },
 76        });
 77        setAddingPassenger(null);
 78        slideToTravel(travel.id);
 79        addToast(
 80          t('passenger.success.added_to_car', {
 81            name: addingPassenger.name,
 82          })
 83        );
 84      } catch (error) {
 85        console.error(error);
 86        addToast(t('passenger.errors.cant_select_travel'));
 87      }
 88    },
 89    [event, addingPassenger] // eslint-disable-line
 90  );
 91
 92  const onPress = useCallback(
 93    (passengerId: string) => {
 94      const selectedPassenger = event.waitingList.find(
 95        item => item.id === passengerId
 96      );
 97      if (isEditing) setRemovingPassenger(selectedPassenger);
 98      else setAddingPassenger(selectedPassenger);
 99    },
100    [isEditing, event]
101  );
102
103  const onRemove = async () => {
104    try {
105      await removePassengerFromWaitingListFallBack({
106        passenger: removingPassenger,
107        event,
108      });
109      addToEvent(event.id);
110    } catch (error) {
111      console.error(error);
112      addToast(t('passenger.errors.cant_remove_passenger'));
113    }
114  };
115
116  const ListButton = isEditing
117    ? ({onClick}: {onClick: () => void}) => (
118        <ClearButton icon="close" onClick={onClick} tabIndex={-1} />
119      )
120    : ({onClick, disabled}: {onClick: () => void; disabled: boolean}) => (
121        <AssignButton onClick={onClick} tabIndex={-1} disabled={disabled} />
122      );
123
124  return (
125    <>
126      <Paper className={classes.root}>
127        <div className={clsx(classes.header, 'tour_waiting_list')}>
128          <IconButton
129            size="small"
130            color="primary"
131            className={classes.editBtn}
132            disabled={!event.waitingList?.length}
133            onClick={toggleEditing}
134          >
135            {isEditing ? <Icon>check</Icon> : <Icon>edit</Icon>}
136          </IconButton>
137          <Typography variant="h5">{t('passenger.title')}</Typography>
138          <Typography variant="overline">
139            {t('passenger.availability.seats', {count: availability})}
140          </Typography>
141        </div>
142        <Divider />
143        <AddPassengerButtons
144          getOnClickFunction={getToggleNewPassengerDialogFunction}
145          canAddSelf={canAddSelf}
146          variant="waitingList"
147        />
148        <Divider />
149        <PassengersList
150          passengers={event.waitingList}
151          onPress={onPress}
152          Button={ListButton}
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={onRemove}
168      />
169      <TravelDialog
170        eventName={event.name}
171        travels={travels}
172        passenger={addingPassenger}
173        open={!!addingPassenger}
174        onClose={() => setAddingPassenger(null)}
175        onSelect={selectTravel}
176      />
177    </>
178  );
179};
180
181const sortTravels = (a, b) => {
182  const dateA = new Date(a.departure).getTime();
183  const dateB = new Date(b.departure).getTime();
184  if (dateA === dateB)
185    return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
186  else return dateA - dateB;
187};
188
189const useStyles = makeStyles(theme => ({
190  root: {
191    position: 'relative',
192  },
193  header: {
194    padding: theme.spacing(2),
195  },
196  editBtn: {
197    position: 'absolute',
198    top: 0,
199    right: 0,
200    margin: theme.spacing(1),
201    zIndex: theme.zIndex.speedDial,
202  },
203}));
204
205export default WaitingList;