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