all repos — caroster @ 5edb8b7bb7b7df7b1a86170523ee4ccdbdad8e52

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