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