frontend/containers/WaitingList/index.tsx (view raw)
1import {useReducer, useState, useMemo, useCallback} from 'react';
2import Typography from '@material-ui/core/Typography';
3import IconButton from '@material-ui/core/IconButton';
4import Icon from '@material-ui/core/Icon';
5import Paper from '@material-ui/core/Paper';
6import Divider from '@material-ui/core/Divider';
7import {makeStyles} from '@material-ui/core/styles';
8import {Trans, useTranslation} from 'react-i18next';
9import useToastStore from '../../stores/useToastStore';
10import useEventStore from '../../stores/useEventStore';
11import usePassengersActions from '../../hooks/usePassengersActions';
12import PassengersList from '../PassengersList';
13import RemoveDialog from '../RemoveDialog';
14import AddPassengerButtons from '../AddPassengerButtons';
15import ClearButton from '../ClearButton';
16import AssignButton from './AssignButton';
17import TravelDialog from './TravelDialog';
18import Button from '@material-ui/core/Button';
19import router from 'next/dist/client/router';
20import Box from '@material-ui/core/Box';
21import Container from '@material-ui/core/Container';
22
23interface Props {
24 getToggleNewPassengerDialogFunction: (addSelf: boolean) => () => void;
25 canAddSelf: boolean;
26}
27
28const WaitingList = ({
29 getToggleNewPassengerDialogFunction,
30 canAddSelf,
31}: Props) => {
32 const classes = useStyles();
33 const {t} = useTranslation();
34 const clearToast = useToastStore(s => s.clearToast);
35 const event = useEventStore(s => s.event);
36 const addToast = useToastStore(s => s.addToast);
37 const [isEditing, toggleEditing] = useReducer(i => !i, false);
38 const [removingPassenger, setRemovingPassenger] = useState(null);
39 const [addingPassenger, setAddingPassenger] = useState(null);
40 const travels =
41 event?.travels?.length > 0 ? event?.travels.slice().sort(sortTravels) : [];
42 const {updatePassenger, removePassenger} = usePassengersActions();
43
44 const availability = useMemo(() => {
45 if (!travels) return;
46 return travels.reduce((count, {vehicle, passengers = []}) => {
47 if (!vehicle) return 0;
48 else if (!passengers) return count + vehicle.seats;
49 return count + vehicle.seats - passengers.length;
50 }, 0);
51 }, [travels]);
52
53 const removePassengerCallback = useCallback(removePassenger, [event]);
54
55 const selectTravel = useCallback(
56 async travel => {
57 try {
58 await updatePassenger(addingPassenger.id, {
59 event: null,
60 travel: travel.id,
61 });
62 setAddingPassenger(null);
63 addToast(
64 t('passenger.success.added_to_car', {
65 name: addingPassenger.name,
66 }),
67 <Button
68 size="small"
69 color="primary"
70 variant="contained"
71 onClick={() => {
72 router.push(`/e/${event.uuid}`);
73 clearToast();
74 }}
75 >
76 {t('passenger.success.goToTravels')}
77 </Button>
78 );
79 } catch (error) {
80 console.error(error);
81 addToast(t('passenger.errors.cant_select_travel'));
82 }
83 },
84 [event, addingPassenger] // eslint-disable-line
85 );
86
87 const onPress = useCallback(
88 (passengerId: string) => {
89 const selectedPassenger = event?.waitingPassengers.find(
90 item => item.id === passengerId
91 );
92 if (isEditing) setRemovingPassenger(selectedPassenger);
93 else setAddingPassenger(selectedPassenger);
94 },
95 [isEditing, event]
96 );
97
98 const onRemove = async () => {
99 try {
100 await removePassengerCallback(removingPassenger.id);
101 } catch (error) {
102 console.error(error);
103 addToast(t('passenger.errors.cant_remove_passenger'));
104 }
105 };
106
107 const ListButton = isEditing
108 ? ({onClick}: {onClick: () => void}) => (
109 <ClearButton icon="close" onClick={onClick} tabIndex={-1} />
110 )
111 : ({onClick, disabled}: {onClick: () => void; disabled: boolean}) => (
112 <AssignButton onClick={onClick} tabIndex={-1} disabled={disabled} />
113 );
114
115 return (
116 <Box className={classes.root}>
117 <Container maxWidth="sm" className={classes.card}>
118 <Paper>
119 <div className={classes.header}>
120 <IconButton
121 size="small"
122 color="primary"
123 className={classes.editBtn}
124 disabled={!event?.waitingPassengers?.length}
125 onClick={toggleEditing}
126 >
127 {isEditing ? <Icon>check</Icon> : <Icon>edit</Icon>}
128 </IconButton>
129 <Typography variant="h5">{t('passenger.title')}</Typography>
130 <Typography variant="overline">
131 {t('passenger.availability.seats', {count: availability})}
132 </Typography>
133 </div>
134 <Divider />
135 <AddPassengerButtons
136 getOnClickFunction={getToggleNewPassengerDialogFunction}
137 canAddSelf={canAddSelf}
138 variant="waitingList"
139 />
140 <Divider />
141 <PassengersList
142 passengers={event?.waitingPassengers}
143 onPress={onPress}
144 Button={ListButton}
145 />
146 </Paper>
147 </Container>
148 <RemoveDialog
149 text={
150 <Trans
151 i18nKey="passenger.actions.remove_alert"
152 values={{
153 name: removingPassenger?.name,
154 }}
155 components={{italic: <i />, bold: <strong />}}
156 />
157 }
158 open={!!removingPassenger}
159 onClose={() => setRemovingPassenger(null)}
160 onRemove={onRemove}
161 />
162 <TravelDialog
163 eventName={event?.name}
164 travels={travels}
165 passenger={addingPassenger}
166 open={!!addingPassenger}
167 onClose={() => setAddingPassenger(null)}
168 onSelect={selectTravel}
169 />
170 </Box>
171 );
172};
173
174const sortTravels = (a, b) => {
175 const dateA = new Date(a.departure).getTime();
176 const dateB = new Date(b.departure).getTime();
177 if (dateA === dateB)
178 return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
179 else return dateA - dateB;
180};
181
182const useStyles = makeStyles(theme => ({
183 root: {
184 position: 'relative',
185 paddingLeft: '80px',
186
187 [theme.breakpoints.down('sm')]: {
188 paddingLeft: 0,
189 },
190 },
191 card: {
192 marginTop: theme.spacing(6),
193 },
194 header: {
195 position: 'relative',
196 padding: theme.spacing(2),
197 },
198 editBtn: {
199 position: 'absolute',
200 top: 0,
201 right: 0,
202 margin: theme.spacing(1),
203 zIndex: theme.zIndex.speedDial,
204 },
205}));
206
207export default WaitingList;