search filters in modal, todo mobile

This commit is contained in:
2025-04-29 19:13:25 +05:00
parent 9a7e52b95f
commit 5b09ff703b
9 changed files with 348 additions and 246 deletions
+1 -1
View File
@@ -1 +1 @@
VITE_API_URL=http://localhost:3000
VITE_API_URL=http://192.168.1.250:3000
+1
View File
@@ -11,6 +11,7 @@ node_modules
dist
dist-ssr
*.local
.env
# Editor directories and files
.vscode/*
+10 -1
View File
@@ -15,6 +15,8 @@ function ProjectSelect({
}) {
const [selectedProject, setSelectedProject] = useState(defaultProject);
useEffect(() => setSelectedProject(defaultProject), [defaultProject]);
useEffect(() => onSelect(selectedProject), [selectedProject]);
return (
@@ -36,7 +38,14 @@ function ProjectSelect({
alt={project.title}
className="object-cover 2xl:w-[2.778vw] w-10 aspect-square rounded-full"
/>
<p className="2xl:mr-[1.111vw] mr-6">{project.title}</p>
<p
className={clsx(
"2xl:mr-[1.111vw] mr-6",
selectedProject.title !== project.title && "text-[#0D1922]/70"
)}
>
{project.title}
</p>
</div>
))}
</div>
+242 -160
View File
@@ -1,18 +1,20 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useQuery } from "@tanstack/react-query";
import RestartIcon from "./icons/RestartIcon";
import Button from "./ui/Button";
import MultiRangeSlider from "./ui/MultiRangeSlider";
import { api } from "../api/ky";
import { RefObject, useEffect, useState } from "react";
import { useNavigate, useSearchParams } from "react-router";
import { useSearchParams } from "react-router";
import { projects } from "../data/projects";
import { useDebounce } from "../hooks/useDebounce";
import clsx from "clsx";
import ProjectSelect from "./ProjectSelect";
import Project from "../types/Project";
import UnitTypesSelect from "./UnitTypesSelect";
import Select from "./ui/Select";
import { AnimatePresence, motion } from "motion/react";
import CloseIcon from "./icons/CloseIcon";
import Project from "../types/Project";
export interface Filters {
unitTypes: string[];
@@ -24,12 +26,24 @@ export interface Filters {
function SearchFilters({
inModal = false,
filters,
setInModal,
ref,
cost,
floor,
area,
setCost,
setFloor,
setArea,
}: {
inModal?: boolean;
filters?: Filters;
setInModal: (inModal: boolean) => void;
ref?: RefObject<HTMLDivElement | null>;
cost: [number, number];
floor: [number, number];
area: [number, number];
setCost: (cost: [number, number]) => void;
setFloor: (floor: [number, number]) => void;
setArea: (area: [number, number]) => void;
}) {
const [project, setProject] = useState<string>();
const [unitTypes, setUnitTypes] = useState<string[]>([]);
@@ -37,57 +51,52 @@ function SearchFilters({
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const { data: filters } = useQuery({
queryKey: ["filters", project],
enabled: !!project,
queryFn: () =>
api
.get(`units/filters?${project ? `project=${project}` : ""}`)
.json<Filters>(),
});
useEffect(() => {
const projectValue = searchParams.get("project");
const projectValue = searchParams.get("project") || projects[0].title;
if (projectValue) setProject(projectValue);
const viewValue = searchParams.get("view");
if (viewValue) setView(viewValue);
const unitTypesValue = searchParams.getAll("unitTypes");
if (unitTypesValue) setUnitTypes(unitTypesValue);
}, [searchParams]);
function resetFilters() {
window.location.href = "/search";
}
const [costInModal, setCostInModal] = useState<[number, number]>(cost);
const [areaInModal, setAreaInModal] = useState<[number, number]>(area);
const [floorInModal, setFloorInModal] = useState<[number, number]>(floor);
useEffect(() => {
if (filters) {
setCurrentMinCost(filters.cost[0]);
setCurrentMaxCost(filters.cost[1]);
setCurrentMinArea(filters.area[0]);
setCurrentMaxArea(filters.area[1]);
setCurrentMinFloor(filters.floor[0]);
setCurrentMaxFloor(filters.floor[1]);
setCostInModal(filters.cost);
setAreaInModal(filters.area);
setFloorInModal(filters.floor);
}
}, [filters]);
}, [filters, setAreaInModal, setCostInModal, setFloorInModal]);
const [currentMinCost, setCurrentMinCost] = useState<number>();
const [currentMaxCost, setCurrentMaxCost] = useState<number>();
useEffect(() => {
if (inModal) return;
setCost(costInModal);
setArea(areaInModal);
setFloor(floorInModal);
}, [setCost, costInModal, setArea, areaInModal, setFloor, floorInModal]);
const [currentMinArea, setCurrentMinArea] = useState<number>();
const [currentMaxArea, setCurrentMaxArea] = useState<number>();
const [currentMinFloor, setCurrentMinFloor] = useState<number>();
const [currentMaxFloor, setCurrentMaxFloor] = useState<number>();
function resetFilters() {
setUnitTypes([]);
if (filters) {
setCurrentMinCost(filters.cost[0]);
setCurrentMaxCost(filters.cost[1]);
setCurrentMinArea(filters.area[0]);
setCurrentMaxArea(filters.area[1]);
setCurrentMinFloor(filters.floor[0]);
setCurrentMaxFloor(filters.floor[1]);
}
navigate("/search");
}
const debouncedMinCost = useDebounce(currentMinCost, 500);
const debouncedMaxCost = useDebounce(currentMaxCost, 500);
const debouncedMinArea = useDebounce(currentMinArea, 500);
const debouncedMaxArea = useDebounce(currentMaxArea, 500);
const debouncedMinFloor = useDebounce(currentMinFloor, 500);
const debouncedMaxFloor = useDebounce(currentMaxFloor, 500);
const debouncedCost = useDebounce(inModal ? costInModal : cost, 500);
const debouncedArea = useDebounce(inModal ? areaInModal : area, 500);
const debouncedFloor = useDebounce(inModal ? floorInModal : floor, 500);
const { data: count } = useQuery({
queryKey: [
@@ -96,35 +105,39 @@ function SearchFilters({
project,
unitTypes,
view,
debouncedMinCost,
debouncedMaxCost,
debouncedMinArea,
debouncedMaxArea,
debouncedMinFloor,
debouncedMaxFloor,
debouncedCost,
debouncedArea,
debouncedFloor,
],
enabled: !!project,
enabled:
!!project &&
debouncedCost[0] >= 0 &&
debouncedCost[1] >= 0 &&
debouncedArea[0] >= 0 &&
debouncedArea[1] >= 0 &&
debouncedFloor[0] >= 0 &&
debouncedFloor[1] >= 0,
queryFn: () =>
api
.get(
`units/count?${project ? `project=${project}` : ""}${unitTypes
.map((unitType) => `&unitTypes=${unitType}`)
.join("")}${view !== "Any view" ? `&view=${view}` : ""}${
debouncedMinCost && debouncedMaxCost
? `&cost=${Math.round(debouncedMinCost)},${Math.round(
debouncedMaxCost
debouncedCost
? `&cost=${Math.round(debouncedCost[0])},${Math.round(
debouncedCost[1]
)}`
: ""
}${
debouncedMinArea && debouncedMaxArea
? `&area=${Math.round(debouncedMinArea)},${Math.round(
debouncedMaxArea
debouncedArea
? `&area=${Math.round(debouncedArea[0])},${Math.round(
debouncedArea[1]
)}`
: ""
}${
debouncedMinFloor && debouncedMaxFloor
? `&floor=${Math.round(debouncedMinFloor)},${Math.round(
debouncedMaxFloor
debouncedFloor
? `&floor=${Math.round(debouncedFloor[0])},${Math.round(
debouncedFloor[1]
)}`
: ""
}`
@@ -132,123 +145,192 @@ function SearchFilters({
.json<number>(),
});
function handleProjectSelect(project: Project) {
function handleClose() {
const projectValue = searchParams.get("project") || projects[0].title;
if (projectValue) setProject(projectValue);
const viewValue = searchParams.get("view");
setView(viewValue || "Any view");
const unitTypesValue = searchParams.getAll("unitTypes");
if (unitTypesValue) setUnitTypes(unitTypesValue);
setInModal(false);
}
function handleSelectProject(project: Project) {
setProject(project.title);
setSearchParams((prev) => {
prev.set("project", project.title);
return prev;
});
if (!inModal)
setSearchParams((prev) => {
prev.set("project", project.title);
return prev;
});
}
function handleUnitTypesSelect(unitTypes: string[]) {
setUnitTypes(unitTypes);
}
function handleViewSelect(view: string) {
function handleSelectView(view: string) {
setView(view);
if (!inModal)
setSearchParams((prev) => {
if (view !== "Any view") prev.set("view", view);
else prev.delete("view");
return prev;
});
}
function handleSelectUnitTypes(unitTypes: string[]) {
setUnitTypes(unitTypes);
if (!inModal)
setSearchParams((prev) => {
prev.delete("unitTypes");
unitTypes.forEach((unitType) => prev.append("unitTypes", unitType));
return prev;
});
}
function applyFilters() {
setInModal(false);
setSearchParams((prev) => {
prev.set("project", project!);
if (view !== "Any view") prev.set("view", view);
else prev.delete("view");
prev.delete("unitTypes");
unitTypes.forEach((unitType) => prev.append("unitTypes", unitType));
return prev;
});
setCost(costInModal);
setArea(areaInModal);
setFloor(floorInModal);
window.scroll({ top: 0, behavior: "smooth" });
}
return (
<div
ref={ref}
className={clsx(
"2xl:p-[2.222vw] md:max-2xl:p-6 p-4 bg-white 2xl:rounded-b-[1.667vw] rounded-b-3xl 2xl:space-y-[2.222vw] md:max-2xl:space-y-8 space-y-4",
inModal && "fixed top-[7.222vw] w-[calc(100vw-4.444vw)]"
<>
{inModal && (
<div
className="fixed inset-0 2xl:top-[4.444vw] bg-[#0D1922]/40 cursor-pointer"
onClick={handleClose}
/>
)}
>
<div className="2xl:space-y-[1.111vw] space-y-4">
<p className="2xl:text-[2.222vw] md:max-2xl:text-[32px] text-2xl font-semibold leading-[135%]">
Search
</p>
{project && (
<ProjectSelect
projects={projects}
onSelect={handleProjectSelect}
defaultProject={projects.find(({ title }) => title === project)!}
/>
<div
ref={ref}
className={clsx(
"2xl:p-[2.222vw] md:max-2xl:p-6 p-4 bg-white 2xl:rounded-b-[1.667vw] rounded-b-3xl 2xl:space-y-[2.222vw] md:max-2xl:space-y-8 space-y-4",
inModal &&
"fixed 2xl:top-[calc(2.778vw+4.444vw)] 2xl:left-[2.778vw] 2xl:right-[2.778vw] 2xl:rounded-[1.667vw] rounded-3xl md:max-2xl:left-6 md:max-2xl:right-6 md:max-2xl:top-24"
)}
>
{inModal && (
<Button
onlyIcon
className="absolute right-[2.222vw] !bg-[#F3F3F2]"
onClick={handleClose}
>
<div className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5 text-[#0D1922]">
<CloseIcon />
</div>
</Button>
)}
<div className="2xl:space-y-[1.111vw] space-y-4">
<p className="2xl:text-[2.222vw] md:max-2xl:text-[32px] text-2xl font-semibold leading-[135%]">
Search
</p>
{project && (
<ProjectSelect
projects={projects}
onSelect={handleSelectProject}
defaultProject={projects.find(({ title }) => title === project)!}
/>
)}
</div>
<hr className="2xl:h-[0.069vw] h-px border-[#E2E2DC]" />
{filters && (
<>
<div className="2xl:space-y-[0.556vw] space-y-2">
<p className="text-s text-[#0D1922]/70">Apartment type</p>
<UnitTypesSelect
unitTypes={filters.unitTypes}
onSelect={handleSelectUnitTypes}
defaultSelected={unitTypes}
/>
</div>
<div className="grid 2xl:grid-cols-4 md:max-2xl:grid-cols-2 md:max-2xl:grid-rows-2 2xl:gap-[1.111vw] gap-4">
<MultiRangeSlider
min={filters?.cost[0]}
max={filters?.cost[1]}
currentMin={inModal ? costInModal[0] : cost[0]}
currentMax={inModal ? costInModal[1] : cost[1]}
offset={1}
onChange={setCostInModal}
label="Cost, AED"
/>
<MultiRangeSlider
min={filters?.floor[0]}
max={filters?.floor[1]}
currentMin={inModal ? floorInModal[0] : floor[0]}
currentMax={inModal ? floorInModal[1] : floor[1]}
offset={1}
onChange={setFloorInModal}
label="Floor"
/>
<MultiRangeSlider
min={filters?.area[0]}
max={filters?.area[1]}
currentMin={inModal ? areaInModal[0] : area[0]}
currentMax={inModal ? areaInModal[1] : area[1]}
offset={1}
onChange={setAreaInModal}
label="Total Area, Sqft"
/>
<Select
defaultOption={view}
label="View"
options={["Any view", ...filters.views]}
onSelect={handleSelectView}
/>
</div>
<div className="flex items-center 2xl:gap-[1.111vw] md:max-2xl:gap-4">
{inModal ? (
<Button onClick={applyFilters}>
Show{" "}
<AnimatePresence mode="wait">
{count !== undefined && (
<motion.span
key={count}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{count}
</motion.span>
)}
</AnimatePresence>{" "}
apartments
</Button>
) : (
<AnimatePresence mode="wait">
{count && (
<motion.p
key={count}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="text-[#00BED7] text-s"
>
{count} Apartments found
</motion.p>
)}
</AnimatePresence>
)}
<Button variant="secondary" onClick={resetFilters}>
<span className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
<RestartIcon />
</span>
<p className="text-s">Reset filters</p>
</Button>
</div>
</>
)}
</div>
<hr className="2xl:h-[0.069vw] h-px border-[#E2E2DC]" />
{filters && (
<>
<div className="2xl:space-y-[0.556vw] space-y-2">
<p>Apartment type</p>
<UnitTypesSelect
filters={filters}
onSelect={handleUnitTypesSelect}
/>
</div>
<div className="grid 2xl:grid-cols-4 md:max-2xl:grid-cols-2 md:max-2xl:grid-rows-2 2xl:gap-[1.111vw] gap-4">
<MultiRangeSlider
min={filters?.cost[0]}
max={filters?.cost[1]}
currentMin={currentMinCost!}
currentMax={currentMaxCost!}
offset={1}
onChangeMin={setCurrentMinCost}
onChangeMax={setCurrentMaxCost}
label="Cost, AED"
/>
<MultiRangeSlider
min={filters?.floor[0]}
max={filters?.floor[1]}
currentMin={currentMinFloor!}
currentMax={currentMaxFloor!}
offset={1}
onChangeMin={setCurrentMinFloor}
onChangeMax={setCurrentMaxFloor}
label="Floor"
/>
<MultiRangeSlider
min={filters?.area[0]}
max={filters?.area[1]}
currentMin={currentMinArea!}
currentMax={currentMaxArea!}
offset={1}
onChangeMin={setCurrentMinArea}
onChangeMax={setCurrentMaxArea}
label="Total Area, Sqft"
/>
<Select
label="View"
options={["Any view", ...filters.views]}
onSelect={handleViewSelect}
defaultOption={view}
/>
</div>
<div className="flex items-center 2xl:gap-[1.111vw] md:max-2xl:gap-4">
{inModal ? (
<Button>Show {count} apartments</Button>
) : (
<AnimatePresence mode="wait">
{count && (
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
key={count}
className="text-[#00BED7] text-s"
>
{count} Apartments found
</motion.p>
)}
</AnimatePresence>
)}
<Button variant="secondary" onClick={resetFilters}>
<span className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
<RestartIcon />
</span>
<p className="text-s">Reset filters</p>
</Button>
</div>
</>
)}
</div>
</>
);
}
+2 -2
View File
@@ -14,7 +14,7 @@ function UnitCard({
<div className="2xl:p-[1.111vw] p-4 2xl:rounded-[1.111vw] rounded-2xl flex flex-col justify-between 2xl:gap-[1.111vw] gap-4 bg-white 2xl:aspect-[332/396] md:max-2xl:aspect-[352/396] aspect-[328/396]">
<div className="flex items-center justify-between">
<div className="2xl:space-y-[0.278vw] space-y-1">
<p>{project}</p>
<p className="text-s text-[#00BED7]">{project}</p>
<div className="flex items-center 2xl:gap-[0.556vw] gap-2">
<p className="text-caption-m">
{(unitNo.split("-")[0] === "W" ? "West" : "East") + " Wing"}
@@ -37,7 +37,7 @@ function UnitCard({
{squareFt.toLocaleString(undefined, { maximumFractionDigits: 2 })}{" "}
Sqft
</p>
<p className="text-[#00BED7]">
<p className="text-[#00BED7] text-subheadline-s font-medium">
AED {Intl.NumberFormat("en").format(salesPrice)}
</p>
</div>
+14 -23
View File
@@ -1,30 +1,27 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from "react";
import { Filters } from "./SearchFilters";
import clsx from "clsx";
import { useSearchParams } from "react-router";
function UnitTypesSelect({
filters,
unitTypes,
onSelect,
defaultSelected = [],
}: {
filters: Filters;
unitTypes: string[];
onSelect: (unitTypes: string[]) => void;
defaultSelected: string[];
}) {
const [selectedUnitTypes, setSelectedUnitTypes] = useState<string[]>([]);
const [selectedUnitTypes, setSelectedUnitTypes] =
useState<string[]>(defaultSelected);
const [searchParams, setSearchParams] = useSearchParams();
useEffect(() => setSelectedUnitTypes(defaultSelected), [defaultSelected]);
useEffect(() => onSelect(selectedUnitTypes), [onSelect, selectedUnitTypes]);
useEffect(() => {
const unitTypesValue = searchParams.getAll("unitTypes");
if (unitTypesValue) setSelectedUnitTypes(unitTypesValue);
}, [searchParams]);
useEffect(() => onSelect(selectedUnitTypes), [selectedUnitTypes]);
return (
<div className="flex 2xl:gap-[0.556vw] gap-2">
{filters.unitTypes.map((unitType) => (
<div
{unitTypes.map((unitType) => (
<p
key={unitType}
onClick={() => {
setSelectedUnitTypes((prev) =>
@@ -32,22 +29,16 @@ function UnitTypesSelect({
? prev.filter((type) => type !== unitType)
: [...prev, unitType]
);
setSearchParams((prev) => {
if (prev.getAll("unitTypes").includes(unitType))
prev.delete("unitTypes", unitType);
else prev.append("unitTypes", unitType);
return prev;
});
}}
className={clsx(
"2xl:px-[1.389vw] 2xl:py-[0.833vw] px-5 py-3 2xl:rounded-[2.778vw] rounded-[40px] 2xl:ring-[0.069vw] ring transition-[box-shadow] cursor-pointer",
"2xl:px-[1.389vw] 2xl:py-[0.833vw] px-5 py-3 2xl:rounded-[2.778vw] rounded-[40px] 2xl:ring-[0.069vw] ring transition-[box-shadow] cursor-pointer text-s",
selectedUnitTypes.includes(unitType)
? "ring-[#00BED7]"
: "ring-[#E2E2DC]"
: "ring-[#E2E2DC] text-[#0D1922]/70"
)}
>
{unitType}
</div>
</p>
))}
</div>
);
+4 -6
View File
@@ -16,8 +16,7 @@ interface IMultiRangeSlider {
offset: number;
disabled?: boolean;
label: string;
onChangeMin: (min: number) => void;
onChangeMax: (max: number) => void;
onChange: (value: [number, number]) => void;
}
function MultiRangeSlider({
@@ -25,8 +24,7 @@ function MultiRangeSlider({
currentMin,
max,
min,
onChangeMin,
onChangeMax,
onChange,
offset,
label,
disabled = false,
@@ -55,8 +53,8 @@ function MultiRangeSlider({
const value = calculateValue(clientX, current === "min");
if (value !== undefined) {
if (current === "min") onChangeMin(value);
else onChangeMax(value);
if (current === "min") onChange([value, currentMax]);
else onChange([currentMin, value]);
}
}
+2
View File
@@ -25,6 +25,8 @@ function Select({
const ref = useClickAway<HTMLDivElement>(() => setIsShow(false));
useEffect(() => setSelectedOption(defaultOption), [defaultOption]);
useEffect(() => onSelect(selectedOption), [selectedOption]);
return (
+72 -53
View File
@@ -1,15 +1,16 @@
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
import { useInfiniteQuery } from "@tanstack/react-query";
import { api } from "../api/ky";
import { IUnit } from "../types/IUnit";
import UnitCard from "../components/UnitCard";
import { useEffect, useRef, useState } from "react";
import SearchFilters, { Filters } from "../components/SearchFilters";
import SearchFilters from "../components/SearchFilters";
import { useNavigate, useSearchParams } from "react-router";
import Button from "../components/ui/Button";
import FiltersIcon from "../components/icons/FiltersIcon";
import RestartIcon from "../components/icons/RestartIcon";
import clsx from "clsx";
import { AnimatePresence, motion } from "motion/react";
import { useDebounce } from "../hooks/useDebounce";
const STEP = 12;
@@ -17,42 +18,40 @@ function SearchPage() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
// const project = searchParams.get("project");
// const unitTypes = searchParams.getAll("unitTypes");
// const cost = searchParams.getAll("cost");
// const floor = searchParams.getAll("floor");
// const area = searchParams.getAll("area");
// const view = searchParams.get("view");
const project = searchParams.get("project");
const unitTypes = searchParams.getAll("unitTypes");
const view = searchParams.get("view");
const [project, setProject] = useState<string>();
const [unitTypes, setUnitTypes] = useState<string[]>([]);
const [cost, setCost] = useState<string[]>([]);
const [floor, setFloor] = useState<string[]>([]);
const [area, setArea] = useState<string[]>([]);
const [view, setView] = useState<string>();
const [filtersInModal, setFiltersInModal] = useState(false);
const { data: filters } = useQuery({
queryKey: ["filters", project],
enabled: !!project,
queryFn: () =>
api
.get(`units/filters?${project ? `project=${project}` : ""}`)
.json<Filters>(),
});
const [cost, setCost] = useState<[number, number]>([-1, -1]);
const [floor, setFloor] = useState<[number, number]>([-1, -1]);
const [area, setArea] = useState<[number, number]>([-1, -1]);
useEffect(() => {
setProject(searchParams.get("project"));
setUnitTypes(searchParams.getAll("unitTypes"));
setCost(searchParams.getAll("cost"));
setFloor(searchParams.getAll("floor"));
setArea(searchParams.getAll("area"));
setView(searchParams.get("view"));
}, [searchParams]);
const debouncedCost = useDebounce(cost, 500);
const debouncedFloor = useDebounce(floor, 500);
const debouncedArea = useDebounce(area, 500);
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useInfiniteQuery({
initialPageParam: 0,
queryKey: ["units", project, unitTypes, view, cost, floor, area],
queryKey: [
"units",
project,
unitTypes,
view,
debouncedCost,
debouncedFloor,
debouncedArea,
],
enabled:
!!project &&
debouncedCost[0] >= 0 &&
debouncedCost[1] >= 0 &&
debouncedFloor[0] >= 0 &&
debouncedFloor[1] >= 0 &&
debouncedArea[0] >= 0 &&
debouncedArea[1] >= 0,
queryFn: async ({ pageParam = 0 }) =>
await api
.get(
@@ -60,9 +59,19 @@ function SearchPage() {
project ? `&project=${project}` : ""
}${unitTypes.map((unitType) => `&unitTypes=${unitType}`).join("")}${
view ? `&view=${view}` : ""
}${cost.length > 0 ? `&cost=${cost.join(",")}` : ""}${
floor.length > 0 ? `&floor=${floor.join(",")}` : ""
}${area.length > 0 ? `&area=${area.join(",")}` : ""}`
}${
debouncedCost.length > 0
? `&cost=${debouncedCost.map(Math.round).join(",")}`
: ""
}${
debouncedFloor.length > 0
? `&floor=${debouncedFloor.map(Math.round).join(",")}`
: ""
}${
debouncedArea.length > 0
? `&area=${debouncedArea.map(Math.round).join(",")}`
: ""
}`
)
.json<IUnit[]>(),
getNextPageParam: (lastPage, _, lastPageIndex) =>
@@ -72,8 +81,6 @@ function SearchPage() {
const filtersRef = useRef<HTMLDivElement>(null);
const observerRef = useRef<HTMLDivElement>(null);
const [filtersInModal, setFiltersInModal] = useState(false);
useEffect(() => {
if (!hasNextPage || isFetchingNextPage) return;
const observerElement = observerRef.current;
@@ -119,27 +126,39 @@ function SearchPage() {
}, []);
return (
<div>
<>
<SearchFilters
ref={filtersRef}
inModal={filtersInModal}
filters={filters}
setInModal={setFiltersInModal}
{...{ area, cost, floor, setArea, setCost, setFloor }}
/>
<div className="2xl:p-[2.222vw] p-4">
<div className="2xl:p-[2.222vw] p-4 min-h-dvh">
<AnimatePresence mode="wait">
{project && unitTypes && cost && area && floor && (
<motion.div
key={project + unitTypes + view + cost + area + floor}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="2xl:grid-cols-4 md:max-2xl:grid-cols-2 grid 2xl:gap-[1.111vw] gap-4"
>
{data?.pages.map((page) =>
page.map((unit) => <UnitCard key={unit.id} {...unit} />)
)}
</motion.div>
)}
{project &&
unitTypes &&
debouncedCost &&
debouncedArea &&
debouncedFloor && (
<motion.div
key={
project +
unitTypes +
view +
debouncedCost +
debouncedArea +
debouncedFloor
}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="2xl:grid-cols-4 md:max-2xl:grid-cols-2 grid 2xl:gap-[1.111vw] gap-4"
>
{data?.pages.map((page) =>
page.map((unit) => <UnitCard key={unit.id} {...unit} />)
)}
</motion.div>
)}
</AnimatePresence>
</div>
{showButtons && (
@@ -170,7 +189,7 @@ function SearchPage() {
</div>
)}
<div ref={observerRef} />
</div>
</>
);
}