frontend/containers/NewTravelDialog/index.tsx (view raw)
1import {useState, forwardRef, useMemo, useEffect} from 'react';
2import {makeStyles} from '@material-ui/core/styles';
3import Dialog from '@material-ui/core/Dialog';
4import DialogActions from '@material-ui/core/DialogActions';
5import DialogContent from '@material-ui/core/DialogContent';
6import DialogTitle from '@material-ui/core/DialogTitle';
7import Button from '@material-ui/core/Button';
8import Slide from '@material-ui/core/Slide';
9import TextField from '@material-ui/core/TextField';
10import Slider from '@material-ui/core/Slider';
11import Typography from '@material-ui/core/Typography';
12import {DatePicker, TimePicker} from '@material-ui/pickers';
13import moment, {Moment} from 'moment';
14import {useTranslation} from 'react-i18next';
15import useEventStore from '../../stores/useEventStore';
16import useActions from './useActions';
17import {Vehicle} from '../../generated/graphql';
18import {Box, Divider} from '@material-ui/core';
19import FAQLink from './FAQLink';
20
21interface Props {
22 context: {
23 vehicle: Vehicle;
24 opened: boolean;
25 };
26 toggle: ({opened: boolean}) => void;
27}
28
29const NewTravelDialog = ({context, toggle}: Props) => {
30 const {t} = useTranslation();
31 const classes = useStyles();
32 const event = useEventStore(s => s.event);
33 const {createTravel} = useActions({event});
34
35 const dateMoment = useMemo(() => {
36 if (!event?.date) return moment();
37 else return moment(event.date);
38 }, [event?.date]);
39
40 // States
41 const [name, setName] = useState('');
42 const [seats, setSeats] = useState(4);
43 const [meeting, setMeeting] = useState('');
44 const [date, setDate] = useState(dateMoment);
45 const [time, setTime] = useState(dateMoment);
46 const [phone, setPhone] = useState('');
47 const [details, setDetails] = useState('');
48 const canCreate = !!name && !!seats;
49
50 const clearState = () => {
51 setName('');
52 setSeats(4);
53 setMeeting('');
54 setDate(moment());
55 setPhone('');
56 setDetails('');
57 };
58
59 useEffect(() => {
60 if (context.vehicle) {
61 setName(context.vehicle.name);
62 setSeats(context.vehicle.seats);
63 setPhone(context.vehicle.phone_number);
64 }
65 }, [context.vehicle]);
66
67 const onCreate = async e => {
68 if (e.preventDefault) e.preventDefault();
69
70 const travel = {
71 meeting,
72 details,
73 seats,
74 vehicleName: name,
75 phone_number: phone,
76 departure: formatDate(date, time),
77 event: event.id,
78 };
79 const createVehicle = !context.vehicle;
80
81 await createTravel(travel, createVehicle);
82 toggle({opened: false});
83
84 clearState();
85 };
86
87 return (
88 <Dialog
89 fullWidth
90 maxWidth="xs"
91 open={context?.opened}
92 onClose={() => {
93 toggle({opened: false});
94 clearState();
95 }}
96 TransitionComponent={Transition}
97 >
98 <form onSubmit={onCreate}>
99 <DialogTitle className={classes.title}>
100 {t('travel.creation.title')}
101 </DialogTitle>
102 <DialogContent className={classes.content}>
103 <Typography className={classes.sectionTitle}>
104 {t('travel.creation.car.title')}
105 </Typography>
106 <TextField
107 variant="outlined"
108 size="small"
109 className={classes.field}
110 label={t('travel.creation.name')}
111 fullWidth
112 helperText=" "
113 value={name}
114 onChange={e => setName(e.target.value)}
115 name="name"
116 id="NewTravelName"
117 />
118 <TextField
119 variant="outlined"
120 size="small"
121 className={classes.field}
122 label={t('travel.creation.phone')}
123 fullWidth
124 inputProps={{type: 'tel'}}
125 helperText=" "
126 value={phone}
127 onChange={e => setPhone(e.target.value)}
128 name="phone"
129 FormHelperTextProps={{
130 component: () => (
131 <FAQLink
132 className={classes.faqHelper}
133 link={t('travel.creation.phoneHelper.faq')}
134 text={t('travel.creation.phoneHelper.why')}
135 />
136 ),
137 }}
138 id="NewTravelPhone"
139 />
140 <div className={classes.slider}>
141 <Typography variant="caption">
142 {t('travel.creation.seats')}
143 </Typography>
144 <Slider
145 value={seats}
146 onChange={(e, value) => setSeats(value)}
147 step={1}
148 marks={MARKS}
149 min={1}
150 max={MARKS.length}
151 valueLabelDisplay="auto"
152 id="NewTravelSeats"
153 />
154 </div>
155 <Divider className={classes.divider} />
156 <Typography className={classes.sectionTitle}>
157 {t('travel.creation.travel.title')}
158 </Typography>
159 <Box className={classes.halfWidthWrapper}>
160 <DatePicker
161 className={classes.halfWidthField}
162 inputVariant="outlined"
163 size="small"
164 label={t('travel.creation.date')}
165 helperText=" "
166 value={date}
167 onChange={setDate}
168 format="DD/MM/YYYY"
169 cancelLabel={t('generic.cancel')}
170 autoFocus
171 id="NewTravelDateTime"
172 />
173 <TimePicker
174 className={classes.halfWidthField}
175 inputVariant="outlined"
176 size="small"
177 label={t('travel.creation.time')}
178 helperText=" "
179 value={time}
180 onChange={setTime}
181 cancelLabel={t('generic.cancel')}
182 ampm={false}
183 minutesStep={5}
184 id="NewTravelTime"
185 />
186 </Box>
187 <TextField
188 variant="outlined"
189 size="small"
190 className={classes.field}
191 label={t('travel.creation.meeting')}
192 fullWidth
193 multiline
194 rowsMax={4}
195 inputProps={{maxLength: 250}}
196 helperText={`${meeting.length}/250`}
197 value={meeting}
198 onChange={e => setMeeting(e.target.value)}
199 name="meeting"
200 id="NewTravelMeeting"
201 />
202 <TextField
203 variant="outlined"
204 size="small"
205 className={classes.field}
206 label={t('travel.creation.notes')}
207 fullWidth
208 multiline
209 rowsMax={4}
210 inputProps={{maxLength: 250}}
211 helperText={`${details.length}/250`}
212 value={details}
213 onChange={e => setDetails(e.target.value)}
214 name="details"
215 id="NewTravelDetails"
216 />
217 </DialogContent>
218 <DialogActions className={classes.actions}>
219 <Button
220 color="primary"
221 id="NewTravelCancel"
222 onClick={() => toggle({opened: false})}
223 tabIndex={-1}
224 >
225 {t('generic.cancel')}
226 </Button>
227 <Button
228 color="primary"
229 variant="contained"
230 type="submit"
231 disabled={!canCreate}
232 aria-disabled={!canCreate}
233 id="NewTravelSubmit"
234 >
235 {t('travel.creation.submit')}
236 </Button>
237 </DialogActions>
238 </form>
239 </Dialog>
240 );
241};
242
243const Transition = forwardRef(function Transition(props, ref) {
244 return <Slide direction="up" ref={ref} {...props} />;
245});
246
247const formatDate = (date: Moment, time: Moment) => {
248 return moment(
249 `${moment(date).format('YYYY-MM-DD')} ${moment(time).format('HH:mm')}`,
250 'YYYY-MM-DD HH:mm'
251 ).toISOString();
252};
253
254const MARKS = [1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
255 value,
256 label: value,
257}));
258
259const addSpacing = (theme, ratio) => ({
260 margin: `0 ${theme.spacing(3 * ratio)}px`,
261 width: `calc(100% - ${theme.spacing(6 * ratio)}px)`,
262});
263
264const useStyles = makeStyles(theme => ({
265 title: {
266 paddingBottom: 0,
267 },
268 sectionTitle: {
269 ...addSpacing(theme, 1),
270 paddingBottom: theme.spacing(1.5),
271 },
272 content: {
273 padding: `${theme.spacing(2)}px 0`,
274 },
275 faqHelper: {
276 fontSize: '12px',
277 },
278 field: {
279 ...addSpacing(theme, 1),
280 paddingBottom: theme.spacing(1),
281 },
282 halfWidthWrapper: {
283 ...addSpacing(theme, 0.5),
284 },
285 halfWidthField: {
286 margin: `0 ${theme.spacing(1.5)}px`,
287 width: `calc(50% - ${theme.spacing(3)}px)`,
288
289 '& > .MuiFormLabel-root': {
290 textOverflow: 'ellipsis',
291 whiteSpace: 'nowrap',
292 width: '100%',
293 overflow: 'hidden',
294 },
295 },
296 slider: {
297 ...addSpacing(theme, 1),
298 },
299 divider: {
300 margin: `${theme.spacing(2)}px 0`,
301 },
302 actions: {
303 paddingTop: 0,
304 },
305}));
306
307export default NewTravelDialog;