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