all repos — caroster @ 1410b54dc70ad59a92a3fe31c76237c46a37c1d8

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