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 'react-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 maxWidth="sm" sx={{mt: 11, mx: 0, px: mobile ? 2 : 4}}>
86 <Paper sx={{width: '480px', maxWidth: '100%', position: 'relative'}}>
87 <Box p={2}>
88 <IconButton
89 size="small"
90 color="primary"
91 sx={{
92 position: 'absolute',
93 top: 0,
94 right: 0,
95 margin: 1,
96 }}
97 disabled={!event?.waitingPassengers?.data?.length}
98 onClick={toggleEditing}
99 >
100 {isEditing ? <Icon>check</Icon> : <TuneIcon />}
101 </IconButton>
102 <Typography variant="h5">{t('passenger.title')}</Typography>
103 <Typography variant="overline">
104 {t('passenger.availability.seats', {count: availability})}
105 </Typography>
106 </Box>
107 <Divider />
108 <AddPassengerButtons
109 onAddOther={props.onAddOther}
110 onAddSelf={props.onAddSelf}
111 canAddSelf={canAddSelf}
112 registered={registered}
113 variant="waitingList"
114 />
115 <Divider />
116 {event?.waitingPassengers?.data?.length > 0 && (
117 <PassengersList
118 passengers={event.waitingPassengers.data}
119 onClickPassenger={onClickPassenger}
120 Actions={({passenger}) =>
121 isEditing ? (
122 <ListItemSecondaryAction>
123 <IconButton size="small" color="primary">
124 <CancelOutlinedIcon />
125 </IconButton>
126 </ListItemSecondaryAction>
127 ) : (
128 <AssignButton
129 tabIndex={-1}
130 onClick={() => onClickPassenger(passenger.id)}
131 />
132 )
133 }
134 />
135 )}
136 </Paper>
137 <RemoveDialog
138 text={
139 <Trans
140 i18nKey="passenger.actions.remove_alert"
141 values={{
142 name: removingPassenger?.name,
143 }}
144 components={{italic: <i />, bold: <strong />}}
145 />
146 }
147 open={!!removingPassenger}
148 onClose={() => setRemovingPassenger(null)}
149 onRemove={onRemove}
150 />
151 </Container>
152 );
153};
154
155const sortTravels = (a, b) => {
156 const dateA = new Date(a.departure).getTime();
157 const dateB = new Date(b.departure).getTime();
158 if (dateA === dateB)
159 return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
160 else return dateA - dateB;
161};
162
163export default WaitingList;