From fe465451978a0972992631624de1b865dcf0e123 Mon Sep 17 00:00:00 2001 From: Gurikov Maxim Date: Mon, 11 Nov 2024 23:14:45 +0500 Subject: [PATCH] done task --- src/Pages/main-page.tsx | 56 +++++---------------- src/components/app.tsx | 43 ++++++++-------- src/components/cities-list.tsx | 39 +++++++++++++++ src/components/map/icons.ts | 13 +++++ src/components/map/map.tsx | 17 ++----- src/consts/cities.ts | 64 ++++++++++++++++++++++++ src/dataTypes/city.ts | 8 ++- src/dataTypes/store-types.ts | 5 ++ src/{components/map => hooks}/use-map.ts | 18 +++++-- src/mocks/offers.ts | 14 +++--- src/store/actions.ts | 7 +++ src/store/store.ts | 27 ++++++++++ 12 files changed, 221 insertions(+), 90 deletions(-) create mode 100644 src/components/cities-list.tsx create mode 100644 src/components/map/icons.ts create mode 100644 src/consts/cities.ts create mode 100644 src/dataTypes/store-types.ts rename src/{components/map => hooks}/use-map.ts (72%) create mode 100644 src/store/actions.ts create mode 100644 src/store/store.ts diff --git a/src/Pages/main-page.tsx b/src/Pages/main-page.tsx index b9e70c1..75ef3f0 100644 --- a/src/Pages/main-page.tsx +++ b/src/Pages/main-page.tsx @@ -5,61 +5,31 @@ import { Layout } from '../components/layout.tsx'; import { Helmet } from 'react-helmet-async'; import { Nullable } from 'vitest'; import { Map } from '../components/map/map.tsx'; +import { useAppSelector } from '../store/store.ts'; +import { CitiesList } from '../components/cities-list.tsx'; +import { pluralizeAndCombine } from '../utils/string-utils.ts'; -interface MainPageProps { - offers: Offer[]; -} - -export function MainPage({ offers }: MainPageProps): React.JSX.Element { +export function MainPage(): React.JSX.Element { const [activeOffer, setActiveOffer] = useState>(null); + const city = useAppSelector((state) => state.city); + const offers = useAppSelector((state) => state.offers).filter( + (offer) => offer.city.name === city.name, + ); + return (
6 cities

Cities

- +

Places

- {offers.length} places to stay in Amsterdam + {pluralizeAndCombine('place', offers.length)} to stay in{' '} + {city.name}
Sort by @@ -96,7 +66,7 @@ export function MainPage({ offers }: MainPageProps): React.JSX.Element {
({ location: x.location, id: x.id, diff --git a/src/components/app.tsx b/src/components/app.tsx index 35672f6..1d523c1 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -8,6 +8,8 @@ import { AuthorizationWrapper } from './authorization-wrapper.tsx'; import { AppRoutes } from '../dataTypes/enums/app-routes.ts'; import { HelmetProvider } from 'react-helmet-async'; import { Offer } from '../dataTypes/offer.ts'; +import { Provider } from 'react-redux'; +import { store } from '../store/store.ts'; interface AppProps { offers: Offer[]; @@ -15,26 +17,25 @@ interface AppProps { export function App({ offers }: AppProps): React.JSX.Element { return ( - - - - } - /> - } /> - - - - } - /> - } /> - } /> - - - + + + + + } /> + } /> + + + + } + /> + } /> + } /> + + + + ); } diff --git a/src/components/cities-list.tsx b/src/components/cities-list.tsx new file mode 100644 index 0000000..8892216 --- /dev/null +++ b/src/components/cities-list.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { CITIES } from '../consts/cities.ts'; +import { City } from '../dataTypes/city.ts'; +import { useAppDispatch } from '../store/store.ts'; +import { changeCity } from '../store/actions.ts'; + +interface CitiesListProps { + activeCityName: string; +} + +export function CitiesList({ + activeCityName, +}: CitiesListProps): React.JSX.Element { + const dispatch = useAppDispatch(); + return ( +
+
+ +
+
+ ); +} diff --git a/src/components/map/icons.ts b/src/components/map/icons.ts new file mode 100644 index 0000000..7b44966 --- /dev/null +++ b/src/components/map/icons.ts @@ -0,0 +1,13 @@ +import { Icon } from 'leaflet'; + +export const defaultCustomIcon = new Icon({ + iconUrl: 'public/img/pin.svg', + iconSize: [40, 40], + iconAnchor: [20, 40], +}); + +export const currentCustomIcon = new Icon({ + iconUrl: 'public/img/pin-active.svg', + iconSize: [40, 40], + iconAnchor: [20, 40], +}); diff --git a/src/components/map/map.tsx b/src/components/map/map.tsx index 6ae3b62..7258223 100644 --- a/src/components/map/map.tsx +++ b/src/components/map/map.tsx @@ -1,10 +1,11 @@ import React, { useRef, useEffect } from 'react'; -import { Icon, Marker, layerGroup } from 'leaflet'; +import { Marker, layerGroup } from 'leaflet'; import 'leaflet/dist/leaflet.css'; -import { useMap } from './use-map.ts'; +import { useMap } from '../../hooks/use-map.ts'; import { City } from '../../dataTypes/city.ts'; import { Point } from '../../dataTypes/point.ts'; import cn from 'classnames'; +import { currentCustomIcon, defaultCustomIcon } from './icons.ts'; interface MapProps { city: City; @@ -13,18 +14,6 @@ interface MapProps { isOnMainPage?: boolean; } -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): React.JSX.Element { const { city, points, selectedPoint, isOnMainPage } = props; diff --git a/src/consts/cities.ts b/src/consts/cities.ts new file mode 100644 index 0000000..dfee7ec --- /dev/null +++ b/src/consts/cities.ts @@ -0,0 +1,64 @@ +import { City } from '../dataTypes/city.ts'; + +export const PARIS: City = { + name: 'Paris', + location: { + latitude: 48.864716, + longitude: 2.349014, + zoom: 12, + }, +}; + +export const COLOGNE: City = { + name: 'Cologne', + location: { + latitude: 50.935173, + longitude: 6.953101, + zoom: 12, + }, +}; + +export const BRUSSELS: City = { + name: 'Brussels', + location: { + latitude: 50.85045, + longitude: 4.34878, + zoom: 12, + }, +}; + +export const AMSTERDAM: City = { + name: 'Amsterdam', + location: { + latitude: 52.377956, + longitude: 4.89707, + zoom: 12, + }, +}; + +export const HAMBURG: City = { + name: 'Hamburg', + location: { + latitude: 53.551086, + longitude: 9.993682, + zoom: 12, + }, +}; + +export const DUSSELDORF: City = { + name: 'Dusseldorf', + location: { + latitude: 51.233334, + longitude: 6.783333, + zoom: 12, + }, +}; + +export const CITIES: City[] = [ + PARIS, + COLOGNE, + BRUSSELS, + AMSTERDAM, + HAMBURG, + DUSSELDORF, +]; diff --git a/src/dataTypes/city.ts b/src/dataTypes/city.ts index d60307e..acbd55c 100644 --- a/src/dataTypes/city.ts +++ b/src/dataTypes/city.ts @@ -1,6 +1,12 @@ import { Location } from './location.ts'; export type City = { - name: string; + name: + | 'Paris' + | 'Cologne' + | 'Brussels' + | 'Amsterdam' + | 'Hamburg' + | 'Dusseldorf'; location: Location; }; diff --git a/src/dataTypes/store-types.ts b/src/dataTypes/store-types.ts new file mode 100644 index 0000000..77d1e9d --- /dev/null +++ b/src/dataTypes/store-types.ts @@ -0,0 +1,5 @@ +import { store } from '../store/store.ts'; + +export type State = ReturnType; + +export type AppDispatch = typeof store.dispatch; diff --git a/src/components/map/use-map.ts b/src/hooks/use-map.ts similarity index 72% rename from src/components/map/use-map.ts rename to src/hooks/use-map.ts index e311d97..e407298 100644 --- a/src/components/map/use-map.ts +++ b/src/hooks/use-map.ts @@ -1,6 +1,6 @@ import { useEffect, useState, MutableRefObject, useRef } from 'react'; import { Map, TileLayer } from 'leaflet'; -import { City } from '../../dataTypes/city.ts'; +import { City } from '../dataTypes/city.ts'; export function useMap( mapRef: MutableRefObject, @@ -10,13 +10,13 @@ export function useMap( const isRenderedRef = useRef(false); useEffect(() => { - if (mapRef.current !== null && !isRenderedRef.current) { + if (mapRef.current && !isRenderedRef.current) { const instance = new Map(mapRef.current, { center: { lat: city.location.latitude, lng: city.location.longitude, }, - zoom: 10, + zoom: city.location.zoom, }); const layer = new TileLayer( @@ -28,10 +28,20 @@ export function useMap( ); instance.addLayer(layer); - setMap(instance); isRenderedRef.current = true; } + if (isRenderedRef.current) { + setMap((prevMap) => + prevMap!.setView( + { + lat: city.location.latitude, + lng: city.location.longitude, + }, + city.location.zoom, + ), + ); + } }, [mapRef, city]); return map; diff --git a/src/mocks/offers.ts b/src/mocks/offers.ts index b988356..b3cd5ca 100644 --- a/src/mocks/offers.ts +++ b/src/mocks/offers.ts @@ -1,5 +1,5 @@ -import { Offer } from '../dataTypes/offer.ts'; -import { RoomType } from '../dataTypes/enums/room-type.ts'; +import {Offer} from '../dataTypes/offer.ts'; +import {RoomType} from '../dataTypes/enums/room-type.ts'; export const offerMocks: Offer[] = [ { @@ -54,16 +54,16 @@ export const offerMocks: Offer[] = [ type: RoomType.Room, price: 14, city: { - name: 'Amsterdam', + name: 'Paris', location: { - latitude: 52.35514938496378, - longitude: 4.673877537499948, + latitude: 48.864716123123, + longitude: 2.34901412113, zoom: 8, }, }, location: { - latitude: 52.3909553943508, - longitude: 4.929309666406198, + latitude: 48.864716123123, + longitude: 2.34901412113, zoom: 8, }, isFavorite: true, diff --git a/src/store/actions.ts b/src/store/actions.ts new file mode 100644 index 0000000..9eb1142 --- /dev/null +++ b/src/store/actions.ts @@ -0,0 +1,7 @@ +import { createAction } from '@reduxjs/toolkit'; +import { City } from '../dataTypes/city.ts'; +import { Offer } from '../dataTypes/offer.ts'; + +export const changeCity = createAction('mainPage/changeCity'); + +export const fillOffers = createAction('mainPage/fillOffers'); diff --git a/src/store/store.ts b/src/store/store.ts new file mode 100644 index 0000000..e797120 --- /dev/null +++ b/src/store/store.ts @@ -0,0 +1,27 @@ +import { configureStore, createReducer } from '@reduxjs/toolkit'; +import { changeCity, fillOffers } from './actions.ts'; +import { offerMocks } from '../mocks/offers.ts'; +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; +import { AppDispatch, State } from '../dataTypes/store-types.ts'; +import { PARIS } from '../consts/cities.ts'; + +const initialState = { + city: PARIS, + offers: offerMocks, +}; + +const reducer = createReducer(initialState, (builder) => { + builder + .addCase(changeCity, (state, action) => { + state.city = action.payload; + }) + .addCase(fillOffers, (state, action) => { + state.offers = action.payload; + }); +}); + +export const store = configureStore({ reducer }); + +export const useAppDispatch = () => useDispatch(); + +export const useAppSelector: TypedUseSelectorHook = useSelector;