all repos — caroster @ 86db106f053562bc10976f2c0c8d0d5520291370

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

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

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