Удалены компоненты FlatCard и ProjectFilter; обновлены стили в Footer и SearchFilters, добавлены новые компоненты ProjectSelect и UnitTypesSelect для улучшения фильтрации проектов.
This commit is contained in:
+36
-27
@@ -7,96 +7,105 @@ import TwitterIcon from "./icons/TwitterIcon";
|
||||
|
||||
function Footer() {
|
||||
return (
|
||||
<footer className="lg:px-[2.222vw] lg:pb-[2.222vw] lg:pt-[2.778vw] md:max-lg:p-6 px-4 py-6 grid md:grid-cols-6 grid-cols-3 lg:grid-rows-2 lg:gap-x-[1.667vw] lg:gap-y-[1.111vw] max-lg:gap-y-6 lg:rounded-t-[1.667vw] rounded-t-3xl outline outline-[#E2E2DC] bg-white">
|
||||
<footer className="2xl:px-[2.222vw] 2xl:pb-[2.222vw] 2xl:pt-[2.778vw] md:max-2xl:p-6 px-4 py-6 grid 2xl:grid-cols-6 md:max-2xl:grid-cols-4 grid-cols-2 2xl:grid-rows-2 2xl:gap-x-[1.667vw] 2xl:gap-y-[1.111vw] max-2xl:gap-y-6 2xl:rounded-t-[1.667vw] rounded-t-3xl outline outline-[#E2E2DC] bg-white">
|
||||
<img
|
||||
src="/images/logo.svg"
|
||||
className="lg:w-[5.972vw] w-[86px] cursor-pointer"
|
||||
className="2xl:w-[5.972vw] w-[86px] cursor-pointer"
|
||||
onClick={() => {
|
||||
window.location.href = "/";
|
||||
}}
|
||||
alt="IRTH"
|
||||
/>
|
||||
|
||||
<p className="lg:max-w-[17.083vw] text-s text-[#0D1922]/40 lg:col-start-1 md:max-lg:col-start-4 max-lg:col-span-3 md:max-lg:row-start-2 max-md:row-start-3 max-lg:pt-4 max-lg:border-t border-[#E2E2DC]">
|
||||
<p className="2xl:max-w-[17.083vw] text-s text-[#0D1922]/40 2xl:col-start-1 md:max-2xl:col-start-3 max-2xl:col-span-3 md:max-2xl:row-start-2 max-md:row-start-3 max-md:mt-12">
|
||||
For more information, visit
|
||||
<br />
|
||||
our website:
|
||||
<Link className="text-[#00BED7] underline" to={"https://www.irth.ae"}>
|
||||
<Link
|
||||
className="text-[#00BED7] md:underline"
|
||||
to={"https://www.irth.ae"}
|
||||
>
|
||||
www.irth.ae
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<div className="lg:space-y-[0.833vw] space-y-3 md:max-lg:col-start-4 max-lg:col-span-3 md:max-lg:row-start-3 max-md:row-start-4 max-lg:mb-9">
|
||||
<div className="2xl:space-y-[0.833vw] space-y-3 md:max-2xl:col-start-3 max-2xl:col-span-3 md:max-2xl:row-start-3 max-md:row-start-4 max-md:mt-6">
|
||||
<p className="text-s text-[#0D1922]/40">Follow us for more:</p>
|
||||
<div className="flex lg:gap-[0.278vw] gap-1">
|
||||
<div className="lg:p-[0.417vw] p-1.5 bg-[#E2E2DC] lg:rounded-[0.278vw] rounded">
|
||||
<div className="lg:w-[1.389vw] lg:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<div className="flex 2xl:gap-[0.278vw] gap-1">
|
||||
<div className="2xl:p-[0.417vw] p-1.5 bg-[#E2E2DC] 2xl:rounded-[0.278vw] rounded">
|
||||
<div className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<YoutubeIcon />
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:p-[0.417vw] p-1.5 bg-[#E2E2DC] lg:rounded-[0.278vw] rounded">
|
||||
<div className="lg:w-[1.389vw] lg:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<div className="2xl:p-[0.417vw] p-1.5 bg-[#E2E2DC] 2xl:rounded-[0.278vw] rounded">
|
||||
<div className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<InstagramIcon />
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:p-[0.417vw] p-1.5 bg-[#E2E2DC] lg:rounded-[0.278vw] rounded">
|
||||
<div className="lg:w-[1.389vw] lg:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<div className="2xl:p-[0.417vw] p-1.5 bg-[#E2E2DC] 2xl:rounded-[0.278vw] rounded">
|
||||
<div className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<FacebookIcon />
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:p-[0.417vw] p-1.5 bg-[#E2E2DC] lg:rounded-[0.278vw] rounded">
|
||||
<div className="lg:w-[1.389vw] lg:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<div className="2xl:p-[0.417vw] p-1.5 bg-[#E2E2DC] 2xl:rounded-[0.278vw] rounded">
|
||||
<div className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<LinkedInIcon />
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:p-[0.417vw] p-1.5 bg-[#E2E2DC] lg:rounded-[0.278vw] rounded">
|
||||
<div className="lg:w-[1.389vw] lg:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<div className="2xl:p-[0.417vw] p-1.5 bg-[#E2E2DC] 2xl:rounded-[0.278vw] rounded">
|
||||
<div className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<TwitterIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:border-l-[0.069vw] border-[#E2E2DC] lg:pl-[1.111vw] flex flex-col lg:col-start-4 lg:row-start-1 lg:row-span-2 md:max-lg:col-start-4 col-start-1 max-lg:pt-4 max-lg:border-t">
|
||||
<Link to={"/"} className="text-m flex-1 content-center max-lg:py-2.5">
|
||||
<div className="2xl:border-l-[0.069vw] border-l border-[#E2E2DC] 2xl:pl-[1.111vw] pl-4 flex flex-col 2xl:col-start-4 2xl:row-start-1 2xl:row-span-2 md:max-2xl:col-start-3 col-start-1 max-md:mt-4">
|
||||
<Link
|
||||
to={"/"}
|
||||
className="text-m flex-1 content-center max-2xl:py-2.5 text-[#0D1922]/70 w-fit"
|
||||
>
|
||||
Map
|
||||
</Link>
|
||||
<Link
|
||||
to={"/unit-types"}
|
||||
className="text-m flex-1 content-center max-lg:py-2.5"
|
||||
className="text-m flex-1 content-center max-2xl:py-2.5 text-[#0D1922]/70 w-fit"
|
||||
>
|
||||
Unit Types
|
||||
</Link>
|
||||
<Link
|
||||
to={"/about-irth"}
|
||||
className="text-m flex-1 content-center max-lg:py-2.5"
|
||||
className="text-m flex-1 content-center max-2xl:py-2.5 text-[#0D1922]/70 w-fit"
|
||||
>
|
||||
About IRTH
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="lg:border-l-[0.069vw] border-[#E2E2DC] lg:pl-[1.111vw] flex flex-col lg:col-start-5 lg:row-start-1 lg:row-span-2 max-lg:col-span-2 max-lg:pt-4 max-lg:border-t">
|
||||
<div className="2xl:border-l-[0.069vw] border-l border-[#E2E2DC] 2xl:pl-[1.111vw] pl-4 flex flex-col 2xl:col-start-5 2xl:row-start-1 2xl:row-span-2 max-2xl:col-span-2 max-md:mt-4">
|
||||
<Link
|
||||
to={"/favorites"}
|
||||
className="text-m flex-1 content-center max-lg:py-2.5"
|
||||
to={"/favourites"}
|
||||
className="text-m flex-1 content-center max-2xl:py-2.5 text-[#0D1922]/70 w-fit"
|
||||
>
|
||||
Favorites
|
||||
</Link>
|
||||
<Link
|
||||
to={"/search"}
|
||||
className="text-m flex-1 content-center max-lg:py-2.5"
|
||||
className="text-m flex-1 content-center max-2xl:py-2.5 text-[#0D1922]/70 w-fit"
|
||||
>
|
||||
Search
|
||||
</Link>
|
||||
<Link to={"/"} className="text-m flex-1 content-center max-lg:py-2.5">
|
||||
<Link
|
||||
to={"/"}
|
||||
className="text-m flex-1 content-center max-2xl:py-2.5 text-[#0D1922]/70 w-fit"
|
||||
>
|
||||
Brochures
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="content-end lg:text-right lg:col-start-6 lg:row-start-1 lg:row-span-2 md:max-lg:col-start-1 md:max-lg:row-start-3 max-md:col-span-3 max-md:pt-3 max-md:border-t border-[#E2E2DC]">
|
||||
<div className="content-end 2xl:text-right 2xl:col-start-6 2xl:row-start-1 2xl:row-span-2 md:max-2xl:col-start-1 md:max-2xl:row-start-3 max-md:col-span-3 max-md:pt-3 max-md:border-t border-[#E2E2DC]">
|
||||
<Link
|
||||
to={"/"}
|
||||
className="lg:text-caption-m text-caption-s max-lg:text-[#73787C]"
|
||||
className="2xl:text-caption-m text-caption-s max-2xl:text-[#73787C] text-[#0D1922]/70"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import clsx from "clsx";
|
||||
import { useSearchParams } from "react-router";
|
||||
|
||||
function ProjectFilter({ title, img }: { title: string; img: string }) {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
setSearchParams((prev) => {
|
||||
prev.set("project", title);
|
||||
return prev;
|
||||
});
|
||||
}}
|
||||
className={clsx(
|
||||
"2xl:rounded-[2.778vw] rounded-[40px] 2xl:p-[0.278vw] p-1 flex items-center 2xl:gap-[0.556vw] gap-2 text-s 2xl:outline-[0.069vw] outline transition-colors duration-300 cursor-pointer",
|
||||
searchParams.get("project") === title
|
||||
? "outline-[#00BED7]"
|
||||
: "outline-[#E2E2DC]"
|
||||
)}
|
||||
>
|
||||
<img
|
||||
src={img}
|
||||
alt=""
|
||||
className="object-cover 2xl:w-[2.778vw] w-10 aspect-square rounded-full"
|
||||
/>
|
||||
<p className="2xl:mr-[1.111vw] mr-6">{title}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectFilter;
|
||||
@@ -22,11 +22,12 @@ function ProjectSelect({
|
||||
<div className="flex 2xl:gap-[0.556vw] gap-2 max-md:hidden">
|
||||
{projects.map((project) => (
|
||||
<div
|
||||
key={project.title}
|
||||
className={clsx(
|
||||
"2xl:rounded-[2.778vw] rounded-[40px] 2xl:p-[0.278vw] p-1 flex items-center 2xl:gap-[0.556vw] gap-2 text-s 2xl:outline-[0.069vw] outline transition-colors duration-300 cursor-pointer",
|
||||
"2xl:rounded-[2.778vw] rounded-[40px] 2xl:p-[0.278vw] p-1 flex items-center 2xl:gap-[0.556vw] gap-2 text-s 2xl:ring-[0.069vw] ring transition-[box-shadow] cursor-pointer",
|
||||
project.title === selectedProject.title
|
||||
? "outline-[#00BED7]"
|
||||
: "outline-[#E2E2DC]"
|
||||
? "ring-[#00BED7]"
|
||||
: "ring-[#E2E2DC]"
|
||||
)}
|
||||
onClick={() => setSelectedProject(project)}
|
||||
>
|
||||
|
||||
@@ -4,14 +4,17 @@ 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";
|
||||
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";
|
||||
|
||||
interface Filters {
|
||||
export interface Filters {
|
||||
unitTypes: string[];
|
||||
views: string[];
|
||||
area: [number, number];
|
||||
@@ -26,8 +29,9 @@ function SearchFilters({
|
||||
inModal?: boolean;
|
||||
ref?: RefObject<HTMLDivElement | null>;
|
||||
}) {
|
||||
const [project, setProject] = useState<string>();
|
||||
const [project, setProject] = useState<string>(projects[0].title);
|
||||
const [unitTypes, setUnitTypes] = useState<string[]>([]);
|
||||
const [view, setView] = useState<string>("Any view");
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
@@ -43,9 +47,11 @@ function SearchFilters({
|
||||
|
||||
useEffect(() => {
|
||||
const projectValue = searchParams.get("project");
|
||||
const unitTypesValue = searchParams.getAll("unitTypes");
|
||||
// const unitTypesValue = searchParams.getAll("unitTypes");
|
||||
if (projectValue) setProject(projectValue);
|
||||
if (unitTypesValue) setUnitTypes(unitTypesValue);
|
||||
// if (unitTypesValue) setUnitTypes(unitTypesValue);
|
||||
const viewValue = searchParams.get("view");
|
||||
if (viewValue) setView(viewValue);
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -69,7 +75,6 @@ function SearchFilters({
|
||||
const [currentMaxFloor, setCurrentMaxFloor] = useState<number>();
|
||||
|
||||
function resetFilters() {
|
||||
setProject(undefined);
|
||||
setUnitTypes([]);
|
||||
if (filters) {
|
||||
setCurrentMinCost(filters.cost[0]);
|
||||
@@ -97,6 +102,7 @@ function SearchFilters({
|
||||
"count",
|
||||
project,
|
||||
unitTypes,
|
||||
view,
|
||||
debouncedMinCost,
|
||||
debouncedMaxCost,
|
||||
debouncedMinArea,
|
||||
@@ -109,7 +115,7 @@ function SearchFilters({
|
||||
.get(
|
||||
`units/count?${project ? `project=${project}` : ""}${unitTypes
|
||||
.map((unitType) => `&unitTypes=${unitType}`)
|
||||
.join("")}${
|
||||
.join("")}${view !== "Any view" ? `&view=${view}` : ""}${
|
||||
debouncedMinCost && debouncedMaxCost
|
||||
? `&cost=${Math.round(debouncedMinCost)},${Math.round(
|
||||
debouncedMaxCost
|
||||
@@ -169,11 +175,32 @@ function SearchFilters({
|
||||
setSearchParams,
|
||||
]);
|
||||
|
||||
function handleProjectSelect(project: Project) {
|
||||
setProject(project.title);
|
||||
setSearchParams((prev) => {
|
||||
prev.set("project", project.title);
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
|
||||
function handleUnitTypesSelect(unitTypes: string[]) {
|
||||
setUnitTypes(unitTypes);
|
||||
}
|
||||
|
||||
function handleViewSelect(view: string) {
|
||||
setView(view);
|
||||
setSearchParams((prev) => {
|
||||
if (view !== "Any view") prev.set("view", view);
|
||||
else prev.delete("view");
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
"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",
|
||||
"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)]"
|
||||
)}
|
||||
>
|
||||
@@ -181,26 +208,17 @@ function SearchFilters({
|
||||
<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>
|
||||
<ProjectSelect projects={projects} onSelect={handleProjectSelect} />
|
||||
</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>
|
||||
<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
|
||||
@@ -233,12 +251,29 @@ function SearchFilters({
|
||||
onChangeMax={setCurrentMaxArea}
|
||||
label="Total Area, Sqft"
|
||||
/>
|
||||
<Select
|
||||
label="View"
|
||||
options={["Any view", ...filters.views]}
|
||||
onSelect={handleViewSelect}
|
||||
/>
|
||||
</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>
|
||||
<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">
|
||||
|
||||
@@ -2,7 +2,7 @@ import { IUnit } from "../types/IUnit";
|
||||
import HeartIcon from "./icons/HeartIcon";
|
||||
import Button from "./ui/Button";
|
||||
|
||||
function FlatCard({
|
||||
function UnitCard({
|
||||
project,
|
||||
unitNo,
|
||||
floor,
|
||||
@@ -45,4 +45,4 @@ function FlatCard({
|
||||
);
|
||||
}
|
||||
|
||||
export default FlatCard;
|
||||
export default UnitCard;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Filters } from "./SearchFilters";
|
||||
import clsx from "clsx";
|
||||
import { useSearchParams } from "react-router";
|
||||
|
||||
function UnitTypesSelect({
|
||||
filters,
|
||||
onSelect,
|
||||
}: {
|
||||
filters: Filters;
|
||||
onSelect: (unitTypes: string[]) => void;
|
||||
}) {
|
||||
const [selectedUnitTypes, setSelectedUnitTypes] = useState<string[]>([]);
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
useEffect(() => onSelect(selectedUnitTypes), [onSelect, selectedUnitTypes]);
|
||||
|
||||
useEffect(() => {
|
||||
const unitTypesValue = searchParams.getAll("unitTypes");
|
||||
|
||||
if (unitTypesValue) setSelectedUnitTypes(unitTypesValue);
|
||||
}, [searchParams]);
|
||||
|
||||
return (
|
||||
<div className="flex 2xl:gap-[0.556vw] gap-2">
|
||||
{filters.unitTypes.map((unitType) => (
|
||||
<div
|
||||
key={unitType}
|
||||
onClick={() => {
|
||||
setSelectedUnitTypes((prev) =>
|
||||
prev.includes(unitType)
|
||||
? 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",
|
||||
selectedUnitTypes.includes(unitType)
|
||||
? "ring-[#00BED7]"
|
||||
: "ring-[#E2E2DC]"
|
||||
)}
|
||||
>
|
||||
{unitType}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UnitTypesSelect;
|
||||
@@ -83,17 +83,17 @@ function MultiRangeSlider({
|
||||
|
||||
return (
|
||||
<div className="2xl:space-y-[0.556vw] space-y-2">
|
||||
<p className="text-s">{label}</p>
|
||||
<div className="bg-white/80 rounded-lg relative p-3 flex justify-between border !border-[#E2E2DC]">
|
||||
<p className="text-s text-[#0D1922]/70">{label}</p>
|
||||
<div className="bg-white/80 2xl:rounded-[0.833vw] rounded-xl relative 2xl:px-[1.111vw] 2xl:py-[0.972vw] px-4 py-3.5 flex justify-between 2xl:ring-[0.069vw] ring-1 ring-[#E2E2DC]">
|
||||
<p className={clsx("text-s", disabled && "text-[#0D1922]/40")}>
|
||||
{Intl.NumberFormat("en").format(Math.round(currentMin))}
|
||||
</p>
|
||||
<p className={clsx("text-s", disabled && "text-[#0D1922]/40")}>
|
||||
{Intl.NumberFormat("en").format(Math.round(currentMax))}
|
||||
</p>
|
||||
<div className="absolute bottom-0 left-0 w-full px-4 translate-y-1/2">
|
||||
<div className="absolute bottom-0 left-0 w-full 2xl:px-[1.111vw] px-4 translate-y-1/2">
|
||||
<div
|
||||
className="relative flex h-4"
|
||||
className="relative flex 2xl:h-[1.111vw] h-4"
|
||||
ref={rangeRef}
|
||||
onMouseMove={
|
||||
handleChange as React.MouseEventHandler<HTMLDivElement>
|
||||
@@ -108,7 +108,7 @@ function MultiRangeSlider({
|
||||
left: `${((currentMin - min) / (max - min)) * 100}%`,
|
||||
}}
|
||||
className={clsx(
|
||||
"h-0.5 self-center relative",
|
||||
"2xl:h-[0.139vw] h-0.5 self-center relative",
|
||||
disabled ? "bg-[#D2D2D2]" : "bg-[#00BED7]"
|
||||
)}
|
||||
/>
|
||||
@@ -119,7 +119,7 @@ function MultiRangeSlider({
|
||||
onTouchStart={() => setCurrent(type as "min" | "max")}
|
||||
style={getThumbStyle(type === "min" ? currentMin : currentMax)}
|
||||
className={clsx(
|
||||
"rounded-full w-4 h-4 absolute bottom-0 -translate-x-1/2",
|
||||
"rounded-full 2xl:w-[1.111vw] 2xl:h-[1.111vw] w-4 h-4 absolute bottom-0 -translate-x-1/2",
|
||||
current === type ? "cursor-grabbing" : "cursor-grab",
|
||||
disabled ? "bg-[#D2D2D2] !cursor-default" : "bg-[#00BED7]"
|
||||
)}
|
||||
|
||||
@@ -10,10 +10,12 @@ function Select({
|
||||
options,
|
||||
onSelect,
|
||||
className = "",
|
||||
label = "",
|
||||
}: {
|
||||
options: string[];
|
||||
onSelect: (option: string) => void;
|
||||
className?: string;
|
||||
label?: string;
|
||||
}) {
|
||||
const [isShow, setIsShow] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState(options[0]);
|
||||
@@ -25,9 +27,14 @@ function Select({
|
||||
|
||||
return (
|
||||
<div ref={ref} className={clsx("relative", className)}>
|
||||
{label && (
|
||||
<p className="text-s text-[#0D1922]/70 2xl:mb-[0.556vw] mb-2">
|
||||
{label}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
className={clsx(
|
||||
"px-4 py-[14px] rounded-xl ring-1 transition-[box-shadow] w-full text-left flex items-center justify-between group",
|
||||
"2xl:px-[1.111vw] px-4 2xl:py-[0.833vw] py-3 2xl:rounded-[0.833vw] rounded-xl 2xl:ring-[0.069vw] ring-1 transition-[box-shadow] w-full text-left flex items-center justify-between group",
|
||||
isShow ? " ring-[#00BED7]" : "ring-[#E2E2DC]"
|
||||
)}
|
||||
onClick={() => setIsShow(!isShow)}
|
||||
@@ -35,7 +42,7 @@ function Select({
|
||||
<p className="text-s">{selectedOption}</p>
|
||||
<ChevronDownIcon
|
||||
className={clsx(
|
||||
"w-6 h-6 flex-shrink-0 group-hover:text-[#00BED7] transition-[color,rotate]",
|
||||
"2xl:w-[1.667vw] 2xl:h-[1.667vw] w-6 h-6 flex-shrink-0 group-hover:text-[#00BED7] transition-[color,rotate]",
|
||||
isShow && "rotate-180"
|
||||
)}
|
||||
/>
|
||||
@@ -47,7 +54,7 @@ function Select({
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
className="absolute mt-1 p-1 space-y-0.5 shadow-[0px_2px_8px_rgba(0,0,0,0.15)] rounded-xl bg-white w-full z-10"
|
||||
className="absolute 2xl:mt-[0.278vw] 2xl:pt-[0.278vw] mt-1 p-1 2xl:space-y-[0.139vw] space-y-0.5 shadow-[0px_2px_8px_rgba(0,0,0,0.15)] rounded-xl bg-white w-full z-10"
|
||||
>
|
||||
{options.map((option, index) => (
|
||||
<button
|
||||
@@ -67,7 +74,7 @@ function Select({
|
||||
{option}
|
||||
</p>
|
||||
{selectedOption === option && (
|
||||
<CheckIcon className="w-6 h-6 flex-shrink-0 text-[#00BED7]" />
|
||||
<CheckIcon className="2xl:w-[1.667vw] 2xl:h-[1.667vw] w-6 h-6 flex-shrink-0 text-[#00BED7]" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
|
||||
+25
-11
@@ -1,7 +1,7 @@
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { api } from "../api/ky";
|
||||
import { IUnit } from "../types/IUnit";
|
||||
import FlatCard from "../components/FlatCard";
|
||||
import UnitCard from "../components/UnitCard";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import SearchFilters from "../components/SearchFilters";
|
||||
import { useNavigate, useSearchParams } from "react-router";
|
||||
@@ -9,6 +9,7 @@ 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";
|
||||
|
||||
const STEP = 12;
|
||||
|
||||
@@ -21,21 +22,22 @@ function SearchPage() {
|
||||
const cost = searchParams.getAll("cost");
|
||||
const floor = searchParams.getAll("floor");
|
||||
const area = searchParams.getAll("area");
|
||||
const view = searchParams.get("view");
|
||||
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
||||
useInfiniteQuery({
|
||||
initialPageParam: 0,
|
||||
queryKey: ["units", project, unitTypes, cost, floor, area],
|
||||
queryKey: ["units", project, unitTypes, view, cost, floor, area],
|
||||
queryFn: async ({ pageParam = 0 }) =>
|
||||
await api
|
||||
.get(
|
||||
`units?offset=${pageParam}&limit=${STEP}${
|
||||
project ? `&project=${project}` : ""
|
||||
}${unitTypes.map((unitType) => `&unitTypes=${unitType}`).join("")}${
|
||||
cost.length > 0 ? `&cost=${cost.join(",")}` : ""
|
||||
}${floor.length > 0 ? `&floor=${floor.join(",")}` : ""}${
|
||||
area.length > 0 ? `&area=${area.join(",")}` : ""
|
||||
}`
|
||||
view ? `&view=${view}` : ""
|
||||
}${cost.length > 0 ? `&cost=${cost.join(",")}` : ""}${
|
||||
floor.length > 0 ? `&floor=${floor.join(",")}` : ""
|
||||
}${area.length > 0 ? `&area=${area.join(",")}` : ""}`
|
||||
)
|
||||
.json<IUnit[]>(),
|
||||
getNextPageParam: (lastPage, _, lastPageIndex) =>
|
||||
@@ -92,12 +94,24 @@ function SearchPage() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div>
|
||||
<SearchFilters ref={filtersRef} inModal={filtersInModal} />
|
||||
<div className="2xl:grid-cols-4 md:max-2xl:grid-cols-2 grid 2xl:gap-[1.111vw] gap-4 2xl:pt-[1.111vw] pt-4">
|
||||
{data?.pages.flat().map((unit) => (
|
||||
<FlatCard key={unit.id} {...unit} />
|
||||
))}
|
||||
<div className="2xl:p-[2.222vw] p-4">
|
||||
<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>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
{showButtons && (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user