frontend/containers/TravelColumns/index.tsx (view raw)
1import {useState} from 'react';
2import Container from '@mui/material/Container';
3import Masonry from '@mui/lab/Masonry';
4import Box from '@mui/material/Box';
5import {useTranslation} from 'react-i18next';
6import {useTheme} from '@mui/material/styles';
7import useEventStore from '../../stores/useEventStore';
8import useToastStore from '../../stores/useToastStore';
9import useMapStore from '../../stores/useMapStore';
10import useProfile from '../../hooks/useProfile';
11import useAddToEvents from '../../hooks/useAddToEvents';
12import usePassengersActions from '../../hooks/usePassengersActions';
13import Map from '../Map';
14import Travel from '../Travel';
15import NoCar from './NoCar';
16import TravelPopup from './TravelPopup';
17import EventPopup from '../EventPopup';
18import {Travel as TravelData, TravelEntity} from '../../generated/graphql';
19import {AddPassengerToTravel} from '../NewPassengerDialog';
20
21type TravelType = TravelData & {id: string};
22
23interface Props {
24 toggle: () => void;
25}
26
27const TravelColumns = (props: Props) => {
28 const theme = useTheme();
29 const {preventUpdateKey, setPreventUpdateKey, setCenter, setMarkers} =
30 useMapStore();
31 const event = useEventStore(s => s.event);
32 const travels = event?.travels?.data || [];
33 const {t} = useTranslation();
34 const addToast = useToastStore(s => s.addToast);
35 const {addToEvent} = useAddToEvents();
36 const {profile, userId} = useProfile();
37
38 const [newPassengerTravelContext, toggleNewPassengerToTravel] = useState<{
39 travel: TravelType;
40 } | null>(null);
41 const {addPassenger} = usePassengersActions();
42 const sortedTravels = travels?.slice().sort(sortTravels);
43
44 const addSelfToTravel = async (travel: TravelType) => {
45 const hasName = profile.firstName && profile.lastName;
46 const userName = profile.firstName + ' ' + profile.lastName;
47 try {
48 await addPassenger({
49 user: userId,
50 email: profile.email,
51 name: hasName ? userName : profile.username,
52 travel: travel.id,
53 event: event.id,
54 });
55 addToEvent(event.id);
56 addToast(t('passenger.success.added_self_to_car'));
57 } catch (error) {
58 console.error(error);
59 }
60 };
61
62 if (!event || travels?.length === 0)
63 return (
64 <NoCar
65 showImage
66 eventName={event?.name}
67 title={t('event.no_travel.title')}
68 />
69 );
70
71 const {latitude, longitude} = event;
72 const showMap = latitude && longitude;
73 let coordsString = `${latitude}${longitude}`;
74 const markers = travels.reduce((markers, travel) => {
75 const {
76 attributes: {meeting_latitude, meeting_longitude},
77 } = travel;
78 if (meeting_latitude && meeting_longitude) {
79 const travelObject = {id: travel.id, ...travel.attributes};
80 coordsString =
81 coordsString + String(meeting_latitude) + String(meeting_longitude);
82 return [
83 ...markers,
84 {
85 center: [meeting_latitude, meeting_longitude],
86 popup: <TravelPopup travel={travelObject} />,
87 },
88 ];
89 }
90 return markers;
91 }, []);
92
93 const mapUpdateKey = `${event.uuid}.travels+${coordsString}`;
94 if (preventUpdateKey !== mapUpdateKey) {
95 setPreventUpdateKey(mapUpdateKey);
96 setCenter([latitude, longitude]);
97 setMarkers([
98 {
99 double: true,
100 center: [latitude, longitude],
101 popup: <EventPopup event={event} />,
102 },
103 ...markers,
104 ]);
105 }
106
107 return (
108 <>
109 {showMap && <Map />}
110 <Box
111 p={0}
112 pt={showMap ? 4 : 9}
113 pb={11}
114 sx={{
115 overflowX: 'hidden',
116 overflowY: 'auto',
117 maxHeight: showMap ? '50vh' : '100vh',
118 [theme.breakpoints.down('md')]: {
119 maxHeight: showMap ? '50vh' : '100vh',
120 px: 1,
121 },
122 }}
123 >
124 <Masonry columns={{xl: 4, lg: 3, md: 2, sm: 2, xs: 1}} spacing={0}>
125 {sortedTravels?.map(({id, attributes}) => {
126 const travel = {id, ...attributes};
127 return (
128 <Container
129 key={travel.id}
130 maxWidth="sm"
131 sx={{
132 padding: theme.spacing(1),
133 marginBottom: theme.spacing(10),
134 outline: 'none',
135 '& > *': {
136 cursor: 'default',
137 },
138
139 [theme.breakpoints.down('md')]: {
140 marginBottom: `calc(${theme.spacing(10)} + 56px)`,
141 },
142 }}
143 >
144 <Travel
145 travel={travel}
146 {...props}
147 getAddPassengerFunction={(addSelf: boolean) => () =>
148 addSelf
149 ? addSelfToTravel(travel)
150 : toggleNewPassengerToTravel({travel})}
151 />
152 </Container>
153 );
154 })}
155 <Container
156 maxWidth="sm"
157 sx={{
158 padding: theme.spacing(1),
159 marginBottom: theme.spacing(10),
160 outline: 'none',
161 '& > *': {
162 cursor: 'default',
163 },
164
165 [theme.breakpoints.down('md')]: {
166 marginBottom: `calc(${theme.spacing(10)} + 56px)`,
167 },
168 }}
169 >
170 <NoCar
171 eventName={event?.name}
172 title={t('event.no_other_travel.title')}
173 />
174 </Container>
175 </Masonry>
176 </Box>
177 {!!newPassengerTravelContext && (
178 <AddPassengerToTravel
179 open={!!newPassengerTravelContext}
180 toggle={() => toggleNewPassengerToTravel(null)}
181 travel={newPassengerTravelContext.travel}
182 />
183 )}
184 </>
185 );
186};
187
188const sortTravels = (
189 {attributes: a}: TravelEntity,
190 {attributes: b}: TravelEntity
191) => {
192 if (!b) return 1;
193 const dateA = new Date(a.departure).getTime();
194 const dateB = new Date(b.departure).getTime();
195 if (dateA === dateB)
196 return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
197 else return dateA - dateB;
198};
199
200export default TravelColumns;