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