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