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