all repos — caroster @ e22ee9c064d006eb9bd3af3cc9709ce4d28df633

[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';
 22import {PassengerEntity} from '../../generated/graphql';
 23
 24interface Props {
 25  getToggleNewPassengerDialogFunction: (addSelf: boolean) => () => void;
 26  canAddSelf: boolean;
 27}
 28
 29const WaitingList = ({
 30  getToggleNewPassengerDialogFunction,
 31  canAddSelf,
 32}: Props) => {
 33  const classes = useStyles();
 34  const {t} = useTranslation();
 35  const clearToast = useToastStore(s => s.clearToast);
 36  const event = useEventStore(s => s.event);
 37  const addToast = useToastStore(s => s.addToast);
 38  const [isEditing, toggleEditing] = useReducer(i => !i, false);
 39  const [removingPassenger, setRemovingPassenger] = useState(null);
 40  const [addingPassenger, setAddingPassenger] = useState<PassengerEntity>(null);
 41  const travels =
 42    event?.travels?.data?.length > 0
 43      ? event?.travels?.data.slice().sort(sortTravels)
 44      : [];
 45  const {updatePassenger, removePassenger} = usePassengersActions();
 46
 47  const availability = useMemo(() => {
 48    if (!travels) return;
 49    return travels.reduce((count, {attributes: {seats, passengers}}) => {
 50      if (!seats) return 0;
 51      else if (!passengers) return count + seats;
 52      return count + seats - passengers?.data?.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.attributes.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?.data.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      addToast(t('passenger.deleted'));
105    } catch (error) {
106      console.error(error);
107      addToast(t('passenger.errors.cant_remove_passenger'));
108    }
109  };
110
111  const ListButton = isEditing
112    ? ({onClick}: {onClick: () => void}) => (
113        <ClearButton icon="close" onClick={onClick} tabIndex={-1} />
114      )
115    : ({onClick, disabled}: {onClick: () => void; disabled: boolean}) => (
116        <AssignButton onClick={onClick} tabIndex={-1} disabled={disabled} />
117      );
118
119  return (
120    <Box className={classes.root}>
121      <Container maxWidth="sm" className={classes.card}>
122        <Paper>
123          <div className={classes.header}>
124            <IconButton
125              size="small"
126              color="primary"
127              className={classes.editBtn}
128              disabled={!event?.waitingPassengers?.data?.length}
129              onClick={toggleEditing}
130            >
131              {isEditing ? <Icon>check</Icon> : <Icon>edit</Icon>}
132            </IconButton>
133            <Typography variant="h5">{t('passenger.title')}</Typography>
134            <Typography variant="overline">
135              {t('passenger.availability.seats', {count: availability})}
136            </Typography>
137          </div>
138          <Divider />
139          <AddPassengerButtons
140            getOnClickFunction={getToggleNewPassengerDialogFunction}
141            canAddSelf={canAddSelf}
142            variant="waitingList"
143          />
144          <Divider />
145          <PassengersList
146            passengers={event?.waitingPassengers?.data}
147            onPress={onPress}
148            Button={ListButton}
149          />
150        </Paper>
151      </Container>
152      <RemoveDialog
153        text={
154          <Trans
155            i18nKey="passenger.actions.remove_alert"
156            values={{
157              name: removingPassenger?.name,
158            }}
159            components={{italic: <i />, bold: <strong />}}
160          />
161        }
162        open={!!removingPassenger}
163        onClose={() => setRemovingPassenger(null)}
164        onRemove={onRemove}
165      />
166      <TravelDialog
167        eventName={event?.name}
168        travels={travels}
169        passenger={addingPassenger}
170        open={!!addingPassenger}
171        onClose={() => setAddingPassenger(null)}
172        onSelect={selectTravel}
173      />
174    </Box>
175  );
176};
177
178const sortTravels = (a, b) => {
179  const dateA = new Date(a.departure).getTime();
180  const dateB = new Date(b.departure).getTime();
181  if (dateA === dateB)
182    return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
183  else return dateA - dateB;
184};
185
186const useStyles = makeStyles(theme => ({
187  root: {
188    position: 'relative',
189    paddingLeft: '80px',
190
191    [theme.breakpoints.down('sm')]: {
192      paddingLeft: 0,
193    },
194  },
195  card: {
196    marginTop: theme.spacing(6),
197  },
198  header: {
199    position: 'relative',
200    padding: theme.spacing(2),
201  },
202  editBtn: {
203    position: 'absolute',
204    top: 0,
205    right: 0,
206    margin: theme.spacing(1),
207    zIndex: theme.zIndex.speedDial,
208  },
209}));
210
211export default WaitingList;