all repos — caroster @ 60a3e00d5cd2a134abb141309d5b52ea81a7861b

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

frontend/containers/Car/HeaderEditing.js (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: ['event']});
 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              id: event.id,
 68              eventUpdate: {
 69                waiting_list: [
 70                  ...(event.waiting_list ?? []),
 71                  ...lostPassengers,
 72                ],
 73              },
 74            },
 75          });
 76      }
 77      const departure = moment(
 78        `${date} ${time}`,
 79        'YYYY-MM-DD HH:mm'
 80      ).toISOString();
 81      await updateCar({
 82        variables: {
 83          id: car.id,
 84          carUpdate: {
 85            name,
 86            seats,
 87            meeting,
 88            departure,
 89            phone_number: phone,
 90            details,
 91            passengers: car.passengers ? car.passengers.slice(0, seats) : [],
 92          },
 93        },
 94      });
 95      toggleEditing();
 96    } catch (error) {
 97      console.error(error);
 98      addToast('car.errors.cant_update');
 99    }
100    return false;
101  };
102
103  const onRemove = async () => {
104    try {
105      // Put passengers in event waiting list (if any)
106      if (Array.isArray(car?.passengers) && car.passengers.length > 0)
107        await updateEvent({
108          variables: {
109            id: event.id,
110            eventUpdate: {
111              waiting_list: [...(event.waiting_list ?? []), ...car.passengers],
112            },
113          },
114        });
115      await deleteCar({
116        variables: {
117          id: car.id,
118        },
119      });
120      addToast(t('car.actions.removed'));
121      toggleEditing();
122    } catch (error) {
123      console.error(error);
124      addToast('car.errors.cant_remove');
125    }
126  };
127
128  return (
129    <div className={classes.header}>
130      <form onSubmit={onSave}>
131        <IconButton
132          size="small"
133          color="primary"
134          type="submit"
135          className={classes.edit}
136        >
137          <Icon>done</Icon>
138        </IconButton>
139        <TextField
140          label={t('car.creation.date')}
141          value={date}
142          onChange={e => setDate(e.target.value)}
143          className={classes.picker}
144          fullWidth
145          id="NewCarDate"
146          name="date"
147          type="date"
148          InputLabelProps={{
149            shrink: true,
150          }}
151        />
152        <TextField
153          label={t('car.creation.time')}
154          value={time}
155          onChange={e => setTime(e.target.value)}
156          className={classes.picker}
157          fullWidth
158          id="NewCarTime"
159          name="time"
160          type="time"
161          InputLabelProps={{
162            shrink: true,
163          }}
164        />
165        <TextField
166          label={t('car.creation.name')}
167          fullWidth
168          autoFocus
169          margin="dense"
170          value={name}
171          onChange={e => setName(e.target.value)}
172          id="EditCarName"
173          name="name"
174        />
175        <TextField
176          label={t('car.creation.phone')}
177          fullWidth
178          autoFocus
179          margin="dense"
180          value={phone}
181          onChange={e => setPhone(e.target.value)}
182          id="EditCarPhone"
183          name="phone"
184        />
185        <TextField
186          label={t('car.creation.meeting')}
187          fullWidth
188          margin="dense"
189          multiline
190          rows={2}
191          value={meeting}
192          onChange={e => setMeeting(e.target.value)}
193          id="EditCarMeeting"
194          name="meeting"
195        />
196        <TextField
197          label={t('car.creation.notes')}
198          fullWidth
199          margin="dense"
200          inputProps={{maxLength: 250}}
201          helperText={`${details.length}/250`}
202          multiline
203          rows={2}
204          value={details}
205          onChange={e => setDetails(e.target.value)}
206          id="EditCarDetails"
207          name="details"
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 useStyles = makeStyles(theme => ({
255  header: {
256    padding: theme.spacing(2),
257  },
258  edit: {
259    position: 'absolute',
260    top: 0,
261    right: 0,
262    margin: theme.spacing(1),
263    zIndex: theme.zIndex.speedDial,
264  },
265  section: {
266    marginTop: theme.spacing(2),
267  },
268  slider: {
269    marginTop: theme.spacing(2),
270  },
271  actions: {
272    display: 'flex',
273    flexDirection: 'column',
274    justifyContent: 'center',
275    margin: theme.spacing(2, 0),
276    '& > *:first-child': {
277      marginBottom: theme.spacing(2),
278    },
279  },
280  picker: {
281    marginBottom: theme.spacing(2),
282  },
283}));
284
285export default HeaderEditing;