all repos — caroster @ 8b292b59765e5453a63272be05822c149c511ef0

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

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

  1import {useMemo, useState} from 'react';
  2import dynamic from 'next/dynamic';
  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 {TravelEntity} from '../../generated/graphql';
 17import {AddPassengerToTravel} from '../NewPassengerDialog';
 18import MasonryContainer from './MasonryContainer';
 19import LoginToAttend from './LoginToAttend';
 20import usePermissions from '../../hooks/usePermissions';
 21import useDisplayTravels from './useDisplayTravels';
 22import FilterByDate from '../../components/FilterByDate';
 23import moment from 'moment';
 24
 25const EventMarker = dynamic(() => import('../EventMarker'), {ssr: false});
 26const TravelMarker = dynamic(() => import('../TravelMarker'), {ssr: false});
 27
 28interface Props {
 29  toggle: () => void;
 30}
 31
 32const TravelColumns = (props: Props) => {
 33  const theme = useTheme();
 34  const {
 35    focusedTravel,
 36    preventUpdateKey,
 37    setPreventUpdateKey,
 38    setMarkers,
 39    setBounds,
 40  } = useMapStore();
 41  const event = useEventStore(s => s.event);
 42  const travels = event?.travels?.data || [];
 43  const {t} = useTranslation();
 44  const addToast = useToastStore(s => s.addToast);
 45  const {addToEvent} = useAddToEvents();
 46  const {profile, userId} = useProfile();
 47  const {
 48    userPermissions: {canAddTravel},
 49  } = usePermissions();
 50
 51  const [selectedTravel, setSelectedTravel] = useState<TravelEntity>();
 52  const {addPassenger} = usePassengersActions();
 53  const {selectedDates, setSelectedDates, displayedTravels} =
 54    useDisplayTravels();
 55
 56  const buttonFilterContent = useMemo(() => {
 57    if (selectedDates.length > 1) return t('event.filter.dates');
 58    else if (selectedDates.length === 1)
 59      return selectedDates.map(date => date.format('dddd Do MMMM'));
 60    else return t('event.filter.allDates');
 61  }, [selectedDates, t]);
 62
 63  const addSelfToTravel = async (travel: TravelEntity) => {
 64    const hasName = profile.firstName && profile.lastName;
 65    const userName = profile.firstName + ' ' + profile.lastName;
 66    try {
 67      await addPassenger({
 68        user: userId,
 69        email: profile.email,
 70        name: hasName ? userName : profile.username,
 71        travel: travel.id,
 72        event: event.id,
 73      });
 74      addToEvent(event.id);
 75      addToast(t('passenger.success.added_self_to_car'));
 76    } catch (error) {
 77      console.error(error);
 78    }
 79  };
 80
 81  if (!event || travels?.length === 0)
 82    return (
 83      <NoCar
 84        showImage
 85        eventName={event?.name}
 86        title={t('event.no_travel.title')}
 87      />
 88    );
 89
 90  const {latitude, longitude} = event;
 91  const showMap =
 92    (latitude && longitude) ||
 93    travels.some(
 94      ({attributes: {meeting_latitude, meeting_longitude}}) =>
 95        meeting_latitude && meeting_longitude
 96    );
 97  let coordsString = `${latitude}${longitude}`;
 98
 99  const {markers, bounds} = travels.reduce(
100    ({markers, bounds}, travel) => {
101      const {
102        attributes: {meeting_latitude, meeting_longitude},
103      } = travel;
104      if (meeting_latitude && meeting_longitude) {
105        const travelObject = {id: travel.id, ...travel.attributes};
106        coordsString =
107          coordsString + String(meeting_latitude) + String(meeting_longitude);
108        return {
109          markers: [
110            ...markers,
111            <TravelMarker
112              key={travel.id}
113              travel={travelObject}
114              focused={focusedTravel === travel.id}
115            />,
116          ],
117          bounds: [...bounds, [meeting_latitude, meeting_longitude]],
118        };
119      }
120      return {markers, bounds};
121    },
122    {markers: [], bounds: []}
123  );
124
125  const mapUpdateKey = `${event.uuid}.travels+${coordsString}.${latitude}.${longitude}.${focusedTravel}`;
126  if (preventUpdateKey !== mapUpdateKey) {
127    setPreventUpdateKey(mapUpdateKey);
128    if (latitude && longitude) {
129      bounds.push([latitude, longitude]);
130      markers.push(<EventMarker key="event" event={event} />);
131    }
132    if (!focusedTravel) {
133      setBounds(bounds);
134    }
135    setMarkers(markers);
136  }
137
138  const dates = Array.from(
139    new Set(travels.map(travel => travel?.attributes?.departure))
140  )
141    .map(date => moment(date))
142    .filter(date => date.isValid())
143    .sort((a, b) => (a.isAfter(b) ? 1 : -1));
144
145  return (
146    <>
147      {showMap && <Map />}
148      <FilterByDate
149        dates={dates}
150        setSelectedDates={setSelectedDates}
151        buttonFilterContent={buttonFilterContent}
152      />
153      <Box
154        p={0}
155        pt={showMap ? 4 : 13}
156        pb={11}
157        sx={{
158          overflowX: 'hidden',
159          overflowY: 'auto',
160          maxHeight: showMap ? '50vh' : '100vh',
161          [theme.breakpoints.down('md')]: {
162            maxHeight: showMap ? '50vh' : '100vh',
163            px: 1,
164          },
165        }}
166      >
167        <Masonry columns={{xl: 4, lg: 3, md: 2, sm: 2, xs: 1}} spacing={0}>
168          {!canAddTravel() && (
169            <MasonryContainer key="no_other_travel">
170              <LoginToAttend />
171            </MasonryContainer>
172          )}
173          {displayedTravels?.map(travel => {
174            return (
175              <MasonryContainer key={travel.id}>
176                <Travel
177                  travel={travel}
178                  onAddSelf={() => addSelfToTravel(travel)}
179                  onAddOther={() => setSelectedTravel(travel)}
180                  {...props}
181                />
182              </MasonryContainer>
183            );
184          })}
185          <MasonryContainer key="no_other_travel">
186            <NoCar
187              eventName={event?.name}
188              title={t('event.no_other_travel.title')}
189            />
190          </MasonryContainer>
191        </Masonry>
192      </Box>
193      {!!selectedTravel && (
194        <AddPassengerToTravel
195          open={!!selectedTravel}
196          toggle={() => setSelectedTravel(null)}
197          travel={selectedTravel}
198        />
199      )}
200    </>
201  );
202};
203
204export default TravelColumns;