all repos — caroster @ 441454a3d9f592007489fb7f437051b8402af111

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

🐛 Fix filtering departures by date #480
Tim Izzo tim@octree.ch
Fri, 05 Apr 2024 09:15:58 +0000
commit

441454a3d9f592007489fb7f437051b8402af111

parent

8b292b59765e5453a63272be05822c149c511ef0

M frontend/components/FilterByDate/index.tsxfrontend/components/FilterByDate/index.tsx

@@ -79,8 +79,9 @@ 'aria-labelledby': 'lock-button',

role: 'listbox', }} > - {Array.isArray(dates) && - dates.map((date, index) => ( + {dates + ?.filter(date => date.isValid()) + .map((date, index) => ( <MenuItem key={index}> <FormControlLabel control={
M frontend/containers/Map/Map.tsxfrontend/containers/Map/Map.tsx

@@ -9,7 +9,7 @@ process.env.DEV_TILES_URL ||

'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; const Map = () => { - const {markers} = useMapStore(); + const markers = useMapStore(s => s.markers); return ( <MapWrapper>
M frontend/containers/Travel/Header.tsxfrontend/containers/Travel/Header.tsx

@@ -38,7 +38,7 @@ return (

<Box p={2} onClick={() => { - setFocusOnTravel(focusedTravel === travel.id ? undefined : travel); + setFocusOnTravel(travel); const mapElement = document?.getElementById('map'); mapElement?.scrollIntoView({behavior: 'smooth'}); }}
M frontend/containers/TravelColumns/index.tsxfrontend/containers/TravelColumns/index.tsx

@@ -6,7 +6,6 @@ import {useTranslation} from 'react-i18next';

import {useTheme} from '@mui/material/styles'; import useEventStore from '../../stores/useEventStore'; import useToastStore from '../../stores/useToastStore'; -import useMapStore from '../../stores/useMapStore'; import useProfile from '../../hooks/useProfile'; import useAddToEvents from '../../hooks/useAddToEvents'; import usePassengersActions from '../../hooks/usePassengersActions';

@@ -19,25 +18,16 @@ import MasonryContainer from './MasonryContainer';

import LoginToAttend from './LoginToAttend'; import usePermissions from '../../hooks/usePermissions'; import useDisplayTravels from './useDisplayTravels'; +import useDisplayMarkers from './useDisplayMarkers'; import FilterByDate from '../../components/FilterByDate'; import moment from 'moment'; -const EventMarker = dynamic(() => import('../EventMarker'), {ssr: false}); -const TravelMarker = dynamic(() => import('../TravelMarker'), {ssr: false}); - interface Props { toggle: () => void; } const TravelColumns = (props: Props) => { const theme = useTheme(); - const { - focusedTravel, - preventUpdateKey, - setPreventUpdateKey, - setMarkers, - setBounds, - } = useMapStore(); const event = useEventStore(s => s.event); const travels = event?.travels?.data || []; const {t} = useTranslation();

@@ -52,6 +42,7 @@ const [selectedTravel, setSelectedTravel] = useState<TravelEntity>();

const {addPassenger} = usePassengersActions(); const {selectedDates, setSelectedDates, displayedTravels} = useDisplayTravels(); + useDisplayMarkers({event, selectedDates}); const buttonFilterContent = useMemo(() => { if (selectedDates.length > 1) return t('event.filter.dates');

@@ -87,53 +78,12 @@ title={t('event.no_travel.title')}

/> ); - const {latitude, longitude} = event; const showMap = - (latitude && longitude) || + (event.latitude && event.longitude) || travels.some( ({attributes: {meeting_latitude, meeting_longitude}}) => meeting_latitude && meeting_longitude ); - let coordsString = `${latitude}${longitude}`; - - const {markers, bounds} = travels.reduce( - ({markers, bounds}, travel) => { - const { - attributes: {meeting_latitude, meeting_longitude}, - } = travel; - if (meeting_latitude && meeting_longitude) { - const travelObject = {id: travel.id, ...travel.attributes}; - coordsString = - coordsString + String(meeting_latitude) + String(meeting_longitude); - return { - markers: [ - ...markers, - <TravelMarker - key={travel.id} - travel={travelObject} - focused={focusedTravel === travel.id} - />, - ], - bounds: [...bounds, [meeting_latitude, meeting_longitude]], - }; - } - return {markers, bounds}; - }, - {markers: [], bounds: []} - ); - - const mapUpdateKey = `${event.uuid}.travels+${coordsString}.${latitude}.${longitude}.${focusedTravel}`; - if (preventUpdateKey !== mapUpdateKey) { - setPreventUpdateKey(mapUpdateKey); - if (latitude && longitude) { - bounds.push([latitude, longitude]); - markers.push(<EventMarker key="event" event={event} />); - } - if (!focusedTravel) { - setBounds(bounds); - } - setMarkers(markers); - } const dates = Array.from( new Set(travels.map(travel => travel?.attributes?.departure))
A frontend/containers/TravelColumns/useDisplayMarkers.tsx

@@ -0,0 +1,80 @@

+import moment, {Moment} from 'moment'; +import {useEffect, useMemo} from 'react'; +import useMapStore from '../../stores/useMapStore'; +import dynamic from 'next/dynamic'; +import {Event} from '../../generated/graphql'; + +const EventMarker = dynamic(() => import('../EventMarker'), {ssr: false}); +const TravelMarker = dynamic(() => import('../TravelMarker'), {ssr: false}); + +interface Props { + event: Event & {id: string}; + selectedDates: Moment[]; +} + +const useDisplayMarkers = ({event, selectedDates}: Props) => { + const setMarkers = useMapStore(s => s.setMarkers); + const setBounds = useMapStore(s => s.setBounds); + const focusedTravel = useMapStore(s => s.focusedTravel); + + const travelsWithGeoloc = useMemo(() => { + const travels = event?.travels.data || []; + const filteredTravels = + selectedDates.length >= 1 + ? travels.filter(travel => { + const departureDate = moment(travel?.attributes?.departure); + return selectedDates.some(date => + date.isSame(departureDate, 'day') + ); + }) + : travels; + + return filteredTravels.filter( + travel => + travel.attributes.meeting_latitude && + travel.attributes.meeting_longitude + ); + }, [event, selectedDates]); + + // Set markers + useEffect(() => { + let markers = []; + + // Set event marker + const {latitude, longitude} = event || {}; + if (latitude && longitude) + markers.push(<EventMarker key="event" event={event} />); + + // Set travels markers + const travelMarkers = travelsWithGeoloc.map(travel => ( + <TravelMarker + key={travel.id} + travel={travel} + focused={focusedTravel === travel.id} + /> + )); + markers.push(...travelMarkers); + + setMarkers(markers); + }, [event, travelsWithGeoloc, focusedTravel, setMarkers]); + + // Set bounds + useEffect(() => { + let bounds = []; + + // Set event bounds + const {latitude, longitude} = event || {}; + if (latitude && longitude) bounds.push([latitude, longitude]); + + // Set travels bounds + const travelCoords = travelsWithGeoloc.map(travel => [ + travel.attributes.meeting_latitude, + travel.attributes.meeting_longitude, + ]); + bounds.push(...travelCoords); + + setBounds(bounds); + }, [event, travelsWithGeoloc, setBounds]); +}; + +export default useDisplayMarkers;
M frontend/containers/TravelMarker/TravelPopup.tsxfrontend/containers/TravelMarker/TravelPopup.tsx

@@ -3,39 +3,39 @@ import Card from '@mui/material/Card';

import Typography from '@mui/material/Typography'; import Link from '@mui/material/Link'; import Box from '@mui/material/Box'; -import {Travel} from '../../generated/graphql'; +import {TravelEntity} from '../../generated/graphql'; import {Popup} from 'react-leaflet'; import {useTranslation} from 'react-i18next'; import getMapsLink from '../../lib/getMapsLink'; interface Props { - travel: Travel & {id: string}; + travel: TravelEntity; } const TravelPopup = ({travel}: Props) => { const {t} = useTranslation(); return ( <Popup> - <Card - sx={{p: 2, width: '350px', maxWidth: '100%', cursor: 'pointer'}} - > - {!!travel.departure && ( + <Card sx={{p: 2, width: '350px', maxWidth: '100%', cursor: 'pointer'}}> + {!!travel.attributes.departure && ( <Typography variant="overline" color="Graytext" id="TravelDeparture"> - {moment(travel.departure).format('LLLL')} + {moment(travel.attributes.departure).format('LLLL')} </Typography> )} <Box> - <Typography variant="h5">{travel.vehicleName}</Typography> + <Typography variant="h5">{travel.attributes.vehicleName}</Typography> </Box> - {!!travel.phone_number && ( + {!!travel.attributes.phone_number && ( <Box sx={{marginTop: 2}}> <Typography variant="overline" color="GrayText"> {t('travel.fields.phone')} </Typography> - <Typography variant="body1">{travel.phone_number}</Typography> + <Typography variant="body1"> + {travel.attributes.phone_number} + </Typography> </Box> )} - {!!travel.meeting && ( + {!!travel.attributes.meeting && ( <Box sx={{marginTop: 2}}> <Typography variant="overline" color="GrayText"> {t('travel.fields.meeting_point')}

@@ -45,11 +45,11 @@ <Link

component="a" target="_blank" rel="noopener noreferrer" - href={getMapsLink(travel.meeting)} + href={getMapsLink(travel.attributes.meeting)} onClick={e => e.stopPropagation()} sx={{color: 'primary.main'}} > - {travel.meeting} + {travel.attributes.meeting} </Link> </Typography> </Box>
M frontend/containers/TravelMarker/index.tsxfrontend/containers/TravelMarker/index.tsx

@@ -1,25 +1,25 @@

-import {useRef} from 'react'; +import {useEffect, useRef} from 'react'; import {CircleMarker} from 'react-leaflet'; import {useTheme} from '@mui/material'; import useMapStore from '../../stores/useMapStore'; import TravelPopup from './TravelPopup'; -import {Travel} from '../../generated/graphql'; +import {TravelEntity} from '../../generated/graphql'; interface Props { - travel: Travel & {id: string}; + travel: TravelEntity; focused: boolean; } const TravelMarker = ({travel, focused}: Props) => { - const {meeting_longitude, meeting_latitude} = travel; + const {meeting_longitude, meeting_latitude} = travel.attributes; const {setFocusOnTravel} = useMapStore(); const markerRef = useRef(null); const theme = useTheme(); - const marker = markerRef.current; - if (marker && focused) { - marker.openPopup(); - } + useEffect(() => { + if (focused) markerRef.current?.openPopup(); + else markerRef.current?.closePopup(); + }, [focused]); const markerStyle = { radius: 12,

@@ -42,7 +42,7 @@ ref={markerRef}

{...markerStyle} center={[meeting_latitude, meeting_longitude]} eventHandlers={{ - click: onClick + click: onClick, }} > <TravelPopup travel={travel} />
M frontend/stores/useMapStore.tsfrontend/stores/useMapStore.ts

@@ -5,12 +5,10 @@ import {TravelEntity} from '../generated/graphql';

type State = { map?: any; - preventUpdateKey: string; markers: Array<ReactNode>; focusedTravel?: string; bounds: Array<LatLngExpression>; setMap: (map: any) => void; - setPreventUpdateKey: (preventUpdateKey: string) => void; setMarkers: (markers: Array<ReactNode>) => void; setFocusOnTravel: (travel?: TravelEntity) => void; setBounds: (bounds: Array<LatLngExpression>) => void;

@@ -18,20 +16,19 @@ };

const useMapStore = create<State>((set, get) => ({ map: undefined, - preventUpdateKey: '', markers: [], focusedTravel: undefined, bounds: [], setMap: map => set({map}), - setPreventUpdateKey: preventUpdateKey => set({preventUpdateKey}), setMarkers: markers => set({markers}), setFocusOnTravel: travel => { - if (!travel) { + const currentFocusId = get().focusedTravel; + if (!travel || travel.id === currentFocusId) set({focusedTravel: undefined}); - } else { + else { set({focusedTravel: travel.id}); - const lat = travel.attributes.meeting_latitude; - const long = travel.attributes.meeting_longitude; + const lat = travel.attributes?.meeting_latitude; + const long = travel.attributes?.meeting_longitude; if (lat && long) get().map?.panTo([lat, long]); } },