search pages with filters in process

This commit is contained in:
2025-04-23 17:26:28 +05:00
parent dfab85a424
commit f0a06a9053
29 changed files with 602 additions and 39 deletions
+252
View File
@@ -0,0 +1,252 @@
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 ProjectFilter from "./ProjectFilter";
import { useNavigate, useSearchParams } from "react-router";
import UnitTypesFilter from "./UnitTypesFilter";
import { projects } from "../data/projects";
import { useDebounce } from "../hooks/useDebounce";
interface Filters {
unitTypes: string[];
views: string[];
area: [number, number];
cost: [number, number];
floor: [number, number];
}
function SearchFilters({
inModal = false,
ref,
}: {
inModal?: boolean;
ref: RefObject<HTMLDivElement | null>;
}) {
const [project, setProject] = useState<string>();
const [unitTypes, setUnitTypes] = useState<string[]>([]);
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const { data: filters } = useQuery({
queryKey: ["filters", project],
queryFn: () =>
api
.get(`units/filters?${project ? `project=${project}` : ""}`)
.json<Filters>(),
});
useEffect(() => {
const projectValue = searchParams.get("project");
const unitTypesValue = searchParams.getAll("unitTypes");
if (projectValue) setProject(projectValue);
if (unitTypesValue) setUnitTypes(unitTypesValue);
}, [searchParams]);
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]);
}
}, [filters]);
const [currentMinCost, setCurrentMinCost] = useState<number>();
const [currentMaxCost, setCurrentMaxCost] = useState<number>();
const [currentMinArea, setCurrentMinArea] = useState<number>();
const [currentMaxArea, setCurrentMaxArea] = useState<number>();
const [currentMinFloor, setCurrentMinFloor] = useState<number>();
const [currentMaxFloor, setCurrentMaxFloor] = useState<number>();
function resetFilters() {
setProject(undefined);
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 { data: count } = useQuery({
queryKey: [
"units",
"count",
project,
unitTypes,
debouncedMinCost,
debouncedMaxCost,
debouncedMinArea,
debouncedMaxArea,
debouncedMinFloor,
debouncedMaxFloor,
],
queryFn: () =>
api
.get(
`units/count?${project ? `project=${project}` : ""}${unitTypes
.map((unitType) => `&unitTypes=${unitType}`)
.join("")}${
debouncedMinCost && debouncedMaxCost
? `&cost=${Math.round(debouncedMinCost)},${Math.round(
debouncedMaxCost
)}`
: ""
}${
debouncedMinArea && debouncedMaxArea
? `&area=${Math.round(debouncedMinArea)},${Math.round(
debouncedMaxArea
)}`
: ""
}${
debouncedMinFloor && debouncedMaxFloor
? `&floor=${Math.round(debouncedMinFloor)},${Math.round(
debouncedMaxFloor
)}`
: ""
}`
)
.json<number>(),
});
useEffect(() => {
if (debouncedMinCost && debouncedMaxCost)
setSearchParams((prev) => {
prev.set(
"cost",
`${Math.round(debouncedMinCost)},${Math.round(debouncedMaxCost)}`
);
return prev;
});
if (debouncedMinArea && debouncedMaxArea)
setSearchParams((prev) => {
prev.set(
"area",
`${Math.round(debouncedMinArea)},${Math.round(debouncedMaxArea)}`
);
return prev;
});
if (debouncedMinFloor && debouncedMaxFloor)
setSearchParams((prev) => {
prev.set(
"floor",
`${Math.round(debouncedMinFloor)},${Math.round(debouncedMaxFloor)}`
);
return prev;
});
}, [
debouncedMinCost,
debouncedMaxCost,
debouncedMinArea,
debouncedMaxArea,
debouncedMinFloor,
debouncedMaxFloor,
setSearchParams,
]);
return (
<div
ref={ref}
className="2xl:p-[2.222vw] md:max-2xl:p-6 p-4 2xl:-mx-[2.222vw] md:max-2xl:-mx-6 -mx-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"
>
<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>
<div className="flex 2xl:gap-[0.556vw] gap-2">
{projects.map((project) => (
<ProjectFilter
key={project.title}
title={project.title}
img={project.img}
/>
))}
</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>
<div className="flex 2xl:gap-[0.556vw] gap-2">
{filters.unitTypes.map((unitType) => (
<UnitTypesFilter key={unitType} title={unitType} />
))}
</div>
</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!}
step={1}
onChangeMin={setCurrentMinCost}
onChangeMax={setCurrentMaxCost}
label="Cost, AED"
/>
<MultiRangeSlider
min={filters?.floor[0]}
max={filters?.floor[1]}
currentMin={currentMinFloor!}
currentMax={currentMaxFloor!}
step={1}
onChangeMin={setCurrentMinFloor}
onChangeMax={setCurrentMaxFloor}
label="Floor"
/>
<MultiRangeSlider
min={filters?.area[0]}
max={filters?.area[1]}
currentMin={currentMinArea!}
currentMax={currentMaxArea!}
step={1}
onChangeMin={setCurrentMinArea}
onChangeMax={setCurrentMaxArea}
label="Total Area, Sqft"
/>
</div>
<div className="flex items-center 2xl:gap-[1.111vw] md:max-2xl:gap-4">
{inModal ? (
<Button>Show {count} apartments</Button>
) : (
<p className="text-[#00BED7] text-s">{count} Apartments found</p>
)}
<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>
);
}
export default SearchFilters;