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