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 helperText=" "
125 value={phone}
126 onChange={e => setPhone(e.target.value)}
127 name="phone"
128 FormHelperTextProps={{
129 component: () => (
130 <FAQLink
131 link={t('travel.creation.phoneHelper.faq')}
132 text={t('travel.creation.phoneHelper.why')}
133 />
134 ),
135 }}
136 id="NewTravelPhone"
137 />
138 <div className={classes.slider}>
139 <Typography variant="caption">
140 {t('travel.creation.seats')}
141 </Typography>
142 <Slider
143 value={seats}
144 onChange={(e, value) => setSeats(value)}
145 step={1}
146 marks={MARKS}
147 min={1}
148 max={MARKS.length}
149 valueLabelDisplay="auto"
150 id="NewTravelSeats"
151 />
152 </div>
153 <Divider className={classes.divider} />
154 <Typography className={classes.sectionTitle}>
155 {t('travel.creation.travel.title')}
156 </Typography>
157 <Box className={classes.halfWidthWrapper}>
158 <DatePicker
159 className={classes.halfWidthField}
160 inputVariant="outlined"
161 size="small"
162 label={t('travel.creation.date')}
163 helperText=" "
164 value={date}
165 onChange={setDate}
166 format="DD/MM/YYYY"
167 cancelLabel={t('generic.cancel')}
168 autoFocus
169 id="NewTravelDateTime"
170 />
171 <TimePicker
172 className={classes.halfWidthField}
173 inputVariant="outlined"
174 size="small"
175 label={t('travel.creation.time')}
176 helperText=" "
177 value={time}
178 onChange={setTime}
179 cancelLabel={t('generic.cancel')}
180 ampm={false}
181 minutesStep={5}
182 id="NewTravelTime"
183 />
184 </Box>
185 <TextField
186 variant="outlined"
187 size="small"
188 className={classes.field}
189 label={t('travel.creation.meeting')}
190 fullWidth
191 multiline
192 rowsMax={4}
193 inputProps={{maxLength: 250}}
194 helperText={`${meeting.length}/250`}
195 value={meeting}
196 onChange={e => setMeeting(e.target.value)}
197 name="meeting"
198 id="NewTravelMeeting"
199 />
200 <TextField
201 variant="outlined"
202 size="small"
203 className={classes.field}
204 label={t('travel.creation.notes')}
205 fullWidth
206 multiline
207 rowsMax={4}
208 inputProps={{maxLength: 250}}
209 helperText={`${details.length}/250`}
210 value={details}
211 onChange={e => setDetails(e.target.value)}
212 name="details"
213 id="NewTravelDetails"
214 />
215 </DialogContent>
216 <DialogActions className={classes.actions}>
217 <Button
218 color="primary"
219 id="NewTravelCancel"
220 onClick={() => toggle({opened: false})}
221 tabIndex={-1}
222 >
223 {t('generic.cancel')}
224 </Button>
225 <Button
226 color="primary"
227 variant="contained"
228 type="submit"
229 disabled={!canCreate}
230 aria-disabled={!canCreate}
231 id="NewTravelSubmit"
232 >
233 {t('travel.creation.submit')}
234 </Button>
235 </DialogActions>
236 </form>
237 </Dialog>
238 );
239};
240
241const Transition = forwardRef(function Transition(props, ref) {
242 return <Slide direction="up" ref={ref} {...props} />;
243});
244
245const formatDate = (date: Moment, time: Moment) => {
246 return moment(
247 `${moment(date).format('YYYY-MM-DD')} ${moment(time).format('HH:mm')}`,
248 'YYYY-MM-DD HH:mm'
249 ).toISOString();
250};
251
252const MARKS = [1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
253 value,
254 label: value,
255}));
256
257const addSpacing = (theme, ratio) => ({
258 margin: `0 ${theme.spacing(3 * ratio)}px`,
259 width: `calc(100% - ${theme.spacing(6 * ratio)}px)`,
260});
261
262const useStyles = makeStyles(theme => ({
263 title: {
264 paddingBottom: 0,
265 },
266 sectionTitle: {
267 ...addSpacing(theme, 1),
268 paddingBottom: theme.spacing(1.5),
269 },
270 content: {
271 padding: `${theme.spacing(2)}px 0`,
272 },
273 field: {
274 ...addSpacing(theme, 1),
275 paddingBottom: theme.spacing(1)
276 },
277 halfWidthWrapper: {
278 ...addSpacing(theme, .5)
279 },
280 halfWidthField: {
281 margin: `0 ${theme.spacing(1.5)}px`,
282 width: `calc(50% - ${theme.spacing(3)}px)`,
283 },
284 slider: {
285 ...addSpacing(theme, 1),
286 },
287 divider: {
288 margin: `${theme.spacing(2)}px 0`,
289 },
290 actions: {
291 paddingTop: 0,
292 }
293}));
294
295export default NewTravelDialog;