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