From 07d460ea27c5f6c262776273829885a231f6e3e3 Mon Sep 17 00:00:00 2001 From: zojgame Date: Thu, 20 Jun 2024 18:35:04 +0500 Subject: [PATCH] filters --- client/package.json | 1 + client/src/api/apartments.ts | 53 ++- client/src/api/updateAccessToken.ts | 3 +- .../modals/mobile/SearchFiltersModal.tsx | 10 +- .../components/searchPage/LayoutOptions.tsx | 182 +++++++++-- .../components/searchPage/SidebarFilters.tsx | 37 ++- .../searchParticularApartmentsPage/Units.tsx | 3 +- client/src/consts/initialMasterplanFilters.ts | 91 +++++- client/src/consts/initialSearchFilters.ts | 57 ---- client/src/consts/initialSearchPage.ts | 26 +- client/src/pages/Favorites.tsx | 2 +- client/src/store/useSearchFilters.ts | 30 +- client/src/types/checkbox.ts | 1 + client/src/types/sortType.ts | 1 + client/yarn.lock | 5 + server/src/index.ts | 1 - server/src/routes/apartments.ts | 308 +++++++++++++----- 17 files changed, 567 insertions(+), 244 deletions(-) delete mode 100644 client/src/consts/initialSearchFilters.ts diff --git a/client/package.json b/client/package.json index 716d28d..fd5fc7c 100644 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,7 @@ "@react-three/drei": "^9.105.6", "@react-three/fiber": "^8.16.6", "@types/react-router-dom": "^5.3.3", + "@uidotdev/usehooks": "^2.4.1", "gsap": "^3.12.5", "ky": "^1.3.0", "react": "^18.2.0", diff --git a/client/src/api/apartments.ts b/client/src/api/apartments.ts index 7e33819..2b36438 100644 --- a/client/src/api/apartments.ts +++ b/client/src/api/apartments.ts @@ -1,11 +1,56 @@ import { apartmentsApi } from "./urls"; import ky from "ky"; -async function getApartments(token: string | null) { - // const token = - // "1000.04c54423a172cff4f2075ef60dc46403.b4a66d1663ed3b39fa86c9b28dbed2fa"; +async function getApartments( + token: string | null, + roveHomeFilters: string[], + apartmentTypeFilters: string[], + viewFilters: string[], + totalAreaFilter: number[], + floorFilter: number[], + sortBy: string | undefined, + page: number, + perPage: number +) { + const roveHomeQuery = + roveHomeFilters.length !== 0 + ? `rove_home=${roveHomeFilters.join(",")}` + : ""; + const apartmentQuery = + apartmentTypeFilters.length !== 0 + ? `apartment_type=${apartmentTypeFilters.join(",")}` + : ""; + const viewQuery = + viewFilters.length !== 0 ? `views=${viewFilters.join(",")}` : ""; + const totalAreaSliderQuery = + totalAreaFilter.length !== 0 + ? `total_area_between=${totalAreaFilter[0]},${totalAreaFilter[1]}` + : ""; + const floorQuery = + floorFilter.length !== 0 + ? `floor_between=${floorFilter[0]},${floorFilter[1]}` + : ""; - const res = await ky.get(apartmentsApi, { + const sortByQuery = sortBy ? `sort_by=${sortBy}` : ""; + + const pageQuery = `page=${page}`; + const perPageQuery = `per_page=${perPage}`; + + const query = [ + apartmentQuery, + roveHomeQuery, + totalAreaSliderQuery, + floorQuery, + viewQuery, + sortByQuery, + pageQuery, + perPageQuery, + ] + .filter((q) => q.length !== 0) + .join("&"); + + const url = `${apartmentsApi}?${query}`; + const res = await ky.get(url, { headers: { Authorization: `Zoho-oauthtoken ${token}`, }, diff --git a/client/src/api/updateAccessToken.ts b/client/src/api/updateAccessToken.ts index 8127d9c..7564807 100644 --- a/client/src/api/updateAccessToken.ts +++ b/client/src/api/updateAccessToken.ts @@ -6,10 +6,11 @@ async function updateAccessToken() { try { const res = await ky.get(updateAccessTokenApi); - res.json().then((data) => { + return res.json().then((data) => { const accessToken = (data as IUpdateAccessToken).accessToken; localStorage.setItem("ACCESS_TOKEN", accessToken); + return accessToken; }); } catch (error) { console.log("error", error); diff --git a/client/src/components/modals/mobile/SearchFiltersModal.tsx b/client/src/components/modals/mobile/SearchFiltersModal.tsx index da92267..289d5ef 100644 --- a/client/src/components/modals/mobile/SearchFiltersModal.tsx +++ b/client/src/components/modals/mobile/SearchFiltersModal.tsx @@ -10,12 +10,12 @@ import Switch from "../../Switch"; import ResetIcon from "../../icons/ResetIcon"; import { useSwipeable } from "react-swipeable"; import { - initialApartmentTypeCheckboxes, - initialRoveHomeCheckboxes, + initialAparmentTypeCheckboxes, initialSliders, initialSwitchers, -} from "../../../consts/initialSearchFilters"; -import { initialViewCheckboxes } from "../../../consts/initialMasterplanFilters"; + initialViewCheckboxes, + initialRoveHomeCheckboxes, +} from "../../../consts/initialMasterplanFilters"; const SearchFiltersModal = () => { // const [viewCheckboxes, setViewCheckboxes] = useState( @@ -119,7 +119,7 @@ const SearchFiltersModal = () => { const handleOnResetClick = () => { setMultirangeSliders(initialSliders); setViewCheckboxes(initialViewCheckboxes); - setApartmentTypeCheckboxes(initialApartmentTypeCheckboxes); + setApartmentTypeCheckboxes(initialAparmentTypeCheckboxes); setRoveHomeTypeCheckboxes(initialRoveHomeCheckboxes); setSwitchers(initialSwitchers); }; diff --git a/client/src/components/searchPage/LayoutOptions.tsx b/client/src/components/searchPage/LayoutOptions.tsx index 6e53b3a..2ff83f4 100644 --- a/client/src/components/searchPage/LayoutOptions.tsx +++ b/client/src/components/searchPage/LayoutOptions.tsx @@ -1,7 +1,6 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import LayoutCard from "./LayoutCard"; import SortButton from "./SortButton"; -import { initialSortList } from "../../consts/initialSearchPage"; import SearchIcon from "../icons/SearchIcon"; import Button from "../Button"; import FilterIcon from "../icons/FilterIcon"; @@ -9,19 +8,112 @@ import useModal from "../../store/useModal"; import { MobileModalWrapper } from "../modals/mobile/MobileModalWrapper"; import SearchFiltersModal from "../modals/mobile/SearchFiltersModal"; import { getApartments } from "../../api/apartments"; -import { IApartmentsRes, InfoRes } from "../../types/apartmentsRes"; +import { IAparmentRes, IApartmentsRes } from "../../types/apartmentsRes"; import { updateAccessToken } from "../../api/updateAccessToken"; import useApartments from "../../store/useApartments"; import useSearchFilters from "../../store/useSearchFilters"; +import { ICheckbox } from "../../types/checkbox"; +import { IMultirangeSlider } from "../../types/multirangeSlider"; +import { useDebounce } from "@uidotdev/usehooks"; +import { ISwitcher } from "../../types/switcher"; +import { initialSliders } from "../../consts/initialMasterplanFilters"; +import { ISort } from "../../types/sortType"; + +const getFilteredApartments = async ( + zohoToken: string | null, + setApartments: (apartments: IAparmentRes[]) => void, + roveHomeTypeCheckboxes: ICheckbox[], + apartmentTypeCheckboxes: ICheckbox[], + multirangeSliders: IMultirangeSlider[], + switchers: ISwitcher[], + viewCheckboxes: ICheckbox[], + sortList: ISort[], + page: number, + perPage: number +) => { + const roveHomeFilters = roveHomeTypeCheckboxes + .filter((chx) => chx.selected) + .map((chx) => chx.value); + + const apartmentTypeFilters = apartmentTypeCheckboxes + .filter((chx) => chx.selected) + .map((chx) => chx.value); + + const viewFilters = viewCheckboxes + .filter((chx) => chx.selected) + .map((chx) => chx.value); + + const sortBy = sortList.find((sortType) => sortType.isSelected)?.value; + + const totalAreaSlider = multirangeSliders.find( + (sld) => sld.title === "Total Area" + ); + const totalAreaFilter = [ + totalAreaSlider?.startValue, + totalAreaSlider?.endValue, + ] as number[]; + const isNotFirstFloor = switchers.find( + (swch) => swch.isSwitched && swch.title === "Not the first floor" + ); + const isNotTopFloor = switchers.find( + (swch) => swch.isSwitched && swch.title === "Not the top floor" + ); + const initialFloorSlider = initialSliders.find( + (sld) => sld.title === "Floor" + ) as IMultirangeSlider; + // если значение ввода в границах слайдера + const maxFloor = initialFloorSlider?.maxValue; + const minFloor = initialFloorSlider?.minValue; + + const floorSlider = multirangeSliders.find( + (sld) => sld.title === "Floor" + ) as IMultirangeSlider; + + const startFloorValue = isNotFirstFloor + ? Math.max(minFloor + 1, floorSlider.startValue) + : floorSlider.startValue; + const endFloorValue = isNotTopFloor + ? Math.min(maxFloor - 1, floorSlider?.endValue) + : floorSlider?.endValue; + + const floorFilter = [startFloorValue, endFloorValue]; + + const res = await getApartments( + zohoToken, + roveHomeFilters, + apartmentTypeFilters, + viewFilters, + totalAreaFilter, + floorFilter, + sortBy, + page, + perPage + ).then((data) => { + const apartmentsData = (data as IApartmentsRes).apartments; + setApartments(apartmentsData); + }); + + return res; +}; const LayoutOptions = () => { - const [sortList, setSortList] = useState(initialSortList); const { apartments, setApartments } = useApartments(); - const [infoRes, setInfoRes] = useState(null); const { setModal } = useModal(); - const { roveHomeTypeCheckboxes, apartmentTypeCheckboxes, multirangeSliders } = - useSearchFilters(); + const { + roveHomeTypeCheckboxes, + apartmentTypeCheckboxes, + multirangeSliders, + viewCheckboxes, + switchers, + sortList, + setSortList, + page, + setPerPage, + setPage, + perPage, + } = useSearchFilters(); + const debouncedSliders = useDebounce(multirangeSliders, 300); const handleOnSortClick = (sortId: string) => { const updatedSortList = sortList.map((sort) => { @@ -39,39 +131,58 @@ const LayoutOptions = () => { ); }; + function handleOnShowMoreBtnClick(): void { + const nextPage = page + 1; + setPage(nextPage); + // setPerPage(nextPage); + } + useEffect(() => { const zohoToken = localStorage.getItem("ACCESS_TOKEN"); - getApartments(zohoToken) - .then((data) => { - const apartmentsData = (data as IApartmentsRes).apartments; - console.log("apartmentsData", apartmentsData); - setApartments(apartmentsData); - }) - .catch((error) => { - const errorStatus = error.response.status; + getFilteredApartments( + zohoToken, + setApartments, + roveHomeTypeCheckboxes, + apartmentTypeCheckboxes, + debouncedSliders, + switchers, + viewCheckboxes, + sortList, + page, + perPage + ).catch((error) => { + const errorStatus = error.response.status; - if (errorStatus === 401) { - updateAccessToken(); - const zohoToken = localStorage.getItem("ACCESS_TOKEN"); - - getApartments(zohoToken).then((data) => { - const apartmentsData = (data as unknown as IApartmentsRes) - .apartments; - - setApartments(apartmentsData); - }); - } - }); - }, [setApartments]); - - useEffect(() => { - console.log("apartments", apartments); + if (errorStatus === 401) { + updateAccessToken().then((token) => { + if (token) { + getFilteredApartments( + token, + setApartments, + roveHomeTypeCheckboxes, + apartmentTypeCheckboxes, + debouncedSliders, + switchers, + viewCheckboxes, + sortList, + page, + perPage + ); + } + }); + } + }); }, [ + setApartments, roveHomeTypeCheckboxes, apartmentTypeCheckboxes, - multirangeSliders, - apartments, + debouncedSliders, + switchers, + viewCheckboxes, + sortList, + page, + perPage, ]); return ( @@ -101,7 +212,10 @@ const LayoutOptions = () => { ))} -
+
Show 12 more apartments
diff --git a/client/src/components/searchPage/SidebarFilters.tsx b/client/src/components/searchPage/SidebarFilters.tsx index 8e030ed..806a591 100644 --- a/client/src/components/searchPage/SidebarFilters.tsx +++ b/client/src/components/searchPage/SidebarFilters.tsx @@ -3,16 +3,33 @@ import ResetIcon from "../icons/ResetIcon"; import Checkbox from "../Checkbox"; import Switch from "../Switch"; import MultiRangeSlider from "../MultiRangeSlider"; +import useSearchFilters from "../../store/useSearchFilters"; import { - initialApartmentTypeCheckboxes, + initialViewCheckboxes, + initialAparmentTypeCheckboxes, initialSliders, initialSwitchers, initialRoveHomeCheckboxes, -} from "../../consts/initialSearchFilters"; -import useSearchFilters from "../../store/useSearchFilters"; -import { initialViewCheckboxes } from "../../consts/initialMasterplanFilters"; -import { useState } from "react"; -import { ICheckbox } from "../../types/checkbox"; +} from "../../consts/initialMasterplanFilters"; +// import { IMultirangeSlider } from "../../types/multirangeSlider"; + +// function updateSlider(switcherTitle: string, sliders: IMultirangeSlider[]) { +// if(switcherTitle === 'Not the first floor'){ +// const updatedSlider = sliders.map((sld) => { +// if(sld.title === 'Floor'){ +// const floorSlider = initialSliders.find(sld1 => sld1.title === 'Floor'); +// const minValue = floorSlider?.minValue; +// const isValueEqualMinValue = sld.minValue === minValue; +// //Если значение равно минимальному значение, то уменьшаем минимальное значение +// if(isValueEqualMinValue){ +// return {...sld, startValue: minValue, minValue: minValue} +// } +// return +// } +// return sld +// }) +// } +// } const SidebarFilters = () => { const { @@ -24,12 +41,10 @@ const SidebarFilters = () => { setApartmentTypeCheckboxes, roveHomeTypeCheckboxes, setRoveHomeTypeCheckboxes, + viewCheckboxes, + setViewCheckboxes, } = useSearchFilters(); - const [viewCheckboxes, setViewCheckboxes] = useState( - initialViewCheckboxes - ); - const handleOnCheckboxApartmentClick = (checkboxId: string) => { const updatedCheckboxes = apartmentTypeCheckboxes.map((cbox) => { if (checkboxId !== cbox.id) return cbox; @@ -89,7 +104,7 @@ const SidebarFilters = () => { const handleOnResetClick = () => { setViewCheckboxes(initialViewCheckboxes); - setApartmentTypeCheckboxes(initialApartmentTypeCheckboxes); + setApartmentTypeCheckboxes(initialAparmentTypeCheckboxes); setRoveHomeTypeCheckboxes(initialRoveHomeCheckboxes); setMultirangeSliders(initialSliders); setSwitchers(initialSwitchers); diff --git a/client/src/components/searchParticularApartmentsPage/Units.tsx b/client/src/components/searchParticularApartmentsPage/Units.tsx index 0a7799a..9f5da38 100644 --- a/client/src/components/searchParticularApartmentsPage/Units.tsx +++ b/client/src/components/searchParticularApartmentsPage/Units.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from "react"; -import { initialSortList, layoutsCards } from "../../consts/initialSearchPage"; +import { layoutsCards } from "../../consts/initialSearchPage"; +import { initialSortList } from "../../consts/initialMasterplanFilters"; import useSearchFilters from "../../store/useSearchFilters"; import SearchIcon from "../icons/SearchIcon"; import SortButton from "../searchPage/SortButton"; diff --git a/client/src/consts/initialMasterplanFilters.ts b/client/src/consts/initialMasterplanFilters.ts index 715df19..df40004 100644 --- a/client/src/consts/initialMasterplanFilters.ts +++ b/client/src/consts/initialMasterplanFilters.ts @@ -1,5 +1,6 @@ import { ICheckbox } from "../types/checkbox"; import { IMultirangeSlider } from "../types/multirangeSlider"; +import { ISort } from "../types/sortType"; import { ISwitcher } from "../types/switcher"; const initialSliders: IMultirangeSlider[] = [ @@ -15,8 +16,8 @@ const initialSliders: IMultirangeSlider[] = [ { minValue: 341, startValue: 341, - endValue: 891, - maxValue: 891, + endValue: 1058, + maxValue: 1058, title: "Total Area", unit: "Sqrt", id: "2", @@ -24,27 +25,38 @@ const initialSliders: IMultirangeSlider[] = [ { minValue: 5, startValue: 5, - endValue: 81, - maxValue: 81, + endValue: 31, + maxValue: 31, title: "Floor", id: "3", }, ]; const initialAparmentTypeCheckboxes: ICheckbox[] = [ - { title: "Studio Flex", id: "1", disabled: true, selected: false }, - { title: "Studio", id: "2", selected: false }, - { title: "1 Bedroom", id: "3", selected: false }, - { title: "2 Bedroom", id: "4", selected: false }, + { + title: "Studio Flex", + id: "1", + disabled: false, + selected: false, + value: "Studio Flex", + }, + { title: "Studio", id: "2", selected: false, value: "Studio Squared" }, + { title: "1 Bedroom", id: "3", selected: false, value: "1 BR Squared" }, + { title: "2 Bedroom", id: "4", selected: false, value: "2 BR Squared" }, ]; const initialViewCheckboxes: ICheckbox[] = [ - { title: "Burj Khalifa", id: "1", disabled: true, selected: false }, - { title: "Amenties", id: "2", selected: false }, - { title: "Downtown", id: "3", selected: false }, - { title: "Canal", id: "4", selected: false }, - { title: "Business Bay", id: "5", selected: false }, - { title: "Park", id: "6", selected: false }, + { title: "Burj Khalifa", id: "1", selected: false, value: "BK" }, + { title: "Amenties", id: "2", selected: false, value: "Amenities" }, + { title: "Downtown", id: "3", selected: false, value: "DT" }, + { title: "Canal", id: "4", selected: false, value: "Canal" }, + { title: "Business Bay", id: "5", selected: false, value: "Business Bay" }, + { + title: "Park", + id: "6", + selected: false, + value: "Partial Park,Park Facing", + }, ]; const initialSwitchers: ISwitcher[] = [ @@ -52,9 +64,60 @@ const initialSwitchers: ISwitcher[] = [ { id: "2", title: "Not the top floor", isSwitched: false }, ]; +const initialRoveHomeCheckboxes: ICheckbox[] = [ + { + title: "Downtown", + id: "1", + disabled: false, + selected: false, + value: "Rove Home Downtown", + }, + { + title: "Marasi Drive", + id: "2", + selected: false, + value: "Rove Home Marasi Drive", + }, + { + title: "Dubai Marina", + id: "3", + selected: false, + value: "Rove Home Dubai Marina", + }, +]; + +const initialSortList: ISort[] = [ + { + id: "1", + title: "Ascending price", + isSelected: true, + value: "asc_price", + }, + { + id: "2", + title: "Decreasing price", + isSelected: false, + value: "decr_price", + }, + { + id: "3", + title: "Ascending Squares", + isSelected: false, + value: "asc_sqr", + }, + { + id: "4", + title: "Ascending Floor", + isSelected: false, + value: "asc_floor", + }, +]; + export { initialAparmentTypeCheckboxes, initialSliders, initialSwitchers, initialViewCheckboxes, + initialRoveHomeCheckboxes, + initialSortList, }; diff --git a/client/src/consts/initialSearchFilters.ts b/client/src/consts/initialSearchFilters.ts deleted file mode 100644 index e54ef77..0000000 --- a/client/src/consts/initialSearchFilters.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ICheckbox } from "../types/checkbox"; -import { IMultirangeSlider } from "../types/multirangeSlider"; -import { ISwitcher } from "../types/switcher"; - -const initialSliders: IMultirangeSlider[] = [ - { - minValue: 1048888, - startValue: 1048888, - endValue: 2408888, - maxValue: 2408888, - title: "Cost", - unit: "AED", - id: "1", - }, - { - minValue: 341, - startValue: 341, - endValue: 891, - maxValue: 891, - title: "Total Area", - unit: "Sqrt", - id: "2", - }, - { - minValue: 5, - startValue: 5, - endValue: 81, - maxValue: 81, - title: "Floor", - id: "3", - }, -]; - -const initialApartmentTypeCheckboxes: ICheckbox[] = [ - { title: "Studio Flex", id: "1", selected: false }, - { title: "Studio", id: "2", selected: false }, - { title: "1 Bedroom", id: "3", selected: false }, - { title: "2 Bedroom", id: "4", selected: false }, -]; - -const initialRoveHomeCheckboxes: ICheckbox[] = [ - { title: "Downtown", id: "1", disabled: false, selected: false }, - { title: "Marasi Drive", id: "2", selected: false }, - { title: "Dubai Marina", id: "3", selected: false }, -]; - -const initialSwitchers: ISwitcher[] = [ - { id: "1", title: "Not the first floor", isSwitched: false }, - { id: "2", title: "Not the top floor", isSwitched: false }, -]; - -export { - initialApartmentTypeCheckboxes, - initialSliders, - initialSwitchers, - initialRoveHomeCheckboxes, -}; diff --git a/client/src/consts/initialSearchPage.ts b/client/src/consts/initialSearchPage.ts index 034c728..dc6f461 100644 --- a/client/src/consts/initialSearchPage.ts +++ b/client/src/consts/initialSearchPage.ts @@ -1,5 +1,4 @@ import { ILayoutCard } from "../types/layoutCard"; -import { ISort } from "../types/sortType"; const layoutsCards: ILayoutCard[] = [ { @@ -136,27 +135,4 @@ const layoutsCards: ILayoutCard[] = [ }, ]; -const initialSortList: ISort[] = [ - { - id: "1", - title: "Ascending price", - isSelected: true, - }, - { - id: "2", - title: "Decreasing price", - isSelected: false, - }, - { - id: "3", - title: "Ascending Squares", - isSelected: false, - }, - { - id: "4", - title: "Ascending Floor", - isSelected: false, - }, -]; - -export { initialSortList, layoutsCards }; +export { layoutsCards }; diff --git a/client/src/pages/Favorites.tsx b/client/src/pages/Favorites.tsx index eb5a112..54cecc8 100644 --- a/client/src/pages/Favorites.tsx +++ b/client/src/pages/Favorites.tsx @@ -4,7 +4,7 @@ import Button from "../components/Button"; import Footer from "../components/Footer"; import TrashIcon from "../components/icons/TrashIcon"; import SortButton from "../components/searchPage/SortButton"; -import { initialSortList } from "../consts/initialSearchPage"; +import { initialSortList } from "../consts/initialMasterplanFilters"; import { ILayoutCard } from "../types/layoutCard"; import Switch from "../components/Switch"; import { ISwitcher } from "../types/switcher"; diff --git a/client/src/store/useSearchFilters.ts b/client/src/store/useSearchFilters.ts index b01967d..ed49b2b 100644 --- a/client/src/store/useSearchFilters.ts +++ b/client/src/store/useSearchFilters.ts @@ -1,18 +1,26 @@ import { create } from "zustand"; import { ICheckbox } from "../types/checkbox"; import { - initialApartmentTypeCheckboxes, - initialRoveHomeCheckboxes, + initialAparmentTypeCheckboxes, initialSliders, initialSwitchers, -} from "../consts/initialSearchFilters"; + initialViewCheckboxes, + initialRoveHomeCheckboxes, + initialSortList, +} from "../consts/initialMasterplanFilters"; import { IMultirangeSlider } from "../types/multirangeSlider"; import { ISwitcher } from "../types/switcher"; -import { initialViewCheckboxes } from "../consts/initialMasterplanFilters"; +import { ISort } from "../types/sortType"; interface Store { apartmentTypeCheckboxes: ICheckbox[]; viewCheckboxes: ICheckbox[]; + sortList: ISort[]; + page: number; + perPage: number; + setPage: (page: number) => void; + setPerPage: (perPage: number) => void; + setSortList: (sortList: ISort[]) => void; setApartmentTypeCheckboxes: (typeCheckboxes: ICheckbox[]) => void; setViewCheckboxes: (checkboxes: ICheckbox[]) => void; roveHomeTypeCheckboxes: ICheckbox[]; @@ -24,19 +32,25 @@ interface Store { } const useSearchFilters = create((set) => ({ - apartmentTypeCheckboxes: initialApartmentTypeCheckboxes, + apartmentTypeCheckboxes: initialAparmentTypeCheckboxes, viewCheckboxes: initialViewCheckboxes, + roveHomeTypeCheckboxes: initialRoveHomeCheckboxes, + multirangeSliders: initialSliders, + switchers: initialSwitchers, + sortList: initialSortList, + page: 1, + perPage: 12, + setPerPage: (perPage) => set(() => ({ perPage: perPage })), + setPage: (page) => set(() => ({ page: page })), + setSortList: (sortList) => set(() => ({ sortList: sortList })), setApartmentTypeCheckboxes: (typeCheckboxes) => set(() => ({ apartmentTypeCheckboxes: typeCheckboxes })), setViewCheckboxes: (typeCheckboxes) => set(() => ({ viewCheckboxes: typeCheckboxes })), - roveHomeTypeCheckboxes: initialRoveHomeCheckboxes, setRoveHomeTypeCheckboxes: (typeCheckboxes) => set(() => ({ roveHomeTypeCheckboxes: typeCheckboxes })), - multirangeSliders: initialSliders, setMultirangeSliders: (multirangeSliders) => set(() => ({ multirangeSliders: multirangeSliders })), - switchers: initialSwitchers, setSwitchers: (switchers) => set(() => ({ switchers: switchers })), })); diff --git a/client/src/types/checkbox.ts b/client/src/types/checkbox.ts index e852868..329347a 100644 --- a/client/src/types/checkbox.ts +++ b/client/src/types/checkbox.ts @@ -3,6 +3,7 @@ interface ICheckbox { id: string; disabled?: boolean; selected: boolean; + value: string; } export type { ICheckbox }; diff --git a/client/src/types/sortType.ts b/client/src/types/sortType.ts index 59f2985..f9d2419 100644 --- a/client/src/types/sortType.ts +++ b/client/src/types/sortType.ts @@ -2,6 +2,7 @@ interface ISort { id: string; title: string; isSelected: boolean; + value: string; } export type { ISort }; diff --git a/client/yarn.lock b/client/yarn.lock index 866618a..a57bc2e 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -884,6 +884,11 @@ "@typescript-eslint/types" "7.6.0" eslint-visitor-keys "^3.4.3" +"@uidotdev/usehooks@^2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@uidotdev/usehooks/-/usehooks-2.4.1.tgz#4b733eaeae09a7be143c6c9ca158b56cc1ea75bf" + integrity sha512-1I+RwWyS+kdv3Mv0Vmc+p0dPYH0DTRAo04HLyXReYBL9AeseDWUJyi4THuksBJcu9F0Pih69Ak150VDnqbVnXg== + "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" diff --git a/server/src/index.ts b/server/src/index.ts index e7fadd1..bea89e3 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -24,7 +24,6 @@ app.use(json()); app.use(morgan("combined", { stream: accessLogStream })); app.use("/apartments", apartmentsRoute); - app.use("/updateAccessToken", updateAccessToken); app.listen(port, () => { diff --git a/server/src/routes/apartments.ts b/server/src/routes/apartments.ts index ef035fd..72c0532 100644 --- a/server/src/routes/apartments.ts +++ b/server/src/routes/apartments.ts @@ -2,12 +2,28 @@ import { Router } from "express"; import { aparmentsApi, searchApartmentApi } from "../consts.js"; import { logger } from "../utils/logger.js"; +interface IApartment { + Floor: number; + Property_Status: string; + Unit_Type: string; + Project_Name: string; + Suite_Area_Sqft: number; + Balcony_Area_Sqft: number; + No_Of_Bedrooms: number; + Unit_No: string; + id: string; + Total_Area_Sqft: number; + No_of_Bathrooms: number; + Property_Name: string; + Unit_View: string; +} + const router = Router(); async function getAllApartments( page: number, accessToken: string, - apartments: any[] + apartments: IApartment[] ) { const response = await fetch(`${aparmentsApi}&page=${page}&per_page=200`, { headers: { @@ -31,109 +47,237 @@ async function getAllApartments( return await getAllApartments(page + 1, accessToken, updatedApartment); } +function filterApartments( + apartments: IApartment[], + roveHome?: string[], + apartmentType?: string[], + costBetween?: string[], + totalAreaBetween?: string[], + floorBetween?: string[], + views?: string[] +) { + const filteredApartments = apartments + .filter((apart) => { + // Rove home filter + if (!roveHome || roveHome.length === 0) { + return true; + } + + if (roveHome.some((rove) => rove === apart.Project_Name)) { + return true; + } + + return false; + }) + .filter((apart) => { + // Apartment type filter + if (!apartmentType || apartmentType.length === 0) { + return true; + } + + if (apartmentType.some((rove) => rove === apart.Unit_Type)) { + return true; + } + + return false; + }) + .filter((apart) => { + // Cost between filter еще не доступен тк нет цен + + if (!costBetween || costBetween.length === 0) { + return true; + } + + const leftCost = Number(costBetween[0]); + const rightCost = Number(costBetween[1]); + + return true; + + // if (costBetween.some((rove) => rove === apart.Unit_Type)) { + // return true; + // } + + // return false; + }) + .filter((apart) => { + // Total area filter + if (!totalAreaBetween || totalAreaBetween.length === 0) { + return true; + } + + const leftArea = Number(totalAreaBetween[0]); + const rightArea = Number(totalAreaBetween[1]); + + if ( + (leftArea <= apart.Total_Area_Sqft && + rightArea >= apart.Total_Area_Sqft) || + (leftArea >= apart.Total_Area_Sqft && + rightArea <= apart.Total_Area_Sqft) + ) { + return true; + } + + return false; + }) + .filter((apart) => { + // Views filter + if (!views || views.length === 0) { + return true; + } + + //view "Canal" "Amenities" + // Canal / Amenities + + // разделение по / с пробелом и без + const apartViews = apart.Unit_View.split(" / ").join("/").split("/"); + for (let i = 0; i < apartViews.length; i++) { + // Удаление Corner- + const withoutCorner = + apartViews[i].split("-").length > 1 + ? apartViews[i].split("-")[1] + : apartViews[i]; + + //удаление View + const withoutView = withoutCorner.replace(" View", ""); + + if (views.some((view) => view === withoutView)) { + return true; + } + } + + return false; + }) + .filter((apart) => { + // Floor filter + + if (!floorBetween || floorBetween.length === 0) { + return true; + } + + const leftFloor = Number(floorBetween[0]); + const rightFloor = Number(floorBetween[1]); + + if ( + (leftFloor <= apart.Floor && rightFloor >= apart.Floor) || + (leftFloor >= apart.Floor && rightFloor <= apart.Floor) + ) { + return true; + } + + return false; + }); + + return filteredApartments; +} + router.get("/", async (req, res) => { const accessToken = req?.headers?.authorization; const { - per_page: perPage = 20, - page = 10, + per_page = 20, + page = 1, rove_home = "", apartment_type = "", cost_between = "", total_area_between = "", floor_between = "", views = "", + sort_by = "", } = req.query; - let api = aparmentsApi; - - if (!accessToken) - return res - .status(401) - .json({ message: "Отсутсвует access token", code: 401 }); - try { - const allApartments = await getAllApartments(1, accessToken, []); - const filteredApartments = allApartments.slice( - page as number, - perPage as number - ); + const roveHomeFilter = (rove_home as string) + .split(",") + .filter((rove) => rove !== ""); + const apartmentTypeFilter = (apartment_type as string) + .split(",") + .filter((rove) => rove !== ""); + const costBetweenFilter = (cost_between as string) + .split(",") + .filter((cost) => cost !== ""); + const totalAreaBetween = (total_area_between as string) + .split(",") + .filter((area) => area !== ""); + const viewsFilter = (views as string) + .split(",") + .filter((view) => view !== ""); + const floorBetweenFilter = (floor_between as string) + .split(",") + .filter((floor) => floor !== ""); - res.status(200).json({ - message: "ok", - apartments: filteredApartments, - code: 200, - }); + if (!accessToken) + return res + .status(401) + .json({ message: "Отсутсвует access token", code: 401 }); + + try { + const allApartments = await getAllApartments(1, accessToken, []); + const filteredApartments = filterApartments( + allApartments, + roveHomeFilter, + apartmentTypeFilter, + costBetweenFilter, + totalAreaBetween, + floorBetweenFilter, + viewsFilter + ); + + const sortedApartments = [...filteredApartments].sort((a, b) => { + if (sort_by === "asc_price") { + return 1; + } + + if (sort_by === "decr_price") { + return 1; + } + + if (sort_by === "asc_sqr") { + return a.Total_Area_Sqft - b.Total_Area_Sqft; + } + + if (sort_by === "asc_floor") { + return a.Floor - b.Floor; + } + + return 1; + }); + + const slicedApartments = sortedApartments.slice( + ((page as number) - 1) * (per_page as number), + per_page as number + ); + console.log("slicedApartments", slicedApartments); + + res.status(200).json({ + message: "ok", + apartments: slicedApartments, + code: 200, + }); + + return; + } catch (error) { + if ( + (error as Error).message === "invalid oauth token" || + (error as Error).message === "INVALID_TOKEN" + ) { + console.log("error", error); + logger.error(error); + + return res + .status(401) + .json({ message: "Неправильный токен или токен устарел", code: 401 }); + } - return; - } catch (error) { - if ( - (error as Error).message === "invalid oauth token" || - (error as Error).message === "INVALID_TOKEN" - ) { console.log("error", error); logger.error(error); - return res - .status(401) - .json({ message: "Неправильный токен или токен устарел", code: 401 }); + return res.status(500).json({ message: "Server Error", code: 500 }); } - - console.log("error", error); + } catch (error) { logger.error(error); - - return res.status(500).json({ message: "Server Error", code: 500 }); + console.log("error", error); } }); -// router.get("/", async (req, res) => { -// const accessToken = req.headers.authorization; -// const { perPage, criteria } = req.query; - -// let api = perPage || criteria ? searchApartmentApi : aparmentsApi; - -// if (criteria) { -// api = `${api}&criteria=${criteria}`; -// } - -// console.log("api", api); - -// if (!accessToken) -// return res -// .status(401) -// .json({ message: "Отсутсвует access token", code: 401 }); - -// try { -// const apartments = await fetch(api, { -// headers: { -// Authorization: accessToken, -// }, -// }); - -// const result = await apartments.json(); - -// if (result?.code && result?.code === "INVALID_TOKEN") { -// return res.status(401).json({ code: 401, message: "INVALID_TOKEN" }); -// } - -// res.status(200).json({ message: "ok", apartments: result, code: 200 }); -// return; -// } catch (error) { -// if ((error as Error).message === "invalid oauth token") { -// console.log("error", error); -// logger.error(error); - -// return res -// .status(401) -// .json({ message: "Неправильный токен или токен устарел", code: 401 }); -// } - -// console.log("error", error); -// logger.error(error); - -// return res.status(500).json({ message: "Server Error", code: 500 }); -// } -// }); - const apartmentsRoute = router; export default apartmentsRoute;