frontend/containers/WaitingList/index.tsx (view raw)
1import {useReducer, useState, useMemo, useCallback} from 'react';
2import {makeStyles} from '@material-ui/core/styles';
3import Typography from '@material-ui/core/Typography';
4import IconButton from '@material-ui/core/IconButton';
5import Icon from '@material-ui/core/Icon';
6import Paper from '@material-ui/core/Paper';
7import Divider from '@material-ui/core/Divider';
8import clsx from 'clsx';
9import {Trans, useTranslation} from 'react-i18next';
10import useToastStore from '../../stores/useToastStore';
11import useEventStore from '../../stores/useEventStore';
12import useAddToEvents from '../../hooks/useAddToEvents';
13import PassengersList from '../PassengersList';
14import RemoveDialog from '../RemoveDialog';
15import AddPassengerButtons from '../AddPassengerButtons';
16import TravelDialog from './TravelDialog';
17import ClearButton from '../ClearButton';
18import AssignButton from './AssignButton';
19import usePassengersActions from '../../hooks/usePassengersActions';
20
21interface Props {
22 getToggleNewPassengerDialogFunction: (addSelf: boolean) => () => void;
23 canAddSelf: boolean;
24}
25
26const WaitingList = ({
27 getToggleNewPassengerDialogFunction,
28 canAddSelf,
29}: Props) => {
30 const classes = useStyles();
31 const {t} = useTranslation();
32 const event = useEventStore(s => s.event);
33 const addToast = useToastStore(s => s.addToast);
34 const {addToEvent} = useAddToEvents();
35 const [isEditing, toggleEditing] = useReducer(i => !i, false);
36 const [removingPassenger, setRemovingPassenger] = useState(null);
37 const [addingPassenger, setAddingPassenger] = useState(null);
38 const travels =
39 event?.travels?.length > 0 ? event.travels.slice().sort(sortTravels) : [];
40 const {addPassengerToTravel, removePassengerFromWaitingList} = usePassengersActions();
41
42 const availability = useMemo(() => {
43 if (!travels) return;
44 return travels.reduce((count, {vehicle, passengers = []}) => {
45 if (!passengers) return count + vehicle.seats;
46 return count + vehicle.seats - passengers.length;
47 }, 0);
48 }, [travels]);
49
50 const removePassengerFromWaitingListFallBack = useCallback(removePassengerFromWaitingList, [event]);
51
52 const selectTravel = useCallback(
53 async travel => {
54 const {id, ...passenger} = addingPassenger;
55 const onError = () => addToast(t('passenger.errors.cant_select_travel'));
56 addPassengerToTravel({
57 travel,
58 passenger,
59 onError,
60 onSucceed: () =>
61 removePassengerFromWaitingListFallBack({
62 passenger: addingPassenger,
63 event: {
64 ...event,
65 waitingList: event.waitingList.filter(
66 item => item.id !== addingPassenger.id
67 ),
68 },
69
70 onError,
71 onSucceed: () => setAddingPassenger(null),
72 }),
73 });
74 },
75 [event, addingPassenger] // eslint-disable-line
76 );
77
78 const onPress = useCallback(
79 (passengerId: string) => {
80 const selectedPassenger = event.waitingList.find(
81 item => item.id === passengerId
82 );
83 if (isEditing) setRemovingPassenger(selectedPassenger);
84 else setAddingPassenger(selectedPassenger);
85 },
86 [isEditing, event]
87 );
88
89 const ListButton = isEditing
90 ? ({onClick}: {onClick: () => void}) => (
91 <ClearButton icon="close" onClick={onClick} tabIndex={-1} />
92 )
93 : ({onClick}: {onClick: () => void}) => (
94 <AssignButton onClick={onClick} tabIndex={-1} />
95 );
96
97 return (
98 <>
99 <Paper className={classes.root}>
100 <div className={clsx(classes.header, 'tour_waiting_list')}>
101 <IconButton
102 size="small"
103 color="primary"
104 className={classes.editBtn}
105 disabled={!event.waitingList?.length}
106 onClick={toggleEditing}
107 >
108 {isEditing ? <Icon>check</Icon> : <Icon>edit</Icon>}
109 </IconButton>
110 <Typography variant="h5">{t('passenger.title')}</Typography>
111 <Typography variant="overline">
112 {t('passenger.availability.seats', {count: availability})}
113 </Typography>
114 </div>
115 <Divider />
116 <AddPassengerButtons
117 getOnClickFunction={getToggleNewPassengerDialogFunction}
118 canAddSelf={canAddSelf}
119 />
120 <Divider />
121 <PassengersList
122 passengers={event.waitingList}
123 onPress={onPress}
124 Button={ListButton}
125 disabled={!isEditing && availability <= 0}
126 />
127 </Paper>
128 <RemoveDialog
129 text={
130 <Trans
131 i18nKey="passenger.actions.remove_alert"
132 values={{
133 name: removingPassenger?.name,
134 }}
135 components={{italic: <i />, bold: <strong />}}
136 />
137 }
138 open={!!removingPassenger}
139 onClose={() => setRemovingPassenger(null)}
140 onRemove={() =>
141 removePassengerFromWaitingListFallBack({
142 passenger: removingPassenger,
143 event,
144 onSucceed: () => addToEvent(event.id),
145 onError: () =>
146 addToast(t('passenger.errors.cant_remove_passenger')),
147 })
148 }
149 />
150 <TravelDialog
151 travels={travels}
152 open={!!addingPassenger}
153 onClose={() => setAddingPassenger(null)}
154 onSelect={selectTravel}
155 />
156 </>
157 );
158};
159
160const sortTravels = (a, b) => {
161 const dateA = new Date(a.departure).getTime();
162 const dateB = new Date(b.departure).getTime();
163 if (dateA === dateB)
164 return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
165 else return dateA - dateB;
166};
167
168const useStyles = makeStyles(theme => ({
169 root: {
170 position: 'relative',
171 },
172 header: {
173 padding: theme.spacing(2),
174 },
175 editBtn: {
176 position: 'absolute',
177 top: 0,
178 right: 0,
179 margin: theme.spacing(1),
180 zIndex: theme.zIndex.speedDial,
181 },
182}));
183
184export default WaitingList;