all repos — caroster @ 124dfa21a7100f628775f5a0f49841048f8584ea

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