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