all repos — caroster @ e8ac86df85718ab0870d106bbec4bf9eda68347c

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

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

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