all repos — caroster @ 1e4cd2561ddba0d298160a89d3e9b78da1e68fe2

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

feat: :sparkles: Add search feature for the map view

#550
Tim Izzo tim@octree.ch
Fri, 06 Dec 2024 14:07:20 +0100
commit

1e4cd2561ddba0d298160a89d3e9b78da1e68fe2

parent

d48304caf9bbcf05225577509e9eff78e5a0df95

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

@@ -1,28 +1,23 @@

import {useState} from 'react'; import { - Box, Button, Checkbox, FormControlLabel, Icon, - List, Menu, MenuItem, } from '@mui/material'; -import theme from '../../theme'; import {type Moment} from 'moment'; +import useTravelsStore from '../../stores/travelsStore'; interface FilterByDateProps { dates: Moment[]; - setSelectedDates: React.Dispatch<React.SetStateAction<Moment[]>>; buttonFilterContent: string | string[]; } -const FilterByDate = ({ - dates, - setSelectedDates, - buttonFilterContent, -}: FilterByDateProps) => { +const FilterByDate = ({dates, buttonFilterContent}: FilterByDateProps) => { + const datesFilters = useTravelsStore(s => s.datesFilter); + const setDatesFilter = useTravelsStore(s => s.setDatesFilter); const [anchorElement, setAnchorElement] = useState<null | HTMLElement>(null); const [checkedItems, setCheckedItems] = useState<boolean[]>( new Array(dates.length).fill(false)

@@ -37,39 +32,23 @@ const newCheckedItems = [...checkedItems];

newCheckedItems[index] = !checkedItems[index]; setCheckedItems(newCheckedItems); const selectedDate = dates[index]; - setSelectedDates(prevSelectedDates => { - if (checkedItems[index]) - return prevSelectedDates.filter(date => !date.isSame(selectedDate)); - else return [...prevSelectedDates, selectedDate]; - }); + const newDatesFilters = checkedItems[index] + ? datesFilters.filter(date => !date.isSame(selectedDate)) + : [...datesFilters, selectedDate]; + setDatesFilter(newDatesFilters); }; return ( - <Box - sx={{ - outline: 'none', - '& > *': { - cursor: 'default', - }, - position: 'absolute', - top: 60, - zIndex: 666, - [theme.breakpoints.down('sm')]: { - left: 15, - }, - [theme.breakpoints.up('sm')]: { - left: 25, - }, - }} - > - <List sx={{bgcolor: 'background.paper', borderRadius: 1, p: 0}}> - <Button - onClick={handleClickListItem} - endIcon={<Icon>calendar_today_outlined</Icon>} - > - {buttonFilterContent} - </Button> - </List> + <> + <Button + sx={{bgcolor: 'background.paper'}} + variant="contained" + color="inherit" + onClick={handleClickListItem} + endIcon={<Icon>calendar_today_outlined</Icon>} + > + {buttonFilterContent} + </Button> <Menu anchorEl={anchorElement} open={menuOpen}

@@ -95,7 +74,7 @@ />

</MenuItem> ))} </Menu> - </Box> + </> ); };
M frontend/containers/EventMarker/EventPopup.tsxfrontend/containers/Markers/EventMarker/EventPopup.tsx

@@ -5,8 +5,8 @@ import Link from '@mui/material/Link';

import Box from '@mui/material/Box'; import {useTranslation} from 'next-i18next'; import {Popup} from 'react-leaflet'; -import getMapsLink from '../../lib/getMapsLink'; -import {Event} from '../../generated/graphql'; +import getMapsLink from '../../../lib/getMapsLink'; +import {Event} from '../../../generated/graphql'; interface Props { event: Event & {id: string};
M frontend/containers/EventMarker/index.tsxfrontend/containers/Markers/EventMarker/index.tsx

@@ -1,7 +1,7 @@

import {CircleMarker} from 'react-leaflet'; import {useTheme} from '@mui/material'; import EventPopup from './EventPopup'; -import {Event} from '../../generated/graphql'; +import {Event} from '../../../generated/graphql'; interface Props { event: Event & {id: string};
M frontend/containers/Map/Bounds.tsxfrontend/containers/Map/Bounds.tsx

@@ -5,18 +5,14 @@ import useMapStore from '../../stores/useMapStore';

const Bounds = () => { const map = useMap(); - const {bounds: storedBounds} = useMapStore(); - const debouncedBounds = useDebounce(storedBounds, 750); - const bounds = useMemo( - () => debouncedBounds.map(bound => bound), - [debouncedBounds] - ); + const storedBounds = useMapStore(s => s.bounds); + const bounds = useDebounce(storedBounds, 750); useEffect(() => { if (bounds.length >= 1) { map.fitBounds(bounds, {padding: [30, 30]}); } - }, [bounds]); + }, [bounds, map]); return null; };
M frontend/containers/Map/Map.tsxfrontend/containers/Map/Map.tsx

@@ -3,6 +3,8 @@ import MapController from './MapController';

import MapWrapper from './MapWrapper'; import useMapStore from '../../stores/useMapStore'; import Bounds from './Bounds'; +import {Box} from '@mui/material'; +import MapActions from './MapActions'; const TOKEN_FREE_TILES_URL = process.env.TOKEN_FREE_TILES_URL ||

@@ -26,6 +28,9 @@ attribution={TOKEN_FREE_TILES_LAYER_ATTRIBUTION}

/> <MapController /> {markers} + <Box zIndex={400} position="relative" top={75} left={25}> + <MapActions /> + </Box> </MapContainer> </MapWrapper> );
A frontend/containers/Map/MapActions.tsx

@@ -0,0 +1,14 @@

+import {Box, Paper} from '@mui/material'; +import SearchField from './SearchField'; + +type Props = {}; + +const MapActions = (props: Props) => { + return ( + <Box component={Paper} p={1} maxWidth={350}> + <SearchField /> + </Box> + ); +}; + +export default MapActions;
M frontend/containers/Map/MapController.tsxfrontend/containers/Map/MapController.tsx

@@ -3,12 +3,11 @@ import useMapStore from '../../stores/useMapStore';

const MapController = () => { const map = useMap(); - const {setMap, map: storedMap} = useMapStore(); + const setMap = useMapStore(s => s.setMap); + const storedMap = useMapStore(s => s.map); map.scrollWheelZoom.disable(); - if (storedMap !== map) { - setMap(map); - } + if (storedMap !== map) setMap(map); return <></>; };
A frontend/containers/Map/SearchField.tsx

@@ -0,0 +1,44 @@

+import {Icon, InputAdornment, TextField} from '@mui/material'; +import {useTranslation} from 'react-i18next'; +import PlaceInput from '../PlaceInput'; +import useTravelsStore from '../../stores/travelsStore'; + +type Props = {}; + +const SearchField = (props: Props) => { + const {t} = useTranslation(); + const meetingFilter = useTravelsStore(s => s.meetingFilter); + const setMeetingFilter = useTravelsStore(s => s.setMeetingFilter); + + const onSelect = (location: { + latitude?: number; + longitude?: number; + place: string; + }) => { + setMeetingFilter(location); + }; + + return ( + <PlaceInput + place={meetingFilter.place} + onSelect={onSelect} + textFieldProps={{ + variant: 'outlined', + placeholder: t`travel.fields.meeting_point`, + size: 'small', + slotProps: { + input: { + size: 'small', + startAdornment: ( + <InputAdornment position="start"> + <Icon>search</Icon> + </InputAdornment> + ), + }, + }, + }} + /> + ); +}; + +export default SearchField;
A frontend/containers/Markers/MeetingFilterMarker/index.tsx

@@ -0,0 +1,3 @@

+import {CircleMarker} from 'react-leaflet'; + +export default CircleMarker;
M frontend/containers/PlaceInput/index.tsxfrontend/containers/PlaceInput/index.tsx

@@ -47,7 +47,7 @@ const [options, setOptions] = useState([] as Array<any>);

const previousOption = place ? {place_name: place, previous: true} : null; const onChange = async (_event, selectedOption) => { - if (selectedOption.center) { + if (selectedOption?.center) { const [optionLongitude, optionLatitude] = selectedOption.center; setNoCoordinates(false); onSelect({

@@ -58,7 +58,7 @@ });

} else { setNoCoordinates(true); onSelect({ - place: selectedOption.place_name, + place: selectedOption?.place_name || '', latitude: null, longitude: null, });

@@ -109,7 +109,6 @@

return ( <Autocomplete freeSolo - disableClearable autoComplete getOptionLabel={option => option?.place_name} options={options}

@@ -125,17 +124,23 @@ label={label}

multiline maxRows={4} helperText={MAPBOX_CONFIGURED && getHelperText()} - FormHelperTextProps={{sx: {color: 'warning.main'}}} - InputProps={{ - type: 'search', - endAdornment: ( - <InputAdornment position="end" sx={{mr: -0.5}}> - <PlaceOutlinedIcon /> - </InputAdornment> - ), + {...textFieldProps} + slotProps={{ + formHelperText: { + sx: {color: 'warning.main'}, + }, + input: { + ...params.InputProps, + ...(textFieldProps?.slotProps?.input || { + endAdornment: ( + <InputAdornment position="end" sx={{mr: -0.5}}> + <PlaceOutlinedIcon /> + </InputAdornment> + ), + }), + }, }} {...params} - {...textFieldProps} /> )} renderOption={({key, ...props}, option) => {
M frontend/containers/Travel/Header.tsxfrontend/containers/Travel/Header.tsx

@@ -31,7 +31,7 @@ const {t} = useTranslation();

const { userPermissions: {canEditTravel, canSeeTravelDetails}, } = usePermissions(); - const {setFocusOnTravel} = useMapStore(); + const setFocusOnTravel = useMapStore(s => s.setFocusOnTravel); const {userId} = useProfile(); const isUserTripCreator = userId && userId === travel.attributes.user?.data?.id;
M frontend/containers/Travel/index.tsxfrontend/containers/Travel/index.tsx

@@ -36,7 +36,7 @@ i => !i,

false ); const {userId, connected} = useProfile(); - const {focusedTravel} = useMapStore(); + const focusedTravel = useMapStore(s => s.focusedTravel); const focused = focusedTravel === travel.id; const disableNewPassengers = travel.attributes.passengers?.data?.length >= travel.attributes.seats;
M frontend/containers/TravelColumns/index.tsxfrontend/containers/TravelColumns/index.tsx

@@ -19,7 +19,9 @@ import LoginToAttend from '../LoginToAttend/LoginToAttend';

import usePermissions from '../../hooks/usePermissions'; import useDisplayTravels from './useDisplayTravels'; import useDisplayMarkers from './useDisplayMarkers'; -import FilterByDate from '../../components/FilterByDate'; +import FilterByDate from './FilterByDate'; +import {Button, Icon} from '@mui/material'; +import useTravelsStore from '../../stores/travelsStore'; interface Props { toggle: () => void;

@@ -38,17 +40,17 @@ userPermissions: {canAddTravel},

} = usePermissions(); const [selectedTravel, setSelectedTravel] = useState<TravelEntity>(); + const datesFilters = useTravelsStore(s => s.datesFilter); const {addPassenger} = usePassengersActions(); - const {selectedDates, setSelectedDates, displayedTravels} = - useDisplayTravels(); - useDisplayMarkers({event, selectedDates}); + const {displayedTravels} = useDisplayTravels(); + useDisplayMarkers({event}); const buttonFilterContent = useMemo(() => { - if (selectedDates.length > 1) return t('event.filter.dates'); - else if (selectedDates.length === 1) - return selectedDates.map(date => date.format('dddd Do MMMM')); + if (datesFilters.length > 1) return t('event.filter.dates'); + else if (datesFilters.length === 1) + return datesFilters.map(date => date.format('dddd Do MMMM')); else return t('event.filter.allDates'); - }, [selectedDates, t]); + }, [datesFilters, t]); const addSelfToTravel = async (travel: TravelEntity) => { const hasName = profile.firstName && profile.lastName;

@@ -97,14 +99,23 @@

return ( <> {showMap && <Map />} - <FilterByDate - dates={dates} - setSelectedDates={setSelectedDates} - buttonFilterContent={buttonFilterContent} - /> + <Box px={3} py={2} display="flex" gap={2}> + <FilterByDate dates={dates} buttonFilterContent={buttonFilterContent} /> + {canAddTravel() && ( + <Button + onClick={props.toggle} + aria-label="add-car" + variant="contained" + color="secondary" + endIcon={<Icon>add</Icon>} + > + {t('travel.creation.title')} + </Button> + )} + </Box> <Box p={0} - pt={showMap ? 4 : 13} + pt={showMap ? 0 : 13} pb={11} sx={{ overflowX: 'hidden',
M frontend/containers/TravelColumns/useDisplayMarkers.tsxfrontend/containers/TravelColumns/useDisplayMarkers.tsx

@@ -1,31 +1,40 @@

-import moment, {Moment} from 'moment'; +import moment from 'moment'; import {useEffect, useMemo} from 'react'; import useMapStore from '../../stores/useMapStore'; import dynamic from 'next/dynamic'; import {Event} from '../../generated/graphql'; +import useTravelsStore from '../../stores/travelsStore'; +import {calculateHaversineDistance} from '../../lib/geography'; -const EventMarker = dynamic(() => import('../EventMarker'), {ssr: false}); -const TravelMarker = dynamic(() => import('../TravelMarker'), {ssr: false}); +const EventMarker = dynamic(() => import('../Markers/EventMarker'), { + ssr: false, +}); +const TravelMarker = dynamic(() => import('../Markers/TravelMarker'), { + ssr: false, +}); +const MeetingFilterMarker = dynamic( + () => import('../Markers/MeetingFilterMarker'), + {ssr: false} +); interface Props { event: Event & {id: string}; - selectedDates: Moment[]; } -const useDisplayMarkers = ({event, selectedDates}: Props) => { +const useDisplayMarkers = ({event}: Props) => { const setMarkers = useMapStore(s => s.setMarkers); const setBounds = useMapStore(s => s.setBounds); const focusedTravel = useMapStore(s => s.focusedTravel); + const datesFilters = useTravelsStore(s => s.datesFilter); + const meetingFilter = useTravelsStore(s => s.meetingFilter); const travelsWithGeoloc = useMemo(() => { const travels = event?.travels?.data || []; const filteredTravels = - selectedDates.length >= 1 + datesFilters.length >= 1 ? travels.filter(travel => { const departureDate = moment(travel?.attributes?.departureDate); - return selectedDates.some(date => - date.isSame(departureDate, 'day') - ); + return datesFilters.some(date => date.isSame(departureDate, 'day')); }) : travels;

@@ -34,7 +43,7 @@ travel =>

travel.attributes.meeting_latitude && travel.attributes.meeting_longitude ); - }, [event, selectedDates]); + }, [event, datesFilters]); // Set markers useEffect(() => {

@@ -45,6 +54,14 @@ const {latitude, longitude} = event || {};

if (latitude && longitude) markers.push(<EventMarker key="event" event={event} />); + // Set meeting filter marker + if (meetingFilter?.latitude && meetingFilter?.longitude) + markers.push( + <MeetingFilterMarker + center={[meetingFilter.latitude, meetingFilter.longitude]} + /> + ); + // Set travels markers const travelMarkers = travelsWithGeoloc.map(travel => ( <TravelMarker

@@ -56,7 +73,7 @@ ));

markers.push(...travelMarkers); setMarkers(markers); - }, [event, travelsWithGeoloc, focusedTravel, setMarkers]); + }, [event, travelsWithGeoloc, focusedTravel, setMarkers, meetingFilter]); // Set bounds useEffect(() => {

@@ -66,15 +83,39 @@ // 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); + if (meetingFilter?.latitude && meetingFilter?.longitude) { + bounds.push([meetingFilter.latitude, meetingFilter.longitude]); + + // Set travels bounds + const travelCoords = travelsWithGeoloc + .map(travel => ({ + ...travel, + distance: calculateHaversineDistance( + [meetingFilter.latitude, meetingFilter.longitude], + [ + travel.attributes.meeting_latitude, + travel.attributes.meeting_longitude, + ] + ), + })) + .sort((a, b) => a.distance - b.distance) + .slice(0, 3) + .map(travel => [ + travel.attributes.meeting_latitude, + travel.attributes.meeting_longitude, + ]); + bounds.push(...travelCoords); + } else { + // Set travels bounds + const travelCoords = travelsWithGeoloc.map(travel => [ + travel.attributes.meeting_latitude, + travel.attributes.meeting_longitude, + ]); + bounds.push(...travelCoords); + } setBounds(bounds); - }, [event, travelsWithGeoloc, setBounds]); + }, [event, travelsWithGeoloc, setBounds, meetingFilter]); }; export default useDisplayMarkers;
M frontend/containers/TravelColumns/useDisplayTravels.tsxfrontend/containers/TravelColumns/useDisplayTravels.tsx

@@ -1,19 +1,56 @@

-import {useState} from 'react'; import useEventStore from '../../stores/useEventStore'; import {TravelEntity} from '../../generated/graphql'; import useProfile from '../../hooks/useProfile'; -import moment, {Moment} from 'moment'; +import moment from 'moment'; +import useTravelsStore from '../../stores/travelsStore'; +import {useMemo} from 'react'; +import {calculateHaversineDistance} from '../../lib/geography'; const useDisplayTravels = () => { - const [selectedDates, setSelectedDates] = useState<Moment[]>([]); + const datesFilter = useTravelsStore(s => s.datesFilter); + const meetingFilter = useTravelsStore(s => s.meetingFilter); const {userId} = useProfile(); const event = useEventStore(s => s.event); - const travels = event?.travels?.data || []; - const sortTravels = ( - {attributes: a}: TravelEntity, - {attributes: b}: TravelEntity - ) => { + const dateFileredTravels = useMemo(() => { + const travels = event?.travels?.data || []; + return travels + .slice() + .sort(sortTravels(userId)) + .filter(travel => { + if (datesFilter.length === 0) return true; + const departureDate = travel?.attributes?.departureDate + ? moment(travel.attributes.departureDate) + : null; + return datesFilter.some(date => date.isSame(departureDate)); + }); + }, [event?.travels?.data, datesFilter, userId]); + + const meetingFilteredTravels = useMemo(() => { + if (!meetingFilter.latitude || !meetingFilter.longitude) + return dateFileredTravels; + + return dateFileredTravels + .map(travel => ({ + ...travel, + distance: calculateHaversineDistance( + [meetingFilter.latitude, meetingFilter.longitude], + [ + travel.attributes.meeting_latitude, + travel.attributes.meeting_longitude, + ] + ), + })) + .sort((a, b) => a.distance - b.distance) + .slice(0, 3); + }, [dateFileredTravels, meetingFilter]); + + return {displayedTravels: meetingFilteredTravels}; +}; + +const sortTravels = + userId => + ({attributes: a}: TravelEntity, {attributes: b}: TravelEntity) => { if (a?.user?.data?.id === userId && b?.user?.data?.id !== userId) return -1; else if (a?.user?.data?.id !== userId && b?.user?.data?.id == userId) return 1;

@@ -36,20 +73,5 @@ if (dateA === dateB)

return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); else return dateA - dateB; }; - - const sortedTravels = travels?.slice().sort(sortTravels); - - const filteredTravels = sortedTravels.filter(travel => { - const departureDate = travel?.attributes?.departureDate - ? moment(travel.attributes.departureDate) - : null; - return selectedDates.some(date => date.isSame(departureDate)); - }); - - const displayedTravels = - selectedDates.length > 0 ? filteredTravels : sortedTravels; - - return {selectedDates, setSelectedDates, displayedTravels}; -}; export default useDisplayTravels;
M frontend/containers/TravelMarker/TravelPopup.tsxfrontend/containers/Markers/TravelMarker/TravelPopup.tsx

@@ -3,12 +3,12 @@ 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 {TravelEntity} from '../../generated/graphql'; +import {TravelEntity} from '../../../generated/graphql'; import {Popup} from 'react-leaflet'; import {useTranslation} from 'next-i18next'; -import getMapsLink from '../../lib/getMapsLink'; -import {getFormatedPhoneNumber} from '../../lib/phoneNumbers'; -import usePermissions from '../../hooks/usePermissions'; +import getMapsLink from '../../../lib/getMapsLink'; +import {getFormatedPhoneNumber} from '../../../lib/phoneNumbers'; +import usePermissions from '../../../hooks/usePermissions'; interface Props { travel: TravelEntity;
M frontend/containers/TravelMarker/index.tsxfrontend/containers/Markers/TravelMarker/index.tsx

@@ -1,9 +1,9 @@

import {useEffect, useRef} from 'react'; import {CircleMarker} from 'react-leaflet'; import {useTheme} from '@mui/material'; -import useMapStore from '../../stores/useMapStore'; +import useMapStore from '../../../stores/useMapStore'; import TravelPopup from './TravelPopup'; -import {TravelEntity} from '../../generated/graphql'; +import {TravelEntity} from '../../../generated/graphql'; interface Props { travel: TravelEntity;

@@ -12,7 +12,7 @@ }

const TravelMarker = ({travel, focused}: Props) => { const {meeting_longitude, meeting_latitude} = travel.attributes; - const {setFocusOnTravel} = useMapStore(); + const setFocusOnTravel = useMapStore(s => s.setFocusOnTravel); const markerRef = useRef(null); const theme = useTheme();
A frontend/lib/geography.ts

@@ -0,0 +1,22 @@

+export const calculateHaversineDistance = ( + [lat1, lon1]: [number, number], + [lat2, lon2]: [number, number] +): number => { + const R = 6371; // Radius of the Earth in kilometers + + const dLat = degreesToRadians(lat2 - lat1); + const dLon = degreesToRadians(lon2 - lon1); + + const a = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(degreesToRadians(lat1)) * + Math.cos(degreesToRadians(lat2)) * + Math.sin(dLon / 2) * + Math.sin(dLon / 2); + + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + const distance = R * c; // Distance in kilometers + return distance; +}; + +const degreesToRadians = (degrees: number): number => degrees * (Math.PI / 180);
M frontend/pages/e/[uuid]/index.tsxfrontend/pages/e/[uuid]/index.tsx

@@ -1,13 +1,10 @@

import {useState, useReducer, PropsWithChildren} from 'react'; import Box from '@mui/material/Box'; -import {useTranslation} from 'next-i18next'; import {getSession, useSession} from 'next-auth/react'; import TravelColumns from '../../../containers/TravelColumns'; import NewTravelDialog from '../../../containers/NewTravelDialog'; import VehicleChoiceDialog from '../../../containers/VehicleChoiceDialog'; -import Fab from '../../../containers/Fab'; import pageUtils from '../../../lib/pageUtils'; -import usePermissions from '../../../hooks/usePermissions'; import EventLayout, {TabComponent} from '../../../layouts/Event'; import { EventByUuidDocument,

@@ -26,11 +23,7 @@ return <EventLayout {...props} Tab={TravelsTab} />;

}; const TravelsTab: TabComponent<Props> = () => { - const {t} = useTranslation(); const session = useSession(); - const { - userPermissions: {canAddTravel}, - } = usePermissions(); const [openNewTravelDialog, setNewTravelDialog] = useState(false); const [openVehicleChoice, toggleVehicleChoice] = useReducer(i => !i, false); const [selectedVehicle, setSelectedVehicle] = useState<VehicleEntity>();

@@ -49,11 +42,6 @@

return ( <Box> <TravelColumns toggle={addTravelClickHandler} /> - {canAddTravel() && ( - <Fab onClick={addTravelClickHandler} aria-label="add-car"> - {t('travel.creation.title')} - </Fab> - )} <NewTravelDialog key={selectedVehicle?.id || 'noVehicle'} opened={openNewTravelDialog}
A frontend/stores/travelsStore.ts

@@ -0,0 +1,26 @@

+import {Moment} from 'moment'; +import {create} from 'zustand'; + +interface Meeting { + place: string; + latitude?: number; + longitude?: number; +} + +interface State { + datesFilter: Moment[]; + setDatesFilter: (dates: Moment[]) => void; + + meetingFilter: Meeting; + setMeetingFilter: (meeting: Meeting) => void; +} + +const useTravelsStore = create<State>((set, get) => ({ + datesFilter: [], + setDatesFilter: datesFilter => set({datesFilter}), + + meetingFilter: {place: ''}, + setMeetingFilter: meetingFilter => set({meetingFilter}), +})); + +export default useTravelsStore;
M frontend/stores/useMapStore.tsfrontend/stores/useMapStore.ts

@@ -1,14 +1,14 @@

import {ReactNode} from 'react'; -import type {LatLngExpression} from 'leaflet'; +import type {LatLngExpression, Map as LMap} from 'leaflet'; import {create} from 'zustand'; import {TravelEntity} from '../generated/graphql'; type State = { - map?: any; + map?: LMap; markers: Array<ReactNode>; focusedTravel?: string; bounds: Array<LatLngExpression>; - setMap: (map: any) => void; + setMap: (map: LMap) => void; setMarkers: (markers: Array<ReactNode>) => void; setFocusOnTravel: (travel?: TravelEntity) => void; setBounds: (bounds: Array<LatLngExpression>) => void;