all repos — caroster @ 8a4f9e069b406ad3b9c2405dbef767b2a5304477

[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 useEventStore from '../../stores/useEventStore';
  7import useToastStore from '../../stores/useToastStore';
  8import useProfile from '../../hooks/useProfile';
  9import useAddToEvents from '../../hooks/useAddToEvents';
 10import usePassengersActions from '../../hooks/usePassengersActions';
 11import Map from '../Map';
 12import Travel from '../Travel';
 13import NoCar from './NoCar';
 14import {TravelEntity} from '../../generated/graphql';
 15import {AddPassengerToTravel} from '../NewPassengerDialog';
 16import MasonryContainer from './MasonryContainer';
 17import useDisplayTravels from './useDisplayTravels';
 18import useDisplayMarkers from './useDisplayMarkers';
 19import FilterByDate from './FilterByDate';
 20import {Button, Icon, useMediaQuery} from '@mui/material';
 21import useTravelsStore from '../../stores/useTravelsStore';
 22import AddTravelButton from '../AddTravelButton';
 23import MapActions from '../MapActions';
 24import theme from '../../theme';
 25
 26interface Props {}
 27
 28const TravelColumns = (props: Props) => {
 29  const event = useEventStore(s => s.event);
 30  const travels = event?.travels?.data || [];
 31  const {t} = useTranslation();
 32  const addToast = useToastStore(s => s.addToast);
 33  const {addToEvent} = useAddToEvents();
 34  const {profile, userId} = useProfile();
 35  const isMobile = useMediaQuery(theme.breakpoints.down('md'));
 36
 37  const [selectedTravel, setSelectedTravel] = useState<TravelEntity>();
 38  const [mapEnabled, toggleMap] = useReducer(i => !i, true);
 39  const datesFilters = useTravelsStore(s => s.datesFilter);
 40  const {addPassenger} = usePassengersActions();
 41  const {displayedTravels} = useDisplayTravels();
 42  useDisplayMarkers({event});
 43
 44  const buttonFilterContent = useMemo(() => {
 45    if (datesFilters.length > 1) return t('event.filter.dates');
 46    else if (datesFilters.length === 1)
 47      return datesFilters.map(date => date.format('dddd Do MMMM'));
 48    else return t('event.filter.allDates');
 49  }, [datesFilters, t]);
 50
 51  const addSelfToTravel = async (travel: TravelEntity) => {
 52    const hasName = profile.firstName && profile.lastName;
 53    const userName = profile.firstName + ' ' + profile.lastName;
 54    try {
 55      await addPassenger({
 56        user: userId,
 57        email: profile.email,
 58        name: hasName ? userName : profile.username,
 59        travel: travel.id,
 60        event: event.id,
 61      });
 62      addToEvent(event.id);
 63      addToast(t('passenger.success.added_self_to_car'));
 64    } catch (error) {
 65      console.error(error);
 66    }
 67  };
 68
 69  const isCarosterPlus = event?.enabled_modules?.includes('caroster-plus');
 70
 71  const haveGeopoints =
 72    (!!event?.latitude && !!event?.longitude) ||
 73    travels?.some(
 74      ({attributes: {meeting_latitude, meeting_longitude}}) =>
 75        meeting_latitude && meeting_longitude
 76    );
 77  const showMap = mapEnabled && haveGeopoints;
 78
 79  if (!event || travels?.length === 0)
 80    return (
 81      <NoCar
 82        noTravel
 83        eventName={event?.name}
 84        title={t('event.no_travel.title')}
 85        isCarosterPlus={isCarosterPlus}
 86      />
 87    );
 88
 89  const dates = Array.from(
 90    new Set(travels.map(travel => travel?.attributes?.departureDate))
 91  )
 92    .map(date => moment(date))
 93    .filter(date => date.isValid())
 94    .sort((a, b) => (a.isAfter(b) ? 1 : -1));
 95
 96  return (
 97    <>
 98      {showMap && <Map />}
 99      <MapActions />
100      <Box
101        px={3}
102        pb={2}
103        pt={showMap ? 2 : isMobile ? 22 : 18}
104        display="flex"
105        gap={2}
106        maxWidth="100%"
107        flexWrap="wrap"
108      >
109        <FilterByDate dates={dates} buttonFilterContent={buttonFilterContent} />
110        <AddTravelButton />
111        {haveGeopoints && (
112          <Button
113            sx={{width: {xs: 1, sm: 'auto'}}}
114            onClick={toggleMap}
115            startIcon={<Icon>{mapEnabled ? 'visibility_off' : 'map'}</Icon>}
116          >
117            {mapEnabled ? t`travel.hideMap` : t`travel.showMap`}
118          </Button>
119        )}
120      </Box>
121      <Box
122        p={0}
123        pt={showMap ? 0 : 3}
124        pb={11}
125        sx={{
126          overflowX: 'hidden',
127          overflowY: 'auto',
128          maxHeight: showMap ? '50vh' : '100vh',
129          [theme.breakpoints.down('md')]: {
130            maxHeight: showMap ? '50vh' : '100vh',
131            px: 1,
132          },
133        }}
134      >
135        <Masonry columns={{xl: 4, lg: 3, md: 2, sm: 2, xs: 1}} spacing={0}>
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;