search page completed, unit cards skeletons
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-router": "^7.5.0",
|
||||
"react-swipeable": "^7.0.2",
|
||||
"tailwindcss": "^4.1.3",
|
||||
@@ -475,6 +476,8 @@
|
||||
|
||||
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
|
||||
|
||||
"react-loading-skeleton": ["react-loading-skeleton@3.5.0", "", { "peerDependencies": { "react": ">=16.8.0" } }, "sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ=="],
|
||||
|
||||
"react-router": ["react-router@7.5.0", "", { "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0", "turbo-stream": "2.4.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g=="],
|
||||
|
||||
"react-swipeable": ["react-swipeable@7.0.2", "", { "peerDependencies": { "react": "^16.8.3 || ^17 || ^18 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-v1Qx1l+aC2fdxKa9aKJiaU/ZxmJ5o98RMoFwUqAAzVWUcxgfHFXDDruCKXhw6zIYXm6V64JiHgP9f6mlME5l8w=="],
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-router": "^7.5.0",
|
||||
"react-swipeable": "^7.0.2",
|
||||
"tailwindcss": "^4.1.3",
|
||||
|
||||
@@ -687,11 +687,13 @@ function Map({ maxZoom = 1 }: MapProps) {
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
<FullScreenButton
|
||||
isFullScreen={isFullScreen}
|
||||
onFullScreenChange={setIsFullScreen}
|
||||
onClick={handleFullScreenClick}
|
||||
/>
|
||||
<div className="absolute 2xl:right-[2.222vw] 2xl:top-[2.222vw] right-8 bottom-8">
|
||||
<FullScreenButton
|
||||
isFullScreen={isFullScreen}
|
||||
onFullScreenChange={setIsFullScreen}
|
||||
onClick={handleFullScreenClick}
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute 2xl:right-[2.222vw] 2xl:bottom-[2.222vw] right-4 bottom-4 flex 2xl:gap-[0.556vw] gap-2">
|
||||
<DisclaimerButton />
|
||||
<PrivacyPolicyButton />
|
||||
|
||||
+429
-334
@@ -50,13 +50,13 @@ function SearchFilters({
|
||||
const debouncedArea = useDebounce(inModal ? areaInModal : area, 1000);
|
||||
const debouncedFloor = useDebounce(inModal ? floorInModal : floor, 1000);
|
||||
|
||||
const [costChanged, setCostChanged] = useState(true);
|
||||
const [areaChanged, setAreaChanged] = useState(true);
|
||||
const [floorChanged, setFloorChanged] = useState(true);
|
||||
const [costTouched, setCostTouched] = useState(false);
|
||||
const [areaTouched, setAreaTouched] = useState(false);
|
||||
const [floorTouched, setFloorTouched] = useState(false);
|
||||
|
||||
const debouncedCostChanged = useDebounce(costChanged, 1000);
|
||||
const debouncedAreaChanged = useDebounce(areaChanged, 1000);
|
||||
const debouncedFloorChanged = useDebounce(floorChanged, 1000);
|
||||
const debouncedCostChanged = useDebounce(costTouched, 1000);
|
||||
const debouncedAreaChanged = useDebounce(areaTouched, 1000);
|
||||
const debouncedFloorChanged = useDebounce(floorTouched, 1000);
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
@@ -65,62 +65,28 @@ function SearchFilters({
|
||||
"filters",
|
||||
"unitTypes",
|
||||
project,
|
||||
debouncedCostChanged ? debouncedCost : undefined,
|
||||
debouncedAreaChanged ? debouncedArea : undefined,
|
||||
debouncedFloorChanged ? debouncedFloor : undefined,
|
||||
searchParams.get("cost"),
|
||||
searchParams.get("floor"),
|
||||
searchParams.get("area"),
|
||||
view,
|
||||
],
|
||||
enabled: !!project,
|
||||
enabled: !!project && !searchParams.has("unitTypes"),
|
||||
initialData: searchParams.has("unitTypes")
|
||||
? searchParams.getAll("unitTypes")
|
||||
: undefined,
|
||||
queryFn: () =>
|
||||
api
|
||||
.get(
|
||||
`units/filters/unitTypes?${project ? `project=${project}` : ""}${
|
||||
view !== "Any view" ? `&view=${view}` : ""
|
||||
}${
|
||||
debouncedCost[0] >= 0 && debouncedCost[1] >= 0
|
||||
? `&cost=${debouncedCost.map(Math.round).join()}`
|
||||
searchParams.has("cost") ? `&cost=${searchParams.get("cost")}` : ""
|
||||
}${
|
||||
searchParams.has("floor")
|
||||
? `&floor=${searchParams.get("floor")}`
|
||||
: ""
|
||||
}${
|
||||
debouncedFloor[0] >= 0 && debouncedFloor[1] >= 0
|
||||
? `&floor=${debouncedFloor.map(Math.round).join()}`
|
||||
: ""
|
||||
}${
|
||||
debouncedArea[0] >= 0 && debouncedArea[1] >= 0
|
||||
? `&area=${debouncedArea.map(Math.round).join()}`
|
||||
: ""
|
||||
}`
|
||||
)
|
||||
.json<string[]>(),
|
||||
});
|
||||
|
||||
const { data: viewsInFilters } = useQuery({
|
||||
queryKey: [
|
||||
"filters",
|
||||
"views",
|
||||
project,
|
||||
debouncedCostChanged ? debouncedCost : undefined,
|
||||
debouncedAreaChanged ? debouncedArea : undefined,
|
||||
debouncedFloorChanged ? debouncedFloor : undefined,
|
||||
unitTypes,
|
||||
],
|
||||
enabled: !!project,
|
||||
queryFn: () =>
|
||||
api
|
||||
.get(
|
||||
`units/filters/views?${project ? `project=${project}` : ""}${unitTypes
|
||||
.map((unitType) => `&unitTypes=${unitType}`)
|
||||
.join("")}${
|
||||
debouncedCost[0] >= 0 && debouncedCost[1] >= 0
|
||||
? `&cost=${debouncedCost.map(Math.round).join()}`
|
||||
: ""
|
||||
}${
|
||||
debouncedFloor[0] >= 0 && debouncedFloor[1] >= 0
|
||||
? `&floor=${debouncedFloor.map(Math.round).join()}`
|
||||
: ""
|
||||
}${
|
||||
debouncedArea[0] >= 0 && debouncedArea[1] >= 0
|
||||
? `&area=${debouncedArea.map(Math.round).join()}`
|
||||
: ""
|
||||
searchParams.has("area") ? `&area=${searchParams.get("area")}` : ""
|
||||
}`
|
||||
)
|
||||
.json<string[]>(),
|
||||
@@ -131,25 +97,29 @@ function SearchFilters({
|
||||
"filters",
|
||||
"cost",
|
||||
project,
|
||||
debouncedAreaChanged ? debouncedArea : undefined,
|
||||
debouncedFloorChanged ? debouncedFloor : undefined,
|
||||
unitTypes,
|
||||
searchParams.get("floor"),
|
||||
searchParams.get("area"),
|
||||
view,
|
||||
],
|
||||
enabled: !!project,
|
||||
enabled: !!project && !searchParams.has("cost") && !debouncedCostChanged,
|
||||
initialData: searchParams.has("cost")
|
||||
? {
|
||||
min: searchParams.get("cost")!.split(",").map(Number)[0],
|
||||
max: searchParams.get("cost")!.split(",").map(Number)[1],
|
||||
}
|
||||
: undefined,
|
||||
queryFn: () =>
|
||||
api
|
||||
.get(
|
||||
`units/filters/cost?${project ? `project=${project}` : ""}${unitTypes
|
||||
.map((unitType) => `&unitTypes=${unitType}`)
|
||||
.join("")}${view !== "Any view" ? `&view=${view}` : ""}${
|
||||
debouncedFloor[0] >= 0 && debouncedFloor[1] >= 0
|
||||
? `&floor=${debouncedFloor.map(Math.round).join()}`
|
||||
searchParams.has("floor")
|
||||
? `&floor=${searchParams.get("floor")}`
|
||||
: ""
|
||||
}${
|
||||
debouncedArea[0] >= 0 && debouncedArea[1] >= 0
|
||||
? `&area=${debouncedArea.map(Math.round).join()}`
|
||||
: ""
|
||||
searchParams.has("area") ? `&area=${searchParams.get("area")}` : ""
|
||||
}`
|
||||
)
|
||||
.json<{ min: number; max: number }>(),
|
||||
@@ -160,25 +130,32 @@ function SearchFilters({
|
||||
"filters",
|
||||
"floor",
|
||||
project,
|
||||
debouncedCostChanged ? debouncedCost : undefined,
|
||||
debouncedAreaChanged ? debouncedArea : undefined,
|
||||
unitTypes,
|
||||
searchParams.get("cost"),
|
||||
searchParams.get("area"),
|
||||
view,
|
||||
],
|
||||
enabled: !!project,
|
||||
enabled: !!project && !searchParams.has("floor") && !debouncedFloorChanged,
|
||||
initialData: searchParams.has("floor")
|
||||
? {
|
||||
min: searchParams.get("floor")!.split(",").map(Number)[0],
|
||||
max: searchParams.get("floor")!.split(",").map(Number)[1],
|
||||
}
|
||||
: floorInModal.every((bound) => bound >= 0)
|
||||
? {
|
||||
min: floorInModal[0],
|
||||
max: floorInModal[1],
|
||||
}
|
||||
: undefined,
|
||||
queryFn: () =>
|
||||
api
|
||||
.get(
|
||||
`units/filters/floor?${project ? `project=${project}` : ""}${unitTypes
|
||||
.map((unitType) => `&unitTypes=${unitType}`)
|
||||
.join("")}${view !== "Any view" ? `&view=${view}` : ""}${
|
||||
debouncedCost[0] >= 0 && debouncedCost[1] >= 0
|
||||
? `&cost=${debouncedCost.map(Math.round).join()}`
|
||||
: ""
|
||||
searchParams.has("cost") ? `&cost=${searchParams.get("cost")}` : ""
|
||||
}${
|
||||
debouncedArea[0] >= 0 && debouncedArea[1] >= 0
|
||||
? `&area=${debouncedArea.map(Math.round).join()}`
|
||||
: ""
|
||||
searchParams.has("area") ? `&area=${searchParams.get("area")}` : ""
|
||||
}`
|
||||
)
|
||||
.json<{ min: number; max: number }>(),
|
||||
@@ -189,95 +166,67 @@ function SearchFilters({
|
||||
"filters",
|
||||
"area",
|
||||
project,
|
||||
debouncedCostChanged ? debouncedCost : undefined,
|
||||
debouncedFloorChanged ? debouncedFloor : undefined,
|
||||
unitTypes,
|
||||
searchParams.get("cost"),
|
||||
searchParams.get("floor"),
|
||||
view,
|
||||
],
|
||||
enabled: !!project,
|
||||
enabled: !!project && !searchParams.has("area") && !debouncedAreaChanged,
|
||||
initialData: searchParams.has("area")
|
||||
? {
|
||||
min: searchParams.get("area")!.split(",").map(Number)[0],
|
||||
max: searchParams.get("area")!.split(",").map(Number)[1],
|
||||
}
|
||||
: areaInModal.every((bound) => bound >= 0)
|
||||
? {
|
||||
min: areaInModal[0],
|
||||
max: areaInModal[1],
|
||||
}
|
||||
: undefined,
|
||||
queryFn: () =>
|
||||
api
|
||||
.get(
|
||||
`units/filters/area?${project ? `project=${project}` : ""}${unitTypes
|
||||
.map((unitType) => `&unitTypes=${unitType}`)
|
||||
.join("")}${view !== "Any view" ? `&view=${view}` : ""}${
|
||||
debouncedCost[0] >= 0 && debouncedCost[1] >= 0
|
||||
? `&cost=${debouncedCost.map(Math.round).join()}`
|
||||
: ""
|
||||
searchParams.has("cost") ? `&cost=${searchParams.get("cost")}` : ""
|
||||
}${
|
||||
debouncedFloor[0] >= 0 && debouncedFloor[1] >= 0
|
||||
? `&floor=${debouncedFloor.map(Math.round).join()}`
|
||||
searchParams.has("floor")
|
||||
? `&floor=${searchParams.get("floor")}`
|
||||
: ""
|
||||
}`
|
||||
)
|
||||
.json<{ min: number; max: number }>(),
|
||||
});
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (areaInFilters) {
|
||||
setAreaInModal([areaInFilters.min, areaInFilters.max]);
|
||||
setAreaChanged(false);
|
||||
}
|
||||
}, [areaInFilters]);
|
||||
|
||||
useEffect(() => {
|
||||
if (costInFilters) {
|
||||
setCostInModal([costInFilters.min, costInFilters.max]);
|
||||
return () => setCostChanged(false);
|
||||
}
|
||||
}, [costInFilters]);
|
||||
|
||||
useEffect(() => {
|
||||
if (floorInFilters) {
|
||||
setFloorInModal([floorInFilters.min, floorInFilters.max]);
|
||||
setFloorChanged(false);
|
||||
}
|
||||
}, [floorInFilters]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inModal) return;
|
||||
setCost(costInModal);
|
||||
}, [costInModal, inModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inModal) return;
|
||||
setArea(areaInModal);
|
||||
}, [areaInModal, inModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inModal) return;
|
||||
setFloor(floorInModal);
|
||||
}, [floorInModal, inModal]);
|
||||
|
||||
function handleCostChange([min, max]: [number, number]) {
|
||||
setCostInModal([min, max]);
|
||||
setCostChanged(true);
|
||||
}
|
||||
|
||||
function handleFloorChange([min, max]: [number, number]) {
|
||||
setFloorInModal([min, max]);
|
||||
setFloorChanged(true);
|
||||
}
|
||||
|
||||
function handleAreaChange([min, max]: [number, number]) {
|
||||
setAreaInModal([min, max]);
|
||||
setAreaChanged(true);
|
||||
}
|
||||
const { data: viewsInFilters } = useQuery({
|
||||
queryKey: [
|
||||
"filters",
|
||||
"views",
|
||||
project,
|
||||
searchParams.get("cost"),
|
||||
searchParams.get("floor"),
|
||||
searchParams.get("area"),
|
||||
unitTypes,
|
||||
],
|
||||
enabled: !!project,
|
||||
queryFn: () =>
|
||||
api
|
||||
.get(
|
||||
`units/filters/views?${project ? `project=${project}` : ""}${unitTypes
|
||||
.map((unitType) => `&unitTypes=${unitType}`)
|
||||
.join("")}${
|
||||
searchParams.has("cost") ? `&cost=${searchParams.get("cost")}` : ""
|
||||
}${
|
||||
searchParams.has("floor")
|
||||
? `&floor=${searchParams.get("floor")}`
|
||||
: ""
|
||||
}${
|
||||
searchParams.has("area") ? `&area=${searchParams.get("area")}` : ""
|
||||
}`
|
||||
)
|
||||
.json<string[]>(),
|
||||
});
|
||||
|
||||
const { data: count } = useQuery({
|
||||
queryKey: [
|
||||
@@ -285,10 +234,10 @@ function SearchFilters({
|
||||
"count",
|
||||
project,
|
||||
unitTypes,
|
||||
searchParams.get("cost"),
|
||||
searchParams.get("area"),
|
||||
searchParams.get("floor"),
|
||||
view,
|
||||
debouncedCost,
|
||||
debouncedArea,
|
||||
debouncedFloor,
|
||||
],
|
||||
enabled:
|
||||
!!project &&
|
||||
@@ -304,57 +253,137 @@ function SearchFilters({
|
||||
`units/count?${project ? `project=${project}` : ""}${unitTypes
|
||||
.map((unitType) => `&unitTypes=${unitType}`)
|
||||
.join("")}${view !== "Any view" ? `&view=${view}` : ""}${
|
||||
debouncedCost ? `&cost=${debouncedCost.map(Math.round).join()}` : ""
|
||||
searchParams.has("cost") ? `&cost=${searchParams.get("cost")}` : ""
|
||||
}${
|
||||
debouncedArea ? `&area=${debouncedArea.map(Math.round).join()}` : ""
|
||||
searchParams.has("area") ? `&area=${searchParams.get("area")}` : ""
|
||||
}${
|
||||
debouncedFloor
|
||||
? `&floor=${debouncedFloor.map(Math.round).join()}`
|
||||
searchParams.has("floor")
|
||||
? `&floor=${searchParams.get("floor")}`
|
||||
: ""
|
||||
}`
|
||||
)
|
||||
.json<number>(),
|
||||
});
|
||||
|
||||
function handleClose() {
|
||||
useEffect(() => {
|
||||
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);
|
||||
}
|
||||
|
||||
const costValue = searchParams.get("cost");
|
||||
if (costValue)
|
||||
setCostInModal(costValue.split(",").map(Number) as [number, number]);
|
||||
|
||||
const floorValue = searchParams.get("floor");
|
||||
if (floorValue)
|
||||
setFloorInModal(floorValue.split(",").map(Number) as [number, number]);
|
||||
|
||||
const areaValue = searchParams.get("area");
|
||||
if (areaValue)
|
||||
setAreaInModal(areaValue.split(",").map(Number) as [number, number]);
|
||||
|
||||
const viewValue = searchParams.get("view");
|
||||
if (viewValue) setView(viewValue);
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (costInFilters) setCostInModal([costInFilters.min, costInFilters.max]);
|
||||
}, [costInFilters]);
|
||||
|
||||
useEffect(() => {
|
||||
if (floorInFilters)
|
||||
setFloorInModal([floorInFilters.min, floorInFilters.max]);
|
||||
}, [floorInFilters]);
|
||||
|
||||
useEffect(() => {
|
||||
if (areaInFilters) setAreaInModal([areaInFilters.min, areaInFilters.max]);
|
||||
}, [areaInFilters]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inModal) return;
|
||||
setCost(costInModal);
|
||||
}, [costInModal, inModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inModal) return;
|
||||
setFloor(floorInModal);
|
||||
}, [floorInModal, inModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inModal) return;
|
||||
setArea(areaInModal);
|
||||
}, [areaInModal, inModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedCostChanged)
|
||||
setSearchParams((prev) => {
|
||||
prev.set("cost", `${debouncedCost[0]},${debouncedCost[1]}`);
|
||||
return prev;
|
||||
});
|
||||
}, [debouncedCost, debouncedCostChanged]);
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedAreaChanged)
|
||||
setSearchParams((prev) => {
|
||||
prev.set("area", `${debouncedArea[0]},${debouncedArea[1]}`);
|
||||
return prev;
|
||||
});
|
||||
}, [debouncedArea, debouncedAreaChanged]);
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedFloorChanged)
|
||||
setSearchParams((prev) => {
|
||||
prev.set("floor", `${debouncedFloor[0]},${debouncedFloor[1]}`);
|
||||
return prev;
|
||||
});
|
||||
}, [debouncedFloor, debouncedFloorChanged]);
|
||||
|
||||
function handleSelectProject(project: Project) {
|
||||
setProject(project.title);
|
||||
if (!inModal)
|
||||
setSearchParams((prev) => {
|
||||
prev.set("project", project.title);
|
||||
return prev;
|
||||
});
|
||||
setSearchParams((prev) => {
|
||||
prev.set("project", project.title);
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
|
||||
function handleSelectUnitTypes(unitTypes: string[]) {
|
||||
setUnitTypes(unitTypes);
|
||||
if (!inModal)
|
||||
setSearchParams((prev) => {
|
||||
prev.delete("unitTypes");
|
||||
unitTypes.forEach((unitType) => prev.append("unitTypes", unitType));
|
||||
return prev;
|
||||
});
|
||||
setSearchParams((prev) => {
|
||||
prev.delete("unitTypes");
|
||||
unitTypes.forEach((unitType) => prev.append("unitTypes", unitType));
|
||||
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;
|
||||
});
|
||||
setSearchParams((prev) => {
|
||||
if (view !== "Any view") prev.set("view", view);
|
||||
else prev.delete("view");
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
setCostTouched(false);
|
||||
setFloorTouched(false);
|
||||
setAreaTouched(false);
|
||||
if (costInFilters) setCostInModal([costInFilters.min, costInFilters.max]);
|
||||
if (floorInFilters)
|
||||
setFloorInModal([floorInFilters.min, floorInFilters.max]);
|
||||
if (areaInFilters) setAreaInModal([areaInFilters.min, areaInFilters.max]);
|
||||
setView("Any view");
|
||||
setUnitTypes([]);
|
||||
setSearchParams((prev) => {
|
||||
prev.delete("cost");
|
||||
prev.delete("floor");
|
||||
prev.delete("area");
|
||||
prev.delete("view");
|
||||
prev.delete("unitTypes");
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
@@ -378,7 +407,7 @@ function SearchFilters({
|
||||
{inModal && (
|
||||
<div
|
||||
className="fixed inset-0 z-20 bg-[#0D1922]/40 cursor-pointer"
|
||||
onClick={handleClose}
|
||||
onClick={() => setInModal(false)}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
@@ -393,175 +422,241 @@ function SearchFilters({
|
||||
<Button
|
||||
onlyIcon
|
||||
className="absolute right-[2.222vw] !bg-[#F3F3F2]"
|
||||
onClick={handleClose}
|
||||
onClick={() => setInModal(false)}
|
||||
>
|
||||
<div className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5 text-[#0D1922]">
|
||||
<CloseIcon />
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
{costInFilters &&
|
||||
areaInFilters &&
|
||||
floorInFilters &&
|
||||
viewsInFilters &&
|
||||
unitTypesInFilters && (
|
||||
<>
|
||||
<div className="2xl:space-y-[2.222vw] space-y-8">
|
||||
<div className="2xl:space-y-[1.111vw] space-y-4">
|
||||
<p className="2xl:text-[2.222vw] md:max-2xl:text-[32px] text-2xl font-medium leading-[135%]">
|
||||
{inModal ? "Filters" : "Search"}
|
||||
</p>
|
||||
<div className={clsx(!inModal && "max-md:hidden")}>
|
||||
{project && (
|
||||
<ProjectSelect
|
||||
projects={projects}
|
||||
onSelect={handleSelectProject}
|
||||
defaultProject={
|
||||
projects.find(({ title }) => title === project)!
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
className={clsx(
|
||||
"2xl:h-[0.069vw] h-px border-[#E2E2DC]",
|
||||
!inModal && "max-md:hidden"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
<div
|
||||
className={clsx(
|
||||
"2xl:space-y-[0.556vw] space-y-2",
|
||||
!inModal && "max-md:hidden"
|
||||
)}
|
||||
>
|
||||
<p className="text-s text-[#0D1922]/70">Apartment type</p>
|
||||
<UnitTypesSelect
|
||||
unitTypes={unitTypesInFilters}
|
||||
onSelect={handleSelectUnitTypes}
|
||||
defaultSelected={unitTypes}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"grid 2xl:grid-cols-4 md:max-2xl:grid-cols-2 md:max-2xl:grid-rows-2 2xl:gap-[1.111vw] gap-6",
|
||||
!inModal && "max-md:hidden"
|
||||
)}
|
||||
>
|
||||
<MultiRangeSlider
|
||||
{...costInFilters}
|
||||
currentMin={inModal ? costInModal[0] : cost[0]}
|
||||
currentMax={inModal ? costInModal[1] : cost[1]}
|
||||
offset={1}
|
||||
onChange={handleCostChange}
|
||||
label="Cost, AED"
|
||||
/>
|
||||
<MultiRangeSlider
|
||||
{...floorInFilters}
|
||||
currentMin={inModal ? floorInModal[0] : floor[0]}
|
||||
currentMax={inModal ? floorInModal[1] : floor[1]}
|
||||
offset={1}
|
||||
onChange={handleFloorChange}
|
||||
label="Floor"
|
||||
/>
|
||||
<MultiRangeSlider
|
||||
{...areaInFilters}
|
||||
currentMin={inModal ? areaInModal[0] : area[0]}
|
||||
currentMax={inModal ? areaInModal[1] : area[1]}
|
||||
offset={1}
|
||||
onChange={handleAreaChange}
|
||||
label="Total Area, Sqft"
|
||||
/>
|
||||
<Select
|
||||
defaultOption={view}
|
||||
label="View"
|
||||
options={["Any view", ...viewsInFilters]}
|
||||
onSelect={handleSelectView}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center 2xl:gap-[1.111vw] md:max-2xl:gap-4 gap-2",
|
||||
inModal &&
|
||||
"max-md:flex-col max-md:sticky max-md:shadow-[0px_-4px_20px_rgba(0,0,0,0.05)] max-md:rounded-t-2xl max-md:-m-4 max-md:p-4 bottom-0 bg-white"
|
||||
)}
|
||||
>
|
||||
{inModal ? (
|
||||
<Button onClick={applyFilters} className="max-md:w-full">
|
||||
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={clsx(
|
||||
"text-[#00BED7] text-s",
|
||||
!inModal && "max-md:hidden"
|
||||
)}
|
||||
>
|
||||
{count} Apartments found
|
||||
</motion.p>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)}
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={clsx(
|
||||
"hidden",
|
||||
!inModal &&
|
||||
"max-md:flex !justify-center flex-1 !bg-[#F3F3F2]"
|
||||
)}
|
||||
onClick={() => setInModal(true)}
|
||||
<div className="2xl:space-y-[2.222vw] space-y-8">
|
||||
<div className="2xl:space-y-[1.111vw] space-y-4">
|
||||
<p className="2xl:text-[2.222vw] md:max-2xl:text-[32px] text-2xl font-medium leading-[135%]">
|
||||
{inModal ? "Filters" : "Search"}
|
||||
</p>
|
||||
<div className={clsx(!inModal && "max-md:hidden")}>
|
||||
<AnimatePresence mode="wait">
|
||||
{project && (
|
||||
<motion.div
|
||||
key={project}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<div className="w-5 h-5">
|
||||
<FiltersIcon />
|
||||
</div>
|
||||
<p className="text-sm leading-0">Filters</p>
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onlyIcon={!inModal && innerWidth < 768}
|
||||
onClick={resetFilters}
|
||||
className={clsx(
|
||||
!inModal && "max-md:bg-[#F3F3F2]",
|
||||
"max-md:!transition-none"
|
||||
)}
|
||||
>
|
||||
<span className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<RestartIcon />
|
||||
</span>
|
||||
<p
|
||||
className={clsx(
|
||||
"text-s max-md:w-full",
|
||||
!inModal && "max-md:hidden"
|
||||
)}
|
||||
>
|
||||
Reset filters
|
||||
</p>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</>
|
||||
<ProjectSelect
|
||||
projects={projects}
|
||||
onSelect={handleSelectProject}
|
||||
defaultProject={
|
||||
projects.find(({ title }) => title === project)!
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
className={clsx(
|
||||
"2xl:h-[0.069vw] h-px border-[#E2E2DC]",
|
||||
!inModal && "max-md:hidden"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<AnimatePresence mode="wait">
|
||||
{unitTypesInFilters && (
|
||||
<motion.div
|
||||
key={unitTypesInFilters.join()}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className={clsx(
|
||||
"2xl:space-y-[0.556vw] space-y-2",
|
||||
!inModal && "max-md:hidden"
|
||||
)}
|
||||
>
|
||||
<p className="text-s text-[#0D1922]/70">Apartment type</p>
|
||||
<UnitTypesSelect
|
||||
unitTypes={unitTypesInFilters}
|
||||
onSelect={handleSelectUnitTypes}
|
||||
defaultSelected={unitTypes}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<div
|
||||
className={clsx(
|
||||
"grid 2xl:grid-cols-4 md:max-2xl:grid-cols-2 md:max-2xl:grid-rows-2 2xl:gap-[1.111vw] gap-6",
|
||||
!inModal && "max-md:hidden"
|
||||
)}
|
||||
>
|
||||
<AnimatePresence mode="wait">
|
||||
{costInFilters && (
|
||||
<motion.div
|
||||
key={`${costInFilters.min}-${costInFilters.max}`}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<MultiRangeSlider
|
||||
{...costInFilters}
|
||||
currentMin={inModal ? costInModal[0] : cost[0]}
|
||||
currentMax={inModal ? costInModal[1] : cost[1]}
|
||||
offset={0}
|
||||
onChange={setCostInModal}
|
||||
setTouched={setCostTouched}
|
||||
label="Cost, AED"
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<AnimatePresence mode="wait">
|
||||
{floorInFilters && (
|
||||
<motion.div
|
||||
key={`${floorInFilters.min}-${floorInFilters.max}`}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<MultiRangeSlider
|
||||
{...floorInFilters}
|
||||
currentMin={inModal ? floorInModal[0] : floor[0]}
|
||||
currentMax={inModal ? floorInModal[1] : floor[1]}
|
||||
offset={0}
|
||||
onChange={setFloorInModal}
|
||||
setTouched={setFloorTouched}
|
||||
label="Floor"
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<AnimatePresence mode="wait">
|
||||
{areaInFilters && (
|
||||
<motion.div
|
||||
key={`${areaInFilters.min}-${areaInFilters.max}`}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<MultiRangeSlider
|
||||
{...areaInFilters}
|
||||
currentMin={inModal ? areaInModal[0] : area[0]}
|
||||
currentMax={inModal ? areaInModal[1] : area[1]}
|
||||
offset={0}
|
||||
onChange={setAreaInModal}
|
||||
setTouched={setAreaTouched}
|
||||
label="Total Area, Sqft"
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<AnimatePresence mode="wait">
|
||||
{viewsInFilters && (
|
||||
<motion.div
|
||||
key={viewsInFilters.join()}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<Select
|
||||
defaultOption={view}
|
||||
label="View"
|
||||
options={["Any view", ...viewsInFilters]}
|
||||
onSelect={handleSelectView}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center 2xl:gap-[1.111vw] md:max-2xl:gap-4 gap-2",
|
||||
inModal &&
|
||||
"max-md:flex-col max-md:sticky max-md:shadow-[0px_-4px_20px_rgba(0,0,0,0.05)] max-md:rounded-t-2xl max-md:-m-4 max-md:p-4 bottom-0 bg-white"
|
||||
)}
|
||||
>
|
||||
{inModal ? (
|
||||
<Button onClick={applyFilters} className="max-md:w-full">
|
||||
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>
|
||||
) : (
|
||||
<p
|
||||
className={clsx(
|
||||
"text-[#00BED7] text-s",
|
||||
!inModal && "max-md:hidden"
|
||||
)}
|
||||
>
|
||||
<AnimatePresence mode="wait">
|
||||
{count ? (
|
||||
<motion.span
|
||||
key={count}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
{count}
|
||||
</motion.span>
|
||||
) : (
|
||||
<motion.span
|
||||
key={count}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
...
|
||||
</motion.span>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
Apartments found
|
||||
</p>
|
||||
)}
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={clsx(
|
||||
"hidden",
|
||||
!inModal && "max-md:flex !justify-center flex-1 !bg-[#F3F3F2]"
|
||||
)}
|
||||
onClick={() => setInModal(true)}
|
||||
>
|
||||
<div className="w-5 h-5">
|
||||
<FiltersIcon />
|
||||
</div>
|
||||
<p className="text-sm leading-0">Filters</p>
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onlyIcon={!inModal && innerWidth < 768}
|
||||
onClick={resetFilters}
|
||||
className={clsx(
|
||||
!inModal && "max-md:bg-[#F3F3F2]",
|
||||
"max-md:!transition-none"
|
||||
)}
|
||||
>
|
||||
<span className="2xl:w-[1.389vw] 2xl:h-[1.389vw] w-5 h-5 text-[#0D1922]/70">
|
||||
<RestartIcon />
|
||||
</span>
|
||||
<p
|
||||
className={clsx(
|
||||
"text-s max-md:w-full",
|
||||
!inModal && "max-md:hidden"
|
||||
)}
|
||||
>
|
||||
Reset filters
|
||||
</p>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
+29
-23
@@ -1,8 +1,10 @@
|
||||
import { useFavoritesUnitsStore } from '../stores/useFavoritesUnitsStore';
|
||||
import { IUnit } from '../types/IUnit';
|
||||
import FilledHeartIcon from './icons/FilledHeartIcon';
|
||||
import HeartIcon from './icons/HeartIcon';
|
||||
import Button from './ui/Button';
|
||||
import { useFavoritesUnitsStore } from "../stores/useFavoritesUnitsStore";
|
||||
import { IUnit } from "../types/IUnit";
|
||||
import FilledHeartIcon from "./icons/FilledHeartIcon";
|
||||
import HeartIcon from "./icons/HeartIcon";
|
||||
import Button from "./ui/Button";
|
||||
import "react-loading-skeleton/dist/skeleton.css";
|
||||
import Skeleton from "react-loading-skeleton";
|
||||
|
||||
function UnitCard({ unit }: { unit: IUnit }) {
|
||||
const { favoriteUnits, setFavoriteUnits } = useFavoritesUnitsStore();
|
||||
@@ -18,22 +20,26 @@ function UnitCard({ unit }: { unit: IUnit }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<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 className='text-s text-[#00BED7]'>{unit.project}</p>
|
||||
<div className='flex items-center 2xl:gap-[0.556vw] gap-2'>
|
||||
<p className='text-caption-m'>
|
||||
{(unit.unitNo.split('-')[0] === 'W' ? 'West' : 'East') + ' Wing'}
|
||||
<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 className="text-s text-[#00BED7]">
|
||||
{unit.project || <Skeleton />}
|
||||
</p>
|
||||
<div className="flex items-center 2xl:gap-[0.556vw] gap-2">
|
||||
<p className="text-caption-m">
|
||||
<span>
|
||||
{`${unit.unitNo.split("-")[0] === "W" ? "West" : "East"} Wing`}
|
||||
</span>
|
||||
</p>
|
||||
<div className='2xl:w-[0.278vw] w-1 aspect-square bg-[#E2E2DC] rounded-full' />
|
||||
<p className='text-caption-m'>Floor {unit.floor}</p>
|
||||
<div className='2xl:w-[0.278vw] w-1 aspect-square bg-[#E2E2DC] rounded-full' />
|
||||
<p className='text-caption-m'>{unit.unitNo}</p>
|
||||
<div className="2xl:w-[0.278vw] w-1 aspect-square bg-[#E2E2DC] rounded-full" />
|
||||
<p className="text-caption-m">Floor {unit.floor}</p>
|
||||
<div className="2xl:w-[0.278vw] w-1 aspect-square bg-[#E2E2DC] rounded-full" />
|
||||
<p className="text-caption-m">{unit.unitNo}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button onlyIcon variant='secondary' onClick={handleFavorite}>
|
||||
<span className='2xl:w-[1.389vw] w-5 aspect-square text-[#0D1922]/70'>
|
||||
<Button onlyIcon variant="secondary" onClick={handleFavorite}>
|
||||
<span className="2xl:w-[1.389vw] w-5 aspect-square text-[#0D1922]/70">
|
||||
{favoriteUnits.some(
|
||||
(favoriteUnit) => favoriteUnit.id === unit.id
|
||||
) ? (
|
||||
@@ -44,15 +50,15 @@ function UnitCard({ unit }: { unit: IUnit }) {
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className='2xl:space-y-[0.278vw] space-y-1'>
|
||||
<p className='text-s'>
|
||||
<div className="2xl:space-y-[0.278vw] space-y-1">
|
||||
<p className="text-s">
|
||||
{`${unit.unitType}, ${unit.squareFt.toLocaleString(undefined, {
|
||||
maximumFractionDigits: 2,
|
||||
})} Sqft`}
|
||||
</p>
|
||||
<p className='text-[#00BED7] text-subheadline-s font-medium'>
|
||||
{`AED ${Intl.NumberFormat('ar-AE', {
|
||||
currency: 'AED',
|
||||
<p className="text-[#00BED7] text-subheadline-s font-medium">
|
||||
{`AED ${Intl.NumberFormat("ar-AE", {
|
||||
currency: "AED",
|
||||
minimumFractionDigits: 0,
|
||||
}).format(unit.salesPrice)}`}
|
||||
</p>
|
||||
|
||||
@@ -17,6 +17,7 @@ interface IMultiRangeSlider {
|
||||
disabled?: boolean;
|
||||
label: string;
|
||||
onChange: (value: [number, number]) => void;
|
||||
setTouched?: (value: boolean) => void;
|
||||
}
|
||||
|
||||
function MultiRangeSlider({
|
||||
@@ -27,6 +28,7 @@ function MultiRangeSlider({
|
||||
onChange,
|
||||
offset,
|
||||
label,
|
||||
setTouched,
|
||||
disabled = false,
|
||||
}: IMultiRangeSlider) {
|
||||
const [current, setCurrent] = useState<"min" | "max" | null>(null);
|
||||
@@ -59,19 +61,26 @@ function MultiRangeSlider({
|
||||
}
|
||||
|
||||
function handleMouseUp() {
|
||||
setCurrent(null);
|
||||
if (current) {
|
||||
setCurrent(null);
|
||||
setTouched?.(true);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (current) {
|
||||
document.addEventListener("mousemove", handleChange as EventListener);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
document.addEventListener("mouseleave", handleMouseUp);
|
||||
}
|
||||
document.addEventListener("mousemove", handleChange as EventListener);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
document.addEventListener("mouseleave", handleMouseUp);
|
||||
document.addEventListener("touchmove", handleChange as EventListener);
|
||||
document.addEventListener("touchend", handleMouseUp);
|
||||
document.addEventListener("touchcancel", handleMouseUp);
|
||||
return () => {
|
||||
document.removeEventListener("mousemove", handleChange as EventListener);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
document.removeEventListener("mouseleave", handleMouseUp);
|
||||
document.removeEventListener("touchmove", handleChange as EventListener);
|
||||
document.removeEventListener("touchend", handleMouseUp);
|
||||
document.removeEventListener("touchcancel", handleMouseUp);
|
||||
};
|
||||
}, [current]);
|
||||
|
||||
@@ -84,10 +93,10 @@ function MultiRangeSlider({
|
||||
<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))}
|
||||
{Intl.NumberFormat("en").format(Math.ceil(currentMin))}
|
||||
</p>
|
||||
<p className={clsx("text-s", disabled && "text-[#0D1922]/40")}>
|
||||
{Intl.NumberFormat("en").format(Math.round(currentMax))}
|
||||
{Intl.NumberFormat("en").format(Math.ceil(currentMax))}
|
||||
</p>
|
||||
<div className="absolute bottom-0 left-0 w-full 2xl:px-[1.111vw] px-4 translate-y-1/2">
|
||||
<div
|
||||
|
||||
@@ -58,7 +58,13 @@ function Select({
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
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"
|
||||
ref={(el) => {
|
||||
if (el)
|
||||
el.style.maxHeight = `calc(100vh - ${
|
||||
el?.getBoundingClientRect().y
|
||||
}px - 0.278vw)`;
|
||||
}}
|
||||
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)] overflow-auto rounded-xl bg-white w-full z-10"
|
||||
>
|
||||
{options.map((option, index) => (
|
||||
<button
|
||||
|
||||
+38
-34
@@ -12,6 +12,7 @@ import clsx from "clsx";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { useDebounce } from "../hooks/useDebounce";
|
||||
import Select from "../components/ui/Select";
|
||||
import Skeleton from "react-loading-skeleton";
|
||||
|
||||
const SORT_OPTIONS = {
|
||||
"Sort by ascending price": "cost asc",
|
||||
@@ -43,7 +44,7 @@ function SearchPage() {
|
||||
"Sort by ascending price"
|
||||
);
|
||||
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
||||
const { data, fetchNextPage, isLoading, hasNextPage, isFetchingNextPage } =
|
||||
useInfiniteQuery({
|
||||
initialPageParam: 0,
|
||||
queryKey: [
|
||||
@@ -139,36 +140,19 @@ function SearchPage() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// const [activeFiltersCount, setActiveFiltersCount] = useState(0);
|
||||
const [activeFiltersCount, setActiveFiltersCount] = useState(0);
|
||||
|
||||
// const queryClient = useQueryClient();
|
||||
|
||||
// useEffect(() => {
|
||||
// const filters = queryClient.getQueryData<Filters>(["filters", project]);
|
||||
// if (filters) {
|
||||
// setActiveFiltersCount(
|
||||
// +!!view +
|
||||
// +!!unitTypes.length +
|
||||
// +(debouncedCost[0] !== filters.minCost) +
|
||||
// +(debouncedCost[1] !== filters.maxCost) +
|
||||
// +(debouncedArea[0] !== filters.minArea) +
|
||||
// +(debouncedArea[1] !== filters.maxArea) +
|
||||
// +(debouncedFloor[0] !== filters.minFloor) +
|
||||
// +(debouncedFloor[1] !== filters.maxFloor)
|
||||
// );
|
||||
// }
|
||||
// }, [
|
||||
// queryClient,
|
||||
// unitTypes,
|
||||
// view,
|
||||
// project,
|
||||
// debouncedCost,
|
||||
// cost,
|
||||
// debouncedArea,
|
||||
// area,
|
||||
// debouncedFloor,
|
||||
// floor,
|
||||
// ]);
|
||||
useEffect(
|
||||
() =>
|
||||
setActiveFiltersCount(
|
||||
+searchParams.has("view") +
|
||||
+searchParams.has("unitTypes") +
|
||||
+searchParams.has("cost") +
|
||||
+searchParams.has("floor") +
|
||||
+searchParams.has("area")
|
||||
),
|
||||
[searchParams]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -187,7 +171,18 @@ function SearchPage() {
|
||||
/>
|
||||
<hr className="2xl:h-[0.069vw] border-[#E2E2DC]" />
|
||||
<AnimatePresence mode="wait">
|
||||
{project &&
|
||||
{isLoading ? (
|
||||
<div className="2xl:grid-cols-4 md:max-2xl:grid-cols-2 grid 2xl:gap-[1.111vw] gap-4">
|
||||
{Array.from({ length: STEP }).map((_, i) => (
|
||||
<Skeleton
|
||||
key={i}
|
||||
borderRadius={16}
|
||||
className="2xl:aspect-[332/396] md:max-2xl:aspect-[352/396] aspect-[328/396]"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
project &&
|
||||
unitTypes &&
|
||||
debouncedCost &&
|
||||
debouncedArea &&
|
||||
@@ -211,8 +206,17 @@ function SearchPage() {
|
||||
{data?.pages.map((page) =>
|
||||
page.map((unit) => <UnitCard key={unit.id} unit={unit} />)
|
||||
)}
|
||||
{isFetchingNextPage &&
|
||||
Array.from({ length: STEP }).map((_, i) => (
|
||||
<Skeleton
|
||||
key={"fetching-" + i}
|
||||
borderRadius={16}
|
||||
className="2xl:aspect-[332/396] md:max-2xl:aspect-[352/396] aspect-[328/396]"
|
||||
/>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
)
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
{showButtons && (
|
||||
@@ -229,11 +233,11 @@ function SearchPage() {
|
||||
<FiltersIcon />
|
||||
</span>
|
||||
<span className="text-caption-m">Filters</span>
|
||||
{/* {!!activeFiltersCount && (
|
||||
{!!activeFiltersCount && (
|
||||
<div className="absolute 2xl:top-[0.139vw] 2xl:right-[0.139vw] top-0.5 right-0.5 rounded-full w-4 text-caption-s aspect-square bg-white text-[#00BED7]">
|
||||
{activeFiltersCount}
|
||||
</div>
|
||||
)} */}
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
|
||||
Reference in New Issue
Block a user