all repos — caroster @ 64425eee42c3bbb4a10591bf227f141cc690b8c0

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