From 58a3be552614280d001197a09ea99d12cc86ca44 Mon Sep 17 00:00:00 2001 From: Alexander Morugin Date: Wed, 10 Apr 2024 20:41:31 +0300 Subject: [PATCH 1/5] enhancement_280-1_page_vouchers --- src/app/router/AppRouter/ui/AppRouter.tsx | 3 +- .../VouchersPage/VouchersPage.module.scss | 17 ++++ src/pages/VouchersPage/VouchersPage.tsx | 20 +++++ .../model/types/types.ts | 3 + .../model/validation/validation.ts | 6 +- .../ui/FormBuyGiftCertificate.module.scss | 77 +++++++++++++------ .../ui/FormBuyGiftCertificate.tsx | 73 ++++++++++++++---- src/widgets/Header/Header.tsx | 2 +- 8 files changed, 159 insertions(+), 42 deletions(-) create mode 100644 src/pages/VouchersPage/VouchersPage.module.scss create mode 100644 src/pages/VouchersPage/VouchersPage.tsx diff --git a/src/app/router/AppRouter/ui/AppRouter.tsx b/src/app/router/AppRouter/ui/AppRouter.tsx index e94ffc66..884fb898 100644 --- a/src/app/router/AppRouter/ui/AppRouter.tsx +++ b/src/app/router/AppRouter/ui/AppRouter.tsx @@ -15,6 +15,7 @@ import { ProductPage } from '@/pages/ProductPage/ProductPage' import { ProductsPage } from '@/pages/ProductsPage/ProductsPage' import RootPage from '@/pages/RootPage/RootPage' import SearchResultsPage from '@/pages/SearchResultsPage/SearchResultsPage' +import VouchersPage from '@/pages/VouchersPage/VouchersPage' import { Routes } from '@/shared/config/routerConfig/routes' export const AppRouter = createBrowserRouter([ @@ -105,7 +106,7 @@ export const AppRouter = createBrowserRouter([ }, { path: Routes.VOUCHERS, - element: // временная заглушка нужна страница с подарочными сертификатами + element: }, { path: Routes.PRODUCT + '/:slug', diff --git a/src/pages/VouchersPage/VouchersPage.module.scss b/src/pages/VouchersPage/VouchersPage.module.scss new file mode 100644 index 00000000..cf281446 --- /dev/null +++ b/src/pages/VouchersPage/VouchersPage.module.scss @@ -0,0 +1,17 @@ +.vouchers { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; + width: 100%; + box-sizing: border-box; + margin: 0; + padding: 56px 0; +} + +.title { + width: 100%; + max-width: 700px; + margin: 0; + padding: 0; +} diff --git a/src/pages/VouchersPage/VouchersPage.tsx b/src/pages/VouchersPage/VouchersPage.tsx new file mode 100644 index 00000000..61dbbb31 --- /dev/null +++ b/src/pages/VouchersPage/VouchersPage.tsx @@ -0,0 +1,20 @@ +import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' +import Heading, { HeadingType } from '@/shared/ui/Heading/Heading' +import FormBuyGiftCertificate from '@/widgets/FormBuyGiftCertificate' + +import styles from './VouchersPage.module.scss' + +const VouchersPage = () => { + return ( + +
+ + Купить подарочный сертификат + + +
+
+ ) +} + +export default VouchersPage diff --git a/src/widgets/FormBuyGiftCertificate/model/types/types.ts b/src/widgets/FormBuyGiftCertificate/model/types/types.ts index 04db04cb..f4f020f9 100644 --- a/src/widgets/FormBuyGiftCertificate/model/types/types.ts +++ b/src/widgets/FormBuyGiftCertificate/model/types/types.ts @@ -4,4 +4,7 @@ export interface IFormBuyGiftCertificate { name: string email: string textArea: string + sum: number + radio: string + checkbox: string } diff --git a/src/widgets/FormBuyGiftCertificate/model/validation/validation.ts b/src/widgets/FormBuyGiftCertificate/model/validation/validation.ts index adfbd06a..1c50006a 100644 --- a/src/widgets/FormBuyGiftCertificate/model/validation/validation.ts +++ b/src/widgets/FormBuyGiftCertificate/model/validation/validation.ts @@ -15,12 +15,14 @@ export const validationSchema = Yup.object().shape({ email: Yup.string() .required('Введите электронную почту') .email('Укажите корректный адрес электронной почты'), - textarea: Yup.string() + message: Yup.string() .min(10, 'Длина текста должна быть от 10 символов') .max(300, 'Длина текста должна быть до 300 символов'), sum: Yup.number() .required('Введите сумму') .min(1, 'Сумма должна быть не менее 1 руб') .max(1000, 'Сумма должна быть не более 1000 руб') - .typeError('Сумма указывается только цифрами') + .typeError('Сумма указывается только цифрами'), + radio: Yup.string().required('Выберите тему подарочного сертификата'), + checkbox: Yup.string().required('Подтвердите уведомление') }) diff --git a/src/widgets/FormBuyGiftCertificate/ui/FormBuyGiftCertificate.module.scss b/src/widgets/FormBuyGiftCertificate/ui/FormBuyGiftCertificate.module.scss index d3de77be..bf369359 100644 --- a/src/widgets/FormBuyGiftCertificate/ui/FormBuyGiftCertificate.module.scss +++ b/src/widgets/FormBuyGiftCertificate/ui/FormBuyGiftCertificate.module.scss @@ -1,7 +1,6 @@ @use '@/shared/styles/utils/variables' as var; .form { - position: relative; display: flex; flex-direction: column; justify-content: center; @@ -12,31 +11,26 @@ margin: 0 0 20px; padding: 25px 30px 20px; - &__input { - background-color: var.$body-bg; - border: 2px solid var.$border-color; - border-radius: 10px; - margin: 0 0 5px; - padding: 10px 16px; - - &_textArea { - min-height: 180px; - } - } - - &__input:focus { - border: 2px solid var.$theme-primary-color; - transition: 0.7s; + &__paragraph { + margin: 0 0 10px; + padding: 0; } &__label { - position: relative; - font-size: 14px; - margin: 0 0 10px; + margin: 0 0 20px; + padding: 0; &_date[data-no-star]::before { content: ''; } + + &_radio { + margin-bottom: 10px; + } + + &_textArea { + margin-bottom: 5px; + } } &__label::before { @@ -49,21 +43,54 @@ } } - &__checkbox { - font-size: 14px; - margin: 0 0 10px; + &__input { + background-color: var.$body-bg; + border: 2px solid var.$border-color; + border-radius: 10px; + margin: 5px 0 0; + padding: 10px 16px; + + &_textArea { + min-height: 180px; + } + } + + &__input:focus { + border: 2px solid var.$theme-primary-color; + transition: 0.7s; } &__error { position: absolute; - top: 62px; - left: 20px; + top: 75px; + left: 17px; font-size: 12px; font-weight: 100; color: var.$promo-color; &_textarea { - top: 203px; + top: 215px; + } + + &_radio { + top: 145px; } + + &_checkbox { + top: 38px; + left: 35px; + } + } + + &__radio { + margin: 0 15px 10px 0; + } + + &__checkbox { + position: relative; + font-size: 14px; + font-weight: 400; + color: var.$body-color; + margin: 0 0 10px; } } diff --git a/src/widgets/FormBuyGiftCertificate/ui/FormBuyGiftCertificate.tsx b/src/widgets/FormBuyGiftCertificate/ui/FormBuyGiftCertificate.tsx index b9e51aae..1b893b63 100644 --- a/src/widgets/FormBuyGiftCertificate/ui/FormBuyGiftCertificate.tsx +++ b/src/widgets/FormBuyGiftCertificate/ui/FormBuyGiftCertificate.tsx @@ -21,24 +21,36 @@ const initialValues: IFormBuyGiftCertificate = { recipientEmail: '', name: '', email: '', - textArea: '' + textArea: '', + sum: 1, + radio: '', + checkbox: '' } +const radioOptions = [ + { label: 'День рождения', value: 'День рождения' }, + { label: 'Другое', value: 'Другое' }, + { label: 'Новый год', value: 'Новый год' } +] + const FormBuyGiftCertificate = () => { return ( { + onSubmit={(values, { setSubmitting, resetForm }) => { + console.log(JSON.stringify(values, null, 2)) setSubmitting(false) + resetForm() }} validationSchema={validationSchema} validateOnBlur={true}> {({ isSubmitting }) => (
- + Подарочный сертификат будет отправлен получателю после того как Вы оплатите стоимость Подарочного сертификата. + + + + -
+ +
+
    + {radioOptions.map(option => { + return ( +
  • + +
  • + ) + })} +
+ + + + +
+
+ diff --git a/src/widgets/Header/Header.tsx b/src/widgets/Header/Header.tsx index ee00a81d..403fa63f 100644 --- a/src/widgets/Header/Header.tsx +++ b/src/widgets/Header/Header.tsx @@ -79,7 +79,7 @@ function Header() {
  • - + Подарочные сертификаты
  • From ef589b12bad94220edc99dfd7895f9410dfce9b5 Mon Sep 17 00:00:00 2001 From: Margarita Shumilina Date: Thu, 11 Apr 2024 11:13:34 +0300 Subject: [PATCH 2/5] #245-filters-in-category-page --- .../StoreProvider/config/StateSchema.ts | 2 + .../providers/StoreProvider/config/store.ts | 4 +- src/components/Dropdown/Dropdown.tsx | 10 +-- .../Dropdown/selectors/selectors.tsx | 5 ++ .../Dropdown/slice/filtersSlice.tsx | 25 +++++++ src/components/Dropdown/types/types.ts | 9 +++ src/components/PageControls/PageControls.tsx | 9 ++- src/features/CategoryItem/CategoryItem.tsx | 2 +- src/mockData/productsPageOptions.ts | 20 ++++-- src/pages/ProductsPage/ProductsPage.tsx | 66 +++++++++---------- .../ProductsPage/services/getProducts.ts | 16 ++++- src/shared/constants/constants.ts | 21 +++++- src/shared/ui/CatalogLink/CatalogLink.tsx | 10 +-- src/shared/ui/Link/Link.tsx | 1 + .../CatalogNodeItem/CatalogNodeItem.tsx | 4 +- src/widgets/CategoryList/CategoryList.tsx | 7 ++ src/widgets/Header/Header.tsx | 3 +- src/widgets/ProductsList/ProductsList.tsx | 40 +++++++++++ 18 files changed, 192 insertions(+), 62 deletions(-) create mode 100644 src/components/Dropdown/selectors/selectors.tsx create mode 100644 src/components/Dropdown/slice/filtersSlice.tsx create mode 100644 src/components/Dropdown/types/types.ts create mode 100644 src/widgets/ProductsList/ProductsList.tsx diff --git a/src/app/providers/StoreProvider/config/StateSchema.ts b/src/app/providers/StoreProvider/config/StateSchema.ts index 2b1b1d0d..ddccf71a 100644 --- a/src/app/providers/StoreProvider/config/StateSchema.ts +++ b/src/app/providers/StoreProvider/config/StateSchema.ts @@ -14,6 +14,7 @@ import { CategoryListSchema } from '@/widgets/CategoryGrid/model/types/types' import { ICategoryProductsSchema } from '@/pages/ProductsPage/types/types' import { IFeedbackSchema } from '@/pages/FeedbackPage/model/types/types' import { ICategorySchema, IMainCategorySchema } from '@/widgets/CategoryList/types/types' +import { ICategoryFiltersSchema } from '@/components/Dropdown/types/types' export interface StateSchema { login: LoginSchema @@ -34,6 +35,7 @@ export interface StateSchema { categorySlug: CategorySlug categoryBranches: ICategorySchema getCategories: IMainCategorySchema + categoryFilters: ICategoryFiltersSchema } export interface ThunkExtraArg { diff --git a/src/app/providers/StoreProvider/config/store.ts b/src/app/providers/StoreProvider/config/store.ts index 8c49b336..44d52a3e 100644 --- a/src/app/providers/StoreProvider/config/store.ts +++ b/src/app/providers/StoreProvider/config/store.ts @@ -19,6 +19,7 @@ import { categorySlugSliceReducer } from '@/entities/Category/slice/categorySlug import { categoryBranchesReducer } from '@/widgets/CategoryList/slice/pageCategoryBranchesSlice' import { getCategoriesReducer } from '@/widgets/CategoryList/slice/pageCategoriesSlice' import { feedbackReducer } from '@/pages/FeedbackPage/model/slice/feedbackSlice' +import { categoryFiltersSliceReducer } from '@/components/Dropdown/slice/filtersSlice' export type RootState = StateSchema @@ -40,7 +41,8 @@ const rootReducer: ReducersMapObject = { categorySlug: categorySlugSliceReducer, categoryBranches: categoryBranchesReducer, getCategories: getCategoriesReducer, - feedback: feedbackReducer + feedback: feedbackReducer, + categoryFilters: categoryFiltersSliceReducer } export function createReduxStore(initialState: RootState) { diff --git a/src/components/Dropdown/Dropdown.tsx b/src/components/Dropdown/Dropdown.tsx index 0728b969..ec74b07e 100644 --- a/src/components/Dropdown/Dropdown.tsx +++ b/src/components/Dropdown/Dropdown.tsx @@ -1,9 +1,11 @@ import React, { ChangeEvent, useState } from 'react' +import type { TSortOptions } from '@/components/PageControls/PageControls' + import styles from './Dropdown.module.scss' interface DropdownProps extends React.HTMLProps { - items: (string | number)[] + items: TSortOptions[] defaultItem?: string onSelect: React.ChangeEventHandler } @@ -25,9 +27,9 @@ export const Dropdown: React.FC = ({ items, defaultItem, onSelect return ( diff --git a/src/components/Dropdown/selectors/selectors.tsx b/src/components/Dropdown/selectors/selectors.tsx new file mode 100644 index 00000000..26cdf7e0 --- /dev/null +++ b/src/components/Dropdown/selectors/selectors.tsx @@ -0,0 +1,5 @@ +import { RootState } from '@/app/providers/StoreProvider/config/store' + +export const selectFilterProducts = (state: RootState) => state.categoryFilters.filterProducts + +export const selectFilterQuantity = (state: RootState) => state.categoryFilters.productQuantityFilter diff --git a/src/components/Dropdown/slice/filtersSlice.tsx b/src/components/Dropdown/slice/filtersSlice.tsx new file mode 100644 index 00000000..75699e11 --- /dev/null +++ b/src/components/Dropdown/slice/filtersSlice.tsx @@ -0,0 +1,25 @@ +import { createSlice } from '@reduxjs/toolkit' + +import type { ICategoryFiltersSchema } from '@/components/Dropdown/types/types' + +const initialState: ICategoryFiltersSchema = { + filterProducts: { name: 'Название А-Я', value: 'name' }, + productQuantityFilter: { name: '15', value: '15' } +} + +const categoryFiltersSlice = createSlice({ + name: 'categoryFilters', + initialState, + reducers: { + setFilterProducts(state, action) { + state.filterProducts = action.payload + }, + setProductQuantityFilter(state, action) { + state.productQuantityFilter = action.payload + } + } +}) + +export const { reducer: categoryFiltersSliceReducer } = categoryFiltersSlice + +export const { setFilterProducts, setProductQuantityFilter } = categoryFiltersSlice.actions diff --git a/src/components/Dropdown/types/types.ts b/src/components/Dropdown/types/types.ts new file mode 100644 index 00000000..5126125c --- /dev/null +++ b/src/components/Dropdown/types/types.ts @@ -0,0 +1,9 @@ +export interface ICategoryFiltersSchema { + filterProducts: IFilterSchema + productQuantityFilter: IFilterSchema +} + +export type IFilterSchema = { + name: string + value: string +} diff --git a/src/components/PageControls/PageControls.tsx b/src/components/PageControls/PageControls.tsx index 426f8179..df371bd6 100644 --- a/src/components/PageControls/PageControls.tsx +++ b/src/components/PageControls/PageControls.tsx @@ -8,13 +8,18 @@ import { ECardView } from '@/shared/model/types/common' import styles from './PageControls.module.scss' +export type TSortOptions = { + name: string + value?: string +} + type TPageControls = { cardView: string handleCardViewChange: (view: ECardView) => void handleItemsPerPageChange: ChangeEventHandler handleSortChange: ChangeEventHandler - itemPerPageOptions: number[] - sortOptions: string[] + itemPerPageOptions: TSortOptions[] + sortOptions: TSortOptions[] } /** diff --git a/src/features/CategoryItem/CategoryItem.tsx b/src/features/CategoryItem/CategoryItem.tsx index b7bcfe64..b853861c 100644 --- a/src/features/CategoryItem/CategoryItem.tsx +++ b/src/features/CategoryItem/CategoryItem.tsx @@ -26,7 +26,7 @@ export const CategoryItem: FC = ({ name, slug, count, id }) => { return (
  • { dispatch(setCategoryId(id)) diff --git a/src/mockData/productsPageOptions.ts b/src/mockData/productsPageOptions.ts index 93b238ea..36edb3c4 100644 --- a/src/mockData/productsPageOptions.ts +++ b/src/mockData/productsPageOptions.ts @@ -1,13 +1,19 @@ export const SORT_OPTION = [ - 'Название А-Я', - 'Название Я-А', - 'Сначала дешевые', - 'Сначала дорогие', - 'Модель А-Я', - 'Модель Я-А' + { name: 'Название А-Я', value: 'name' }, + { name: 'Название Я-А', value: '-name' }, + { name: 'Сначала дешевые', value: 'price' }, + { name: 'Сначала дорогие', value: '-price' }, + { name: 'Модель А-Я', value: 'name' }, + { name: 'Модель Я-А', value: '-name' } ] -export const ITEMS_PER_PAGE_OPTION = [15, 25, 50, 75, 100] +export const ITEMS_PER_PAGE_OPTION = [ + { name: '15', value: '15' }, + { name: '25', value: '25' }, + { name: '50', value: '50' }, + { name: '75', value: '75' }, + { name: '100', value: '100' } +] export const TOTAL_PAGES = 10 diff --git a/src/pages/ProductsPage/ProductsPage.tsx b/src/pages/ProductsPage/ProductsPage.tsx index cd1a6990..f9371bab 100644 --- a/src/pages/ProductsPage/ProductsPage.tsx +++ b/src/pages/ProductsPage/ProductsPage.tsx @@ -1,20 +1,22 @@ import { useEffect, useState } from 'react' import { useSelector } from 'react-redux' +import { useLocation } from 'react-router' +import { selectFilterProducts, selectFilterQuantity } from '@/components/Dropdown/selectors/selectors' +import { setFilterProducts, setProductQuantityFilter } from '@/components/Dropdown/slice/filtersSlice' import { PageControls } from '@/components/PageControls/PageControls' import { PageDescription } from '@/components/PageDescription/PageDescription' import { Pagination } from '@/components/Pagination/Pagination' import WrapperForMainContent from '@/components/WrapperForMainContent/WrapperForMainContent' -import { selectCategoryId } from '@/entities/Category/selectors/categorySelectors' import { selectCategorySlug } from '@/entities/Category/selectors/categorySelectors' -import { ITEMS_PER_PAGE_OPTION, SORT_OPTION, TOTAL_PAGES } from '@/mockData/productsPageOptions' -import { IObjectWithImage } from '@/pages/ProductPage/model/types/productTypes' +import { TOTAL_PAGES } from '@/mockData/productsPageOptions' import { getProductsOfCategorySelector } from '@/pages/ProductsPage/selectors/selectors' import { getProducts } from '@/pages/ProductsPage/services/getProducts' +import { ITEMS_PER_PAGE_OPTION, SORT_OPTION } from '@/shared/constants/constants' import { useAppDispatch } from '@/shared/libs/hooks/store' import { ECardView } from '@/shared/model/types/common' import { CategoryList } from '@/widgets/CategoryList/CategoryList' -import { ProductItem } from '@/widgets/ProductItem/ProductItem' +import { ProductsList } from '@/widgets/ProductsList/ProductsList' import styles from './ProductsPage.module.scss' @@ -29,16 +31,31 @@ export const ProductsPage = () => { const [cardView, setCardView] = useState(ECardView.GRID) const [currentPage, setCurrentPage] = useState(1) + const dispatch = useAppDispatch() + + const categoriesProducts = useSelector(getProductsOfCategorySelector) + const categorySlug = useSelector(selectCategorySlug) + + const location = useLocation() + + const categoryId = Number(location.search.replace('?id=', '')) + + const selectProductsFilter = useSelector(selectFilterProducts) + const filterProducts = selectProductsFilter ? `&ordering=${selectProductsFilter.value}` : '' + + const selectQuantityFilter = useSelector(selectFilterQuantity) + const filterQuantity = selectQuantityFilter ? `&limit=${selectQuantityFilter.value}` : '' + const handleSortChange: React.ChangeEventHandler = event => { - // Handle sort change logic here - // eslint-disable-next-line @typescript-eslint/no-unused-vars const selectedOption = event.target.value + const setCategoryFilters = SORT_OPTION.find(item => item.name === selectedOption) + dispatch(setFilterProducts(setCategoryFilters)) } const handleItemsPerPageChange: React.ChangeEventHandler = event => { - // Handle items per page change logic here - // eslint-disable-next-line @typescript-eslint/no-unused-vars const selectedOption = event.target.value + const setQuantityFilters = ITEMS_PER_PAGE_OPTION.find(item => item.name === selectedOption) + dispatch(setProductQuantityFilter(setQuantityFilters)) } const handleCardViewChange = (view: ECardView) => { @@ -59,14 +76,13 @@ export const ProductsPage = () => { if (currentPage < TOTAL_PAGES) setCurrentPage(currentPage + 1) } - const dispatch = useAppDispatch() - const categoriesProducts = useSelector(getProductsOfCategorySelector) - const categoryId = useSelector(selectCategoryId) - const categorySlug = useSelector(selectCategorySlug) + useEffect(() => { + dispatch(getProducts({ categoryId, filterProducts, filterQuantity })) + }, []) useEffect(() => { - dispatch(getProducts(categoryId)) - }, [categoryId, categorySlug]) + dispatch(getProducts({ categoryId, filterProducts, filterQuantity })) + }, [categoryId, categorySlug, filterProducts, filterQuantity]) return ( <> @@ -86,27 +102,7 @@ export const ProductsPage = () => { itemPerPageOptions={ITEMS_PER_PAGE_OPTION} sortOptions={SORT_OPTION} /> - {categoriesProducts.results.map(item => ( - { - return { - image: img.image, - index - } - })} - label_hit={item.label_hit} - label_popular={item.label_popular} - quantity={item.quantity} - /> - ))} + ) : ( 'В данной категории нет товаров' diff --git a/src/pages/ProductsPage/services/getProducts.ts b/src/pages/ProductsPage/services/getProducts.ts index cf1c17fb..ec9ec5d7 100644 --- a/src/pages/ProductsPage/services/getProducts.ts +++ b/src/pages/ProductsPage/services/getProducts.ts @@ -7,12 +7,22 @@ import { ACTION_GET_PRODUCTS_OF_CATEGORY } from '@/shared/constants/constants' import { ProductsInfo } from '../types/types' -export const getProducts = createAsyncThunk>( +type Params = { + categoryId: number + filterProducts: string + filterQuantity: string +} +export const getProducts = createAsyncThunk>( ACTION_GET_PRODUCTS_OF_CATEGORY, - async (id: number, thunkAPI) => { + async (params, thunkAPI) => { + const { categoryId, filterProducts, filterQuantity } = params const { rejectWithValue, extra } = thunkAPI try { - const { data } = await extra.api.get(`api/${ApiRoutes.PRODUCT}${id > 0 ? `?category=${id}` : ''}`) + const { data } = await extra.api.get( + `api/${ApiRoutes.PRODUCT}${ + categoryId > 0 ? `?category=${categoryId}` : '' + }${filterProducts}${filterQuantity}` + ) return data } catch (error) { return rejectWithValue(apiErrorIdentify(error, ApiErrorTypes.DATA_EMPTY_ERROR)) diff --git a/src/shared/constants/constants.ts b/src/shared/constants/constants.ts index 92d72701..fceedd36 100644 --- a/src/shared/constants/constants.ts +++ b/src/shared/constants/constants.ts @@ -44,10 +44,27 @@ export const ACTION_GET_CATEGORIES = 'get-categories' export const REDUCER_SHOP_NEWS = 'shopNews' export const REDUCER_BLOG_POSTS = 'shopBlogPosts' +export const REDUCER_CATEGORY_BRANCHES = 'getCategoryBranches' +export const REDUCER_CATEGORIES = 'getCategories' export const REDUCER_CATEGORIES_PRODUCTS = 'shopCategoriesProducts' //Product page export const VIEWED_PRODUCTS_LIMIT = 10 -export const REDUCER_CATEGORY_BRANCHES = 'getCategoryBranches' -export const REDUCER_CATEGORIES = 'getCategories' +//Filters for ProductsPage +export const SORT_OPTION = [ + { name: 'Название А-Я', value: 'name' }, + { name: 'Название Я-А', value: '-name' }, + { name: 'Сначала дешевые', value: 'price' }, + { name: 'Сначала дорогие', value: '-price' }, + { name: 'Модель А-Я', value: 'name' }, + { name: 'Модель Я-А', value: '-name' } +] + +export const ITEMS_PER_PAGE_OPTION = [ + { name: '15', value: '15' }, + { name: '25', value: '25' }, + { name: '50', value: '50' }, + { name: '75', value: '75' }, + { name: '100', value: '100' } +] diff --git a/src/shared/ui/CatalogLink/CatalogLink.tsx b/src/shared/ui/CatalogLink/CatalogLink.tsx index b73101db..bdfab7bb 100644 --- a/src/shared/ui/CatalogLink/CatalogLink.tsx +++ b/src/shared/ui/CatalogLink/CatalogLink.tsx @@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux' import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import { setCategoryId } from '@/entities/Category/slice/categoryIdSlice' import { setCategorySlug } from '@/entities/Category/slice/categorySlugSlice' -import Link, { TLinkProps } from '@/shared/ui/Link/Link' +import Link, { type TLinkProps } from '@/shared/ui/Link/Link' import styles from './catalogLink.module.scss' @@ -13,9 +13,10 @@ import styles from './catalogLink.module.scss' * @param {string} className - нужно для изменения некоторых css- параметров * @param to - путь для ссылки * @param children дочерние компоненты - * @param {string} categoryId - id категории + * @param {number} categoryId - id категории + * @param {string} categorySlug- URL категории */ -const CatalogLink: FC = ({ className, to = '', children, categoryId }) => { +const CatalogLink: FC = ({ className, to = '', children, categoryId, categorySlug }) => { const classes = classNames(styles['catalog-link'], className) const dispatch = useDispatch() @@ -24,8 +25,9 @@ const CatalogLink: FC = ({ className, to = '', children, categoryId to={to} className={classes} onClick={() => { + console.log(to) dispatch(setCategoryId(categoryId)) - dispatch(setCategorySlug(to)) + dispatch(setCategorySlug(categorySlug)) }}> {children} diff --git a/src/shared/ui/Link/Link.tsx b/src/shared/ui/Link/Link.tsx index ae201277..bc9ea91c 100644 --- a/src/shared/ui/Link/Link.tsx +++ b/src/shared/ui/Link/Link.tsx @@ -7,6 +7,7 @@ import styles from './Link.module.scss' export type TLinkProps = { className?: string categoryId?: number + categorySlug?: string } & LinkProps /** diff --git a/src/widgets/CatalogNodeItem/CatalogNodeItem.tsx b/src/widgets/CatalogNodeItem/CatalogNodeItem.tsx index 0d711c8c..cf4cae0d 100644 --- a/src/widgets/CatalogNodeItem/CatalogNodeItem.tsx +++ b/src/widgets/CatalogNodeItem/CatalogNodeItem.tsx @@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux' import { AppDispatch } from '@/app/providers/StoreProvider/config/store' import { setCategoryId } from '@/entities/Category/slice/categoryIdSlice' import { setCategorySlug } from '@/entities/Category/slice/categorySlugSlice' -import { Category } from '@/entities/Category/types/types' +import type { Category } from '@/entities/Category/types/types' import { Routes } from '@/shared/config/routerConfig/routes' import Link from '@/shared/ui/Link/Link' @@ -32,7 +32,7 @@ const CatalogNodeItem: FC = ({ slug, name, id }: Category) => { dispatch(setCategoryId(id)) dispatch(setCategorySlug(slug)) }}> - + {name}
  • diff --git a/src/widgets/CategoryList/CategoryList.tsx b/src/widgets/CategoryList/CategoryList.tsx index f1145703..8e02ec3c 100644 --- a/src/widgets/CategoryList/CategoryList.tsx +++ b/src/widgets/CategoryList/CategoryList.tsx @@ -1,5 +1,6 @@ import { FC, useEffect } from 'react' import { useSelector } from 'react-redux' +import { useParams } from 'react-router-dom' import { selectCategorySlug } from '@/entities/Category/selectors/categorySelectors' import { CategoryItem } from '@/features/CategoryItem/CategoryItem' @@ -21,6 +22,12 @@ export const CategoryList: FC = () => { const getMainCategories = useSelector(getCategorySelector) const categorySlug = useSelector(selectCategorySlug) + const { slug } = useParams() + + useEffect(() => { + dispatch(getCategoryBranches(slug)) + }, []) + useEffect(() => { dispatch(getCategoryBranches(categorySlug)) dispatch(getCategories()) diff --git a/src/widgets/Header/Header.tsx b/src/widgets/Header/Header.tsx index ee00a81d..911b9312 100644 --- a/src/widgets/Header/Header.tsx +++ b/src/widgets/Header/Header.tsx @@ -172,7 +172,8 @@ function Header() { {displayedCategories.map(category => ( {category.name} diff --git a/src/widgets/ProductsList/ProductsList.tsx b/src/widgets/ProductsList/ProductsList.tsx new file mode 100644 index 00000000..946b4928 --- /dev/null +++ b/src/widgets/ProductsList/ProductsList.tsx @@ -0,0 +1,40 @@ +import { FC } from 'react' + +import type { IObjectWithImage } from '@/pages/ProductPage/model/types/productTypes' +import type { ProductsInfo } from '@/pages/ProductsPage/types/types' +import { ECardView } from '@/shared/model/types/common' +import { ProductItem } from '@/widgets/ProductItem/ProductItem' + +type Props = { + items: ProductsInfo + cardView: ECardView +} + +/** + * Компонент генерации сетки товаров на странице категории + * @param {ProductsInfo} items - массив товаров для генерации товарной сетки; + * @param {ECardView} cardView - тип выбранной сетки отображения карточек товаров; + * */ +export const ProductsList: FC = ({ items, cardView }) => { + return items.results.map(item => ( + { + return { + image: img.image, + index + } + })} + label_hit={item.label_hit} + label_popular={item.label_popular} + quantity={item.quantity} + /> + )) +} From 46f4768040fd35a8a050aac7d4836824808edf6c Mon Sep 17 00:00:00 2001 From: Yulia Avramenko Date: Fri, 12 Apr 2024 17:46:22 +0300 Subject: [PATCH 3/5] #294-api-get-cart-list --- .../StoreProvider/config/StateSchema.ts | 2 + .../providers/StoreProvider/config/store.ts | 4 +- .../ProductEntity/ProductEntity.stories.tsx | 19 ++- .../ui/ProductEntity/ProductEntity.tsx | 12 +- .../CartEdit/ui/CartEdit/CartEdit.stories.tsx | 28 ++-- .../CartEdit/ui/CartEdit/CartEdit.tsx | 66 ++++----- src/pages/CartPage/CartPage.tsx | 131 +++--------------- src/pages/CartPage/model/selector.ts | 5 + src/pages/CartPage/model/services.ts | 22 +++ src/pages/CartPage/model/slice.ts | 38 +++++ src/pages/CartPage/model/types.ts | 38 +++++ src/shared/api/types.ts | 3 +- 12 files changed, 198 insertions(+), 170 deletions(-) create mode 100644 src/pages/CartPage/model/selector.ts create mode 100644 src/pages/CartPage/model/services.ts create mode 100644 src/pages/CartPage/model/slice.ts create mode 100644 src/pages/CartPage/model/types.ts diff --git a/src/app/providers/StoreProvider/config/StateSchema.ts b/src/app/providers/StoreProvider/config/StateSchema.ts index 295f2608..f9383534 100644 --- a/src/app/providers/StoreProvider/config/StateSchema.ts +++ b/src/app/providers/StoreProvider/config/StateSchema.ts @@ -15,6 +15,7 @@ import { ICategoryProductsSchema } from '@/pages/ProductsPage/types/types' import { IFeedbackSchema } from '@/pages/FeedbackPage/model/types/types' import { ICategorySchema, IMainCategorySchema } from '@/widgets/CategoryList/types/types' import type { IFeedbackFormSchema } from '@/widgets/FeedbackForm/model/scheme/feedbackFormSliceSchema' +import { ICartSchema } from '@/pages/CartPage/model/types' export interface StateSchema { login: LoginSchema @@ -36,6 +37,7 @@ export interface StateSchema { categorySlug: CategorySlug categoryBranches: ICategorySchema getCategories: IMainCategorySchema + cart: ICartSchema } export interface ThunkExtraArg { diff --git a/src/app/providers/StoreProvider/config/store.ts b/src/app/providers/StoreProvider/config/store.ts index 731ea8ba..e9e83087 100644 --- a/src/app/providers/StoreProvider/config/store.ts +++ b/src/app/providers/StoreProvider/config/store.ts @@ -20,6 +20,7 @@ import { categoryBranchesReducer } from '@/widgets/CategoryList/slice/pageCatego import { getCategoriesReducer } from '@/widgets/CategoryList/slice/pageCategoriesSlice' import { feedbackReducer } from '@/pages/FeedbackPage/model/slice/feedbackSlice' import { feedbackFormReducer } from '@/widgets/FeedbackForm/model/slice/feedbackFormSlice' +import { cartReducer } from '@/pages/CartPage/model/slice' export type RootState = StateSchema @@ -42,7 +43,8 @@ const rootReducer: ReducersMapObject = { categoryId: categoryIdSliceReducer, categorySlug: categorySlugSliceReducer, categoryBranches: categoryBranchesReducer, - getCategories: getCategoriesReducer + getCategories: getCategoriesReducer, + cart: cartReducer } export function createReduxStore(initialState: RootState) { diff --git a/src/entities/ProductEntity/ui/ProductEntity/ProductEntity.stories.tsx b/src/entities/ProductEntity/ui/ProductEntity/ProductEntity.stories.tsx index fedf7210..d981b87a 100644 --- a/src/entities/ProductEntity/ui/ProductEntity/ProductEntity.stories.tsx +++ b/src/entities/ProductEntity/ui/ProductEntity/ProductEntity.stories.tsx @@ -18,10 +18,19 @@ type Story = StoryObj export const Default: Story = { args: { - src: image1, - name: 'Переходник', - article: '1229239192', - price: 782, - currency: '₽' + id: 1, + category: 'техника', + brand: 'Tefal', + images: [ + { + image: image1 + } + ], + price: '1000', + name: 'Tefal Iron', + slug: '1hfjnfjkf', + description: 'Functional', + code: 108290, + wb_urls: 'jnfne' } } diff --git a/src/entities/ProductEntity/ui/ProductEntity/ProductEntity.tsx b/src/entities/ProductEntity/ui/ProductEntity/ProductEntity.tsx index 4532a1f0..de1d2271 100644 --- a/src/entities/ProductEntity/ui/ProductEntity/ProductEntity.tsx +++ b/src/entities/ProductEntity/ui/ProductEntity/ProductEntity.tsx @@ -1,27 +1,27 @@ import { type FC } from 'react' -import { TProduct } from '@/mockData/productsData' +import { IProductData } from '@/pages/CartPage/model/types' import Subheading from '@/shared/ui/Subheading/Subheading' import styles from './ProductEntity.module.scss' /** * Компонент служит для отображения товаров, пришедших с сервера. - * @param {string} src-картика с изображением продукта; + * @param {IImagesData[]} images-картика с изображением продукта; * @param {string} name- название продукта; - * @param {string} article -артикул продукта; + * @param {number} id -артикул продукта; * @param {number} price -стоимость продукта; * @param {string} currency - валюта, в которой обозначена стоимость; */ -export const ProductEntity: FC = product => { +export const ProductEntity: FC = product => { return (
    - {'product'} + {'product'}
    - {product.article} + {product.id} {product.name}
    diff --git a/src/features/CartEdit/ui/CartEdit/CartEdit.stories.tsx b/src/features/CartEdit/ui/CartEdit/CartEdit.stories.tsx index 379de7d5..76c6cf17 100644 --- a/src/features/CartEdit/ui/CartEdit/CartEdit.stories.tsx +++ b/src/features/CartEdit/ui/CartEdit/CartEdit.stories.tsx @@ -27,17 +27,23 @@ type Story = StoryObj export const Default: Story = { args: { + cartId: 85, + amount: 2, product: { - article: '1866887687', - quantity: 1, - src: image1, - name: 'Переходник', - price: 1634, - currency: 'RUB' - }, - decreaseQuantity: () => {}, - increaseQuantity: () => {}, - setQuantity: () => {}, - removeProduct: () => {} + id: 1, + category: 'техника', + brand: 'Tefal', + images: [ + { + image: image1 + } + ], + price: '1000', + name: 'Tefal Iron', + slug: '1hfjnfjkf', + description: 'Functional', + code: 108290, + wb_urls: 'jnfne' + } } } diff --git a/src/features/CartEdit/ui/CartEdit/CartEdit.tsx b/src/features/CartEdit/ui/CartEdit/CartEdit.tsx index 6c07e5d7..5df00eb5 100644 --- a/src/features/CartEdit/ui/CartEdit/CartEdit.tsx +++ b/src/features/CartEdit/ui/CartEdit/CartEdit.tsx @@ -1,7 +1,7 @@ -import { useEffect, useState } from 'react' +import { useState } from 'react' import { ProductEntity } from '@/entities/ProductEntity/ui/ProductEntity/ProductEntity' -import { TCartItemExt } from '@/mockData/cartData' +import { IProductData } from '@/pages/CartPage/model/types' import ButtonDots from '@/shared/ui/ButtonDots/ButtonDots' import Paragraph from '@/shared/ui/Paragraph/Paragraph' import Subheading from '@/shared/ui/Subheading/Subheading' @@ -9,45 +9,43 @@ import Subheading from '@/shared/ui/Subheading/Subheading' import styles from './CartEdit.module.scss' export type TCartEditProps = { - product: TCartItemExt - decreaseQuantity: (productArticle: string) => void - increaseQuantity: (productArticle: string) => void - setQuantity: (productArticle: string, quantity: number) => void - removeProduct: (productArticle: string) => void + cartId: number + amount: number + product: IProductData } /** * Компонент используется для отображения добавленных в корзину продуктов, изменения кол-ва продуктов в корзине, * для удаления продуктов из корзины, для добавления продуктов в закладки - * @param {TCartItemExt} product - это продукт для определения состояния - * @param {(productArticle: string) => void} decreaseQuantity- функция для уменьшения количества товара в корзине - * @param {(productArticle: string) => void} increaseQuantity -функция для увеличения количества товара в корзине - * @param {(productArticle: string, quantity: number) => void} setQuantity- функция для того, чтобы изменить количество продукта самостоятельно, поставив в input необходимое количество - * @param {(productArticle: string) => void} removeProduct -функция для удаления продукта из корзины + * @param {number} cartId - id корзины + * @param {number} amount - количество товаров в корзине + * @param {IProductData} product - это продукт для определения состояния */ -export const CartEdit: React.FC = ({ - product, - decreaseQuantity, - increaseQuantity, - setQuantity, - removeProduct -}: TCartEditProps) => { - const [amount, setAmount] = useState(0) +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const CartEdit: React.FC = ({ cartId, amount, product }: TCartEditProps) => { const [needToOpenContextMenuButtonDots, setNeedToOpen] = useState(false) - useEffect(() => { - setAmount(product.quantity) - }, [product.quantity]) - function deleteProductHandler() { setNeedToOpen(false) - removeProduct(product.article) + // removeProduct(product.id) переделать на вызов action } function addToFavoritesHandler() { setNeedToOpen(false) } + function increaseAmountHandler() { + // tbd + } + + function decreaseAmountHandler() { + // tbd + } + + function setAmountHandler() { + //tbd + } + return ( <>
    @@ -56,11 +54,13 @@ export const CartEdit: React.FC = ({
    {' '} - {product.price * product.quantity} {product.currency} + {amount * Number(product.price)} {product.brand} + {/* currency, not brand, c Number непонятно пока*/} {' '} - {product.price} {product.currency}/шт + {product.price} {product.brand}/шт + {/* currency, not brand */}
    @@ -68,9 +68,7 @@ export const CartEdit: React.FC = ({ + + = ({ cartId, amount, product }: type="text" className={`${styles.input}`} onChange={setAmountHandler}> - + +
    • - +
    • -
    • - +