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 label={t('car.creation.date')}
147 fullWidth
148 helperText=" "
149 value={date}
150 onChange={setDate}
151 format="DD/MM/YYYY"
152 cancelLabel={t('generic.cancel')}
153 autoFocus
154 id="NewCarDate"
155 />
156 <TimePicker
157 label={t('car.creation.time')}
158 fullWidth
159 helperText=" "
160 value={time}
161 onChange={setTime}
162 cancelLabel={t('generic.cancel')}
163 ampm={false}
164 minutesStep={5}
165 id="NewCarTime"
166 />
167 <TextField
168 label={t('car.creation.name')}
169 fullWidth
170 helperText=" "
171 value={name}
172 onChange={e => setName(e.target.value)}
173 name="name"
174 id="EditCarName"
175 />
176 <TextField
177 label={t('car.creation.phone')}
178 fullWidth
179 helperText=" "
180 value={phone}
181 onChange={e => setPhone(e.target.value)}
182 name="phone"
183 id="EditCarPhone"
184 />
185 <TextField
186 label={t('car.creation.meeting')}
187 fullWidth
188 multiline
189 rowsMax={4}
190 inputProps={{maxLength: 250}}
191 helperText={`${meeting.length}/250`}
192 value={meeting}
193 onChange={e => setMeeting(e.target.value)}
194 name="meeting"
195 id="EditCarMeeting"
196 />
197 <TextField
198 label={t('car.creation.notes')}
199 fullWidth
200 multiline
201 rowsMax={4}
202 inputProps={{maxLength: 250}}
203 helperText={`${details.length}/250`}
204 value={details}
205 onChange={e => setDetails(e.target.value)}
206 name="details"
207 id="EditCarDetails"
208 />
209 <div className={classes.slider}>
210 <Typography variant="caption">{t('car.creation.seats')}</Typography>
211 <Slider
212 value={seats}
213 onChange={(e, value) => setSeats(value)}
214 step={1}
215 marks={[1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
216 value,
217 label: value,
218 }))}
219 min={1}
220 max={8}
221 valueLabelDisplay="auto"
222 id="EditCarSeats"
223 />
224 </div>
225 </form>
226 <div className={classes.actions}>
227 <Button
228 variant="outlined"
229 color="primary"
230 onClick={onSave}
231 id="CarSave"
232 >
233 {t('generic.save')}
234 </Button>
235 <Button
236 variant="outlined"
237 color="primary"
238 onClick={toggleRemoving}
239 id="CarRemove"
240 >
241 {t('generic.remove')}
242 </Button>
243 </div>
244 <RemoveDialog
245 text={t('car.actions.remove_alert')}
246 open={removing}
247 onClose={toggleRemoving}
248 onRemove={onRemove}
249 />
250 </div>
251 );
252};
253
254const formatPassengers = (passengers = [], seats: number = 1000) => {
255 if (!passengers) return [];
256
257 return passengers
258 .slice(0, seats)
259 .map(({__typename, ...passenger}) => passenger);
260};
261
262const useStyles = makeStyles(theme => ({
263 header: {
264 padding: theme.spacing(2),
265 },
266 edit: {
267 position: 'absolute',
268 top: 0,
269 right: 0,
270 margin: theme.spacing(1),
271 zIndex: theme.zIndex.speedDial,
272 },
273 section: {
274 marginTop: theme.spacing(2),
275 },
276 slider: {
277 marginTop: theme.spacing(2),
278 },
279 actions: {
280 display: 'flex',
281 flexDirection: 'column',
282 justifyContent: 'center',
283 margin: theme.spacing(2, 0),
284 '& > *:first-child': {
285 marginBottom: theme.spacing(2),
286 },
287 },
288}));
289
290export default HeaderEditing;