import { useMemo, useState, useCallback } from 'react';
import { Property } from '../../types';
import { formatDuration, formatAge, formatNumber, formatTransactionDate } from '../../lib/format';
import { getNum } from '../../lib/property-fields';
import InfoPopup from '../ui/InfoPopup';
import { SearchInput } from '../ui/SearchInput';
import { EmptyState } from '../ui/EmptyState';
import { InfoIcon } from '../ui/icons';
import { BookmarkIcon } from '../ui/icons/BookmarkIcon';
interface PropertiesPaneProps {
properties: Property[];
total: number;
loading: boolean;
hexagonId: string | null;
onLoadMore: () => void;
onNavigateToSource?: (slug: string) => void;
onSaveProperty?: (property: Property) => void;
onUnsaveProperty?: (id: string) => void;
isPropertySaved?: (address?: string, postcode?: string) => boolean;
getSavedPropertyId?: (address?: string, postcode?: string) => string | undefined;
}
export function PropertiesPane({
properties,
total,
loading,
hexagonId,
onLoadMore,
onNavigateToSource,
onSaveProperty,
onUnsaveProperty,
isPropertySaved,
getSavedPropertyId,
}: PropertiesPaneProps) {
const [search, setSearch] = useState('');
const [showInfo, setShowInfo] = useState(false);
const filtered = useMemo(() => {
const query = search.trim().toLowerCase();
return query
? properties.filter((p) => {
const addr = (p.address || '').toLowerCase();
const pc = (p.postcode || '').toLowerCase();
return addr.includes(query) || pc.includes(query);
})
: properties;
}, [properties, search]);
if (!hexagonId) {
return (
}
title="No area selected"
description="Click a hexagon or postcode to view area statistics"
centered
/>
);
}
return (
{showInfo && (
setShowInfo(false)}
sourceLink={
onNavigateToSource
? {
label: 'View data source',
onClick: () => {
onNavigateToSource('epc');
setShowInfo(false);
},
}
: undefined
}
>
Property data combines Energy Performance Certificates (EPC) with HM Land Registry Price
Paid records, fuzzy-matched by address within each postcode. Includes floor area, energy
ratings, construction year, and tenure from EPC surveys, plus the most recent sale price
from the Land Registry.
)}
{loading && properties.length === 0 ? (
) : (
<>
{filtered.map((property, idx) => (
))}
{properties.length < total && (
{loading ? (
Loading...
) : (
`Load More (${total - properties.length} remaining)`
)}
)}
>
)}
);
}
function PropertyLoadingSkeleton() {
return (
{property.address || 'Unknown Address'}
{property.postcode}
{onSave && (
)}
{property.property_sub_type && (
{property.property_sub_type}
)}
{askingPrice !== undefined && (
{property.price_qualifier && (
{property.price_qualifier}{' '}
)}
£{formatNumber(askingPrice)}
)}
{askingRent !== undefined && (
£{formatNumber(askingRent)}
/mo
)}
{price !== undefined && (
{askingPrice !== undefined || askingRent !== undefined ? (
Last sold: £{formatNumber(price)}
{transactionDate !== undefined && ` (${formatTransactionDate(transactionDate)})`}
) : (
<>
£{formatNumber(price)}
{transactionDate !== undefined && (
{' '}
({formatTransactionDate(transactionDate)})
)}
{pricePerSqm !== undefined && (
{' '}
£{formatNumber(pricePerSqm)}/m²
)}
>
)}
)}
{estimatedPrice !== undefined && (
Est. value:{' '}
£{formatNumber(estimatedPrice)}
{estPricePerSqm !== undefined && (£{formatNumber(estPricePerSqm)}/m²) }
)}
{property.property_type && (
Type: {property.property_type}
)}
{property.built_form && (
Built form: {' '}
{property.built_form}
)}
{property.duration && (
Tenure: {' '}
{formatDuration(property.duration)}
)}
{floorArea !== undefined && (
Floor area: {' '}
{formatNumber(floorArea)}m²
)}
{bedrooms !== undefined && (
Bedrooms: {' '}
{formatNumber(bedrooms)}
)}
{bathrooms !== undefined && (
Bathrooms: {' '}
{formatNumber(bathrooms)}
)}
{rooms !== undefined && (
Rooms: {formatNumber(rooms)}
)}
{age !== undefined && (
Built: {' '}
{formatAge(age, property.is_construction_date_approximate)}
)}
{property.current_energy_rating && (
EPC rating: {' '}
{property.current_energy_rating}
)}
{property.potential_energy_rating && (
EPC potential: {' '}
{property.potential_energy_rating}
)}
{listingDate !== undefined && (
Listed: {' '}
{formatTransactionDate(listingDate)}
)}
{property.listing_features && property.listing_features.length > 0 && (
Key features
{property.listing_features.map((feature, idx) => (
{feature}
))}
)}
{property.renovation_history && property.renovation_history.length > 0 && (
Renovations
{property.renovation_history.map((reno, idx) => (
{reno.event}
{reno.year}
))}
)}
{property.listing_url && (
)}
);
}