all repos — caroster @ 7aaf2e08839a69d11fe38936bef5abaf5789b853

[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 PassengersList from '../PassengersList';
 13import RemoveDialog from '../RemoveDialog';
 14import AddPassengerButtons from '../AddPassengerButtons';
 15import TravelDialog from './TravelDialog';
 16import ClearButton from '../ClearButton';
 17import AssignButton from './AssignButton';
 18import usePassengersActions from '../../hooks/usePassengersActions';
 19
 20interface Props {
 21  getToggleNewPassengerDialogFunction: (addSelf: boolean) => () => void;
 22  canAddSelf: boolean;
 23  slideToTravel: (travelId: string) => void;
 24}
 25
 26const WaitingList = ({
 27  getToggleNewPassengerDialogFunction,
 28  canAddSelf,
 29  slideToTravel,
 30}: Props) => {
 31  const classes = useStyles();
 32  const {t} = useTranslation();
 33  const event = useEventStore(s => s.event);
 34  const addToast = useToastStore(s => s.addToast);
 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 {updatePassenger, removePassenger} = usePassengersActions();
 41
 42  const availability = useMemo(() => {
 43    if (!travels) return;
 44    return travels.reduce((count, {vehicle, passengers = []}) => {
 45      if (!vehicle) return 0;
 46      else if (!passengers) return count + vehicle.seats;
 47      return count + vehicle.seats - passengers.length;
 48    }, 0);
 49  }, [travels]);
 50
 51  const removePassengerCallback = useCallback(removePassenger, [event]);
 52
 53  const selectTravel = useCallback(
 54    async travel => {
 55      try {
 56        await updatePassenger(addingPassenger.id, {
 57          event: null,
 58          travel: travel.id,
 59        });
 60        setAddingPassenger(null);
 61        slideToTravel(travel.id);
 62        addToast(
 63          t('passenger.success.added_to_car', {
 64            name: addingPassenger.name,
 65          })
 66        );
 67      } catch (error) {
 68        console.error(error);
 69        addToast(t('passenger.errors.cant_select_travel'));
 70      }
 71    },
 72    [event, addingPassenger] // eslint-disable-line
 73  );
 74
 75  const onPress = useCallback(
 76    (passengerId: string) => {
 77      const selectedPassenger = event.waitingPassengers.find(
 78        item => item.id === passengerId
 79      );
 80      if (isEditing) setRemovingPassenger(selectedPassenger);
 81      else setAddingPassenger(selectedPassenger);
 82    },
 83    [isEditing, event]
 84  );
 85
 86  const onRemove = async () => {
 87    try {
 88      await removePassengerCallback(removingPassenger.id);
 89    } catch (error) {
 90      console.error(error);
 91      addToast(t('passenger.errors.cant_remove_passenger'));
 92    }
 93  };
 94
 95  const ListButton = isEditing
 96    ? ({onClick}: {onClick: () => void}) => (
 97        <ClearButton icon="close" onClick={onClick} tabIndex={-1} />
 98      )
 99    : ({onClick, disabled}: {onClick: () => void; disabled: boolean}) => (
100        <AssignButton onClick={onClick} tabIndex={-1} disabled={disabled} />
101      );
102
103  return (
104    <>
105      <Paper className={classes.root}>
106        <div className={clsx(classes.header, 'tour_waiting_list')}>
107          <IconButton
108            size="small"
109            color="primary"
110            className={classes.editBtn}
111            disabled={!event.waitingPassengers?.length}
112            onClick={toggleEditing}
113          >
114            {isEditing ? <Icon>check</Icon> : <Icon>edit</Icon>}
115          </IconButton>
116          <Typography variant="h5">{t('passenger.title')}</Typography>
117          <Typography variant="overline">
118            {t('passenger.availability.seats', {count: availability})}
119          </Typography>
120        </div>
121        <Divider />
122        <AddPassengerButtons
123          getOnClickFunction={getToggleNewPassengerDialogFunction}
124          canAddSelf={canAddSelf}
125          variant="waitingList"
126        />
127        <Divider />
128        <PassengersList
129          passengers={event.waitingPassengers}
130          onPress={onPress}
131          Button={ListButton}
132        />
133      </Paper>
134      <RemoveDialog
135        text={
136          <Trans
137            i18nKey="passenger.actions.remove_alert"
138            values={{
139              name: removingPassenger?.name,
140            }}
141            components={{italic: <i />, bold: <strong />}}
142          />
143        }
144        open={!!removingPassenger}
145        onClose={() => setRemovingPassenger(null)}
146        onRemove={onRemove}
147      />
148      <TravelDialog
149        eventName={event.name}
150        travels={travels}
151        passenger={addingPassenger}
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;