all repos — caroster @ be8f93ea0966f0bc0587c2c127ee1ba15594a049

[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          travel: travel.id,
 63        });
 64        setAddingPassenger(null);
 65        addToast(
 66          t('passenger.success.added_to_car', {
 67            name: addingPassenger.attributes.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?.data.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      addToast(t('passenger.deleted'));
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?.data?.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?.data}
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;