Skip to content

Commit

Permalink
Merge pull request #9 from GooDeene/module6-task2
Browse files Browse the repository at this point in the history
  • Loading branch information
keksobot authored May 20, 2024
2 parents a088e98 + 7763d4e commit a61ec1f
Show file tree
Hide file tree
Showing 15 changed files with 177 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {SortingOrder} from '../../types/sorting-order.ts';
import classNames from 'classnames';
import React from 'react';
import {useDispatch} from 'react-redux';
import {setSortingOrder} from '../../store/acions.ts';

type DropdownOffersFilterOptionProps = {
sortingOption: SortingOrder;
isActive: boolean;
dropdownStateSetter: React.Dispatch<React.SetStateAction<boolean>>;
}

function DropdownOffersFilterOption({sortingOption, isActive, dropdownStateSetter} : DropdownOffersFilterOptionProps) : JSX.Element {
const dispatch = useDispatch();
const className = classNames('places__option', {'places__option--active' : isActive});
const handleSortingOptionOnClick = () => {
dropdownStateSetter((state) => !state);
dispatch(setSortingOrder(sortingOption));
};

return (
<li className={className} tabIndex={0} onClick={handleSortingOptionOnClick}>{sortingOption}</li>
);
}

export default DropdownOffersFilterOption;
49 changes: 40 additions & 9 deletions src/components/dropdown-offers-filter/dropdown-offers-filter.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,50 @@
import {useAppSelector} from '../../hooks/redux.ts';
import {SortingOrder} from '../../types/sorting-order.ts';
import DropdownOffersFilterOption from './dropdown-offers-filter-option.tsx';
import {useEffect, useState} from 'react';
import {useDispatch} from 'react-redux';
import {setOffers} from '../../store/acions.ts';
import {SortComparers} from '../../const.ts';
import {mockOffersShort} from '../../moks/offers-short.ts';


function DropdownOffersFilter() {
const dispatch = useDispatch();
const offers = useAppSelector((store) => store.offers);
const currentSortingOrder = useAppSelector((store) => store.sortingOrder);
const [dropdownIsOpen, setDropdownIsOpen] = useState<boolean>(false);

const handleSortingOnClick = () => setDropdownIsOpen((value) => !value);

useEffect(() => {
if (currentSortingOrder === SortingOrder.popular) {
dispatch(setOffers(mockOffersShort));
} else {
dispatch(setOffers(offers.toSorted(SortComparers[currentSortingOrder])));
// dispatch(setOffers(mockOffersShort));
}
}, [currentSortingOrder, offers]);

Check warning on line 26 in src/components/dropdown-offers-filter/dropdown-offers-filter.tsx

View workflow job for this annotation

GitHub Actions / Check

React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array

return (
<form className="places__sorting" action="#" method="get">
<span className="places__sorting-caption">Sort by</span>
<span className="places__sorting-type" tabIndex={0}>
Popular
<span className="places__sorting-caption">Sort by </span>
<span className="places__sorting-type" tabIndex={0} onClick={handleSortingOnClick}>
{currentSortingOrder}
<svg className="places__sorting-arrow" width="7" height="4">
<use xlinkHref="#icon-arrow-select"></use>
</svg>
</span>
<ul className="places__options places__options--custom places__options--opened">
<li className="places__option places__option--active" tabIndex={0}>Popular</li>
<li className="places__option" tabIndex={0}>Price: low to high</li>
<li className="places__option" tabIndex={0}>Price: high to low</li>
<li className="places__option" tabIndex={0}>Top rated first</li>
</ul>
{dropdownIsOpen &&
<ul className="places__options places__options--custom places__options--opened">
{Object.values(SortingOrder).map((value) => (
<DropdownOffersFilterOption
dropdownStateSetter={setDropdownIsOpen}
sortingOption={value}
isActive={currentSortingOrder === value}
key={value}
/>
))}
</ul>}
</form>
);
}
Expand Down
13 changes: 12 additions & 1 deletion src/components/favorites-list/favorites-list-element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@ import {OffersShort} from '../../types/offers/offer-short.ts';
import OfferCard from '../offer/offer-card/offer-card.tsx';
import {Link} from 'react-router-dom';
import {AppRoute} from '../../const.ts';
import {useDispatch} from 'react-redux';
import {setActiveCity} from '../../store/acions.ts';
import {getCityObjectByName} from '../../utils.ts';

type FavoritesListElementProps = {
favoritesAtCity: OffersShort;
cityName: string;
}

function FavoritesListElement({favoritesAtCity, cityName}: FavoritesListElementProps): JSX.Element {
const dispatch = useDispatch();
const handleLinkOnClick = () => {
const city = getCityObjectByName(cityName);
if (city) {
dispatch(setActiveCity(city));
}
};

return (
<li className="favorites__locations-items">
<div className="favorites__locations locations locations--current">
<div className="locations__item">
<Link className="locations__item-link" to={AppRoute.Main}>
<Link className="locations__item-link" to={AppRoute.Main} onClick={handleLinkOnClick}>
<span>{cityName}</span>
</Link>
</div>
Expand Down
13 changes: 7 additions & 6 deletions src/components/map/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import leaflet, {Icon, layerGroup} from 'leaflet';
import 'leaflet/dist/leaflet.css';
import {MapVariants} from '../../types/variants.ts';
import {URL_MARKER_CURRENT, URL_MARKER_DEFAULT} from '../../const.ts';
import {Point, Points} from '../../types/point.ts';
import {Points} from '../../types/point.ts';
import {Nullable} from '../../types/nullable.ts';

type MapProps = {
city: City;
points: Points;
selectedPoint: Point | undefined;
selectedPointId: Nullable<string>;
variant: MapVariants;
};

Expand All @@ -23,10 +24,10 @@ const defaultCustomIcon = new Icon({
const currentCustomIcon = new Icon({
iconUrl: URL_MARKER_CURRENT,
iconSize: [27, 39],
iconAnchor: [2, 4]
iconAnchor: [20, 40]
});

function Map({city, points, selectedPoint, variant}: MapProps): JSX.Element {
function Map({city, points, selectedPointId, variant}: MapProps): JSX.Element {
const sectionClassName = variant === 'main' ? 'cities__map' : 'offer__map';
const mapRef = useRef(null);
const map = useMap({mapRef, center: city.location});
Expand All @@ -45,7 +46,7 @@ function Map({city, points, selectedPoint, variant}: MapProps): JSX.Element {

marker
.setIcon(
selectedPoint !== undefined && point.id === selectedPoint.id
selectedPointId && point.id === selectedPointId
? currentCustomIcon
: defaultCustomIcon
)
Expand All @@ -58,7 +59,7 @@ function Map({city, points, selectedPoint, variant}: MapProps): JSX.Element {
map.removeLayer(markerLayer);
};
}
}, [map, points, markers, selectedPoint]);
}, [map, points, markers, selectedPointId]);
return <section className={sectionClassName} ref={mapRef}></section>;
}

Expand Down
9 changes: 6 additions & 3 deletions src/components/offer/offer-card-list/offer-card-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import {OfferCardListVariants} from '../../../types/variants.ts';
import DropdownOffersFilter from '../../dropdown-offers-filter/dropdown-offers-filter.tsx';
import OfferCardListContainer from './offer-card-list-container.tsx';
import {useOfferCardList} from '../../../hooks/use-offer-card-list.ts';
import {useState} from 'react';
import {Nullable} from '../../../types/nullable.ts';
import {useDispatch} from 'react-redux';
import {setHoverCardId} from '../../../store/acions.ts';

type OfferCardListProps = {
cityName: string;
Expand All @@ -14,8 +15,9 @@ type OfferCardListProps = {
}

function OfferCardList({cityName, offers, variant, offersCount}: OfferCardListProps) {
const [, setHoverCard] = useState<Nullable<string>>(null);
const handleHoverCard = (id: Nullable<string>) => setHoverCard(id);
const dispatch = useDispatch();
const handleHoverCard = (id: Nullable<string>) => dispatch(setHoverCardId(id));

const {
sectionClassName,
headerClassName,
Expand All @@ -25,6 +27,7 @@ function OfferCardList({cityName, offers, variant, offersCount}: OfferCardListPr
isFormActive,
containerClassName,
} = useOfferCardList({variant});

return (
<section className={sectionClassName}>
{isHeaderActive && <h2 className={headerClassName}>{headerContent}</h2>}
Expand Down
9 changes: 9 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import {SortingOrder} from './types/sorting-order.ts';
import {OfferShort} from './types/offers/offer-short.ts';

export enum AvailableCities {
Paris = 'Paris',
Cologne = 'Cologne',
Expand Down Expand Up @@ -26,5 +29,11 @@ export const Settings = {
cityNames: Object.values(AvailableCities),
};

export const SortComparers = {
[SortingOrder.priceToHigh]: (a: OfferShort, b: OfferShort) => a.price - b.price,
[SortingOrder.priceToLow]: (a: OfferShort, b: OfferShort) => b.price - a.price,
[SortingOrder.topRated]: (a: OfferShort, b: OfferShort) => b.rating - a.rating,
};

export const URL_MARKER_DEFAULT = './public/img/pin.svg';
export const URL_MARKER_CURRENT = './public/img/pin-active.svg';
2 changes: 1 addition & 1 deletion src/hooks/use-rating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ type RatingController = {

export function useRating({value, variant} : RatingController) {
const {ratingClassName, starsClassName} = variantData[variant];
const width = `${20 * value}%`;
const width = `${20 * Math.round(value)}%`;
return {ratingClassName, starsClassName, width};
}
2 changes: 1 addition & 1 deletion src/moks/offers-detailed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ mockOffersDetailed.set(
},
'isPremium': true,
'isFavorite': false,
'rating': 2.9,
'rating': 4.9,
'bedrooms': 5,
'maxAdults': 7
});
Expand Down
18 changes: 17 additions & 1 deletion src/moks/offers-short.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const mockOffersShort: OffersShort = [
},
isFavorite: false,
isPremium: true,
rating: 2.9
rating: 4.9
},
{
id: 'df5b1430-63fb-4033-b6b4-b9164930c5ee',
Expand Down Expand Up @@ -83,4 +83,20 @@ export const mockOffersShort: OffersShort = [
isPremium: true,
rating: 2.8
},
{
id: '8b95bdf2-0607-4986-a0a2-7bdf9e86273f',
title: 'Nice, cozy, warm big bed apartment',
type: 'house',
price: 323,
previewImage: 'https://14.design.htmlacademy.pro/static/hotel/17.jpg',
city: mockCities[AvailableCities.Cologne],
location: {
'latitude': 50.950361,
'longitude': 6.961974,
'zoom': 16
},
isFavorite: false,
isPremium: true,
rating: 3.8
},
];
8 changes: 5 additions & 3 deletions src/pages/main-page/main-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {useAppSelector} from '../../hooks/redux.ts';
import {Points} from '../../types/point.ts';

function MainPage(): JSX.Element {
// const dispatch = useDispatch();
const hoverCardId = useAppSelector((store) => store.hoverCardId);
const activeCity = useAppSelector((store) => store.activeCity);
const offers = useAppSelector((store) => store.offers);
const offersByCity = offers.filter((offer) => offer.city.name === activeCity.name);
const offersRaw = useAppSelector((store) => store.offers);
const offersByCity = offersRaw.filter((offer) => offer.city.name === activeCity.name);
const points : Points = offersByCity.map(({location, id}) => ({id, location}));

return (
Expand All @@ -23,7 +25,7 @@ function MainPage(): JSX.Element {
<div className="cities__places-container container">
<OfferCardList cityName={activeCity.name} offers={offersByCity} offersCount={offersByCity.length} variant={'main'} />
<div className="cities__right-section">
<Map city={activeCity} points={points} variant={'main'} selectedPoint={undefined}/>
<Map city={activeCity} points={points} variant={'main'} selectedPointId={hoverCardId}/>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/offer-page/offer-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function OfferPage({offersDetailed,}: OfferPageProps): JSX.Element | null {

</div>`
</div>
<Map city={mockCities[AvailableCities.Paris]} points={nearbyPoints} variant={'offer'} selectedPoint={undefined}/>
<Map city={mockCities[AvailableCities.Paris]} points={nearbyPoints} variant={'offer'} selectedPointId={null}/>
</section>

<div className="container">
Expand Down
8 changes: 8 additions & 0 deletions src/store/acions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import {createAction} from '@reduxjs/toolkit';
import {City} from '../types/city.ts';
import {Nullable} from '../types/nullable.ts';
import {SortingOrder} from '../types/sorting-order.ts';
import {OffersShort} from '../types/offers/offer-short.ts';

export const setActiveCity = createAction<City>('setCity');
export const setHoverCardId = createAction<Nullable<string>>('setHooverCardId');

export const setSortingOrder = createAction<SortingOrder>('setSortingOrder');

export const setOffers = createAction<OffersShort>('setOffers');
17 changes: 16 additions & 1 deletion src/store/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,38 @@ import {AvailableCities} from '../const.ts';
import {mockOffersShort} from '../moks/offers-short.ts';
import {OffersShort} from '../types/offers/offer-short.ts';
import {createReducer} from '@reduxjs/toolkit';
import {setActiveCity} from './acions.ts';
import {setActiveCity, setHoverCardId, setOffers, setSortingOrder} from './acions.ts';
import {City} from '../types/city.ts';
import {mockCities} from '../moks/cities.ts';
import {Nullable} from '../types/nullable.ts';
import {SortingOrder} from '../types/sorting-order.ts';

type AppState = {
activeCity: City;
offers: OffersShort;
hoverCardId: Nullable<string>;
sortingOrder: SortingOrder;
}

const initialState : AppState = {
activeCity: mockCities[AvailableCities.Paris],
offers: mockOffersShort,
hoverCardId: null,
sortingOrder: SortingOrder.popular,
};

export const reducer = createReducer(initialState, (builder) => {
builder
.addCase(setActiveCity, (state, action) => {
state.activeCity = action.payload;
})
.addCase(setHoverCardId, (state, action) => {
state.hoverCardId = action.payload;
})
.addCase(setSortingOrder, (state, action) => {
state.sortingOrder = action.payload;
})
.addCase(setOffers, (state, action) => {
state.offers = action.payload;
});
});
6 changes: 6 additions & 0 deletions src/types/sorting-order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum SortingOrder {
popular = 'Popular',
priceToHigh = 'Price: low to high',
priceToLow = 'Price: high to low',
topRated = 'Top rated first',
}
22 changes: 22 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import {AvailableCities} from './const.ts';
import {mockCities} from './moks/cities.ts';

export function capitalizeFirstLetter(word: string) : string {
return word.charAt(0).toUpperCase() + word.slice(1);
}
Expand All @@ -12,3 +15,22 @@ export const shuffleArray = <T>(unshuffled: T[]) => unshuffled
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value)
.slice(0, 3);

export const getCityObjectByName = (cityName: string) => {
switch (cityName) {
case AvailableCities.Paris:
return mockCities[AvailableCities.Paris];
case AvailableCities.Cologne:
return mockCities[AvailableCities.Cologne];
case AvailableCities.Hamburg:
return mockCities[AvailableCities.Hamburg];
case AvailableCities.Brussels:
return mockCities[AvailableCities.Brussels];
case AvailableCities.Dusseldorf:
return mockCities[AvailableCities.Dusseldorf];
case AvailableCities.Amsterdam:
return mockCities[AvailableCities.Amsterdam];
default:
return null;
}
};

0 comments on commit a61ec1f

Please sign in to comment.