Skip to main content
The property listings feature provides a comprehensive interface for users to browse available properties with powerful filtering, search, and display options.

Overview

Property listings are displayed on the /propiedades route and provide:
  • Real-time property search and filtering
  • Grid and list view modes
  • Responsive design for mobile and desktop
  • Status-based property prioritization
  • URL-shareable filters

Key Components

ListingsPage Component

The main listings page integrates search, filtering, and property display. Location: src/pages/ListingsPage.tsx
import { api } from "../lib/api";
import PropertyCard from "../components/PropertyCard";
import FilterSidebar from "../components/FilterSidebar";
import ListingsSearchBar from "../components/ListingsSearchBar";

const ListingsPage = () => {
  const [properties, setProperties] = useState<Property[]>([]);
  const [searchFilters, setSearchFilters] = useState<PropertyFilters>({
    query: searchParams.query || "",
    propertyType: [],
    listingType: [],
    minPrice: undefined,
    maxPrice: undefined,
    bedrooms: undefined,
    bathrooms: undefined,
    location: "",
    page: 1,
    limit: 20,
  });

  // Fetch properties when filters change
  useEffect(() => {
    const fetchProperties = async () => {
      const response = await api.properties.list(searchFilters);
      let data = response.data || [];
      
      // Apply client-side search if query exists
      if (searchFilters.query) {
        data = searchProperties(data, searchFilters.query);
      }
      
      setProperties(sortPropertiesByStatusPriority(data));
    };
    
    fetchProperties();
  }, [searchFilters]);
};

PropertyCard Component

Displays individual property information in a card format. Location: src/components/PropertyCard.tsx
const PropertyCard = ({ property }: PropertyCardProps) => {
  const { user, addToFavorites, removeFromFavorites, isFavorite } = useAuth();

  return (
    <div className="bg-white rounded-lg shadow-md overflow-hidden group">
      {/* Property Image */}
      <img
        src={getMainImageUrl(property) || getPropertyPlaceholderImage()}
        alt={property.title}
        className="w-full h-48 object-cover group-hover:scale-105"
      />

      {/* Status Overlay for sold/rented properties */}
      {property.status && ['vendido', 'alquilado'].includes(property.status) && (
        <div className="absolute top-0 left-0 right-0 bottom-0">
          <span className="text-sm font-bold uppercase">
            {getStatusLabel(property.status)}
          </span>
        </div>
      )}

      {/* Property Details */}
      <div className="p-4">
        <h3 className="text-lg font-semibold line-clamp-2">
          {property.title}
        </h3>
        
        <div className="text-xl font-bold">
          ${property.price.toLocaleString()} {property.currency}
        </div>

        {/* Property Stats */}
        <div className="grid grid-cols-2 gap-2 text-sm">
          {property.rooms > 0 && (
            <div className="flex items-center space-x-1">
              <LayoutGrid className="h-4 w-4" />
              <span>{property.rooms} Ambientes</span>
            </div>
          )}
          {property.bedrooms > 0 && (
            <div className="flex items-center space-x-1">
              <Bed className="h-4 w-4" />
              <span>{property.bedrooms} Dormitorios</span>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

Filtering System

FilterSidebar Component

Provides advanced filtering options for property search. Location: src/components/FilterSidebar.tsx
1

Load Metadata

Fetch available property types, operation types, and other metadata from the API.
const [metadata, setMetadata] = useState<any>(null);

useEffect(() => {
  const fetchMetadata = async () => {
    const response = await api.metadata.getAll();
    setMetadata(response.data);
  };
  fetchMetadata();
}, []);
2

Property Type Filters

Multi-select checkboxes for property types (casa, departamento, etc.).
const handlePropertyTypeChange = (typeSlug: string, checked: boolean) => {
  setSearchFilters({
    ...searchFilters,
    propertyType: checked
      ? [...(searchFilters.propertyType || []), typeSlug]
      : (searchFilters.propertyType || []).filter((t) => t !== typeSlug),
  });
};

<CheckboxOption
  label="Casa"
  checked={(searchFilters.propertyType || []).includes('casa')}
  onChange={(checked) => handlePropertyTypeChange('casa', checked)}
/>
3

Bedroom and Bathroom Filters

Button-based selection for minimum bedrooms and bathrooms.
const handleBedroomChange = (bedrooms: number) => {
  setSearchFilters({
    ...searchFilters,
    bedrooms: searchFilters.bedrooms === bedrooms ? undefined : bedrooms,
  });
};

<button
  onClick={() => handleBedroomChange(3)}
  className={`px-4 py-2 rounded-full ${
    searchFilters.bedrooms === 3 
      ? 'bg-red-600 text-white' 
      : 'bg-gray-100'
  }`}
>
  3+
</button>

API Integration

Fetching Properties

Properties are fetched using the properties API endpoint with filter parameters.
Property List API
// src/lib/api.ts
properties: {
  list: (params?: PropertyFilters) => {
    const searchParams = new URLSearchParams();
    if (params) {
      Object.entries(params).forEach(([key, value]) => {
        if (value !== undefined && value !== null) {
          if (Array.isArray(value)) {
            value.forEach((v) => searchParams.append(key, String(v)));
          } else {
            searchParams.append(key, String(value));
          }
        }
      });
    }
    return apiCall(`/properties${searchParams.toString() ? `?${searchParams}` : ""}`);
  },
}

Filter Parameters

query
string
Free-text search query across all property fields
propertyType
string[]
Array of property type slugs (casa, departamento, terreno, etc.)
listingType
string[]
Array of operation types (venta, alquiler, alquiler-temporal)
minPrice
number
Minimum price filter
maxPrice
number
Maximum price filter
bedrooms
number
Minimum number of bedrooms
bathrooms
number
Minimum number of bathrooms
location
string
Location search (city, neighborhood, province)
page
number
default:"1"
Page number for pagination
limit
number
default:"20"
Number of results per page

Property Sorting

Properties are sorted to prioritize active listings over sold/rented properties.
Priority Sorting
// src/utils/index.ts
export const sortPropertiesByStatusPriority = (properties: Property[]) => {
  const statusPriority: Record<string, number> = {
    activo: 1,
    pendiente: 2,
    pausado: 3,
    reservado: 4,
    alquilado: 5,
    vendido: 6,
  };

  return [...properties].sort((a, b) => {
    const aPriority = statusPriority[a.status] || 999;
    const bPriority = statusPriority[b.status] || 999;
    return aPriority - bPriority;
  });
};

Responsive Design

The listings page adapts to different screen sizes:
  • Single column grid
  • Slide-out filter sidebar
  • Touch-friendly buttons
  • Simplified property cards

Best Practices

  • Fetch properties with appropriate limits (default 20)
  • Implement pagination for large result sets
  • Cache metadata to avoid repeated API calls
  • Use client-side filtering for real-time search
  • Show loading states during data fetching
  • Display active filters with removal options
  • Provide clear empty state messages
  • Enable URL sharing for filtered results
  • Use semantic HTML elements
  • Add ARIA labels to interactive elements
  • Support keyboard navigation
  • Ensure sufficient color contrast

Search Functionality

Advanced search with synonyms and autocomplete

Favorites

Save and manage favorite properties

Property Management

Create and edit property listings

User Authentication

Login and user session management

Build docs developers (and LLMs) love