Files
irth-new-client/src/components/SearchFilters.tsx
T

338 lines
11 KiB
TypeScript

/* 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 { useSearchParams } from "react-router";
import { projects } from "../data/projects";
import { useDebounce } from "../hooks/useDebounce";
import clsx from "clsx";
import ProjectSelect from "./ProjectSelect";
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[];
views: string[];
area: [number, number];
cost: [number, number];
floor: [number, number];
}
function SearchFilters({
inModal = false,
setInModal,
ref,
cost,
floor,
area,
setCost,
setFloor,
setArea,
}: {
inModal?: boolean;
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[]>([]);
const [view, setView] = useState<string>("Any view");
const [searchParams, setSearchParams] = useSearchParams();
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") || 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) {
setCostInModal(filters.cost);
setAreaInModal(filters.area);
setFloorInModal(filters.floor);
}
}, [filters, setAreaInModal, setCostInModal, setFloorInModal]);
useEffect(() => {
if (inModal) return;
setCost(costInModal);
setArea(areaInModal);
setFloor(floorInModal);
}, [setCost, costInModal, setArea, areaInModal, setFloor, floorInModal]);
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: [
"units",
"count",
project,
unitTypes,
view,
debouncedCost,
debouncedArea,
debouncedFloor,
],
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}` : ""}${
debouncedCost
? `&cost=${Math.round(debouncedCost[0])},${Math.round(
debouncedCost[1]
)}`
: ""
}${
debouncedArea
? `&area=${Math.round(debouncedArea[0])},${Math.round(
debouncedArea[1]
)}`
: ""
}${
debouncedFloor
? `&floor=${Math.round(debouncedFloor[0])},${Math.round(
debouncedFloor[1]
)}`
: ""
}`
)
.json<number>(),
});
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);
if (!inModal)
setSearchParams((prev) => {
prev.set("project", project.title);
return prev;
});
}
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 (
<>
{inModal && (
<div
className="fixed inset-0 2xl:top-[4.444vw] bg-[#0D1922]/40 cursor-pointer"
onClick={handleClose}
/>
)}
<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>
</>
);
}
export default SearchFilters;