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