frontend/containers/WaitingList/index.tsx (view raw)
1import {useReducer, useState, useMemo, useCallback} from 'react';
2import router from 'next/dist/client/router';
3import useMediaQuery from '@mui/material/useMediaQuery';
4import Container from '@mui/material/Container';
5import TuneIcon from '@mui/icons-material/Tune';
6import Box from '@mui/material/Box';
7import Typography from '@mui/material/Typography';
8import IconButton from '@mui/material/IconButton';
9import Icon from '@mui/material/Icon';
10import Paper from '@mui/material/Paper';
11import Divider from '@mui/material/Divider';
12import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
13import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';
14import {Trans, useTranslation} from 'next-i18next';
15import useToastStore from '../../stores/useToastStore';
16import useEventStore from '../../stores/useEventStore';
17import usePassengersActions from '../../hooks/usePassengersActions';
18import RemoveDialog from '../RemoveDialog';
19import AddPassengerButtons from '../AddPassengerButtons';
20import AssignButton from './AssignButton';
21import PassengersList from '../PassengersList';
22import theme from '../../theme';
23
24interface Props {
25 canAddSelf: boolean;
26 registered: boolean;
27 onAddSelf: () => void;
28 onAddOther: () => void;
29}
30
31const WaitingList = (props: Props) => {
32 const {canAddSelf, registered} = props;
33 const {t} = useTranslation();
34 const event = useEventStore(s => s.event);
35 const mobile = useMediaQuery(theme.breakpoints.down('md'));
36 const addToast = useToastStore(s => s.addToast);
37 const [isEditing, toggleEditing] = useReducer(i => !i, false);
38 const [removingPassenger, setRemovingPassenger] = useState(null);
39 const travels = useMemo(
40 () =>
41 event?.travels?.data?.length > 0
42 ? event?.travels?.data.slice().sort(sortTravels)
43 : [],
44 [event?.travels?.data]
45 );
46 const {removePassenger} = usePassengersActions();
47
48 const availability = useMemo(() => {
49 if (!travels) return;
50 return travels.reduce((count, {attributes: {seats, passengers}}) => {
51 if (!seats) return 0;
52 else if (!passengers) return count + seats;
53 return count + seats - passengers?.data?.length;
54 }, 0);
55 }, [travels]);
56
57 const removePassengerCallback = useCallback(removePassenger, [
58 event,
59 removePassenger,
60 ]);
61
62 const onClickPassenger = useCallback(
63 (passengerId: string) => {
64 const selectedPassenger = event?.waitingPassengers?.data.find(
65 item => item.id === passengerId
66 );
67 if (isEditing) setRemovingPassenger(selectedPassenger);
68 else router.push(`/e/${event.uuid}/assign/${selectedPassenger.id}`);
69 },
70 [isEditing, event]
71 );
72
73 const onRemove = async () => {
74 try {
75 await removePassengerCallback(removingPassenger.id);
76 addToast(t('passenger.deleted'));
77 } catch (error) {
78 console.error(error);
79 addToast(t('passenger.errors.cant_remove_passenger'));
80 }
81 };
82
83 return (
84 <Container
85 maxWidth="sm"
86 sx={{mt: mobile ? 15 : 11, mx: 0, px: mobile ? 2 : 4}}
87 >
88 <Paper sx={{width: '480px', maxWidth: '100%', position: 'relative'}}>
89 <Box p={2}>
90 <IconButton
91 size="small"
92 color="primary"
93 sx={{
94 position: 'absolute',
95 top: 0,
96 right: 0,
97 margin: 1,
98 }}
99 disabled={!event?.waitingPassengers?.data?.length}
100 onClick={toggleEditing}
101 >
102 {isEditing ? <Icon>check</Icon> : <TuneIcon />}
103 </IconButton>
104 <Typography variant="h5">{t('passenger.title')}</Typography>
105 <Typography variant="overline">
106 {t('passenger.availability.seats', {count: availability})}
107 </Typography>
108 </Box>
109 <Divider />
110 <AddPassengerButtons
111 onAddOther={props.onAddOther}
112 onAddSelf={props.onAddSelf}
113 canAddSelf={canAddSelf}
114 registered={registered}
115 variant="waitingList"
116 />
117 <Divider />
118 {event?.waitingPassengers?.data?.length > 0 && (
119 <PassengersList
120 passengers={event.waitingPassengers.data}
121 onClickPassenger={onClickPassenger}
122 Actions={({passenger}) =>
123 isEditing ? (
124 <ListItemSecondaryAction>
125 <IconButton size="small" color="primary">
126 <CancelOutlinedIcon />
127 </IconButton>
128 </ListItemSecondaryAction>
129 ) : (
130 <AssignButton
131 tabIndex={-1}
132 onClick={() => onClickPassenger(passenger.id)}
133 />
134 )
135 }
136 />
137 )}
138 </Paper>
139 <RemoveDialog
140 text={
141 <Trans
142 i18nKey="passenger.actions.remove_alert"
143 values={{
144 name: removingPassenger?.name,
145 }}
146 components={{italic: <i />, bold: <strong />}}
147 />
148 }
149 open={!!removingPassenger}
150 onClose={() => setRemovingPassenger(null)}
151 onRemove={onRemove}
152 />
153 </Container>
154 );
155};
156
157const sortTravels = (a, b) => {
158 const dateA = new Date(a.departureDate).getTime();
159 const dateB = new Date(b.departureDate).getTime();
160 if (dateA === dateB)
161 return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
162 else return dateA - dateB;
163};
164
165export default WaitingList;