all repos — caroster @ 27332dc9778a133a03349f1ebc56af222fff16f0

[Octree] Group carpool to your event https://caroster.io

frontend/containers/TravelColumns/index.tsx (view raw)

  1import {useMemo, useReducer, 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, useMediaQuery} from '@mui/material';
 24import useTravelsStore from '../../stores/useTravelsStore';
 25
 26interface Props {
 27  showTravelModal: () => 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  const isMobile = useMediaQuery(theme.breakpoints.down('md'));
 42
 43  const [selectedTravel, setSelectedTravel] = useState<TravelEntity>();
 44  const [mapEnabled, toggleMap] = useReducer(i => !i, true);
 45  const datesFilters = useTravelsStore(s => s.datesFilter);
 46  const {addPassenger} = usePassengersActions();
 47  const {displayedTravels} = useDisplayTravels();
 48  useDisplayMarkers({event});
 49
 50  const buttonFilterContent = useMemo(() => {
 51    if (datesFilters.length > 1) return t('event.filter.dates');
 52    else if (datesFilters.length === 1)
 53      return datesFilters.map(date => date.format('dddd Do MMMM'));
 54    else return t('event.filter.allDates');
 55  }, [datesFilters, t]);
 56
 57  const addSelfToTravel = async (travel: TravelEntity) => {
 58    const hasName = profile.firstName && profile.lastName;
 59    const userName = profile.firstName + ' ' + profile.lastName;
 60    try {
 61      await addPassenger({
 62        user: userId,
 63        email: profile.email,
 64        name: hasName ? userName : profile.username,
 65        travel: travel.id,
 66        event: event.id,
 67      });
 68      addToEvent(event.id);
 69      addToast(t('passenger.success.added_self_to_car'));
 70    } catch (error) {
 71      console.error(error);
 72    }
 73  };
 74
 75  const isCarosterPlus = event?.enabled_modules?.includes('caroster-plus');
 76
 77  const haveGeopoints =
 78    (!!event?.latitude && !!event?.longitude) ||
 79    travels?.some(
 80      ({attributes: {meeting_latitude, meeting_longitude}}) =>
 81        meeting_latitude && meeting_longitude
 82    );
 83  const showMap = mapEnabled && haveGeopoints;
 84
 85  if (!event || travels?.length === 0)
 86    return (
 87      <NoCar
 88        showImage
 89        showTravelModal={props.showTravelModal}
 90        eventName={event?.name}
 91        title={t('event.no_travel.title')}
 92        isCarosterPlus={isCarosterPlus}
 93      />
 94    );
 95
 96  const dates = Array.from(
 97    new Set(travels.map(travel => travel?.attributes?.departureDate))
 98  )
 99    .map(date => moment(date))
100    .filter(date => date.isValid())
101    .sort((a, b) => (a.isAfter(b) ? 1 : -1));
102
103  return (
104    <>
105      {showMap && <Map />}
106      <Box
107        px={3}
108        pb={2}
109        pt={showMap ? 2 : isMobile ? 15 : 10}
110        display="flex"
111        gap={2}
112        maxWidth="100%"
113        flexWrap="wrap"
114      >
115        <FilterByDate dates={dates} buttonFilterContent={buttonFilterContent} />
116        {canAddTravel() && (
117          <Button
118            onClick={props.showTravelModal}
119            aria-label="add-car"
120            variant="contained"
121            color="secondary"
122            endIcon={<Icon>add</Icon>}
123            sx={{width: {xs: 1, sm: 'auto'}}}
124          >
125            {t('travel.creation.title')}
126          </Button>
127        )}
128        {haveGeopoints && (
129          <Button
130            sx={{width: {xs: 1, sm: 'auto'}}}
131            onClick={toggleMap}
132            startIcon={<Icon>{mapEnabled ? 'visibility_off' : 'map'}</Icon>}
133          >
134            {mapEnabled ? t`travel.hideMap` : t`travel.showMap`}
135          </Button>
136        )}
137      </Box>
138      <Box
139        p={0}
140        pt={showMap ? 0 : 3}
141        pb={11}
142        sx={{
143          overflowX: 'hidden',
144          overflowY: 'auto',
145          maxHeight: showMap ? '50vh' : '100vh',
146          [theme.breakpoints.down('md')]: {
147            maxHeight: showMap ? '50vh' : '100vh',
148            px: 1,
149          },
150        }}
151      >
152        <Masonry columns={{xl: 4, lg: 3, md: 2, sm: 2, xs: 1}} spacing={0}>
153          {!canAddTravel() && (
154            <MasonryContainer key="no_other_travel">
155              <LoginToAttend title={t('event.loginToAttend')} />
156            </MasonryContainer>
157          )}
158          {displayedTravels?.map(travel => {
159            return (
160              <MasonryContainer key={travel.id}>
161                <Travel
162                  travel={travel}
163                  onAddSelf={() => addSelfToTravel(travel)}
164                  onAddOther={() => setSelectedTravel(travel)}
165                  {...props}
166                />
167              </MasonryContainer>
168            );
169          })}
170          <MasonryContainer key="no_other_travel">
171            <NoCar
172              eventName={event?.name}
173              title={t('event.no_other_travel.title')}
174              isCarosterPlus={isCarosterPlus}
175            />
176          </MasonryContainer>
177        </Masonry>
178      </Box>
179      {!!selectedTravel && (
180        <AddPassengerToTravel
181          open={!!selectedTravel}
182          toggle={() => setSelectedTravel(null)}
183          travel={selectedTravel}
184        />
185      )}
186    </>
187  );
188};
189
190export default TravelColumns;