all repos — caroster @ 1410b54dc70ad59a92a3fe31c76237c46a37c1d8

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

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

  1import {useReducer, useState, useMemo, useCallback} from 'react';
  2import router from 'next/dist/client/router';
  3import useMediaQuery from '@mui/material/useMediaQuery';
  4import Container from '@mui/material/Container';
  5import TuneIcon from '@mui/icons-material/Tune';
  6import Box from '@mui/material/Box';
  7import Typography from '@mui/material/Typography';
  8import IconButton from '@mui/material/IconButton';
  9import Icon from '@mui/material/Icon';
 10import Paper from '@mui/material/Paper';
 11import Divider from '@mui/material/Divider';
 12import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
 13import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';
 14import {useTheme} from '@mui/material/styles';
 15import {Trans, useTranslation} from 'react-i18next';
 16import useToastStore from '../../stores/useToastStore';
 17import useEventStore from '../../stores/useEventStore';
 18import usePassengersActions from '../../hooks/usePassengersActions';
 19import RemoveDialog from '../RemoveDialog';
 20import AddPassengerButtons from '../AddPassengerButtons';
 21import AssignButton from './AssignButton';
 22import PassengersList from '../PassengersList';
 23
 24interface Props {
 25  canAddSelf: boolean;
 26  registered: boolean;
 27  onAddSelf: () => void;
 28  onAddOther: () => void;
 29}
 30
 31const WaitingList = (props: Props) => {
 32  const {canAddSelf, registered} = props;
 33  const {t} = useTranslation();
 34  const event = useEventStore(s => s.event);
 35  const theme = useTheme();
 36  const mobile = useMediaQuery(theme.breakpoints.down('md'));
 37  const addToast = useToastStore(s => s.addToast);
 38  const [isEditing, toggleEditing] = useReducer(i => !i, false);
 39  const [removingPassenger, setRemovingPassenger] = useState(null);
 40  const travels = useMemo(
 41    () =>
 42      event?.travels?.data?.length > 0
 43        ? event?.travels?.data.slice().sort(sortTravels)
 44        : [],
 45    [event?.travels?.data]
 46  );
 47  const {removePassenger} = usePassengersActions();
 48
 49  const availability = useMemo(() => {
 50    if (!travels) return;
 51    return travels.reduce((count, {attributes: {seats, passengers}}) => {
 52      if (!seats) return 0;
 53      else if (!passengers) return count + seats;
 54      return count + seats - passengers?.data?.length;
 55    }, 0);
 56  }, [travels]);
 57
 58  const removePassengerCallback = useCallback(removePassenger, [
 59    event,
 60    removePassenger,
 61  ]);
 62
 63  const onClickPassenger = useCallback(
 64    (passengerId: string) => {
 65      const selectedPassenger = event?.waitingPassengers?.data.find(
 66        item => item.id === passengerId
 67      );
 68      if (isEditing) setRemovingPassenger(selectedPassenger);
 69      else router.push(`/e/${event.uuid}/assign/${selectedPassenger.id}`);
 70    },
 71    [isEditing, event]
 72  );
 73
 74  const onRemove = async () => {
 75    try {
 76      await removePassengerCallback(removingPassenger.id);
 77      addToast(t('passenger.deleted'));
 78    } catch (error) {
 79      console.error(error);
 80      addToast(t('passenger.errors.cant_remove_passenger'));
 81    }
 82  };
 83
 84  return (
 85    <Container maxWidth="sm" sx={{mt: 11, mx: 0, px: mobile ? 2 : 4}}>
 86      <Paper sx={{width: '480px', maxWidth: '100%', position: 'relative'}}>
 87        <Box p={2}>
 88          <IconButton
 89            size="small"
 90            color="primary"
 91            sx={{
 92              position: 'absolute',
 93              top: 0,
 94              right: 0,
 95              margin: 1,
 96            }}
 97            disabled={!event?.waitingPassengers?.data?.length}
 98            onClick={toggleEditing}
 99          >
100            {isEditing ? <Icon>check</Icon> : <TuneIcon />}
101          </IconButton>
102          <Typography variant="h5">{t('passenger.title')}</Typography>
103          <Typography variant="overline">
104            {t('passenger.availability.seats', {count: availability})}
105          </Typography>
106        </Box>
107        <Divider />
108        <AddPassengerButtons
109          onAddOther={props.onAddOther}
110          onAddSelf={props.onAddSelf}
111          canAddSelf={canAddSelf}
112          registered={registered}
113          variant="waitingList"
114        />
115        <Divider />
116        {event?.waitingPassengers?.data?.length > 0 && (
117          <PassengersList
118            passengers={event.waitingPassengers.data}
119            onClickPassenger={onClickPassenger}
120            Actions={({passenger}) =>
121              isEditing ? (
122                <ListItemSecondaryAction>
123                  <IconButton size="small" color="primary">
124                    <CancelOutlinedIcon />
125                  </IconButton>
126                </ListItemSecondaryAction>
127              ) : (
128                <AssignButton
129                  tabIndex={-1}
130                  onClick={() => onClickPassenger(passenger.id)}
131                />
132              )
133            }
134          />
135        )}
136      </Paper>
137      <RemoveDialog
138        text={
139          <Trans
140            i18nKey="passenger.actions.remove_alert"
141            values={{
142              name: removingPassenger?.name,
143            }}
144            components={{italic: <i />, bold: <strong />}}
145          />
146        }
147        open={!!removingPassenger}
148        onClose={() => setRemovingPassenger(null)}
149        onRemove={onRemove}
150      />
151    </Container>
152  );
153};
154
155const sortTravels = (a, b) => {
156  const dateA = new Date(a.departure).getTime();
157  const dateB = new Date(b.departure).getTime();
158  if (dateA === dateB)
159    return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
160  else return dateA - dateB;
161};
162
163export default WaitingList;