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