all repos — caroster @ 1bea64d2ff9f4e00589034a0c18c9204afb83c27

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

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

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