frontend/containers/Car/HeaderEditing.tsx (view raw)
1import {useState, useReducer, useCallback, useEffect, useMemo} from 'react';
2import Typography from '@material-ui/core/Typography';
3import IconButton from '@material-ui/core/IconButton';
4import Icon from '@material-ui/core/Icon';
5import Button from '@material-ui/core/Button';
6import moment from 'moment';
7import {makeStyles} from '@material-ui/core/styles';
8import {useTranslation} from 'react-i18next';
9import TextField from '@material-ui/core/TextField';
10import Slider from '@material-ui/core/Slider';
11import RemoveDialog from '../RemoveDialog';
12import useToastStore from '../../stores/useToastStore';
13import useEventStore from '../../stores/useEventStore';
14import {
15 useUpdateEventMutation,
16 useUpdateCarMutation,
17 useDeleteCarMutation,
18} from '../../generated/graphql';
19
20const HeaderEditing = ({car, toggleEditing}) => {
21 const classes = useStyles();
22 const {t} = useTranslation();
23 const event = useEventStore(s => s.event);
24 const addToast = useToastStore(s => s.addToast);
25 const [updateEvent] = useUpdateEventMutation();
26 const [updateCar] = useUpdateCarMutation();
27 const [deleteCar] = useDeleteCarMutation({refetchQueries: ['eventByUUID']});
28 const [removing, toggleRemoving] = useReducer(i => !i, false);
29 const dateMoment = useMemo(() => {
30 if (!car?.departure) return moment();
31 else return moment(car.departure);
32 }, [car?.departure]);
33
34 // States
35 const [name, setName] = useState(car?.name ?? '');
36 const [seats, setSeats] = useState(car?.seats ?? 4);
37 const [meeting, setMeeting] = useState(car?.meeting ?? '');
38 const [date, setDate] = useState(dateMoment.format('YYYY-MM-DD'));
39 const [time, setTime] = useState(dateMoment.format('HH:mm'));
40 const [phone, setPhone] = useState(car ? car['phone_number'] : '');
41 const [details, setDetails] = useState(car?.details ?? '');
42
43 // Click on ESQ closes the form
44 const escFunction = useCallback(
45 evt => {
46 if (evt.keyCode === 27) toggleEditing();
47 },
48 [toggleEditing]
49 );
50
51 useEffect(() => {
52 document.addEventListener('keydown', escFunction, false);
53 return () => {
54 document.removeEventListener('keydown', escFunction, false);
55 };
56 }, [escFunction]);
57
58 const onSave = async evt => {
59 if (evt.preventDefault) evt.preventDefault();
60 try {
61 // If new seats count is under current passengers count, put excedent in event waiting list
62 if (!!car.passengers && car.passengers.length > seats) {
63 const lostPassengers = car.passengers.slice(seats);
64 if (lostPassengers.length > 0)
65 await updateEvent({
66 variables: {
67 uuid: event.uuid,
68 eventUpdate: {
69 waitingList: formatPassengers([
70 ...(event.waitingList || []),
71 ...lostPassengers.map(({name}) => ({name})),
72 ]),
73 },
74 },
75 refetchQueries: ['eventByUUID'],
76 });
77 }
78 const departure = moment(
79 `${date} ${time}`,
80 'YYYY-MM-DD HH:mm'
81 ).toISOString();
82 await updateCar({
83 variables: {
84 id: car.id,
85 carUpdate: {
86 name,
87 seats,
88 meeting,
89 departure,
90 phone_number: phone,
91 details,
92 passengers: formatPassengers(car.passengers, seats),
93 },
94 },
95 });
96 toggleEditing();
97 } catch (error) {
98 console.error(error);
99 addToast('car.errors.cant_update');
100 }
101 return false;
102 };
103
104 const onRemove = async () => {
105 try {
106 // Put passengers in event waiting list (if any)
107 if (Array.isArray(car?.passengers) && car.passengers.length > 0)
108 await updateEvent({
109 variables: {
110 uuid: event.uuid,
111 eventUpdate: {
112 waitingList: formatPassengers([
113 ...(event.waitingList || []),
114 ...car.passengers.map(({name}) => ({name})),
115 ]),
116 },
117 },
118 refetchQueries: ['eventByUUID'],
119 });
120 await deleteCar({
121 variables: {
122 id: car.id,
123 },
124 });
125 addToast(t('car.actions.removed'));
126 toggleEditing();
127 } catch (error) {
128 console.error(error);
129 addToast('car.errors.cant_remove');
130 }
131 };
132
133 return (
134 <div className={classes.header}>
135 <form onSubmit={onSave}>
136 <IconButton
137 size="small"
138 color="primary"
139 type="submit"
140 className={classes.edit}
141 >
142 <Icon>done</Icon>
143 </IconButton>
144 <TextField
145 label={t('car.creation.date')}
146 value={date}
147 onChange={e => setDate(e.target.value)}
148 className={classes.picker}
149 fullWidth
150 id="NewCarDate"
151 name="date"
152 type="date"
153 InputLabelProps={{
154 shrink: true,
155 }}
156 />
157 <TextField
158 label={t('car.creation.time')}
159 value={time}
160 onChange={e => setTime(e.target.value)}
161 className={classes.picker}
162 fullWidth
163 id="NewCarTime"
164 name="time"
165 type="time"
166 InputLabelProps={{
167 shrink: true,
168 }}
169 />
170 <TextField
171 label={t('car.creation.name')}
172 fullWidth
173 autoFocus
174 margin="dense"
175 value={name}
176 onChange={e => setName(e.target.value)}
177 id="EditCarName"
178 name="name"
179 />
180 <TextField
181 label={t('car.creation.phone')}
182 fullWidth
183 autoFocus
184 margin="dense"
185 value={phone}
186 onChange={e => setPhone(e.target.value)}
187 id="EditCarPhone"
188 name="phone"
189 />
190 <TextField
191 label={t('car.creation.meeting')}
192 fullWidth
193 margin="dense"
194 multiline
195 rows={2}
196 value={meeting}
197 onChange={e => setMeeting(e.target.value)}
198 id="EditCarMeeting"
199 name="meeting"
200 />
201 <TextField
202 label={t('car.creation.notes')}
203 fullWidth
204 margin="dense"
205 inputProps={{maxLength: 250}}
206 helperText={`${details.length}/250`}
207 multiline
208 rows={2}
209 value={details}
210 onChange={e => setDetails(e.target.value)}
211 id="EditCarDetails"
212 name="details"
213 />
214 <div className={classes.slider}>
215 <Typography variant="caption">{t('car.creation.seats')}</Typography>
216 <Slider
217 value={seats}
218 onChange={(e, value) => setSeats(value)}
219 step={1}
220 marks={[1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
221 value,
222 label: value,
223 }))}
224 min={1}
225 max={8}
226 valueLabelDisplay="auto"
227 id="EditCarSeats"
228 />
229 </div>
230 </form>
231 <div className={classes.actions}>
232 <Button
233 variant="outlined"
234 color="primary"
235 onClick={onSave}
236 id="CarSave"
237 >
238 {t('generic.save')}
239 </Button>
240 <Button
241 variant="outlined"
242 color="primary"
243 onClick={toggleRemoving}
244 id="CarRemove"
245 >
246 {t('generic.remove')}
247 </Button>
248 </div>
249 <RemoveDialog
250 text={t('car.actions.remove_alert')}
251 open={removing}
252 onClose={toggleRemoving}
253 onRemove={onRemove}
254 />
255 </div>
256 );
257};
258
259const formatPassengers = (passengers = [], seats: number = 1000) => {
260 if (!passengers) return [];
261
262 return passengers
263 .slice(0, seats)
264 .map(({__typename, ...passenger}) => passenger);
265};
266
267const useStyles = makeStyles(theme => ({
268 header: {
269 padding: theme.spacing(2),
270 },
271 edit: {
272 position: 'absolute',
273 top: 0,
274 right: 0,
275 margin: theme.spacing(1),
276 zIndex: theme.zIndex.speedDial,
277 },
278 section: {
279 marginTop: theme.spacing(2),
280 },
281 slider: {
282 marginTop: theme.spacing(2),
283 },
284 actions: {
285 display: 'flex',
286 flexDirection: 'column',
287 justifyContent: 'center',
288 margin: theme.spacing(2, 0),
289 '& > *:first-child': {
290 marginBottom: theme.spacing(2),
291 },
292 },
293 picker: {
294 marginBottom: theme.spacing(2),
295 },
296}));
297
298export default HeaderEditing;