From fa6da5c644acf68716e8f292eac6322b2d4025b4 Mon Sep 17 00:00:00 2001 From: Gurikov Maxim Date: Thu, 31 Oct 2024 20:55:00 +0500 Subject: [PATCH] done map --- src/Pages/main-page.tsx | 23 +++++++++-- src/components/map/map.tsx | 57 ++++++++++++++++++++++++++++ src/components/map/use-map.ts | 38 +++++++++++++++++++ src/components/offer/offers-list.tsx | 8 ++-- src/dataTypes/point.ts | 6 +++ src/mocks/offers.ts | 18 ++++----- 6 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 src/components/map/map.tsx create mode 100644 src/components/map/use-map.ts create mode 100644 src/dataTypes/point.ts diff --git a/src/Pages/main-page.tsx b/src/Pages/main-page.tsx index 44d9244..2be8d58 100644 --- a/src/Pages/main-page.tsx +++ b/src/Pages/main-page.tsx @@ -4,13 +4,14 @@ import { OffersList } from '../components/offer/offers-list.tsx'; import { Layout } from '../components/layout.tsx'; import { Helmet } from 'react-helmet-async'; import { Nullable } from 'vitest'; +import { Map } from '../components/map/map.tsx'; interface MainPageProps { offers: Offer[]; } export function MainPage({ offers }: MainPageProps): React.JSX.Element { - const [activeOfferId, setActiveOfferId] = useState>(''); + const [activeOffer, setActiveOffer] = useState>(null); return (
@@ -87,13 +88,27 @@ export function MainPage({ offers }: MainPageProps): React.JSX.Element { ) => - setActiveOfferId(offerId) + onActiveOfferChange={(offer: Nullable) => + setActiveOffer(offer) } />
-
+ ({ + location: x.location, + id: x.id, + }))} + selectedPoint={ + activeOffer + ? { + location: activeOffer?.location, + id: activeOffer?.id, + } + : undefined + } + />
diff --git a/src/components/map/map.tsx b/src/components/map/map.tsx new file mode 100644 index 0000000..c2a345e --- /dev/null +++ b/src/components/map/map.tsx @@ -0,0 +1,57 @@ +import { useRef, useEffect } from 'react'; +import { Icon, Marker, layerGroup } from 'leaflet'; +import 'leaflet/dist/leaflet.css'; +import { useMap } from './use-map.ts'; +import { City } from '../../dataTypes/city.ts'; +import { Point } from '../../dataTypes/point.ts'; + +interface MapProps { + city: City; + points: Point[]; + selectedPoint: Point | undefined; +} + +const defaultCustomIcon = new Icon({ + iconUrl: 'public/img/pin.svg', + iconSize: [40, 40], + iconAnchor: [20, 40], +}); + +const currentCustomIcon = new Icon({ + iconUrl: 'public/img/pin-active.svg', + iconSize: [40, 40], + iconAnchor: [20, 40], +}); + +export function Map(props: MapProps): JSX.Element { + const { city, points, selectedPoint } = props; + + const mapRef = useRef(null); + const map = useMap(mapRef, city); + + useEffect(() => { + if (map) { + const markerLayer = layerGroup().addTo(map); + points.forEach((point) => { + const marker = new Marker({ + lat: point.location.latitude, + lng: point.location.longitude, + }); + + marker + .setIcon( + selectedPoint !== undefined && point.id === selectedPoint.id + ? currentCustomIcon + : defaultCustomIcon, + ) + .addTo(markerLayer); + }); + + return () => { + map.removeLayer(markerLayer); + }; + } + }, [map, points, selectedPoint]); + + return
; +} diff --git a/src/components/map/use-map.ts b/src/components/map/use-map.ts new file mode 100644 index 0000000..e311d97 --- /dev/null +++ b/src/components/map/use-map.ts @@ -0,0 +1,38 @@ +import { useEffect, useState, MutableRefObject, useRef } from 'react'; +import { Map, TileLayer } from 'leaflet'; +import { City } from '../../dataTypes/city.ts'; + +export function useMap( + mapRef: MutableRefObject, + city: City, +): Map | null { + const [map, setMap] = useState(null); + const isRenderedRef = useRef(false); + + useEffect(() => { + if (mapRef.current !== null && !isRenderedRef.current) { + const instance = new Map(mapRef.current, { + center: { + lat: city.location.latitude, + lng: city.location.longitude, + }, + zoom: 10, + }); + + const layer = new TileLayer( + 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', + { + attribution: + '© OpenStreetMap contributors © CARTO', + }, + ); + + instance.addLayer(layer); + + setMap(instance); + isRenderedRef.current = true; + } + }, [mapRef, city]); + + return map; +} diff --git a/src/components/offer/offers-list.tsx b/src/components/offer/offers-list.tsx index 4d5e4d6..9bb90cd 100644 --- a/src/components/offer/offers-list.tsx +++ b/src/components/offer/offers-list.tsx @@ -5,15 +5,15 @@ import { Nullable } from 'vitest'; interface OffersListProps { offers: Offer[]; - onActiveOfferChange?: (offerId: Nullable) => void; + onActiveOfferChange?: (offer: Nullable) => void; } export function OffersList({ offers, onActiveOfferChange, }: OffersListProps): React.JSX.Element { - const handleActiveOfferChange = (id: Nullable): void => { - onActiveOfferChange?.(id); + const handleActiveOfferChange = (offer: Nullable): void => { + onActiveOfferChange?.(offer); }; return (
@@ -25,7 +25,7 @@ export function OffersList({ type={offer.type} image={offer.previewImage} title={offer.title} - onMouseEnter={() => handleActiveOfferChange(offer.id)} + onMouseEnter={() => handleActiveOfferChange(offer)} onMouseLeave={() => handleActiveOfferChange(null)} isFavorite={offer.isFavorite} isPremium={offer.isPremium} diff --git a/src/dataTypes/point.ts b/src/dataTypes/point.ts new file mode 100644 index 0000000..7a214be --- /dev/null +++ b/src/dataTypes/point.ts @@ -0,0 +1,6 @@ +import { Location } from './location.ts'; + +export type Point = { + location: Location; + id: string; +}; diff --git a/src/mocks/offers.ts b/src/mocks/offers.ts index 61424c5..b988356 100644 --- a/src/mocks/offers.ts +++ b/src/mocks/offers.ts @@ -16,8 +16,8 @@ export const offerMocks: Offer[] = [ }, }, location: { - latitude: 52.35514938496378, - longitude: 4.673877537499948, + latitude: 52.3909553943508, + longitude: 4.85309666406198, zoom: 8, }, isFavorite: false, @@ -39,8 +39,8 @@ export const offerMocks: Offer[] = [ }, }, location: { - latitude: 52.35514938496378, - longitude: 4.673877537499948, + latitude: 52.3609553943508, + longitude: 4.85309666406198, zoom: 8, }, isFavorite: false, @@ -62,8 +62,8 @@ export const offerMocks: Offer[] = [ }, }, location: { - latitude: 52.35514938496378, - longitude: 4.673877537499948, + latitude: 52.3909553943508, + longitude: 4.929309666406198, zoom: 8, }, isFavorite: true, @@ -77,7 +77,7 @@ export const offerMocks: Offer[] = [ type: RoomType.Apartment, price: 88, city: { - name: 'Cologne', + name: 'Amsterdam', location: { latitude: 52.35514938496378, longitude: 4.673877537499948, @@ -85,8 +85,8 @@ export const offerMocks: Offer[] = [ }, }, location: { - latitude: 52.35514938496378, - longitude: 4.673877537499948, + latitude: 52.3809553943508, + longitude: 4.939309666406198, zoom: 8, }, isFavorite: true,