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