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;