all repos — caroster @ 2a6cef8fdcb99be287c564a17ba00403a151d327

[Octree] Group carpool to your event https://caroster.io

frontend/containers/WaitingList/index.tsx (view raw)

  1import {useReducer, useState, useMemo, useCallback} from 'react';
  2import Typography from '@material-ui/core/Typography';
  3import IconButton from '@material-ui/core/IconButton';
  4import Icon from '@material-ui/core/Icon';
  5import Paper from '@material-ui/core/Paper';
  6import Divider from '@material-ui/core/Divider';
  7import {makeStyles} from '@material-ui/core/styles';
  8import {Trans, useTranslation} from 'react-i18next';
  9import useToastStore from '../../stores/useToastStore';
 10import useEventStore from '../../stores/useEventStore';
 11import usePassengersActions from '../../hooks/usePassengersActions';
 12import PassengersList from '../PassengersList';
 13import RemoveDialog from '../RemoveDialog';
 14import AddPassengerButtons from '../AddPassengerButtons';
 15import ClearButton from '../ClearButton';
 16import AssignButton from './AssignButton';
 17import TravelDialog from './TravelDialog';
 18import Button from '@material-ui/core/Button';
 19import router from 'next/dist/client/router';
 20import Box from '@material-ui/core/Box';
 21import Container from '@material-ui/core/Container';
 22
 23interface Props {
 24  getToggleNewPassengerDialogFunction: (addSelf: boolean) => () => void;
 25  canAddSelf: boolean;
 26}
 27
 28const WaitingList = ({
 29  getToggleNewPassengerDialogFunction,
 30  canAddSelf,
 31}: Props) => {
 32  const classes = useStyles();
 33  const {t} = useTranslation();
 34  const clearToast = useToastStore(s => s.clearToast);
 35  const event = useEventStore(s => s.event);
 36  const addToast = useToastStore(s => s.addToast);
 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 {updatePassenger, removePassenger} = usePassengersActions();
 43
 44  const availability = useMemo(() => {
 45    if (!travels) return;
 46    return travels.reduce((count, {vehicle, passengers = []}) => {
 47      if (!vehicle) return 0;
 48      else if (!passengers) return count + vehicle.seats;
 49      return count + vehicle.seats - passengers.length;
 50    }, 0);
 51  }, [travels]);
 52
 53  const removePassengerCallback = useCallback(removePassenger, [event]);
 54
 55  const selectTravel = useCallback(
 56    async travel => {
 57      try {
 58        await updatePassenger(addingPassenger.id, {
 59          event: null,
 60          travel: travel.id,
 61        });
 62        setAddingPassenger(null);
 63        addToast(
 64          t('passenger.success.added_to_car', {
 65            name: addingPassenger.name,
 66          }),
 67          <Button
 68            size="small"
 69            color="primary"
 70            variant="contained"
 71            onClick={() => {
 72              router.push(`/e/${event.uuid}`);
 73              clearToast();
 74            }}
 75          >
 76            {t('passenger.success.goToTravels')}
 77          </Button>
 78        );
 79      } catch (error) {
 80        console.error(error);
 81        addToast(t('passenger.errors.cant_select_travel'));
 82      }
 83    },
 84    [event, addingPassenger] // eslint-disable-line
 85  );
 86
 87  const onPress = useCallback(
 88    (passengerId: string) => {
 89      const selectedPassenger = event?.waitingPassengers.find(
 90        item => item.id === passengerId
 91      );
 92      if (isEditing) setRemovingPassenger(selectedPassenger);
 93      else setAddingPassenger(selectedPassenger);
 94    },
 95    [isEditing, event]
 96  );
 97
 98  const onRemove = async () => {
 99    try {
100      await removePassengerCallback(removingPassenger.id);
101    } catch (error) {
102      console.error(error);
103      addToast(t('passenger.errors.cant_remove_passenger'));
104    }
105  };
106
107  const ListButton = isEditing
108    ? ({onClick}: {onClick: () => void}) => (
109        <ClearButton icon="close" onClick={onClick} tabIndex={-1} />
110      )
111    : ({onClick, disabled}: {onClick: () => void; disabled: boolean}) => (
112        <AssignButton onClick={onClick} tabIndex={-1} disabled={disabled} />
113      );
114
115  return (
116    <Box className={classes.root}>
117      <Container maxWidth="sm" className={classes.card}>
118        <Paper>
119          <div className={classes.header}>
120            <IconButton
121              size="small"
122              color="primary"
123              className={classes.editBtn}
124              disabled={!event?.waitingPassengers?.length}
125              onClick={toggleEditing}
126            >
127              {isEditing ? <Icon>check</Icon> : <Icon>edit</Icon>}
128            </IconButton>
129            <Typography variant="h5">{t('passenger.title')}</Typography>
130            <Typography variant="overline">
131              {t('passenger.availability.seats', {count: availability})}
132            </Typography>
133          </div>
134          <Divider />
135          <AddPassengerButtons
136            getOnClickFunction={getToggleNewPassengerDialogFunction}
137            canAddSelf={canAddSelf}
138            variant="waitingList"
139          />
140          <Divider />
141          <PassengersList
142            passengers={event?.waitingPassengers}
143            onPress={onPress}
144            Button={ListButton}
145          />
146        </Paper>
147      </Container>
148      <RemoveDialog
149        text={
150          <Trans
151            i18nKey="passenger.actions.remove_alert"
152            values={{
153              name: removingPassenger?.name,
154            }}
155            components={{italic: <i />, bold: <strong />}}
156          />
157        }
158        open={!!removingPassenger}
159        onClose={() => setRemovingPassenger(null)}
160        onRemove={onRemove}
161      />
162      <TravelDialog
163        eventName={event?.name}
164        travels={travels}
165        passenger={addingPassenger}
166        open={!!addingPassenger}
167        onClose={() => setAddingPassenger(null)}
168        onSelect={selectTravel}
169      />
170    </Box>
171  );
172};
173
174const sortTravels = (a, b) => {
175  const dateA = new Date(a.departure).getTime();
176  const dateB = new Date(b.departure).getTime();
177  if (dateA === dateB)
178    return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
179  else return dateA - dateB;
180};
181
182const useStyles = makeStyles(theme => ({
183  root: {
184    position: 'relative',
185    paddingLeft: '80px',
186
187    [theme.breakpoints.down('sm')]: {
188      paddingLeft: 0,
189    },
190  },
191  card: {
192    marginTop: theme.spacing(6),
193  },
194  header: {
195    position: 'relative',
196    padding: theme.spacing(2),
197  },
198  editBtn: {
199    position: 'absolute',
200    top: 0,
201    right: 0,
202    margin: theme.spacing(1),
203    zIndex: theme.zIndex.speedDial,
204  },
205}));
206
207export default WaitingList;