From 22210d5025a8db7c5d735a74d74064aae7e99952 Mon Sep 17 00:00:00 2001 From: uniks Date: Sat, 2 Nov 2024 15:34:38 +0500 Subject: [PATCH] module 5 task 1 --- src/components/App.tsx | 2 +- src/components/favoritesList.tsx | 2 +- src/components/map.tsx | 44 +++++++++++++++++++++++++++++ src/components/offersList.tsx | 2 +- src/components/pages/Card.tsx | 2 +- src/components/pages/Favorites.tsx | 2 +- src/components/pages/MainScreen.tsx | 12 ++++++-- src/const.ts | 19 +++++++------ src/hooks/use-Map.tsx | 39 +++++++++++++++++++++++++ src/mocks/offers.ts | 34 +++++++++++++++++++++- src/types/{offer.ts => types.ts} | 18 ++++++------ 11 files changed, 149 insertions(+), 27 deletions(-) create mode 100644 src/components/map.tsx create mode 100644 src/hooks/use-Map.tsx rename src/types/{offer.ts => types.ts} (59%) diff --git a/src/components/App.tsx b/src/components/App.tsx index e693b57..a71d0a6 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -7,7 +7,7 @@ import Offer from './pages/Offer'; import NotFound from './NotFound'; import AppRoute, { AuthorizationStatus } from '../const'; import PrivateRoute from './private-route'; -import { OffersType } from '../types/offer'; +import { OffersType } from '../types/types'; type AppScreenProps = { placesCount: number; diff --git a/src/components/favoritesList.tsx b/src/components/favoritesList.tsx index 52ffc60..a39eaf2 100644 --- a/src/components/favoritesList.tsx +++ b/src/components/favoritesList.tsx @@ -1,4 +1,4 @@ -import { OffersType } from '../types/offer'; +import { OffersType } from '../types/types'; import Card from './pages/Card'; type FavoritesListProps = { diff --git a/src/components/map.tsx b/src/components/map.tsx new file mode 100644 index 0000000..e27c5d9 --- /dev/null +++ b/src/components/map.tsx @@ -0,0 +1,44 @@ +import { useRef, useEffect } from 'react'; +import { Icon, Marker, layerGroup } from 'leaflet'; +import useMap from '../hooks/use-Map'; +import { URL_MARKER_DEFAULT } from '../const'; +import 'leaflet/dist/leaflet.css'; +import { OffersType } from '../types/types'; + +type MapProps = { + offers: OffersType[]; + selectedOffer: OffersType; +}; + +const defaultCustomIcon = new Icon({ + iconUrl: URL_MARKER_DEFAULT, + iconSize: [40, 40], + iconAnchor: [20, 40], +}); + +function Map({offers, selectedOffer}: MapProps){ + const mapRef = useRef(null); + const map = useMap(mapRef, selectedOffer); + + useEffect(() => { + if (map) { + const markerLayer = layerGroup().addTo(map); + offers.forEach((offer) => { + const marker = new Marker({ + lat: offer.city.location.latitude, + lng: offer.city.location.longitude + }); + + marker.setIcon(defaultCustomIcon).addTo(markerLayer); + }); + + return () => { + map.removeLayer(markerLayer); + }; + } + }, [map, offers, selectedOffer]); + + return
; +} + +export default Map; diff --git a/src/components/offersList.tsx b/src/components/offersList.tsx index 1571d73..616a131 100644 --- a/src/components/offersList.tsx +++ b/src/components/offersList.tsx @@ -1,5 +1,5 @@ import Card from './pages/Card'; -import { OffersType } from '../types/offer'; +import { OffersType } from '../types/types'; interface OffersListProps { offers: OffersType[]; diff --git a/src/components/pages/Card.tsx b/src/components/pages/Card.tsx index 584c91f..086727b 100644 --- a/src/components/pages/Card.tsx +++ b/src/components/pages/Card.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { OffersType } from '../../types/offer'; +import { OffersType } from '../../types/types'; import { Link } from 'react-router-dom'; type CardProps = { diff --git a/src/components/pages/Favorites.tsx b/src/components/pages/Favorites.tsx index 2afd508..e19ee73 100644 --- a/src/components/pages/Favorites.tsx +++ b/src/components/pages/Favorites.tsx @@ -1,5 +1,5 @@ import { Link } from 'react-router-dom'; -import { OffersType } from '../../types/offer'; +import { OffersType } from '../../types/types'; import FavoritesList from '../favoritesList'; import Logo from '../Logo'; import { Helmet } from 'react-helmet-async'; diff --git a/src/components/pages/MainScreen.tsx b/src/components/pages/MainScreen.tsx index 4a8c031..869c6d4 100644 --- a/src/components/pages/MainScreen.tsx +++ b/src/components/pages/MainScreen.tsx @@ -1,8 +1,9 @@ import Logo from '../Logo'; import { Helmet } from 'react-helmet-async'; -import { OffersType } from '../../types/offer'; +import { OffersType } from '../../types/types'; import OffersList from '../offersList'; import { Link } from 'react-router-dom'; +import Map from '../map'; type MainScreenProps = { placesCount: number; @@ -19,7 +20,10 @@ function MainScreen({ placesCount, offers }: MainScreenProps) {
- +
@@ -123,7 +127,9 @@ function MainScreen({ placesCount, offers }: MainScreenProps) {
-
+
+ +
diff --git a/src/const.ts b/src/const.ts index 15ec495..d755b18 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,14 +1,17 @@ -enum AppRoute{ - Root = '/', - Login = '/login', - Favorites = '/favorites', - Offer = '/offer/:id' +enum AppRoute { + Root = '/', + Login = '/login', + Favorites = '/favorites', + Offer = '/offer/:id', } export enum AuthorizationStatus { - Auth = 'AUTH', - NoAuth = 'NO_AUTH', - Unknown = 'UNKNOWN', + Auth = 'AUTH', + NoAuth = 'NO_AUTH', + Unknown = 'UNKNOWN', } +export const URL_MARKER_DEFAULT = + 'https://assets.htmlacademy.ru/content/intensive/javascript-1/demo/interactive-map/pin.svg'; + export default AppRoute; diff --git a/src/hooks/use-Map.tsx b/src/hooks/use-Map.tsx new file mode 100644 index 0000000..f651b2c --- /dev/null +++ b/src/hooks/use-Map.tsx @@ -0,0 +1,39 @@ +import { useEffect, useState, MutableRefObject, useRef } from 'react'; +import { Map, TileLayer } from 'leaflet'; +import { OffersType } from '../types/types'; + +function useMap( + mapRef: MutableRefObject, + {city}: OffersType): 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: city.location.zoom + }); + + 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.location]); + + return map; +} + +export default useMap; diff --git a/src/mocks/offers.ts b/src/mocks/offers.ts index e0ab1b1..54e7689 100644 --- a/src/mocks/offers.ts +++ b/src/mocks/offers.ts @@ -1,4 +1,4 @@ -import { OffersType } from '../types/offer'; +import { OffersType } from '../types/types'; const offers: OffersType[] = [ { @@ -11,6 +11,14 @@ const offers: OffersType[] = [ title: 'Beautiful & luxurious apartment at great location', type: 'Apartment', favorite: true, + city: { + name: 'Amsterdam', + location: { + latitude: 52.3909553943508, + longitude: 4.85309666406198, + zoom: 5, + }, + }, }, { id: 2, @@ -22,6 +30,14 @@ const offers: OffersType[] = [ title: 'Wood and stone place', type: 'Room', favorite: true, + city: { + name: 'Amsterdam', + location: { + latitude: 52.3609553943508, + longitude: 4.85309666406198, + zoom: 5, + }, + }, }, { id: 3, @@ -33,6 +49,14 @@ const offers: OffersType[] = [ title: 'Canal View Prinsengracht', type: 'Apartment', favorite: true, + city: { + name: 'Amsterdam', + location: { + latitude: 52.3909553943508, + longitude: 4.929309666406198, + zoom: 5, + }, + }, }, { id: 4, @@ -44,6 +68,14 @@ const offers: OffersType[] = [ title: 'Nice, cozy, warm big bed apartment', type: 'Apartment', favorite: true, + city: { + name: 'Amsterdam', + location: { + latitude: 52.3809553943508, + longitude: 4.939309666406198, + zoom: 100, + }, + }, }, ]; diff --git a/src/types/offer.ts b/src/types/types.ts similarity index 59% rename from src/types/offer.ts rename to src/types/types.ts index 01a6059..84faa86 100644 --- a/src/types/offer.ts +++ b/src/types/types.ts @@ -8,14 +8,12 @@ export type OffersType = { title: string; type: string; favorite: boolean; + city: { + name: string; + location: { + latitude: number; + longitude: number; + zoom: number; + }; + }; }; - -// export type Review = { -// comment: string; -// date: string; -// id: string; -// rating: number; -// user: User; -// }; - -