diff --git a/src/Pages/favorites-page.tsx b/src/Pages/favorites-page/favorites-page.tsx similarity index 86% rename from src/Pages/favorites-page.tsx rename to src/Pages/favorites-page/favorites-page.tsx index f8a3e67..e605929 100644 --- a/src/Pages/favorites-page.tsx +++ b/src/Pages/favorites-page/favorites-page.tsx @@ -1,7 +1,7 @@ import { Helmet } from 'react-helmet-async'; -import { Layout } from '../components/layout.tsx'; -import { OfferGroup } from '../components/offer/offer-group.tsx'; -import { Offer } from '../dataTypes/offer.ts'; +import { Layout } from '../../components/layout.tsx'; +import { OfferGroup } from '../../components/offer/offer-group.tsx'; +import { Offer } from '../../dataTypes/offer.ts'; interface FavoritesPageProps { offers: Offer[]; diff --git a/src/Pages/login-page.tsx b/src/Pages/login-page/login-page.tsx similarity index 100% rename from src/Pages/login-page.tsx rename to src/Pages/login-page/login-page.tsx diff --git a/src/Pages/main-page.tsx b/src/Pages/main-page/main-page.tsx similarity index 51% rename from src/Pages/main-page.tsx rename to src/Pages/main-page/main-page.tsx index 75ef3f0..a78219c 100644 --- a/src/Pages/main-page.tsx +++ b/src/Pages/main-page/main-page.tsx @@ -1,21 +1,27 @@ +/* eslint-disable react-hooks/exhaustive-deps */ import React, { useState } from 'react'; -import { Offer } from '../dataTypes/offer.ts'; -import { OffersList } from '../components/offer/offers-list.tsx'; -import { Layout } from '../components/layout.tsx'; +import { Offer } from '../../dataTypes/offer.ts'; +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'; -import { useAppSelector } from '../store/store.ts'; -import { CitiesList } from '../components/cities-list.tsx'; -import { pluralizeAndCombine } from '../utils/string-utils.ts'; +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'; +import { OfferSortSelect } from '../../components/offer/offer-sort-select.tsx'; export function MainPage(): React.JSX.Element { const [activeOffer, setActiveOffer] = useState>(null); const city = useAppSelector((state) => state.city); - const offers = useAppSelector((state) => state.offers).filter( + const unsortedOffers = useAppSelector((state) => state.offers).filter( (offer) => offer.city.name === city.name, ); - + const sort = useAppSelector((state) => state.sorting); + const offers = sort(unsortedOffers); + const offersCountCaption = offers.length === 0 + ? 'No places to stay available' + : `${pluralizeAndCombine('place', offers.length)} to stay in ${city.name}`; return (
@@ -28,35 +34,9 @@ export function MainPage(): React.JSX.Element {

Places

- {pluralizeAndCombine('place', offers.length)} to stay in{' '} - {city.name} + {offersCountCaption} -
- Sort by - - Popular - - - - -
    -
  • - Popular -
  • -
  • - Price: low to high -
  • -
  • - Price: high to low -
  • -
  • - Top rated first -
  • -
-
+ ) => diff --git a/src/Pages/offer-page.tsx b/src/Pages/offer-page/offer-page.tsx similarity index 82% rename from src/Pages/offer-page.tsx rename to src/Pages/offer-page/offer-page.tsx index 8d649f4..2649246 100644 --- a/src/Pages/offer-page.tsx +++ b/src/Pages/offer-page/offer-page.tsx @@ -1,19 +1,19 @@ import { Helmet } from 'react-helmet-async'; -import { Layout } from '../components/layout.tsx'; -import { OffersList } from '../components/offer/offers-list.tsx'; -import { offerMocks } from '../mocks/offers.ts'; -import { Reviews } from '../components/reviews/reviews.tsx'; -import { reviewMocks } from '../mocks/reviews.ts'; -import { Map } from '../components/map/map.tsx'; +import { Layout } from '../../components/layout.tsx'; +import { OffersList } from '../../components/offer/offers-list.tsx'; +import { offerMocks } from '../../mocks/offers.ts'; +import { Reviews } from '../../components/reviews/reviews.tsx'; +import { reviewMocks } from '../../mocks/reviews.ts'; +import { Map } from '../../components/map/map.tsx'; import React from 'react'; import { useParams } from 'react-router-dom'; -import { OfferInsideItems } from '../components/offer/offer-inside-items.tsx'; -import { detailedOfferMocks } from '../mocks/detailed-offer.ts'; -import { OfferHost } from '../components/offer/offer-host.tsx'; -import { capitalize, pluralizeAndCombine } from '../utils/string-utils.ts'; -import { Rating } from '../components/rating.tsx'; -import { OfferGallery } from '../components/offer/offer-gallery.tsx'; -import { BookmarkButton } from '../components/bookmark-button.tsx'; +import { OfferInsideItems } from '../../components/offer/offer-inside-items.tsx'; +import { detailedOfferMocks } from '../../mocks/detailed-offer.ts'; +import { OfferHost } from '../../components/offer/offer-host.tsx'; +import { capitalize, pluralizeAndCombine } from '../../utils/string-utils.ts'; +import { Rating } from '../../components/rating.tsx'; +import { OfferGallery } from '../../components/offer/offer-gallery.tsx'; +import { BookmarkButton } from '../../components/bookmark-button.tsx'; export function OfferPage(): React.JSX.Element { const offerId = useParams().id; diff --git a/src/components/app.tsx b/src/components/app.tsx index 1d523c1..dccd602 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -1,8 +1,8 @@ -import { MainPage } from '../pages/main-page.tsx'; +import { MainPage } from '../pages/main-page/main-page.tsx'; import { BrowserRouter, Route, Routes } from 'react-router-dom'; -import { LoginPage } from '../pages/login-page.tsx'; -import { FavoritesPage } from '../pages/favorites-page.tsx'; -import { OfferPage } from '../pages/offer-page.tsx'; +import { LoginPage } from '../pages/login-page/login-page.tsx'; +import { FavoritesPage } from '../pages/favorites-page/favorites-page.tsx'; +import { OfferPage } from '../pages/offer-page/offer-page.tsx'; import { NotFoundPage } from '../pages/not-found-page/not-found-page.tsx'; import { AuthorizationWrapper } from './authorization-wrapper.tsx'; import { AppRoutes } from '../dataTypes/enums/app-routes.ts'; diff --git a/src/components/offer/offer-sort-select.tsx b/src/components/offer/offer-sort-select.tsx new file mode 100644 index 0000000..9648616 --- /dev/null +++ b/src/components/offer/offer-sort-select.tsx @@ -0,0 +1,61 @@ +import React, { useState } from 'react'; +import { Offer } from '../../dataTypes/offer.ts'; +import { SortOffers } from '../../dataTypes/sort-offers.ts'; +import { useAppDispatch } from '../../store/store.ts'; +import { setSorting } from '../../store/actions.ts'; + +const sortingOptions: [string, SortOffers][] = [ + ['Popular', (offers: Offer[]) => offers], + [ + 'Price: low to high', + (offers: Offer[]) => offers.toSorted((a, b) => a.price - b.price), + ], + [ + 'Price: high to low', + (offers: Offer[]) => offers.toSorted((a, b) => b.price - a.price), + ], + [ + 'Top rated first', + (offers: Offer[]) => offers.toSorted((a, b) => b.rating - a.rating), + ], +]; + +export function OfferSortSelect(): React.JSX.Element { + const [isOpen, setIsOpen] = useState(false); + const [sortingOption, setSortingOption] = useState('Popular'); + const dispatch = useAppDispatch(); + const handleSortChange = (sort: SortOffers, sortingOptionName: string) => { + setSortingOption(sortingOptionName); + dispatch(setSorting(sort)); + setIsOpen(false); + }; + return ( +
+ Sort by + setIsOpen(!isOpen)} + > + {sortingOption} + + + + + {isOpen && ( +
    + {sortingOptions.map(([name, sort]) => ( +
  • handleSortChange(sort, name)} + > + {name} +
  • + ))} +
+ )} +
+ ); +} diff --git a/src/dataTypes/sort-offers.ts b/src/dataTypes/sort-offers.ts new file mode 100644 index 0000000..4aac267 --- /dev/null +++ b/src/dataTypes/sort-offers.ts @@ -0,0 +1,3 @@ +import { Offer } from './offer.ts'; + +export type SortOffers = (offers: Offer[]) => Offer[]; diff --git a/src/mocks/offers.ts b/src/mocks/offers.ts index b3cd5ca..87a63c7 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[] = [ { @@ -45,7 +45,7 @@ export const offerMocks: Offer[] = [ }, isFavorite: false, isPremium: true, - rating: 4, + rating: 3, previewImage: 'apartment-02.jpg', }, { @@ -91,7 +91,30 @@ export const offerMocks: Offer[] = [ }, isFavorite: true, isPremium: true, - rating: 4, + rating: 5, + previewImage: 'apartment-02.jpg', + }, + { + id: '6af6f711-c28d-4121-82cd-e0b462a27f44', + title: 'amogus', + type: RoomType.Apartment, + price: 88, + city: { + name: 'Paris', + location: { + latitude: 52.35514938496378, + longitude: 4.673877537499948, + zoom: 8, + }, + }, + location: { + latitude: 48.9, + longitude: 2.4, + zoom: 8, + }, + isFavorite: true, + isPremium: true, + rating: 5, previewImage: 'apartment-02.jpg', }, ]; diff --git a/src/store/actions.ts b/src/store/actions.ts index 9eb1142..edd99b1 100644 --- a/src/store/actions.ts +++ b/src/store/actions.ts @@ -1,7 +1,10 @@ import { createAction } from '@reduxjs/toolkit'; import { City } from '../dataTypes/city.ts'; import { Offer } from '../dataTypes/offer.ts'; +import { SortOffers } from '../dataTypes/sort-offers.ts'; -export const changeCity = createAction('mainPage/changeCity'); +export const changeCity = createAction('offers/changeCity'); -export const fillOffers = createAction('mainPage/fillOffers'); +export const setOffers = createAction('offers/setOffers'); + +export const setSorting = createAction('offers/setSorting'); diff --git a/src/store/store.ts b/src/store/store.ts index e797120..a46b772 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -1,13 +1,15 @@ import { configureStore, createReducer } from '@reduxjs/toolkit'; -import { changeCity, fillOffers } from './actions.ts'; +import {changeCity, setOffers, setSorting} 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'; +import {Offer} from '../dataTypes/offer.ts'; const initialState = { city: PARIS, offers: offerMocks, + sorting: (offers: Offer[]) => offers, }; const reducer = createReducer(initialState, (builder) => { @@ -15,8 +17,11 @@ const reducer = createReducer(initialState, (builder) => { .addCase(changeCity, (state, action) => { state.city = action.payload; }) - .addCase(fillOffers, (state, action) => { + .addCase(setOffers, (state, action) => { state.offers = action.payload; + }) + .addCase(setSorting, (state, action) => { + state.sorting = action.payload; }); });