Merge pull request 'Complete filter logic' (#2) from hotfix/hotfix-card-image into master
Reviewed-on: #2hotfix/hotfix-swiper
commit
50a8a51a20
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
CheckboxGroup,
|
||||
Checkbox,
|
||||
Button, Skeleton, Accordion, AccordionItem
|
||||
Button, Skeleton, Accordion, AccordionItem, Pagination, Spinner
|
||||
} from "@nextui-org/react";
|
||||
import Link from "next/link";
|
||||
|
||||
|
@ -16,37 +16,79 @@ import Wrapper from "@/components/reusable/wrapper";
|
|||
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query";
|
||||
import LocalAPI from "@/service/localAPI";
|
||||
import {Img} from "react-image";
|
||||
import {Dispatch, SetStateAction, useEffect, useState} from "react";
|
||||
import {element} from "prop-types";
|
||||
|
||||
type SelectedFiltersStruct = {
|
||||
[key: string]: string[]
|
||||
}
|
||||
type SelectedFiltersDispatcher = Dispatch<SetStateAction<SelectedFiltersStruct>>
|
||||
|
||||
const CheckboxUI = (obj: { id: number, code: string, name: string, values: { id: number, value: string }[] } & {
|
||||
dispatcher: SelectedFiltersDispatcher
|
||||
}) => {
|
||||
|
||||
const CheckboxUI = (obj: {id: number, name: string, values: {id: number, value: string}[]}) => {
|
||||
return (
|
||||
<Accordion>
|
||||
<AccordionItem title={obj.name}>
|
||||
<CheckboxGroup>
|
||||
{obj.values && obj.values.map(val => (
|
||||
<Checkbox value={String(val.id)} key={"VALUE_" + val.id}>{val.value}</Checkbox>
|
||||
))}
|
||||
{obj.values && obj.values.map(val => (
|
||||
<Checkbox onChange={
|
||||
() => {
|
||||
obj.dispatcher((prevState) => {
|
||||
|
||||
if (prevState[obj.code] && prevState[obj.code].includes(val.value)) {
|
||||
return {
|
||||
...prevState,
|
||||
[obj.code]: prevState[obj.code].filter(v => v !== val.value)
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
...prevState,
|
||||
[obj.code]: [...(prevState[obj.code] || []), val.value]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} value={String(val.id)} key={"VALUE_" + val.id}>{val.value}</Checkbox>
|
||||
))}
|
||||
</CheckboxGroup>
|
||||
</AccordionItem>
|
||||
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
const FilterGenerator = ({filterPropertiesData}: Pick<InferGetStaticPropsType<typeof getStaticProps>, "filterPropertiesData">) => {
|
||||
const FilterGenerator = ({
|
||||
filterPropertiesData,
|
||||
setSelectedFilter
|
||||
}: Pick<InferGetStaticPropsType<typeof getStaticProps>, "filterPropertiesData"> & {
|
||||
setSelectedFilter: SelectedFiltersDispatcher
|
||||
}) => {
|
||||
return (
|
||||
<Accordion className={'filters mb-10 mr-4 lg:w-1/4 w-full'} defaultExpandedKeys={['1']}>
|
||||
<AccordionItem title={"Фильтры"} key={"1"} aria-label={"Фильтры"} classNames={{trigger:"bg-primary px-4", base: "border-1 rounded", title: "font-bold"}}>
|
||||
{filterPropertiesData.map(obj => (
|
||||
<CheckboxUI key={obj.id} {...obj} />
|
||||
))}
|
||||
<AccordionItem title={"Фильтры"} key={"1"} aria-label={"Фильтры"}
|
||||
classNames={{trigger: "bg-primary px-4", base: "border-1 rounded", title: "font-bold"}}>
|
||||
{filterPropertiesData.map(obj => (
|
||||
<CheckboxUI key={obj.id} {...obj} dispatcher={setSelectedFilter}/>
|
||||
))}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
type CardProps = InferGetStaticPropsType<typeof getStaticProps>['catalog'][0] & { isFavourite?: boolean }
|
||||
type CatalogItemStruct = {
|
||||
id: number
|
||||
code: string,
|
||||
name: string,
|
||||
properties: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
detailText: string,
|
||||
price: {
|
||||
[key: string]: number | null
|
||||
}
|
||||
}
|
||||
type CardProps = CatalogItemStruct & { isFavourite?: boolean }
|
||||
|
||||
|
||||
const CatalogCard = (product: CardProps) => {
|
||||
|
@ -84,7 +126,7 @@ const CatalogCard = (product: CardProps) => {
|
|||
isClient && product.properties.main_image &&
|
||||
<Img src={`https://relynolli.ru/upload/${product.properties.main_image[0]}`} alt={product.name}
|
||||
className={"col-auto mx-auto mb-4 sm:col-span-2 sm:row-span-2"}
|
||||
loader={<Skeleton className={"absolute top-0 left-0 right-3/4 rounded-[20px] h-full"}/>}/>
|
||||
loader={<Spinner/>}/>
|
||||
}
|
||||
<div className="col-auto sm:col-start-4 sm:col-span-6 h-full flex flex-col justify-center">
|
||||
<span
|
||||
|
@ -122,15 +164,48 @@ const CatalogCard = (product: CardProps) => {
|
|||
const Catalog = (props: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
|
||||
const {favourites} = useSnapshot(favouritesStore)
|
||||
const [selectedFilters, setSelectedFilters] = useState<SelectedFiltersStruct>({})
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageCount, setPageCount] = useState(1)
|
||||
const [showPagination, setShowPagination] = useState(true);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const service = new LocalAPI()
|
||||
service.getCatalogItemsCount().then(data => setPageCount(Math.ceil(data / 10)))
|
||||
|
||||
|
||||
})
|
||||
|
||||
const catalogQuery = useQuery<CatalogItemStruct[]>({
|
||||
queryKey: ["catalog", {selectedFilters, page}], queryFn: async () => {
|
||||
const service = new LocalAPI()
|
||||
return await service.getCatalogItems(selectedFilters, page)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
return (<Wrapper title={"Каталог"} breadcrumbs={[{name: "Каталог", link: "/catalog"}]}>
|
||||
<div className="flex flex-col justify-between lg:flex-row">
|
||||
<FilterGenerator filterPropertiesData={props.filterPropertiesData}/>
|
||||
<div className="flex justify-end mb-4">
|
||||
{ !Object.values(selectedFilters).filter(elem => elem.length).length &&
|
||||
<Pagination total={pageCount} initialPage={1} onChange={(page) => {
|
||||
setPage(page)
|
||||
}}/>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div className="flex flex-col justify-between lg:flex-row">
|
||||
<FilterGenerator filterPropertiesData={props.filterPropertiesData}
|
||||
setSelectedFilter={setSelectedFilters}/>
|
||||
<div className="products grid grid-cols-1 2xl:grid-cols-2 gap-7 lg:w-3/4 h-fit w-full">
|
||||
|
||||
{
|
||||
props.catalog.map(product =>
|
||||
{catalogQuery.isFetching &&
|
||||
Array(10).fill(null).map((_, index) => <Skeleton key={index}
|
||||
className={"h-[250px] rounded-[20px]"}/>)
|
||||
}
|
||||
|
||||
{catalogQuery.data &&
|
||||
catalogQuery.data.map(product =>
|
||||
<>
|
||||
<CatalogCard key={product.id} {...product}
|
||||
isFavourite={favourites.includes(product.id)}/>
|
||||
|
@ -150,25 +225,13 @@ export const getStaticProps = async () => {
|
|||
const filterData = await service.getFilters() as {
|
||||
id: number,
|
||||
name: string,
|
||||
values: { id: number, value: string }[]
|
||||
}[]
|
||||
const catalogData = await service.getCatalogItems() as {
|
||||
id: number
|
||||
code: string,
|
||||
name: string,
|
||||
properties: {
|
||||
[key: string]: string | string[]
|
||||
}
|
||||
detailText: string,
|
||||
price: {
|
||||
[key: string]: number | null
|
||||
}
|
||||
values: { id: number, value: string }[]
|
||||
}[]
|
||||
|
||||
return {
|
||||
props: {
|
||||
filterPropertiesData: filterData,
|
||||
catalog: catalogData
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ const Hero = () => {
|
|||
<div className="w-11/12 mx-auto lg:m-0 xl:w-1/2 flex flex-col bg-black bg-opacity-25 py-4 xl:px-7 px-2 rounded-[20px]">
|
||||
<h2 className={"text-2xl leading-[100%] xl:text-title-4 2xl:text-title-2 mb-4 xl:mb-[52px]"}>Оформите заказ
|
||||
на сумму от 5000₽</h2>
|
||||
<span className={"text-sm xl:text-subtitle-1 leading-inherit text-subtitle-1 mb-4 xl:mb-24"}>До 15 января и получите бесплатную доставку</span>
|
||||
<span className={"text-sm xl:text-subtitle-1 leading-inherit text-subtitle-1 mb-4 xl:mb-24"}>И получите бесплатную доставку</span>
|
||||
|
||||
<Link href={"/catalog"} className={'w-full lg:w-1/2 bg-green-2 rounded-[8px] p-6 flex justify-between items-center text-black hover:scale-105 transition'}>
|
||||
<span className={"text-sm text-[1.125rem] font-extrabold italic uppercase"}>Перейти к покупке</span>
|
||||
|
|
|
@ -27,8 +27,40 @@ class LocalAPI {
|
|||
})
|
||||
}
|
||||
|
||||
async getCatalogItems() {
|
||||
const {data} = await this.instance.get('/api/v1/catalog')
|
||||
async getCatalogItemsCount(){
|
||||
const {data} = await this.instance.get<{status: number, info: string}>('/api/v1/catalog/count')
|
||||
return +data.info
|
||||
}
|
||||
|
||||
async getCatalogItems(filters: { [key: string]: string[] }, page: number = 1) {
|
||||
const dataFilters: {[key: string]: string} = {}
|
||||
|
||||
for (const [key, value] of Object.entries(filters)) {
|
||||
if (value.length === 0) {
|
||||
continue
|
||||
}
|
||||
dataFilters[key] = value.join(',')
|
||||
}
|
||||
|
||||
if (Object.keys(dataFilters).length === 0) {
|
||||
const {data} = await this.instance.get('/api/v1/catalog', {
|
||||
params: {
|
||||
limit: 10,
|
||||
page,
|
||||
isFilter: 0,
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
||||
const {data} = await this.instance.get('/api/v1/catalog', {
|
||||
params: {
|
||||
limit: 10,
|
||||
page,
|
||||
isFilter: 1,
|
||||
...dataFilters
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue