From 7948b89047501c5eb18f5071d2c0591316037f6b Mon Sep 17 00:00:00 2001 From: iluvator Date: Fri, 8 Mar 2024 17:08:19 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=20=D0=BA=D0=B0=D1=80=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=B5=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/app.tsx | 13 ++--- src/components/card.tsx | 38 +++++++++------ src/components/list-cards.tsx | 21 +++------ src/components/list-location.tsx | 15 ++++++ src/components/location.tsx | 14 ++++++ src/components/map.tsx | 19 +++++--- src/const.ts | 10 +++- src/hooks/index.ts | 9 ++++ src/hooks/useMap.tsx | 9 ++-- src/index.tsx | 14 ++++-- src/mocks/locations.ts | 1 + src/pages/favorite-page.tsx | 13 +++-- src/pages/main-page.tsx | 81 +++++++++++--------------------- src/pages/offer-page.tsx | 20 ++++---- src/store/action.ts | 6 +++ src/store/index.ts | 6 +++ src/store/reducer.ts | 19 ++++++++ src/types/state.ts | 5 ++ 18 files changed, 189 insertions(+), 124 deletions(-) create mode 100644 src/components/list-location.tsx create mode 100644 src/components/location.tsx create mode 100644 src/hooks/index.ts create mode 100644 src/mocks/locations.ts create mode 100644 src/store/action.ts create mode 100644 src/store/index.ts create mode 100644 src/store/reducer.ts create mode 100644 src/types/state.ts diff --git a/src/components/app.tsx b/src/components/app.tsx index 771b364..bae72fd 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -6,27 +6,22 @@ import { Route, Routes } from 'react-router-dom'; import { AppRoute, authorizationStatus } from '../const'; import NotFoundPage from '../pages/not-found-page'; import ProtectedRoute from './protected-route'; -import { Offer } from '../types/offer'; -type TAppProps = { - offers: Offer[]; -} - -export default function App({ offers }: TAppProps) { +export default function App() { return ( } + element={} /> } + element={} /> } + element={ } /> void; } -export default function Card({ offer, optionCard }: TCardProps) { +export default function Card({ offer, optionCard, handelPointCardMouseOver }: TCardProps) { const { width, height, classCard } = optionCard; + + const onPointCardMouseOver = () => { + handelPointCardMouseOver(offer); + }; + + const onPointCardMouseLeave = () => { + handelPointCardMouseOver(null); + }; + return ( -
+
{offer.isPremium &&
Premium @@ -22,27 +32,27 @@ export default function Card({ offer, optionCard }: TCardProps) {
- Place image + Place image
-
-
-
- €{offer.price} - / night +
+
+
+ €{offer.price} + / night
-
- -

- {offer.title} + +

+ {offer.title}

-

{offer.type}

+

{offer.type}

); diff --git a/src/components/list-cards.tsx b/src/components/list-cards.tsx index c700032..11bd59e 100644 --- a/src/components/list-cards.tsx +++ b/src/components/list-cards.tsx @@ -1,30 +1,23 @@ -import { MouseEvent, useState } from 'react'; -import { optionCard } from '../const'; +import { OptionCard } from '../const'; import { Offer } from '../types/offer'; import Card from './card'; +import { OfferPreviews } from '../types/offer-preview'; type ListOfferNearbyProps = { offers: Offer[]; - onListItemHover: (currentCard: Offer) => void; + onListItemHover: (currentCard: OfferPreviews | null) => void; extraClass: string; } export default function ListCards({ offers, onListItemHover, extraClass }: ListOfferNearbyProps) { - const [cardId, setCardId] = useState(''); - const onPointingCardMouseOver = ({ target }: MouseEvent) => { - setCardId(target.dataset.id); + const handelPointingCardMouseOver = (offer: OfferPreviews | null) => { + onListItemHover(offer); }; - const currentCard = offers.find((offer) => offer.id === cardId); - - if (currentCard !== undefined) { - onListItemHover(currentCard); - } - return ( -
- {offers.map((offer) => )} +
+ {offers.map((offer) => )}
); } diff --git a/src/components/list-location.tsx b/src/components/list-location.tsx new file mode 100644 index 0000000..170815a --- /dev/null +++ b/src/components/list-location.tsx @@ -0,0 +1,15 @@ +import Location from './location'; +import { MouseEvent } from 'react'; + +type ListLocation = { + listLocations: string[]; + handleCurrentCityClick: (evt: MouseEvent) => void; +} + +export default function ListLocation({ listLocations, handleCurrentCityClick }: ListLocation) { + return ( +
    + {listLocations.map((location) => )} +
+ ); +} diff --git a/src/components/location.tsx b/src/components/location.tsx new file mode 100644 index 0000000..e3a9f8b --- /dev/null +++ b/src/components/location.tsx @@ -0,0 +1,14 @@ +type LocationProps = { + city: string; +} + +export default function Location({ city }: LocationProps) { + return ( +
  • + + {city} + +
  • + ); +} + diff --git a/src/components/map.tsx b/src/components/map.tsx index be59b63..894f6b8 100644 --- a/src/components/map.tsx +++ b/src/components/map.tsx @@ -5,11 +5,13 @@ import { useEffect, useRef } from 'react'; import { URL_MARKER_DEFAULT, URL_MARKER_CURRENT } from '../const'; import { City } from '../types/city'; import useMap from '../hooks/useMap'; +import { OfferPreviews } from '../types/offer-preview'; +import { MapSize } from '../const'; type MapProps = { city: City; offers: Offer[]; - selectedOffer: Offer | undefined; + selectedOffer: OfferPreviews | null; }; const defaultCustomIcon = leaflet.icon({ @@ -26,30 +28,35 @@ const currentCustomIcon = leaflet.icon({ export default function Map(props: MapProps): JSX.Element { const { city, offers, selectedOffer } = props; - + console.log(city); const mapRef = useRef(null); const map = useMap(mapRef, city); useEffect(() => { if (map) { + const markerLayer = leaflet.layerGroup().addTo(map); offers.forEach((offer) => { leaflet.marker({ lat: offer.location.latitude, lng: offer.location.longitude }, { - icon: (selectedOffer !== undefined && offer.title === selectedOffer.title) + icon: (selectedOffer !== null && offer.title === selectedOffer.title) ? currentCustomIcon : defaultCustomIcon, }) - .addTo(map); + .addTo(markerLayer); }); + return () => { + map.removeLayer(markerLayer); + mapRef.current = null; + }; } - }, [map, offers, selectedOffer]); + }, [map, offers, selectedOffer, city]); return (
    diff --git a/src/const.ts b/src/const.ts index ec991e4..ccc50f1 100644 --- a/src/const.ts +++ b/src/const.ts @@ -10,7 +10,7 @@ const AuthorizationStatus = { NO_AUTH: false, }; -const optionCard = { +const OptionCard = { CITIES_CARD: { classCard: 'cities__card', width: '260', @@ -23,6 +23,12 @@ const optionCard = { } }; +const MapSize = { + WIDTH: '100%', + HEIGHT: '100%', +}; + + const OptionListCard = { FAVORITES_CARD: 'near-places__list places__list', CITIES_CARD: 'cities__places-list places__list tabs__content', @@ -35,4 +41,4 @@ const URL_MARKER_CURRENT = '../markup/img/pin-active.svg'; const CountStar: number = 5; -export { CountStar, AppRoute, AuthorizationStatus as authorizationStatus, optionCard, URL_MARKER_DEFAULT, URL_MARKER_CURRENT, OptionListCard }; +export { CountStar, AppRoute, AuthorizationStatus as authorizationStatus, OptionCard, URL_MARKER_DEFAULT, URL_MARKER_CURRENT, OptionListCard, MapSize }; diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..9cb3402 --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,9 @@ +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; +import type { State, AppDispatch } from '../types/state'; + +const useAppDispatch = () => useDispatch(); + +const useAppSelector: TypedUseSelectorHook = useSelector; + +export { useAppDispatch, useAppSelector }; + diff --git a/src/hooks/useMap.tsx b/src/hooks/useMap.tsx index 686440a..4251478 100644 --- a/src/hooks/useMap.tsx +++ b/src/hooks/useMap.tsx @@ -8,9 +8,10 @@ function useMap( ): Map | null { const [map, setMap] = useState(null); const isRenderedRef = useRef(false); - + console.log('1 условие:', isRenderedRef.current, '2 условие: ', mapRef.current); useEffect(() => { if (mapRef.current !== null && !isRenderedRef.current) { + console.log('rfhnf', city); const instance = new Map(mapRef.current, { center: { lat: city.location.latitude, @@ -28,10 +29,12 @@ function useMap( ); instance.addLayer(layer); - setMap(instance); } - isRenderedRef.current = true; + + return () => { + isRenderedRef.current = true; + }; }, [mapRef, city]); return map; diff --git a/src/index.tsx b/src/index.tsx index 9ba07ae..7d23b82 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,6 +4,8 @@ import App from './components/app'; import { BrowserRouter } from 'react-router-dom'; import { HelmetProvider } from 'react-helmet-async'; import { offers } from './mocks/offers'; +import { Provider } from 'react-redux'; +import { store } from './store'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement @@ -11,10 +13,12 @@ const root = ReactDOM.createRoot( root.render( - - - - - + + + + + + + ); diff --git a/src/mocks/locations.ts b/src/mocks/locations.ts new file mode 100644 index 0000000..a6fd1f8 --- /dev/null +++ b/src/mocks/locations.ts @@ -0,0 +1 @@ +export const locations = ['Paris', 'Cologne', 'Brussels', 'Amsterdam', 'Hamburg', 'Dusseldorf',]; diff --git a/src/pages/favorite-page.tsx b/src/pages/favorite-page.tsx index a08f6d5..ebcf05c 100644 --- a/src/pages/favorite-page.tsx +++ b/src/pages/favorite-page.tsx @@ -2,16 +2,15 @@ import Container from '../components/container'; import Card from '../components/card'; import FavoriteItems from '../components/favorite-items'; import { Helmet } from 'react-helmet-async'; -import { Offer } from '../types/offer'; -import { optionCard } from '../const'; +import { OptionCard } from '../const'; import FavoritesEmpty from '../components/favorites-empty'; import { getFavoritesByLocation } from '../utils/utils'; +import { useAppSelector } from '../hooks'; -type TOfferPageProps = { - offers: Offer[]; -} -export default function FavoritePage({ offers }: TOfferPageProps) { +export default function FavoritePage() { + const offers = useAppSelector((state) => state.offers); + const favorites = getFavoritesByLocation(offers); return ( @@ -26,7 +25,7 @@ export default function FavoritePage({ offers }: TOfferPageProps) {
      {Object.entries(favorites).map(([location, gropedFavorites]) => ( - {gropedFavorites.map((favorite) => )} + {gropedFavorites.map((favorite) => )} ) )} diff --git a/src/pages/main-page.tsx b/src/pages/main-page.tsx index 0ec2d2f..8881fcd 100644 --- a/src/pages/main-page.tsx +++ b/src/pages/main-page.tsx @@ -1,20 +1,25 @@ import Container from '../components/container'; import { City } from '../types/city'; -import { Offer } from '../types/offer'; import ListCards from '../components/list-cards'; import Map from '../components/map'; import { MouseEvent, useState } from 'react'; import MainEmpty from '../components/main-empty'; import { OptionListCard } from '../const'; +import { OfferPreviews } from '../types/offer-preview'; +import { locations } from '../mocks/locations'; +import ListLocation from '../components/list-location'; +import { useAppDispatch, useAppSelector } from '../hooks'; +import { selectCity } from '../store/action'; +import { offers } from '../mocks/offers'; -type TMainPageProps = { - offers: Offer[]; -} -export default function MainPage({ offers }: TMainPageProps) { +export default function MainPage() { + const baseOffers = offers; + const selectOffers = useAppSelector((state) => state.offers); + const dispatch = useAppDispatch(); - const [selectedOffer, setSelectedOffer] = useState( - undefined + const [selectedOffer, setSelectedOffer] = useState( + null ); const [selectedCity, setSelectedCity] = useState({ @@ -26,21 +31,21 @@ export default function MainPage({ offers }: TMainPageProps) { } }); - const handleListItemHover = (currentCard: Offer) => { - const currentPoint = offers.find((offer) => offer.title === currentCard.title); - if (currentPoint !== undefined) { - setSelectedOffer(currentPoint); - } + const handleListItemHover = (currentCard: OfferPreviews | null) => { + setSelectedOffer(currentCard); }; const handleCurrentCityClick = (evt: MouseEvent) => { evt.preventDefault(); - const currentOffer = offers.find((offer) => offer.city.name === evt.target.textContent); + const currentOffer = baseOffers.find((offer) => offer.city.name === evt.target.textContent); + + if (currentOffer !== undefined) { + dispatch(selectCity(currentOffer)); + } if (currentOffer !== undefined) { - setSelectedCity({ ...currentOffer?.city }); - offers.filter((offer) => offer.city.name === currentOffer.city.name); + setSelectedCity({ ...currentOffer.city }); } }; @@ -50,47 +55,16 @@ export default function MainPage({ offers }: TMainPageProps) {

      Cities

      - {offers.length !== 0 ? + {selectOffers.length !== 0 ?

      Places

      - {offers.length} places to stay in Amsterdam + {selectOffers.length} place{selectOffers.length > 1 ? 's' : ''} to stay in Amsterdam
      Sort by @@ -106,13 +80,13 @@ export default function MainPage({ offers }: TMainPageProps) {
    • Top rated first
    - + : } - {offers.length && + {selectOffers.length &&
    -
    - +
    +
    } @@ -121,3 +95,4 @@ export default function MainPage({ offers }: TMainPageProps) {
    ); } + diff --git a/src/pages/offer-page.tsx b/src/pages/offer-page.tsx index 1bf16b2..8b5185e 100644 --- a/src/pages/offer-page.tsx +++ b/src/pages/offer-page.tsx @@ -2,7 +2,6 @@ import Container from '../components/container'; import OfferInside from '../components/offer-inside'; import Rating from '../components/rating'; import { Helmet } from 'react-helmet-async'; -import { Offer } from '../types/offer'; import CommentsTemplate from '../components/comments-template'; import { CountStar } from '../const'; import { useParams } from 'react-router-dom'; @@ -12,20 +11,19 @@ import Map from '../components/map'; import { useState } from 'react'; import { OptionListCard } from '../const'; import ListCards from '../components/list-cards'; +import { OfferPreviews } from '../types/offer-preview'; +import { useAppSelector } from '../hooks'; -type TOfferPageProps = { - offers: Offer[]; -} +export default function OfferPage() { + const offers = useAppSelector((state) => state.offers); -export default function OfferPage({ offers }: TOfferPageProps) { - const [selectedOffer, setSelectedOffer] = useState( - undefined + const [selectedOffer, setSelectedOffer] = useState( + null ); - const handleListItemHover = (currentCard: Offer) => { - const currentPoint = offers.find((offer) => offer.title === currentCard.title); - if (currentPoint !== undefined) { - setSelectedOffer(currentPoint); + const handleListItemHover = (currentCard: OfferPreviews | null) => { + if (currentCard !== null) { + setSelectedOffer(currentCard); } }; diff --git a/src/store/action.ts b/src/store/action.ts new file mode 100644 index 0000000..4a58be8 --- /dev/null +++ b/src/store/action.ts @@ -0,0 +1,6 @@ +import { createAction } from '@reduxjs/toolkit'; +import { Offer } from '../types/offer'; + +const selectCity = createAction('mainPage/selectCity'); + +export { selectCity }; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..b6e9107 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,6 @@ +import { configureStore } from '@reduxjs/toolkit'; +import { reducer } from './reducer'; + +const store = configureStore({ reducer }); + +export { store }; diff --git a/src/store/reducer.ts b/src/store/reducer.ts new file mode 100644 index 0000000..c634d1b --- /dev/null +++ b/src/store/reducer.ts @@ -0,0 +1,19 @@ +import { createReducer } from '@reduxjs/toolkit'; +import { selectCity } from './action'; +import { offers } from '../mocks/offers'; + +const initialState = { + city: 'Paris', + offers: [...offers].filter((offer) => offer.city.name === 'Paris'), +}; + +const reducer = createReducer(initialState, (builder) => { + builder + .addCase(selectCity, (state, action) => { + state.city = action.payload.city.name; + state.offers = [...offers].filter((offer) => offer.city.name === state.city); + }); +}); + +export { reducer }; + diff --git a/src/types/state.ts b/src/types/state.ts new file mode 100644 index 0000000..70af949 --- /dev/null +++ b/src/types/state.ts @@ -0,0 +1,5 @@ +import { store } from '../store'; + +export type State = ReturnType; + +export type AppDispatch = typeof store.dispatch; From 1cb285dc8618b67028771698460beb03da152328 Mon Sep 17 00:00:00 2001 From: iluvator Date: Sat, 9 Mar 2024 15:41:00 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=20=D1=81=D0=BE=D1=80=D1=82=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/list-location.tsx | 5 +++-- src/components/location.tsx | 7 ++++--- src/components/map.tsx | 4 +++- src/components/places-option.tsx | 15 +++++++++++++++ src/components/places-options.tsx | 15 +++++++++++++++ src/const.ts | 4 +++- src/hooks/useMap.tsx | 4 +--- src/pages/main-page.tsx | 28 ++++++++++++++++++---------- src/store/action.ts | 3 ++- src/store/reducer.ts | 20 +++++++++++++++++++- src/utils/utils.ts | 14 +++++++++++++- 11 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 src/components/places-option.tsx create mode 100644 src/components/places-options.tsx diff --git a/src/components/list-location.tsx b/src/components/list-location.tsx index 170815a..bc06172 100644 --- a/src/components/list-location.tsx +++ b/src/components/list-location.tsx @@ -4,12 +4,13 @@ import { MouseEvent } from 'react'; type ListLocation = { listLocations: string[]; handleCurrentCityClick: (evt: MouseEvent) => void; + currentCity: string; } -export default function ListLocation({ listLocations, handleCurrentCityClick }: ListLocation) { +export default function ListLocation({ listLocations, handleCurrentCityClick, currentCity }: ListLocation) { return (
      - {listLocations.map((location) => )} + {listLocations.map((location) => )}
    ); } diff --git a/src/components/location.tsx b/src/components/location.tsx index e3a9f8b..1691aae 100644 --- a/src/components/location.tsx +++ b/src/components/location.tsx @@ -1,11 +1,12 @@ type LocationProps = { city: string; + isActive: boolean; } -export default function Location({ city }: LocationProps) { +export default function Location({ city, isActive }: LocationProps) { return ( -
  • - +
  • + {city}
  • diff --git a/src/components/map.tsx b/src/components/map.tsx index 894f6b8..b111c7b 100644 --- a/src/components/map.tsx +++ b/src/components/map.tsx @@ -28,7 +28,7 @@ const currentCustomIcon = leaflet.icon({ export default function Map(props: MapProps): JSX.Element { const { city, offers, selectedOffer } = props; - console.log(city); + const mapRef = useRef(null); const map = useMap(mapRef, city); @@ -51,6 +51,8 @@ export default function Map(props: MapProps): JSX.Element { mapRef.current = null; }; } + + }, [map, offers, selectedOffer, city]); return ( diff --git a/src/components/places-option.tsx b/src/components/places-option.tsx new file mode 100644 index 0000000..0b88b2b --- /dev/null +++ b/src/components/places-option.tsx @@ -0,0 +1,15 @@ +import { SyntheticEvent } from 'react'; + +type PlacesOptionProps = { + place: string; + handelSortOfferClick: (sortType: string) => void; +} + +export default function PlacesOption({ place, handelSortOfferClick }: PlacesOptionProps) { + const onSortOfferClick = (evt: SyntheticEvent) => { + handelSortOfferClick(evt.target.textContent); + }; + return ( +
  • {place}
  • + ); +} diff --git a/src/components/places-options.tsx b/src/components/places-options.tsx new file mode 100644 index 0000000..6163c7e --- /dev/null +++ b/src/components/places-options.tsx @@ -0,0 +1,15 @@ +import PlacesOption from './places-option'; +import { placesOption } from '../const'; + +type PlacesOptionsProps = { + isOpen: boolean; + handelSortOfferClick: (sortType: string) => void; +} + +export default function PlacesOptions({ isOpen, handelSortOfferClick }: PlacesOptionsProps) { + return ( +
      + {placesOption.map((place) => )} +
    + ); +} diff --git a/src/const.ts b/src/const.ts index ccc50f1..1776c12 100644 --- a/src/const.ts +++ b/src/const.ts @@ -34,6 +34,8 @@ const OptionListCard = { CITIES_CARD: 'cities__places-list places__list tabs__content', }; +const placesOption = ['Popular', 'Price: low to high', 'Price: high to low', 'Top rated first',]; + const URL_MARKER_DEFAULT = '../markup/img/pin.svg'; const URL_MARKER_CURRENT = '../markup/img/pin-active.svg'; @@ -41,4 +43,4 @@ const URL_MARKER_CURRENT = '../markup/img/pin-active.svg'; const CountStar: number = 5; -export { CountStar, AppRoute, AuthorizationStatus as authorizationStatus, OptionCard, URL_MARKER_DEFAULT, URL_MARKER_CURRENT, OptionListCard, MapSize }; +export { CountStar, AppRoute, AuthorizationStatus as authorizationStatus, OptionCard, URL_MARKER_DEFAULT, URL_MARKER_CURRENT, OptionListCard, MapSize, placesOption }; diff --git a/src/hooks/useMap.tsx b/src/hooks/useMap.tsx index 4251478..3260f7a 100644 --- a/src/hooks/useMap.tsx +++ b/src/hooks/useMap.tsx @@ -32,9 +32,7 @@ function useMap( setMap(instance); } - return () => { - isRenderedRef.current = true; - }; + isRenderedRef.current = true; }, [mapRef, city]); return map; diff --git a/src/pages/main-page.tsx b/src/pages/main-page.tsx index 8881fcd..51b1ee8 100644 --- a/src/pages/main-page.tsx +++ b/src/pages/main-page.tsx @@ -9,9 +9,9 @@ import { OfferPreviews } from '../types/offer-preview'; import { locations } from '../mocks/locations'; import ListLocation from '../components/list-location'; import { useAppDispatch, useAppSelector } from '../hooks'; -import { selectCity } from '../store/action'; +import { selectCity, sortOffer } from '../store/action'; import { offers } from '../mocks/offers'; - +import PlacesOptions from '../components/places-options'; export default function MainPage() { const baseOffers = offers; @@ -22,6 +22,10 @@ export default function MainPage() { null ); + const [isOpenSort, setIsOpenSort] = useState( + false + ); + const [selectedCity, setSelectedCity] = useState({ name: 'Paris', location: { @@ -31,6 +35,15 @@ export default function MainPage() { } }); + const handelSortOfferClick = (sortType: string) => { + dispatch(sortOffer(sortType)); + setIsOpenSort(!isOpenSort); + }; + + const handelOpenPlacesClick = () => { + setIsOpenSort(!isOpenSort); + }; + const handleListItemHover = (currentCard: OfferPreviews | null) => { setSelectedOffer(currentCard); }; @@ -55,7 +68,7 @@ export default function MainPage() {

    Cities

    - +
    @@ -67,18 +80,13 @@ export default function MainPage() { {selectOffers.length} place{selectOffers.length > 1 ? 's' : ''} to stay in Amsterdam
    Sort by - + Popular -
      -
    • Popular
    • -
    • Price: low to high
    • -
    • Price: high to low
    • -
    • Top rated first
    • -
    + : } diff --git a/src/store/action.ts b/src/store/action.ts index 4a58be8..3e2b35b 100644 --- a/src/store/action.ts +++ b/src/store/action.ts @@ -2,5 +2,6 @@ import { createAction } from '@reduxjs/toolkit'; import { Offer } from '../types/offer'; const selectCity = createAction('mainPage/selectCity'); +const sortOffer = createAction('mainPage/sortOffer'); -export { selectCity }; +export { selectCity, sortOffer }; diff --git a/src/store/reducer.ts b/src/store/reducer.ts index c634d1b..c8f495c 100644 --- a/src/store/reducer.ts +++ b/src/store/reducer.ts @@ -1,6 +1,8 @@ import { createReducer } from '@reduxjs/toolkit'; -import { selectCity } from './action'; +import { selectCity, sortOffer } from './action'; import { offers } from '../mocks/offers'; +import { placesOption } from '../const'; +import { sortPriceLow, sortPriceHigh, sortRating } from '../utils/utils'; const initialState = { city: 'Paris', @@ -12,6 +14,22 @@ const reducer = createReducer(initialState, (builder) => { .addCase(selectCity, (state, action) => { state.city = action.payload.city.name; state.offers = [...offers].filter((offer) => offer.city.name === state.city); + }) + .addCase(sortOffer, (state, action) => { + switch (action.payload) { + case placesOption[0]: + state.offers = [...offers].filter((offer) => offer.city.name === state.city); + break; + case placesOption[1]: + state.offers = state.offers.sort(sortPriceHigh); + break; + case placesOption[2]: + state.offers = state.offers.sort(sortPriceLow); + break; + case placesOption[3]: + state.offers = state.offers.sort(sortRating); + break; + } }); }); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 275a9e8..d9bc3b9 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -19,4 +19,16 @@ function humanizeOrderData(date: string, format: string) { return date ? dayjs(date).format(format) : ''; } -export { getFavoritesByLocation, humanizeOrderData }; +function sortPriceLow(offerA: Offer, offerB: Offer,) { + return offerA.price - offerB.price; +} + +function sortPriceHigh(offerA: Offer, offerB: Offer,) { + return offerB.price - offerA.price; +} + +function sortRating(offerA: Offer, offerB: Offer,) { + return offerB.rating - offerA.rating; +} + +export { getFavoritesByLocation, humanizeOrderData, sortPriceLow, sortPriceHigh, sortRating }; From 9d02936aa2eba95f5aba1992bc6881b26b2f7dcd Mon Sep 17 00:00:00 2001 From: iluvator Date: Sat, 9 Mar 2024 23:28:42 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/app.tsx | 2 +- src/components/list-location.tsx | 8 ++--- src/components/location.tsx | 7 ++-- src/components/places-option.tsx | 6 ++-- src/hooks/useMap.tsx | 2 -- src/pages/main-page.tsx | 32 +++++++++++-------- .../not-found-page/not-found-page-model.css | 6 ++++ .../{ => not-found-page}/not-found-page.tsx | 12 ++----- src/store/action.ts | 3 +- src/store/reducer.ts | 2 +- 10 files changed, 44 insertions(+), 36 deletions(-) create mode 100644 src/pages/not-found-page/not-found-page-model.css rename src/pages/{ => not-found-page}/not-found-page.tsx (68%) diff --git a/src/components/app.tsx b/src/components/app.tsx index bae72fd..3ee6b5a 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -4,7 +4,7 @@ import FavoritePage from '../pages/favorite-page'; import LoginPage from '../pages/login-page'; import { Route, Routes } from 'react-router-dom'; import { AppRoute, authorizationStatus } from '../const'; -import NotFoundPage from '../pages/not-found-page'; +import NotFoundPage from '../pages/not-found-page/not-found-page'; import ProtectedRoute from './protected-route'; export default function App() { diff --git a/src/components/list-location.tsx b/src/components/list-location.tsx index bc06172..6af6b3a 100644 --- a/src/components/list-location.tsx +++ b/src/components/list-location.tsx @@ -1,16 +1,16 @@ import Location from './location'; -import { MouseEvent } from 'react'; +import { SyntheticEvent } from 'react'; type ListLocation = { listLocations: string[]; - handleCurrentCityClick: (evt: MouseEvent) => void; + handleCurrentCityClick: (evt: SyntheticEvent) => void; currentCity: string; } export default function ListLocation({ listLocations, handleCurrentCityClick, currentCity }: ListLocation) { return ( -
      - {listLocations.map((location) => )} +
        + {listLocations.map((location) => )}
      ); } diff --git a/src/components/location.tsx b/src/components/location.tsx index 1691aae..fd5d6a9 100644 --- a/src/components/location.tsx +++ b/src/components/location.tsx @@ -1,13 +1,16 @@ +import { SyntheticEvent } from 'react'; + type LocationProps = { city: string; isActive: boolean; + handleCurrentCityClick: (evt: SyntheticEvent) => void; } -export default function Location({ city, isActive }: LocationProps) { +export default function Location({ city, isActive, handleCurrentCityClick }: LocationProps) { return (
    • - {city} + {city}
    • ); diff --git a/src/components/places-option.tsx b/src/components/places-option.tsx index 0b88b2b..4a28f99 100644 --- a/src/components/places-option.tsx +++ b/src/components/places-option.tsx @@ -6,8 +6,10 @@ type PlacesOptionProps = { } export default function PlacesOption({ place, handelSortOfferClick }: PlacesOptionProps) { - const onSortOfferClick = (evt: SyntheticEvent) => { - handelSortOfferClick(evt.target.textContent); + const onSortOfferClick = (evt: SyntheticEvent) => { + if (evt.currentTarget.textContent !== null) { + handelSortOfferClick(evt.currentTarget.textContent); + } }; return (
    • {place}
    • diff --git a/src/hooks/useMap.tsx b/src/hooks/useMap.tsx index 3260f7a..0b4dc41 100644 --- a/src/hooks/useMap.tsx +++ b/src/hooks/useMap.tsx @@ -8,10 +8,8 @@ function useMap( ): Map | null { const [map, setMap] = useState(null); const isRenderedRef = useRef(false); - console.log('1 условие:', isRenderedRef.current, '2 условие: ', mapRef.current); useEffect(() => { if (mapRef.current !== null && !isRenderedRef.current) { - console.log('rfhnf', city); const instance = new Map(mapRef.current, { center: { lat: city.location.latitude, diff --git a/src/pages/main-page.tsx b/src/pages/main-page.tsx index 51b1ee8..484b2f1 100644 --- a/src/pages/main-page.tsx +++ b/src/pages/main-page.tsx @@ -2,7 +2,7 @@ import Container from '../components/container'; import { City } from '../types/city'; import ListCards from '../components/list-cards'; import Map from '../components/map'; -import { MouseEvent, useState } from 'react'; +import { SyntheticEvent, useState } from 'react'; import MainEmpty from '../components/main-empty'; import { OptionListCard } from '../const'; import { OfferPreviews } from '../types/offer-preview'; @@ -16,6 +16,7 @@ import PlacesOptions from '../components/places-options'; export default function MainPage() { const baseOffers = offers; const selectOffers = useAppSelector((state) => state.offers); + const currentCity = useAppSelector((state) => state.city); const dispatch = useAppDispatch(); const [selectedOffer, setSelectedOffer] = useState( @@ -35,6 +36,8 @@ export default function MainPage() { } }); + const [selectedLocation, setSelectedLocation] = useState(currentCity); + const handelSortOfferClick = (sortType: string) => { dispatch(sortOffer(sortType)); setIsOpenSort(!isOpenSort); @@ -48,19 +51,22 @@ export default function MainPage() { setSelectedOffer(currentCard); }; - const handleCurrentCityClick = (evt: MouseEvent) => { + const handleCurrentCityClick = (evt: SyntheticEvent) => { evt.preventDefault(); - const currentOffer = baseOffers.find((offer) => offer.city.name === evt.target.textContent); + const currentOffer = baseOffers.find((offer) => offer.city.name === evt.currentTarget.textContent); - if (currentOffer !== undefined) { - dispatch(selectCity(currentOffer)); - } if (currentOffer !== undefined) { setSelectedCity({ ...currentOffer.city }); } + if (evt.currentTarget.tagName === 'SPAN' && evt.currentTarget.textContent !== null) { + setSelectedLocation(evt.currentTarget.textContent); + dispatch(selectCity(evt.currentTarget.textContent)); + } + + }; return ( @@ -68,7 +74,7 @@ export default function MainPage() {

      Cities

      - +
      @@ -91,12 +97,12 @@ export default function MainPage() { : } - {selectOffers.length && -
      -
      - -
      -
      } +
      +
      + {selectOffers.length && + } +
      +
    diff --git a/src/pages/not-found-page/not-found-page-model.css b/src/pages/not-found-page/not-found-page-model.css new file mode 100644 index 0000000..c9d56ff --- /dev/null +++ b/src/pages/not-found-page/not-found-page-model.css @@ -0,0 +1,6 @@ +.not-found-page-model { + position: absolute; + top: 50%; + left: 50%; + text-align: center; +} diff --git a/src/pages/not-found-page.tsx b/src/pages/not-found-page/not-found-page.tsx similarity index 68% rename from src/pages/not-found-page.tsx rename to src/pages/not-found-page/not-found-page.tsx index dc10c41..364a635 100644 --- a/src/pages/not-found-page.tsx +++ b/src/pages/not-found-page/not-found-page.tsx @@ -1,17 +1,11 @@ import { Link } from 'react-router-dom'; -import { AppRoute } from '../const'; +import { AppRoute } from '../../const'; import { Helmet } from 'react-helmet-async'; - -const divStyle = { - position: 'absolute', - top: '50%', - left: '50%', - textAlign: 'center' -}; +import './not-found-page-model.css'; export default function NotFoundPage() { return ( -
    +
    Not found page diff --git a/src/store/action.ts b/src/store/action.ts index 3e2b35b..66ea6c7 100644 --- a/src/store/action.ts +++ b/src/store/action.ts @@ -1,7 +1,6 @@ import { createAction } from '@reduxjs/toolkit'; -import { Offer } from '../types/offer'; -const selectCity = createAction('mainPage/selectCity'); +const selectCity = createAction('mainPage/selectCity'); const sortOffer = createAction('mainPage/sortOffer'); export { selectCity, sortOffer }; diff --git a/src/store/reducer.ts b/src/store/reducer.ts index c8f495c..594c2fb 100644 --- a/src/store/reducer.ts +++ b/src/store/reducer.ts @@ -12,7 +12,7 @@ const initialState = { const reducer = createReducer(initialState, (builder) => { builder .addCase(selectCity, (state, action) => { - state.city = action.payload.city.name; + state.city = action.payload; state.offers = [...offers].filter((offer) => offer.city.name === state.city); }) .addCase(sortOffer, (state, action) => {