all repos — caroster @ v5.0

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

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

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