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