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