sky-and-soil/src/components/PropertiesClient.tsx

175 lines
6.9 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { useSearchParams } from "next/navigation";
import Footer from "@/components/Footer";
import InnerBanner from "@/components/InnerBanner";
import PropertyFilters, { FilterState } from "@/components/PropertyFilters";
import PropertyCard from "@/components/PropertyCard";
import { properties } from "@/data/properties";
import { motion } from "framer-motion";
interface PropertiesClientProps {
initialFilters?: Partial<FilterState>;
headerRenderer?: (props: { filteredCount: number; totalCount: number }) => React.ReactNode;
bannerTitle?: string;
bannerSubtitle?: string;
bannerBackgroundImage?: string;
}
export default function PropertiesClient({
initialFilters,
headerRenderer,
bannerTitle = "Our Properties",
bannerSubtitle = "Discover your dream home from our exclusive collection of premium properties",
bannerBackgroundImage = "/assets/images/projects/projects-banner.webp"
}: PropertiesClientProps = {}) {
const searchParams = useSearchParams();
const [filteredProperties, setFilteredProperties] = useState(properties);
const [initializedFilters, setInitializedFilters] = useState<Partial<FilterState> | undefined>(undefined);
const [isInitialized, setIsInitialized] = useState(false);
useEffect(() => {
const search = searchParams.get("search") || "";
const type = searchParams.get("type") || "all";
const initial: Partial<FilterState> = {
search,
type,
budgetMin: "",
budgetMax: "",
bhk: "all",
sortBy: "popularity"
};
setInitializedFilters(initial);
// Initial filter run
const filters = {
search,
type,
budgetMin: "",
budgetMax: "",
bhk: "all",
sortBy: "popularity"
};
handleFilterChange(filters);
setIsInitialized(true);
}, [searchParams]);
const handleFilterChange = (filters: FilterState) => {
let filtered = [...properties];
// Search filter
if (filters.search) {
filtered = filtered.filter(p =>
p.title.toLowerCase().includes(filters.search.toLowerCase()) ||
p.location.toLowerCase().includes(filters.search.toLowerCase()) ||
p.builder?.name.toLowerCase().includes(filters.search.toLowerCase())
);
}
// Type filter
if (filters.type !== "all") {
filtered = filtered.filter(p => p.category === filters.type);
}
// BHK filter
if (filters.bhk !== "all") {
filtered = filtered.filter(p => p.overview.bhk.includes(filters.bhk));
}
// Budget filter
if (filters.budgetMin || filters.budgetMax) {
filtered = filtered.filter(p => {
const priceStr = p.price.replace(/[^0-9.]/g, "");
const price = parseFloat(priceStr);
const min = filters.budgetMin ? parseFloat(filters.budgetMin) : 0;
const max = filters.budgetMax ? parseFloat(filters.budgetMax) : Infinity;
return price >= min && price <= max;
});
}
// Sort
if (filters.sortBy === "price-low") {
filtered.sort((a, b) => {
const priceA = parseFloat(a.price.replace(/[^0-9.]/g, ""));
const priceB = parseFloat(b.price.replace(/[^0-9.]/g, ""));
return priceA - priceB;
});
} else if (filters.sortBy === "price-high") {
filtered.sort((a, b) => {
const priceA = parseFloat(a.price.replace(/[^0-9.]/g, ""));
const priceB = parseFloat(b.price.replace(/[^0-9.]/g, ""));
return priceB - priceA;
});
}
setFilteredProperties(filtered);
};
if (!isInitialized) return null; // Logic to wait for params
return (
<div className="min-h-screen bg-gray-50 dark:bg-black">
<InnerBanner
title={bannerTitle}
subtitle={bannerSubtitle}
breadcrumbs={[
{ label: "Home", href: "/" },
{ label: "Properties" }
]}
backgroundImage={bannerBackgroundImage}
/>
<div>
<PropertyFilters onFilterChange={handleFilterChange} initialFilters={initializedFilters} />
<div className="max-w-7xl mx-auto px-6 py-12">
{headerRenderer ? (
headerRenderer({ filteredCount: filteredProperties.length, totalCount: properties.length })
) : (
<motion.h2
className="text-3xl font-bold text-foreground mb-8"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
Browse Our Properties
</motion.h2>
)}
{/* Property Grid */}
{filteredProperties.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredProperties.map((property, index) => (
<motion.div
key={property.id}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
<PropertyCard property={property} />
</motion.div>
))} </div>
) : (
<div className="text-center py-20">
<svg className="w-20 h-20 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
<h3 className="text-xl font-semibold text-gray-700 dark:text-gray-300 mb-2">No properties found</h3>
<p className="text-gray-500 dark:text-gray-400">Try adjusting your filters to see more results</p>
</div>
)}
</div>
</div>
<Footer />
</div >
);
}