frontend/containers/TravelColumns/index.tsx (view raw)
1import {useMemo, useState} from 'react';
2import Masonry from '@mui/lab/Masonry';
3import Box from '@mui/material/Box';
4import moment from 'moment';
5import {useTranslation} from 'next-i18next';
6import {useTheme} from '@mui/material/styles';
7import useEventStore from '../../stores/useEventStore';
8import useToastStore from '../../stores/useToastStore';
9import useProfile from '../../hooks/useProfile';
10import useAddToEvents from '../../hooks/useAddToEvents';
11import usePassengersActions from '../../hooks/usePassengersActions';
12import Map from '../Map';
13import Travel from '../Travel';
14import NoCar from './NoCar';
15import {TravelEntity} from '../../generated/graphql';
16import {AddPassengerToTravel} from '../NewPassengerDialog';
17import MasonryContainer from './MasonryContainer';
18import LoginToAttend from '../LoginToAttend/LoginToAttend';
19import usePermissions from '../../hooks/usePermissions';
20import useDisplayTravels from './useDisplayTravels';
21import useDisplayMarkers from './useDisplayMarkers';
22import FilterByDate from './FilterByDate';
23import {Button, Icon} from '@mui/material';
24import useTravelsStore from '../../stores/travelsStore';
25
26interface Props {
27 toggle: () => void;
28}
29
30const TravelColumns = (props: Props) => {
31 const theme = useTheme();
32 const event = useEventStore(s => s.event);
33 const travels = event?.travels?.data || [];
34 const {t} = useTranslation();
35 const addToast = useToastStore(s => s.addToast);
36 const {addToEvent} = useAddToEvents();
37 const {profile, userId} = useProfile();
38 const {
39 userPermissions: {canAddTravel},
40 } = usePermissions();
41
42 const [selectedTravel, setSelectedTravel] = useState<TravelEntity>();
43 const datesFilters = useTravelsStore(s => s.datesFilter);
44 const {addPassenger} = usePassengersActions();
45 const {displayedTravels} = useDisplayTravels();
46 useDisplayMarkers({event});
47
48 const buttonFilterContent = useMemo(() => {
49 if (datesFilters.length > 1) return t('event.filter.dates');
50 else if (datesFilters.length === 1)
51 return datesFilters.map(date => date.format('dddd Do MMMM'));
52 else return t('event.filter.allDates');
53 }, [datesFilters, t]);
54
55 const addSelfToTravel = async (travel: TravelEntity) => {
56 const hasName = profile.firstName && profile.lastName;
57 const userName = profile.firstName + ' ' + profile.lastName;
58 try {
59 await addPassenger({
60 user: userId,
61 email: profile.email,
62 name: hasName ? userName : profile.username,
63 travel: travel.id,
64 event: event.id,
65 });
66 addToEvent(event.id);
67 addToast(t('passenger.success.added_self_to_car'));
68 } catch (error) {
69 console.error(error);
70 }
71 };
72
73 const isCarosterPlus = event?.enabled_modules?.includes('caroster-plus');
74
75 const showMap =
76 (!!event?.latitude && !!event?.longitude) ||
77 travels?.some(
78 ({attributes: {meeting_latitude, meeting_longitude}}) =>
79 meeting_latitude && meeting_longitude
80 );
81
82 if (!event || travels?.length === 0)
83 return (
84 <NoCar
85 showImage
86 eventName={event?.name}
87 title={t('event.no_travel.title')}
88 isCarosterPlus={isCarosterPlus}
89 />
90 );
91
92 const dates = Array.from(
93 new Set(travels.map(travel => travel?.attributes?.departureDate))
94 )
95 .map(date => moment(date))
96 .filter(date => date.isValid())
97 .sort((a, b) => (a.isAfter(b) ? 1 : -1));
98
99 return (
100 <>
101 {showMap && <Map />}
102 <Box px={3} py={2} display="flex" gap={2}>
103 <FilterByDate dates={dates} buttonFilterContent={buttonFilterContent} />
104 {canAddTravel() && (
105 <Button
106 onClick={props.toggle}
107 aria-label="add-car"
108 variant="contained"
109 color="secondary"
110 endIcon={<Icon>add</Icon>}
111 >
112 {t('travel.creation.title')}
113 </Button>
114 )}
115 </Box>
116 <Box
117 p={0}
118 pt={showMap ? 0 : 13}
119 pb={11}
120 sx={{
121 overflowX: 'hidden',
122 overflowY: 'auto',
123 maxHeight: showMap ? '50vh' : '100vh',
124 [theme.breakpoints.down('md')]: {
125 maxHeight: showMap ? '50vh' : '100vh',
126 px: 1,
127 },
128 }}
129 >
130 <Masonry columns={{xl: 4, lg: 3, md: 2, sm: 2, xs: 1}} spacing={0}>
131 {!canAddTravel() && (
132 <MasonryContainer key="no_other_travel">
133 <LoginToAttend title={t('event.loginToAttend')} />
134 </MasonryContainer>
135 )}
136 {displayedTravels?.map(travel => {
137 return (
138 <MasonryContainer key={travel.id}>
139 <Travel
140 travel={travel}
141 onAddSelf={() => addSelfToTravel(travel)}
142 onAddOther={() => setSelectedTravel(travel)}
143 {...props}
144 />
145 </MasonryContainer>
146 );
147 })}
148 <MasonryContainer key="no_other_travel">
149 <NoCar
150 eventName={event?.name}
151 title={t('event.no_other_travel.title')}
152 isCarosterPlus={isCarosterPlus}
153 />
154 </MasonryContainer>
155 </Masonry>
156 </Box>
157 {!!selectedTravel && (
158 <AddPassengerToTravel
159 open={!!selectedTravel}
160 toggle={() => setSelectedTravel(null)}
161 travel={selectedTravel}
162 />
163 )}
164 </>
165 );
166};
167
168export default TravelColumns;