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 PassengersList from '../PassengersList';
13import RemoveDialog from '../RemoveDialog';
14import AddPassengerButtons from '../AddPassengerButtons';
15import TravelDialog from './TravelDialog';
16import ClearButton from '../ClearButton';
17import AssignButton from './AssignButton';
18import usePassengersActions from '../../hooks/usePassengersActions';
19
20interface Props {
21 getToggleNewPassengerDialogFunction: (addSelf: boolean) => () => void;
22 canAddSelf: boolean;
23 slideToTravel: (travelId: string) => void;
24}
25
26const WaitingList = ({
27 getToggleNewPassengerDialogFunction,
28 canAddSelf,
29 slideToTravel,
30}: Props) => {
31 const classes = useStyles();
32 const {t} = useTranslation();
33 const event = useEventStore(s => s.event);
34 const addToast = useToastStore(s => s.addToast);
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 {updatePassenger, removePassenger} = usePassengersActions();
41
42 const availability = useMemo(() => {
43 if (!travels) return;
44 return travels.reduce((count, {vehicle, passengers = []}) => {
45 if (!vehicle) return 0;
46 else if (!passengers) return count + vehicle.seats;
47 return count + vehicle.seats - passengers.length;
48 }, 0);
49 }, [travels]);
50
51 const removePassengerCallback = useCallback(removePassenger, [event]);
52
53 const selectTravel = useCallback(
54 async travel => {
55 try {
56 await updatePassenger(addingPassenger.id, {
57 event: null,
58 travel: travel.id,
59 });
60 setAddingPassenger(null);
61 slideToTravel(travel.id);
62 addToast(
63 t('passenger.success.added_to_car', {
64 name: addingPassenger.name,
65 })
66 );
67 } catch (error) {
68 console.error(error);
69 addToast(t('passenger.errors.cant_select_travel'));
70 }
71 },
72 [event, addingPassenger] // eslint-disable-line
73 );
74
75 const onPress = useCallback(
76 (passengerId: string) => {
77 const selectedPassenger = event.waitingPassengers.find(
78 item => item.id === passengerId
79 );
80 if (isEditing) setRemovingPassenger(selectedPassenger);
81 else setAddingPassenger(selectedPassenger);
82 },
83 [isEditing, event]
84 );
85
86 const onRemove = async () => {
87 try {
88 await removePassengerCallback(removingPassenger.id);
89 } catch (error) {
90 console.error(error);
91 addToast(t('passenger.errors.cant_remove_passenger'));
92 }
93 };
94
95 const ListButton = isEditing
96 ? ({onClick}: {onClick: () => void}) => (
97 <ClearButton icon="close" onClick={onClick} tabIndex={-1} />
98 )
99 : ({onClick, disabled}: {onClick: () => void; disabled: boolean}) => (
100 <AssignButton onClick={onClick} tabIndex={-1} disabled={disabled} />
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.waitingPassengers?.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.waitingPassengers}
130 onPress={onPress}
131 Button={ListButton}
132 />
133 </Paper>
134 <RemoveDialog
135 text={
136 <Trans
137 i18nKey="passenger.actions.remove_alert"
138 values={{
139 name: removingPassenger?.name,
140 }}
141 components={{italic: <i />, bold: <strong />}}
142 />
143 }
144 open={!!removingPassenger}
145 onClose={() => setRemovingPassenger(null)}
146 onRemove={onRemove}
147 />
148 <TravelDialog
149 eventName={event.name}
150 travels={travels}
151 passenger={addingPassenger}
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;