all repos — caroster @ 8a3f929f7db1c92e4c4b92eee75c20aab48a9950

[Octree] Group carpool to your event https://caroster.io

frontend/containers/Car/HeaderEditing.tsx (view raw)

  1import {useState, useReducer, useCallback, useEffect, useMemo} from 'react';
  2import {makeStyles} from '@material-ui/core/styles';
  3import Typography from '@material-ui/core/Typography';
  4import IconButton from '@material-ui/core/IconButton';
  5import Icon from '@material-ui/core/Icon';
  6import Button from '@material-ui/core/Button';
  7import TextField from '@material-ui/core/TextField';
  8import Slider from '@material-ui/core/Slider';
  9import {DatePicker, TimePicker} from '@material-ui/pickers';
 10import moment from 'moment';
 11import {useTranslation} from 'react-i18next';
 12import useToastStore from '../../stores/useToastStore';
 13import useEventStore from '../../stores/useEventStore';
 14import RemoveDialog from '../RemoveDialog';
 15import {
 16  useUpdateEventMutation,
 17  useUpdateCarMutation,
 18  useDeleteCarMutation,
 19} from '../../generated/graphql';
 20
 21const HeaderEditing = ({car, toggleEditing}) => {
 22  const classes = useStyles();
 23  const {t} = useTranslation();
 24  const event = useEventStore(s => s.event);
 25  const addToast = useToastStore(s => s.addToast);
 26  const [updateEvent] = useUpdateEventMutation();
 27  const [updateCar] = useUpdateCarMutation();
 28  const [deleteCar] = useDeleteCarMutation({refetchQueries: ['eventByUUID']});
 29  const [removing, toggleRemoving] = useReducer(i => !i, false);
 30  const dateMoment = useMemo(() => {
 31    if (!car?.departure) return moment();
 32    else return moment(car.departure);
 33  }, [car?.departure]);
 34
 35  // States
 36  const [name, setName] = useState(car?.name ?? '');
 37  const [seats, setSeats] = useState(car?.seats ?? 4);
 38  const [meeting, setMeeting] = useState(car?.meeting ?? '');
 39  const [date, setDate] = useState(dateMoment);
 40  const [time, setTime] = useState(dateMoment);
 41  const [phone, setPhone] = useState(car ? car['phone_number'] : '');
 42  const [details, setDetails] = useState(car?.details ?? '');
 43
 44  // Click on ESQ closes the form
 45  const escFunction = useCallback(
 46    evt => {
 47      if (evt.keyCode === 27) toggleEditing();
 48    },
 49    [toggleEditing]
 50  );
 51
 52  useEffect(() => {
 53    document.addEventListener('keydown', escFunction, false);
 54    return () => {
 55      document.removeEventListener('keydown', escFunction, false);
 56    };
 57  }, [escFunction]);
 58
 59  const onSave = async evt => {
 60    if (evt.preventDefault) evt.preventDefault();
 61    try {
 62      // If new seats count is under current passengers count, put excedent in event waiting list
 63      if (!!car.passengers && car.passengers.length > seats) {
 64        const lostPassengers = car.passengers.slice(seats);
 65        if (lostPassengers.length > 0)
 66          await updateEvent({
 67            variables: {
 68              uuid: event.uuid,
 69              eventUpdate: {
 70                waitingList: formatPassengers([
 71                  ...(event.waitingList || []),
 72                  ...lostPassengers.map(({name}) => ({name})),
 73                ]),
 74              },
 75            },
 76            refetchQueries: ['eventByUUID'],
 77          });
 78      }
 79      const departure = moment(
 80        `${moment(date).format('YYYY-MM-DD')} ${moment(time).format('HH:mm')}`,
 81        'YYYY-MM-DD HH:mm'
 82      ).toISOString();
 83      await updateCar({
 84        variables: {
 85          id: car.id,
 86          carUpdate: {
 87            name,
 88            seats,
 89            meeting,
 90            departure,
 91            phone_number: phone,
 92            details,
 93            passengers: formatPassengers(car.passengers, seats),
 94          },
 95        },
 96      });
 97      toggleEditing();
 98    } catch (error) {
 99      console.error(error);
100      addToast('car.errors.cant_update');
101    }
102    return false;
103  };
104
105  const onRemove = async () => {
106    try {
107      // Put passengers in event waiting list (if any)
108      if (Array.isArray(car?.passengers) && car.passengers.length > 0)
109        await updateEvent({
110          variables: {
111            uuid: event.uuid,
112            eventUpdate: {
113              waitingList: formatPassengers([
114                ...(event.waitingList || []),
115                ...car.passengers.map(({name}) => ({name})),
116              ]),
117            },
118          },
119          refetchQueries: ['eventByUUID'],
120        });
121      await deleteCar({
122        variables: {
123          id: car.id,
124        },
125      });
126      addToast(t('car.actions.removed'));
127      toggleEditing();
128    } catch (error) {
129      console.error(error);
130      addToast('car.errors.cant_remove');
131    }
132  };
133
134  return (
135    <div className={classes.header}>
136      <form onSubmit={onSave}>
137        <IconButton
138          size="small"
139          color="primary"
140          type="submit"
141          className={classes.edit}
142        >
143          <Icon>done</Icon>
144        </IconButton>
145        <DatePicker
146          id="NewCarDate"
147          className={classes.picker}
148          fullWidth
149          label={t('car.creation.date')}
150          format="DD/MM/YYYY"
151          value={date}
152          onChange={setDate}
153        />
154        <TimePicker
155          id="NewCarTime"
156          className={classes.picker}
157          fullWidth
158          label={t('car.creation.time')}
159          value={time}
160          onChange={setTime}
161          ampm={false}
162          minutesStep={5}
163        />
164        <TextField
165          label={t('car.creation.name')}
166          fullWidth
167          autoFocus
168          margin="dense"
169          value={name}
170          onChange={e => setName(e.target.value)}
171          id="EditCarName"
172          name="name"
173        />
174        <TextField
175          label={t('car.creation.phone')}
176          fullWidth
177          autoFocus
178          margin="dense"
179          value={phone}
180          onChange={e => setPhone(e.target.value)}
181          id="EditCarPhone"
182          name="phone"
183        />
184        <TextField
185          label={t('car.creation.meeting')}
186          fullWidth
187          margin="dense"
188          multiline
189          rows={2}
190          value={meeting}
191          onChange={e => setMeeting(e.target.value)}
192          id="EditCarMeeting"
193          name="meeting"
194        />
195        <TextField
196          label={t('car.creation.notes')}
197          fullWidth
198          margin="dense"
199          inputProps={{maxLength: 250}}
200          helperText={`${details.length}/250`}
201          multiline
202          rows={2}
203          value={details}
204          onChange={e => setDetails(e.target.value)}
205          id="EditCarDetails"
206          name="details"
207        />
208        <div className={classes.slider}>
209          <Typography variant="caption">{t('car.creation.seats')}</Typography>
210          <Slider
211            value={seats}
212            onChange={(e, value) => setSeats(value)}
213            step={1}
214            marks={[1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
215              value,
216              label: value,
217            }))}
218            min={1}
219            max={8}
220            valueLabelDisplay="auto"
221            id="EditCarSeats"
222          />
223        </div>
224      </form>
225      <div className={classes.actions}>
226        <Button
227          variant="outlined"
228          color="primary"
229          onClick={onSave}
230          id="CarSave"
231        >
232          {t('generic.save')}
233        </Button>
234        <Button
235          variant="outlined"
236          color="primary"
237          onClick={toggleRemoving}
238          id="CarRemove"
239        >
240          {t('generic.remove')}
241        </Button>
242      </div>
243      <RemoveDialog
244        text={t('car.actions.remove_alert')}
245        open={removing}
246        onClose={toggleRemoving}
247        onRemove={onRemove}
248      />
249    </div>
250  );
251};
252
253const formatPassengers = (passengers = [], seats: number = 1000) => {
254  if (!passengers) return [];
255
256  return passengers
257    .slice(0, seats)
258    .map(({__typename, ...passenger}) => passenger);
259};
260
261const useStyles = makeStyles(theme => ({
262  header: {
263    padding: theme.spacing(2),
264  },
265  edit: {
266    position: 'absolute',
267    top: 0,
268    right: 0,
269    margin: theme.spacing(1),
270    zIndex: theme.zIndex.speedDial,
271  },
272  section: {
273    marginTop: theme.spacing(2),
274  },
275  slider: {
276    marginTop: theme.spacing(2),
277  },
278  actions: {
279    display: 'flex',
280    flexDirection: 'column',
281    justifyContent: 'center',
282    margin: theme.spacing(2, 0),
283    '& > *:first-child': {
284      marginBottom: theme.spacing(2),
285    },
286  },
287  picker: {
288    marginBottom: theme.spacing(2),
289  },
290}));
291
292export default HeaderEditing;