all repos — caroster @ d0d47704e427face6c4cea5a2e1326c3679f6f5d

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

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

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