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