Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Контроль и ограничения (часть 2) #9

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
};
Loading