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