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