frontend/containers/Travel/HeaderEditing.tsx (view raw)
1import {useState, useReducer, useCallback, useEffect, useMemo} from 'react';
2import Typography from '@mui/material/Typography';
3import Button from '@mui/material/Button';
4import TextField from '@mui/material/TextField';
5import Slider from '@mui/material/Slider';
6import Box from '@mui/material/Box';
7import moment, {Moment} from 'moment';
8import {useTheme} from '@mui/material/styles';
9import {DatePicker} from '@mui/x-date-pickers/DatePicker';
10import {TimePicker} from '@mui/x-date-pickers/TimePicker';
11import {useTranslation} from 'react-i18next';
12import RemoveDialog from '../RemoveDialog';
13import useActions from './useActions';
14import PlaceInput from '../PlaceInput';
15import useEventStore from '../../stores/useEventStore';
16import {TravelEntity} from '../../generated/graphql';
17
18interface Props {
19 travel: TravelEntity;
20 toggleEditing: () => void;
21}
22
23const HeaderEditing = ({travel, toggleEditing}: Props) => {
24 const {t} = useTranslation();
25 const theme = useTheme();
26 const actions = useActions({travel});
27 const isCarosterPlus = useEventStore(s =>
28 s.event.enabled_modules?.includes('caroster-plus')
29 );
30 const [removing, toggleRemoving] = useReducer(i => !i, false);
31 const dateMoment = useMemo(
32 () => (travel?.attributes.departure ? moment(travel.attributes.departure) : null),
33 [travel?.attributes.departure]
34 );
35
36 // States
37 const [name, setName] = useState(travel?.attributes.vehicleName ?? '');
38 const [seats, setSeats] = useState(travel?.attributes.seats ?? 4);
39 const [meeting, setMeeting] = useState(travel?.attributes.meeting ?? '');
40 const [meeting_latitude, setMeetingLatitude] = useState(
41 travel?.attributes.meeting_latitude
42 );
43 const [meeting_longitude, setMeetingLongitude] = useState(
44 travel?.attributes.meeting_longitude
45 );
46 const [date, setDate] = useState(dateMoment);
47 const [time, setTime] = useState(dateMoment);
48 const [phone, setPhone] = useState(travel?.attributes.phone_number ?? '');
49 const [details, setDetails] = useState(travel?.attributes.details ?? '');
50
51 // Click on ESQ closes the form
52 const escFunction = useCallback(
53 evt => {
54 if (evt.keyCode === 27) toggleEditing();
55 },
56 [toggleEditing]
57 );
58
59 useEffect(() => {
60 document.addEventListener('keydown', escFunction, false);
61 return () => {
62 document.removeEventListener('keydown', escFunction, false);
63 };
64 }, [escFunction]);
65
66 const onSave = async event => {
67 if (event.preventDefault) event.preventDefault();
68 const travelUpdate = {
69 meeting,
70 meeting_latitude,
71 meeting_longitude,
72 details,
73 seats,
74 phone_number: phone,
75 vehicleName: name,
76 departure: formatDate(date, time),
77 };
78 await actions.updateTravel(travelUpdate);
79 toggleEditing();
80 };
81
82 const onRemove = async () => {
83 await actions.removeTravel(
84 isCarosterPlus
85 ? t`travel.actions.removed.caroster_plus`
86 : t`travel.actions.removed`
87 );
88 toggleEditing();
89 };
90
91 return (
92 <Box sx={{padding: 2}}>
93 <form onSubmit={onSave}>
94 <DatePicker
95 slotProps={{
96 textField: {
97 sx: {width: '100%', pb: 2},
98 },
99 }}
100 format="DD/MM/YYYY"
101 label={t('travel.creation.date')}
102 value={date}
103 onChange={setDate}
104 autoFocus
105 />
106 <TimePicker
107 label={t('travel.creation.time')}
108 slotProps={{
109 textField: {
110 sx: {width: '100%', pb: 2},
111 },
112 }}
113 value={time}
114 onChange={setTime}
115 ampm={false}
116 minutesStep={5}
117 />
118 <TextField
119 label={t('travel.creation.name')}
120 fullWidth
121 sx={{pb: 2}}
122 value={name}
123 onChange={e => setName(e.target.value)}
124 name="name"
125 id="EditTravelName"
126 />
127 <TextField
128 label={t('travel.creation.phone')}
129 fullWidth
130 sx={{pb: 2}}
131 value={phone}
132 onChange={e => setPhone(e.target.value)}
133 name="phone"
134 id="EditTravelPhone"
135 />
136 <PlaceInput
137 label={t('travel.creation.meeting')}
138 textFieldProps={{sx: {pb: 2}}}
139 place={meeting}
140 latitude={meeting_latitude}
141 longitude={meeting_longitude}
142 onSelect={({place, latitude, longitude}) => {
143 setMeeting(place);
144 setMeetingLatitude(latitude);
145 setMeetingLongitude(longitude);
146 }}
147 />
148 <TextField
149 label={t('travel.creation.notes')}
150 fullWidth
151 sx={{pb: 2}}
152 multiline
153 maxRows={4}
154 inputProps={{maxLength: 250}}
155 helperText={`${details.length}/250`}
156 value={details}
157 onChange={e => setDetails(e.target.value)}
158 name="details"
159 id="EditTravelDetails"
160 />
161 <Box sx={{marginTop: theme.spacing(2)}}>
162 <Typography variant="caption">
163 {t('travel.creation.seats')}
164 </Typography>
165 <Slider
166 value={seats}
167 onChange={(e, value) => setSeats(value)}
168 step={1}
169 marks={[1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
170 value,
171 label: value,
172 }))}
173 min={1}
174 max={8}
175 valueLabelDisplay="auto"
176 id="EditTravelSeats"
177 />
178 </Box>
179 </form>
180 <Box
181 sx={{
182 display: 'flex',
183 flexDirection: 'column',
184 justifyContent: 'center',
185 margin: theme.spacing(2, 0),
186 '& > *:first-child': {
187 marginBottom: theme.spacing(2),
188 },
189 }}
190 >
191 <Button
192 variant="contained"
193 color="primary"
194 onClick={onSave}
195 id="TravelSave"
196 >
197 {t('generic.save')}
198 </Button>
199 <Button
200 variant="outlined"
201 color="primary"
202 onClick={toggleRemoving}
203 id="TravelRemove"
204 >
205 {t('generic.remove')}
206 </Button>
207 </Box>
208 <RemoveDialog
209 text={
210 isCarosterPlus
211 ? t`travel.actions.remove_alert.caroster_plus`
212 : t`travel.actions.remove_alert`
213 }
214 open={removing}
215 onClose={toggleRemoving}
216 onRemove={onRemove}
217 />
218 </Box>
219 );
220};
221
222const formatDate = (date: Moment, time: Moment) => {
223 return moment(
224 `${moment(date).format('YYYY-MM-DD')} ${moment(time).format('HH:mm')}`,
225 'YYYY-MM-DD HH:mm'
226 ).toISOString();
227};
228
229export default HeaderEditing;