all repos — caroster @ b993e0c54ea550d28fbf0c835758183876c25221

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

app/src/containers/WaitingList/index.js (view raw)

  1import React, {
  2  useReducer,
  3  useState,
  4  useEffect,
  5  useMemo,
  6  useCallback,
  7} from 'react';
  8import Typography from '@material-ui/core/Typography';
  9import IconButton from '@material-ui/core/IconButton';
 10import Icon from '@material-ui/core/Icon';
 11import Paper from '@material-ui/core/Paper';
 12import Divider from '@material-ui/core/Divider';
 13import {makeStyles} from '@material-ui/core/styles';
 14import {Trans, useTranslation} from 'react-i18next';
 15import {useStrapi} from 'strapi-react-context';
 16import {useEvent} from '../../contexts/Event';
 17import {useToast} from '../../contexts/Toast';
 18import PassengersList from '../PassengersList';
 19import RemoveDialog from '../RemoveDialog';
 20import CarDialog from './CarDialog';
 21
 22const sortCars = (a, b) => {
 23  const dateA = new Date(a.departure).getTime();
 24  const dateB = new Date(b.departure).getTime();
 25  if (dateA === dateB) return new Date(a.createdAt) - new Date(b.createdAt);
 26  else return dateA - dateB;
 27};
 28
 29const WaitingList = ({car}) => {
 30  const classes = useStyles();
 31  const {t} = useTranslation();
 32  const {event} = useEvent();
 33  const {addToast} = useToast();
 34  const strapi = useStrapi();
 35  const [passengers, setPassengers] = useState(event.waiting_list);
 36  const [isEditing, toggleEditing] = useReducer(i => !i, false);
 37  const [removing, setRemoving] = useState(null);
 38  const [adding, setAdding] = useState(null);
 39
 40  const cars = useMemo(
 41    () =>
 42      strapi.stores.cars
 43        ?.filter(car => car?.event?.id === event?.id)
 44        .sort(sortCars),
 45    [strapi.stores.cars, event]
 46  );
 47
 48  const availability = useMemo(() => {
 49    if (!cars) return;
 50    return cars.reduce((count, {seats, passengers = []}) => {
 51      return count + seats - passengers.length;
 52    }, 0);
 53  }, [cars]);
 54
 55  useEffect(() => {
 56    setPassengers(event.waiting_list);
 57  }, [event.waiting_list]);
 58
 59  const saveWaitingList = useCallback(
 60    async (waitingList, i18nError) => {
 61      try {
 62        await strapi.services.events.update(event.id, {
 63          waiting_list: waitingList,
 64        });
 65      } catch (error) {
 66        console.error(error);
 67        addToast(t(i18nError));
 68      }
 69    },
 70    [event]
 71  );
 72
 73  const addPassenger = useCallback(
 74    async passenger => {
 75      return saveWaitingList(
 76        [...(event.waiting_list || []), passenger],
 77        'passenger.errors.cant_add_passenger'
 78      );
 79    },
 80    [saveWaitingList]
 81  );
 82
 83  const removePassenger = useCallback(
 84    index => {
 85      const updatedPassagers = passengers.filter((_, i) => i !== index);
 86      setPassengers(updatedPassagers);
 87      return saveWaitingList(
 88        updatedPassagers,
 89        'passenger.errors.cant_remove_passenger'
 90      );
 91    },
 92    [passengers, saveWaitingList]
 93  );
 94
 95  const savePassengers = useCallback(
 96    async () =>
 97      saveWaitingList(passengers, 'passenger.errors.cant_save_passengers'),
 98    [passengers, saveWaitingList]
 99  );
100
101  const selectCar = async car => {
102    try {
103      await strapi.services.cars.update(car.id, {
104        passengers: [...(car.passengers || []), passengers[adding]],
105      });
106      await strapi.services.events.update(event.id, {
107        waiting_list: event.waiting_list.filter((_, i) => i !== adding),
108      });
109    } catch (error) {
110      console.error(error);
111      addToast(t('passenger.errors.cant_select_car'));
112    }
113    setAdding(null);
114  };
115
116  const onEdit = () => {
117    if (isEditing) savePassengers();
118    toggleEditing();
119  };
120
121  return (
122    <>
123      <Paper className={classes.root}>
124        <div className={classes.header}>
125          <IconButton
126            size="small"
127            color="primary"
128            className={classes.editBtn}
129            onClick={onEdit}
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        {isEditing ? (
140          <PassengersList
141            hideEmpty
142            places={Number.MAX_SAFE_INTEGER}
143            passengers={passengers}
144            addPassenger={addPassenger}
145            onPress={setRemoving}
146            icon={'close'}
147            disabled={false}
148          />
149        ) : (
150          <PassengersList
151            hideEmpty
152            places={Number.MAX_SAFE_INTEGER}
153            passengers={passengers}
154            addPassenger={addPassenger}
155            onPress={setAdding}
156            icon={'chevron_right'}
157            disabled={availability <= 0}
158          />
159        )}
160      </Paper>
161      <RemoveDialog
162        text={
163          <Trans
164            i18nKey="passenger.actions.remove_alert"
165            values={{name: passengers ? passengers[removing] : null}}
166            components={{italic: <i />, bold: <strong />}}
167          />
168        }
169        open={removing !== null}
170        onClose={() => setRemoving(null)}
171        onRemove={async () => {
172          removePassenger(removing);
173          savePassengers();
174        }}
175      />
176      <CarDialog
177        cars={cars}
178        open={adding !== null}
179        onClose={() => setAdding(null)}
180        onSelect={selectCar}
181      />
182    </>
183  );
184};
185
186const useStyles = makeStyles(theme => ({
187  root: {
188    position: 'relative',
189  },
190  header: {
191    padding: theme.spacing(2),
192  },
193  editBtn: {
194    position: 'absolute',
195    top: 0,
196    right: 0,
197    margin: theme.spacing(1),
198    zIndex: theme.zIndex.speedDial,
199  },
200}));
201
202export default WaitingList;