all repos — caroster @ 010e7c4c0be09d2510ffab44ec663174a79d5192

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

add passenger to car, remove passenger from car is moving it to the waiting list, use of Box in passengers list, ...
Karian Før karian@subtext.studio
Fri, 03 Jul 2020 08:54:57 +0000
commit

010e7c4c0be09d2510ffab44ec663174a79d5192

parent

9079b470260a87959da56f303e6682211cb847d1

M app/src/components/TextField/index.jsapp/src/components/TextField/index.js

@@ -1,12 +1,12 @@

-import React from "react"; -import TextFieldMUI from "@material-ui/core/TextField"; -import { makeStyles } from "@material-ui/core/styles"; +import React from 'react'; +import TextFieldMUI from '@material-ui/core/TextField'; +import {makeStyles} from '@material-ui/core/styles'; -const TextField = ({ className, light, ...props }) => { +const TextField = ({className, light, ...props}) => { const classes = useStyles(); return ( <TextFieldMUI - className={`${classes.input} ${className} ${light ? "light" : ""}`} + className={`${classes.input} ${className} ${light ? 'light' : ''}`} fullWidth margin="dense" {...props}

@@ -14,20 +14,20 @@ />

); }; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ input: { - "&.light .MuiFormLabel-root": { - color: "white", + '&.light .MuiFormLabel-root': { + color: 'white', }, - "&.light .MuiInputBase-input": { color: "white" }, - "&.light .MuiInput-underline::before": { - borderColor: "white", + '&.light .MuiInputBase-input': {color: 'white'}, + '&.light .MuiInput-underline::before': { + borderColor: 'white', }, - "&.light .MuiInput-underline:hover:not(.Mui-disabled)::before": { - borderColor: "white", + '&.light .MuiInput-underline:hover:not(.Mui-disabled)::before': { + borderColor: 'white', }, - "&.light .MuiInput-underline::after": { - transform: "scaleX(0)", + '&.light .MuiInput-underline::after': { + transform: 'scaleX(0)', }, }, }));
M app/src/containers/Car/Header.jsapp/src/containers/Car/Header.js

@@ -2,8 +2,8 @@ import React from 'react';

import Typography from '@material-ui/core/Typography'; import IconButton from '@material-ui/core/IconButton'; import Icon from '@material-ui/core/Icon'; -import moment from 'moment'; import {makeStyles} from '@material-ui/core/styles'; +import moment from 'moment'; import {useTranslation} from 'react-i18next'; const Header = ({car, toggleEditing}) => {

@@ -57,7 +57,9 @@ );

}; const useStyles = makeStyles(theme => ({ - header: {padding: theme.spacing(2)}, + header: { + padding: theme.spacing(2), + }, editBtn: { position: 'absolute', top: 0,
M app/src/containers/Car/HeaderEditing.jsapp/src/containers/Car/HeaderEditing.js

@@ -12,7 +12,7 @@ import Slider from '@material-ui/core/Slider';

import {useStrapi} from 'strapi-react-context'; import {useToast} from '../../contexts/Toast'; import {useEvent} from '../../contexts/Event'; -import RemoveDialog from './RemoveDialog'; +import RemoveDialog from '../RemoveDialog'; const HeaderEditing = ({car, toggleEditing}) => { const classes = useStyles();

@@ -184,8 +184,9 @@ {t('car.actions.remove')}

</Button> </div> <RemoveDialog + text={t('car.actions.remove_alert')} open={removing} - toggle={toggleRemoving} + onClose={toggleRemoving} onRemove={onRemove} /> </div>
M app/src/containers/Car/RemoveDialog.jsapp/src/containers/RemoveDialog/index.js

@@ -11,23 +11,23 @@ const Transition = React.forwardRef(function Transition(props, ref) {

return <Slide direction="up" ref={ref} {...props} />; }); -const RemoveDialog = ({open, toggle, onRemove}) => { +const RemoveDialog = ({text, open, onClose, onRemove}) => { const {t} = useTranslation(); return ( - <Dialog open={open} TransitionComponent={Transition} onClose={toggle}> + <Dialog open={open} TransitionComponent={Transition} onClose={onClose}> <DialogContent> - <DialogContentText>{t('car.actions.remove_alert')}</DialogContentText> + <DialogContentText>{text}</DialogContentText> </DialogContent> <DialogActions> - <Button onClick={toggle} id="CarRemoveCancel"> + <Button onClick={onClose} id="CarRemoveCancel"> {t('generic.cancel')} </Button> <Button id="CarRemoveConfirm" onClick={() => { onRemove(); - toggle(); + onClose(); }} > {t('generic.confirm')}
M app/src/containers/Car/index.jsapp/src/containers/Car/index.js

@@ -1,18 +1,19 @@

import React, {useReducer} from 'react'; import {makeStyles} from '@material-ui/core/styles'; import Divider from '@material-ui/core/Divider'; - import Paper from '@material-ui/core/Paper'; import {useTranslation} from 'react-i18next'; import {useStrapi} from 'strapi-react-context'; +import {useEvent} from '../../contexts/Event'; +import {useToast} from '../../contexts/Toast'; import PassengersList from '../PassengersList'; -import {useToast} from '../../contexts/Toast'; +import HeaderEditing from './HeaderEditing'; import Header from './Header'; -import HeaderEditing from './HeaderEditing'; const Car = ({car}) => { const classes = useStyles(); const {t} = useTranslation(); + const {event} = useEvent(); const {addToast} = useToast(); const strapi = useStrapi(); const [isEditing, toggleEditing] = useReducer(i => !i, false);

@@ -33,6 +34,9 @@

const removePassenger = async idx => { if (!car?.passengers) return false; try { + await strapi.services.events.update(event.id, { + waiting_list: [...(event.waiting_list || []), car.passengers[idx]], + }); return await strapi.services.cars.update(car.id, { passengers: car.passengers.filter((_, i) => i !== idx), });

@@ -55,7 +59,8 @@ <PassengersList

passengers={car.passengers} places={car.seats} addPassenger={addPassenger} - removePassenger={removePassenger} + onClick={removePassenger} + icon="close" /> </Paper> );
D app/src/containers/CarColumns/WaitingList.js

@@ -1,58 +0,0 @@

-import React from 'react'; -import Typography from '@material-ui/core/Typography'; -import Paper from '@material-ui/core/Paper'; -import {makeStyles} from '@material-ui/core/styles'; -import {useTranslation} from 'react-i18next'; -import {useStrapi} from 'strapi-react-context'; -import {useEvent} from '../../contexts/Event'; -import PassengersList from '../PassengersList'; -import Divider from '@material-ui/core/Divider'; - -const WaitingList = ({car}) => { - const {t} = useTranslation(); - const {event} = useEvent(); - const strapi = useStrapi(); - const classes = useStyles(); - - const addPassenger = async passenger => { - try { - await strapi.services.events.update(event.id, { - waiting_list: [...(event.waiting_list || []), passenger], - }); - } catch (error) { - console.error(error); - } - }; - - const removePassenger = async idx => { - try { - await strapi.services.events.update(event.id, { - waiting_list: event.waiting_list.filter((_, i) => i !== idx), - }); - } catch (error) { - console.error(error); - } - }; - - return ( - <Paper> - <div className={classes.header}> - <Typography variant="h5">{t('passenger.title')}</Typography> - </div> - <Divider /> - <PassengersList - hideEmpty - passengers={event.waiting_list} - places={50} - addPassenger={addPassenger} - removePassenger={removePassenger} - /> - </Paper> - ); -}; - -const useStyles = makeStyles(theme => ({ - header: {padding: theme.spacing(2)}, -})); - -export default WaitingList;
M app/src/containers/CarColumns/index.jsapp/src/containers/CarColumns/index.js

@@ -2,11 +2,11 @@ import React, {useMemo} from 'react';

import Slider from 'react-slick'; import Container from '@material-ui/core/Container'; import {makeStyles} from '@material-ui/core/styles'; +import {useStrapi} from 'strapi-react-context'; +import {useEvent} from '../../contexts/Event'; +import WaitingList from '../WaitingList'; import Car from '../Car'; import AddCar from './AddCar'; -import {useEvent} from '../../contexts/Event'; -import {useStrapi} from 'strapi-react-context'; -import WaitingList from './WaitingList'; const settings = { dots: false,

@@ -69,22 +69,20 @@ [strapi.stores.cars, event]

); return ( - <div> - <Slider {...settings}> - <Container maxWidth="sm" className={classes.slide}> - <WaitingList /> - </Container> - {cars && - cars.map(car => ( - <Container key={car.id} maxWidth="sm" className={classes.slide}> - <Car car={car} {...props} /> - </Container> - ))} - <Container maxWidth="sm" className={classes.slide}> - <AddCar {...props} /> - </Container> - </Slider> - </div> + <Slider {...settings}> + <Container maxWidth="sm" className={classes.slide}> + <WaitingList /> + </Container> + {cars && + cars.map(car => ( + <Container key={car.id} maxWidth="sm" className={classes.slide}> + <Car car={car} {...props} /> + </Container> + ))} + <Container maxWidth="sm" className={classes.slide}> + <AddCar {...props} /> + </Container> + </Slider> ); };
M app/src/containers/PassengersList/Input.jsapp/src/containers/PassengersList/Input.js

@@ -1,45 +1,45 @@

-import React, { useState } from "react"; -import TextField from "@material-ui/core/TextField"; -import { useTranslation } from "react-i18next"; -import { makeStyles } from "@material-ui/core/styles"; -import Divider from "@material-ui/core/Divider"; +import React, {useState} from 'react'; +import Box from '@material-ui/core/Box'; +import TextField from '@material-ui/core/TextField'; +import IconButton from '@material-ui/core/IconButton'; +import Divider from '@material-ui/core/Divider'; +import Icon from '@material-ui/core/Icon'; +import {useTranslation} from 'react-i18next'; -const Input = ({ addPassenger }) => { - const classes = useStyles(); - const [name, setName] = useState(""); - const { t } = useTranslation(); +const Input = ({addPassenger}) => { + const [name, setName] = useState(''); + const {t} = useTranslation(); const onSave = () => { if (!!name) { addPassenger(name); - setName(""); + setName(''); } }; - const onKeyDown = (e) => { + const onKeyDown = e => { if (e.keyCode === 13) onSave(); }; return ( - <> - <div className={classes.container}> + <Box pb={1}> + <Box display="flex" flexDirection="row" alignItems="center" px={2} pb={2}> <TextField value={name} - onChange={(e) => setName(e.target.value)} + onChange={e => setName(e.target.value)} onKeyDown={onKeyDown} fullWidth - label={t("car.passengers.add")} + label={t('car.passengers.add')} id="NewPassenger" name="passenger" /> - </div> + <IconButton edge="end" size="small" disabled={!name} onClick={onSave}> + <Icon>check</Icon> + </IconButton> + </Box> <Divider /> - </> + </Box> ); }; - -const useStyles = makeStyles((theme) => ({ - container: { padding: theme.spacing(0, 2, 2) }, -})); export default Input;
M app/src/containers/PassengersList/Passenger.jsapp/src/containers/PassengersList/Passenger.js

@@ -1,34 +1,34 @@

import React from 'react'; -import {makeStyles} from '@material-ui/core/styles'; +import Box from '@material-ui/core/Box'; import Typography from '@material-ui/core/Typography'; -import IconButton from '@material-ui/core/IconButton'; -import Icon from '@material-ui/core/Icon'; +import {makeStyles} from '@material-ui/core/styles'; import {useTranslation} from 'react-i18next'; -const Passenger = ({passenger, removePassenger}) => { +const Passenger = ({passenger, button}) => { const classes = useStyles(); const {t} = useTranslation(); - if (!!passenger) - return ( - <div className={classes.item}> - <Typography variant="body2" className={classes.name}> - {passenger} - </Typography> - <IconButton edge="end" size="small" onClick={removePassenger}> - <Icon>close</Icon> - </IconButton> - </div> - ); - else return <div className={classes.item}>{t('car.passengers.empty')}</div>; + return !!passenger ? ( + <Box display="flex" flexDirection="row" alignItems="center" px={2} py={1}> + <Typography variant="body2" className={classes.name}> + {passenger} + </Typography> + {button} + </Box> + ) : ( + <Box + display="flex" + flexDirection="row" + alignItems="center" + px={2} + py={1} + minHeight={46} + > + <Typography variant="body2">{t('car.passengers.empty')}</Typography> + </Box> + ); }; const useStyles = makeStyles(theme => ({ - item: { - padding: theme.spacing(1, 2), - display: 'flex', - alignItems: 'center', - height: '46px', - }, name: { flexGrow: 1, },
M app/src/containers/PassengersList/index.jsapp/src/containers/PassengersList/index.js

@@ -1,14 +1,17 @@

import React from 'react'; +import IconButton from '@material-ui/core/IconButton'; +import Icon from '@material-ui/core/Icon'; import {makeStyles} from '@material-ui/core/styles'; -import Passenger from './Passenger'; import Input from './Input'; +import Passenger from './Passenger'; const PassengersList = ({ hideEmpty, passengers, places = 0, addPassenger, - removePassenger, + icon, + onClick, }) => { const classes = useStyles();

@@ -32,7 +35,15 @@ list.map((passenger, index) => (

<Passenger key={index} passenger={passenger} - removePassenger={() => removePassenger(index)} + button={ + <IconButton + edge="end" + size="small" + onClick={() => onClick(index)} + > + <Icon>{icon}</Icon> + </IconButton> + } /> ))} </div>

@@ -40,7 +51,9 @@ );

}; const useStyles = makeStyles(theme => ({ - container: {padding: theme.spacing(1, 0)}, + container: { + padding: theme.spacing(1, 0), + }, })); export default PassengersList;
A app/src/containers/WaitingList/CarDialog.js

@@ -0,0 +1,87 @@

+import React, {useMemo} from 'react'; +import Slide from '@material-ui/core/Slide'; +import Dialog from '@material-ui/core/Dialog'; +import AppBar from '@material-ui/core/AppBar'; +import Toolbar from '@material-ui/core/Toolbar'; +import ListItemText from '@material-ui/core/ListItemText'; +import ListItem from '@material-ui/core/ListItem'; +import List from '@material-ui/core/List'; +import Divider from '@material-ui/core/Divider'; +import IconButton from '@material-ui/core/IconButton'; +import Icon from '@material-ui/core/Icon'; +import {makeStyles} from '@material-ui/core/styles'; +import {useTranslation} from 'react-i18next'; +import {useStrapi} from 'strapi-react-context'; +import {useEvent} from '../../contexts/Event'; + +const sortCars = (a, b) => { + const dateA = new Date(a.departure).getTime(); + const dateB = new Date(b.departure).getTime(); + if (dateA === dateB) return new Date(a.createdAt) - new Date(b.createdAt); + else return dateA - dateB; +}; + +const Transition = React.forwardRef(function Transition(props, ref) { + return <Slide direction="up" ref={ref} {...props} />; +}); + +const CarDialog = ({open, onClose, onSelect}) => { + const classes = useStyles(); + const {t} = useTranslation(); + const strapi = useStrapi(); + const {event} = useEvent(); + + const cars = useMemo( + () => + strapi.stores.cars + ?.filter(car => car?.event?.id === event?.id) + .sort(sortCars), + [strapi.stores.cars, event] + ); + + return ( + <Dialog + fullScreen + open={open} + onClose={onClose} + TransitionComponent={Transition} + > + <AppBar> + <Toolbar> + <IconButton onClick={onClose} color="inherit"> + <Icon>arrow_back_ios</Icon> + </IconButton> + </Toolbar> + </AppBar> + <div className={classes.offset}> + <List> + {cars?.map(car => ( + <> + <ListItem + button + disabled={car.passengers?.length === car.seats} + onClick={() => onSelect(car)} + > + <ListItemText + primary={car.name} + secondary={t('passenger.creation.seats', { + seats: `${car.passengers?.length} / ${car.seats}`, + })} + /> + </ListItem> + <Divider /> + </> + ))} + </List> + </div> + </Dialog> + ); +}; + +const useStyles = makeStyles(theme => ({ + offset: { + paddingTop: theme.spacing(7), + }, +})); + +export default CarDialog;
A app/src/containers/WaitingList/index.js

@@ -0,0 +1,135 @@

+import React, {useReducer, useState, useEffect} from 'react'; +import Typography from '@material-ui/core/Typography'; +import IconButton from '@material-ui/core/IconButton'; +import Icon from '@material-ui/core/Icon'; +import Paper from '@material-ui/core/Paper'; +import Divider from '@material-ui/core/Divider'; +import {makeStyles} from '@material-ui/core/styles'; +import {Trans, useTranslation} from 'react-i18next'; +import {useStrapi} from 'strapi-react-context'; +import {useEvent} from '../../contexts/Event'; +import {useToast} from '../../contexts/Toast'; +import PassengersList from '../PassengersList'; +import RemoveDialog from '../RemoveDialog'; +import CarDialog from './CarDialog'; + +const WaitingList = ({car}) => { + const classes = useStyles(); + const {t} = useTranslation(); + const {event} = useEvent(); + const {addToast} = useToast(); + const strapi = useStrapi(); + const [passengers, setPassengers] = useState(event.waiting_list); + const [isEditing, toggleEditing] = useReducer(i => !i, false); + const [removing, setRemoving] = useState(null); + const [adding, setAdding] = useState(null); + + useEffect(() => { + setPassengers(event.waiting_list); + }, [event.waiting_list]); + + const addPassenger = async passenger => { + try { + await strapi.services.events.update(event.id, { + waiting_list: [...(event.waiting_list || []), passenger], + }); + } catch (error) { + console.error(error); + addToast(t('passenger.errors.cant_add_passenger')); + } + }; + + const removePassenger = index => { + setPassengers(passengers.filter((_, i) => i !== index)); + }; + + const savePassengers = async () => { + try { + await strapi.services.events.update(event.id, {waiting_list: passengers}); + } catch (error) { + console.error(error); + addToast(t('passenger.errors.cant_save_passengers')); + } + }; + + const selectCar = async car => { + try { + await strapi.services.cars.update(car.id, { + passengers: [...(car.passengers || []), passengers[adding]], + }); + await strapi.services.events.update(event.id, { + waiting_list: event.waiting_list.filter((_, i) => i !== adding), + }); + } catch (error) { + console.error(error); + addToast(t('passenger.errors.cant_select_car')); + } + setAdding(null); + }; + + const onEdit = () => { + if (isEditing) savePassengers(); + toggleEditing(); + }; + + const onClick = index => { + if (isEditing) setRemoving(index); + else setAdding(index); + }; + + return ( + <> + <Paper className={classes.root}> + <div className={classes.header}> + <IconButton className={classes.editBtn} onClick={onEdit}> + {isEditing ? <Icon>check</Icon> : <Icon>edit</Icon>} + </IconButton> + <Typography variant="h5">{t('passenger.title')}</Typography> + </div> + <Divider /> + <PassengersList + hideEmpty + places={50} + passengers={passengers} + addPassenger={addPassenger} + onClick={onClick} + icon={isEditing ? 'close' : 'chevron_right'} + /> + </Paper> + <RemoveDialog + text={ + <Trans + i18nKey="passenger.actions.remove_alert" + values={{name: passengers[removing]}} + components={{italic: <i />, bold: <strong />}} + /> + } + open={removing !== null} + onClose={() => setRemoving(null)} + onRemove={() => removePassenger(removing)} + /> + <CarDialog + open={adding !== null} + onClose={() => setAdding(null)} + onSelect={selectCar} + /> + </> + ); +}; + +const useStyles = makeStyles(theme => ({ + root: { + position: 'relative', + }, + header: { + padding: theme.spacing(2), + }, + editBtn: { + position: 'absolute', + top: 0, + right: 0, + zIndex: theme.zIndex.speedDial, + }, +})); + +export default WaitingList;
M app/src/locales/fr.jsonapp/src/locales/fr.json

@@ -60,12 +60,23 @@ },

"errors": { "cant_create": "Impossible de créer la voiture", "cant_update": "Impossible de modifier la voiture", - "cant_remove": "Impossile de supprimer la voiture", + "cant_remove": "Impossible de supprimer la voiture", "cant_add_passenger": "Impossible d'ajouter un passager", "cant_remove_passenger": "Impossible de supprimer le passager" } }, "passenger": { - "title": "Liste d'attente" + "title": "Liste d'attente", + "creation": { + "seats": "Nombre de passagers: {{seats}}" + }, + "actions": { + "remove_alert": "Voulez-vous vraiment supprimer <italic><bold>{{name}}</bold></italic> de la liste d'attente ?" + }, + "errors": { + "cant_add_passenger": "Impossible d'ajouter un passager", + "cant_save_passengers": "Impossible de mettre à jour passagers", + "cant_select_car": "Impossible de sélectionner la voiture" + } } }