all repos — caroster @ 7c999d08eda0b123210cd81bce649f1a93bae754

[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          label={t('car.creation.date')}
147          fullWidth
148          helperText=" "
149          value={date}
150          onChange={setDate}
151          format="DD/MM/YYYY"
152          cancelLabel={t('generic.cancel')}
153          autoFocus
154          id="NewCarDate"
155        />
156        <TimePicker
157          label={t('car.creation.time')}
158          fullWidth
159          helperText=" "
160          value={time}
161          onChange={setTime}
162          cancelLabel={t('generic.cancel')}
163          ampm={false}
164          minutesStep={5}
165          id="NewCarTime"
166        />
167        <TextField
168          label={t('car.creation.name')}
169          fullWidth
170          helperText=" "
171          value={name}
172          onChange={e => setName(e.target.value)}
173          name="name"
174          id="EditCarName"
175        />
176        <TextField
177          label={t('car.creation.phone')}
178          fullWidth
179          helperText=" "
180          value={phone}
181          onChange={e => setPhone(e.target.value)}
182          name="phone"
183          id="EditCarPhone"
184        />
185        <TextField
186          label={t('car.creation.meeting')}
187          fullWidth
188          multiline
189          rowsMax={4}
190          inputProps={{maxLength: 250}}
191          helperText={`${meeting.length}/250`}
192          value={meeting}
193          onChange={e => setMeeting(e.target.value)}
194          name="meeting"
195          id="EditCarMeeting"
196        />
197        <TextField
198          label={t('car.creation.notes')}
199          fullWidth
200          multiline
201          rowsMax={4}
202          inputProps={{maxLength: 250}}
203          helperText={`${details.length}/250`}
204          value={details}
205          onChange={e => setDetails(e.target.value)}
206          name="details"
207          id="EditCarDetails"
208        />
209        <div className={classes.slider}>
210          <Typography variant="caption">{t('car.creation.seats')}</Typography>
211          <Slider
212            value={seats}
213            onChange={(e, value) => setSeats(value)}
214            step={1}
215            marks={[1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
216              value,
217              label: value,
218            }))}
219            min={1}
220            max={8}
221            valueLabelDisplay="auto"
222            id="EditCarSeats"
223          />
224        </div>
225      </form>
226      <div className={classes.actions}>
227        <Button
228          variant="outlined"
229          color="primary"
230          onClick={onSave}
231          id="CarSave"
232        >
233          {t('generic.save')}
234        </Button>
235        <Button
236          variant="outlined"
237          color="primary"
238          onClick={toggleRemoving}
239          id="CarRemove"
240        >
241          {t('generic.remove')}
242        </Button>
243      </div>
244      <RemoveDialog
245        text={t('car.actions.remove_alert')}
246        open={removing}
247        onClose={toggleRemoving}
248        onRemove={onRemove}
249      />
250    </div>
251  );
252};
253
254const formatPassengers = (passengers = [], seats: number = 1000) => {
255  if (!passengers) return [];
256
257  return passengers
258    .slice(0, seats)
259    .map(({__typename, ...passenger}) => passenger);
260};
261
262const useStyles = makeStyles(theme => ({
263  header: {
264    padding: theme.spacing(2),
265  },
266  edit: {
267    position: 'absolute',
268    top: 0,
269    right: 0,
270    margin: theme.spacing(1),
271    zIndex: theme.zIndex.speedDial,
272  },
273  section: {
274    marginTop: theme.spacing(2),
275  },
276  slider: {
277    marginTop: theme.spacing(2),
278  },
279  actions: {
280    display: 'flex',
281    flexDirection: 'column',
282    justifyContent: 'center',
283    margin: theme.spacing(2, 0),
284    '& > *:first-child': {
285      marginBottom: theme.spacing(2),
286    },
287  },
288}));
289
290export default HeaderEditing;