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