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