all repos — caroster @ 8396b80d084b9f803ad3232ad760aca778f3301f

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