Add dark mode

This commit is contained in:
Andras Schmelczer 2026-02-01 13:07:24 +00:00
parent 5e210e14bd
commit 7235df0a97
14 changed files with 304 additions and 139 deletions

View file

@ -21,10 +21,19 @@ export function PropertiesPane({
onClose,
}: PropertiesPaneProps) {
const [sortBy, setSortBy] = useState<SortBy>('price');
const [search, setSearch] = useState('');
// Sort properties
const sortedProperties = useMemo(() => {
return [...properties].sort((a, b) => {
// Filter and sort properties
const filteredAndSorted = useMemo(() => {
const query = search.trim().toLowerCase();
const filtered = query
? properties.filter((p) => {
const addr = (p.address || '').toLowerCase();
const pc = (p.postcode || '').toLowerCase();
return addr.includes(query) || pc.includes(query);
})
: properties;
return [...filtered].sort((a, b) => {
switch (sortBy) {
case 'price':
return ((b.latest_price as number) || 0) - ((a.latest_price as number) || 0);
@ -34,11 +43,11 @@ export function PropertiesPane({
return (a.current_energy_rating || 'Z').localeCompare(b.current_energy_rating || 'Z');
}
});
}, [properties, sortBy]);
}, [properties, sortBy, search]);
if (!hexagonId) {
return (
<div className="flex items-center justify-center h-full text-warm-500">
<div className="flex items-center justify-center h-full text-warm-500 dark:text-warm-400">
Click a hexagon to view properties
</div>
);
@ -47,27 +56,36 @@ export function PropertiesPane({
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="p-4 border-b border-warm-200">
<div className="p-4 border-b border-warm-200 dark:border-warm-700">
<div className="flex justify-between items-center">
<h2 className="text-lg font-semibold">Properties in Hexagon</h2>
<h2 className="text-lg font-semibold dark:text-warm-100">Properties in Hexagon</h2>
<button
onClick={onClose}
className="text-warm-500 hover:text-warm-700 text-2xl leading-none"
className="text-warm-500 hover:text-warm-700 dark:text-warm-400 dark:hover:text-warm-200 text-2xl leading-none"
>
×
</button>
</div>
<p className="text-sm text-warm-600">
Showing {properties.length} of {total} properties
<p className="text-sm text-warm-600 dark:text-warm-400">
{search.trim()
? `${filteredAndSorted.length} match${filteredAndSorted.length !== 1 ? 'es' : ''} in ${properties.length} loaded`
: `Showing ${properties.length} of ${total} properties`}
</p>
</div>
{/* Sort controls */}
<div className="p-2 border-b border-warm-200">
{/* Search and sort controls */}
<div className="p-2 border-b border-warm-200 dark:border-warm-700 space-y-2">
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search by address or postcode..."
className="w-full p-2 border border-warm-300 dark:border-warm-700 rounded text-sm bg-white dark:bg-warm-800 dark:text-warm-200 placeholder-warm-400 dark:placeholder-warm-500"
/>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as SortBy)}
className="w-full p-2 border border-warm-300 rounded text-sm"
className="w-full p-2 border border-warm-300 dark:border-warm-700 rounded text-sm bg-white dark:bg-warm-800 dark:text-warm-200"
>
<option value="price">Price (High to Low)</option>
<option value="size">Size (Large to Small)</option>
@ -78,17 +96,17 @@ export function PropertiesPane({
{/* Properties list */}
<div className="flex-1 overflow-y-auto">
{loading && properties.length === 0 ? (
<div className="p-4">Loading...</div>
<div className="p-4 dark:text-warm-400">Loading...</div>
) : (
<>
{sortedProperties.map((property, idx) => (
{filteredAndSorted.map((property, idx) => (
<PropertyCard key={idx} property={property} />
))}
{properties.length < total && (
<button
onClick={onLoadMore}
disabled={loading}
className="w-full p-4 text-teal-600 hover:bg-teal-50 disabled:opacity-50"
className="w-full p-4 text-teal-600 dark:text-teal-400 hover:bg-teal-50 dark:hover:bg-teal-900/30 disabled:opacity-50"
>
{loading ? 'Loading...' : `Load More (${total - properties.length} remaining)`}
</button>
@ -138,61 +156,61 @@ function PropertyCard({ property }: { property: Property }) {
const age = getNum(property, 'Approximate construction age', 'construction_age_band');
return (
<div className="p-4 border-b border-warm-100 hover:bg-warm-50">
<div className="p-4 border-b border-warm-100 dark:border-warm-800 hover:bg-warm-50 dark:hover:bg-warm-800">
{/* Address & postcode */}
<div className="font-semibold">{property.address || 'Unknown Address'}</div>
<div className="text-sm text-warm-600">{property.postcode}</div>
<div className="font-semibold dark:text-warm-100">{property.address || 'Unknown Address'}</div>
<div className="text-sm text-warm-600 dark:text-warm-400">{property.postcode}</div>
{/* Price */}
{price !== undefined && (
<div className="mt-2 text-lg font-bold text-teal-700">
<div className="mt-2 text-lg font-bold text-teal-700 dark:text-teal-400">
£{fmt(price)}
{pricePerSqm !== undefined && (
<span className="text-sm font-normal text-warm-600"> (£{fmt(pricePerSqm)}/m²)</span>
<span className="text-sm font-normal text-warm-600 dark:text-warm-400"> (£{fmt(pricePerSqm)}/m²)</span>
)}
</div>
)}
{/* Property details grid */}
<div className="mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-sm">
<div className="mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-sm dark:text-warm-300">
{property.property_type && (
<div>
<span className="text-warm-500">Type:</span> {property.property_type}
<span className="text-warm-500 dark:text-warm-400">Type:</span> {property.property_type}
</div>
)}
{property.built_form && (
<div>
<span className="text-warm-500">Built form:</span> {property.built_form}
<span className="text-warm-500 dark:text-warm-400">Built form:</span> {property.built_form}
</div>
)}
{property.duration && (
<div>
<span className="text-warm-500">Tenure:</span> {formatDuration(property.duration)}
<span className="text-warm-500 dark:text-warm-400">Tenure:</span> {formatDuration(property.duration)}
</div>
)}
{floorArea !== undefined && (
<div>
<span className="text-warm-500">Floor area:</span> {fmt(floorArea)}m²
<span className="text-warm-500 dark:text-warm-400">Floor area:</span> {fmt(floorArea)}m²
</div>
)}
{rooms !== undefined && (
<div>
<span className="text-warm-500">Rooms:</span> {fmt(rooms)}
<span className="text-warm-500 dark:text-warm-400">Rooms:</span> {fmt(rooms)}
</div>
)}
{age !== undefined && (
<div>
<span className="text-warm-500">Built:</span> {formatAge(age, property.is_construction_date_approximate ?? true)}
<span className="text-warm-500 dark:text-warm-400">Built:</span> {formatAge(age, property.is_construction_date_approximate ?? true)}
</div>
)}
{property.current_energy_rating && (
<div>
<span className="text-warm-500">EPC rating:</span> {property.current_energy_rating}
<span className="text-warm-500 dark:text-warm-400">EPC rating:</span> {property.current_energy_rating}
</div>
)}
{property.potential_energy_rating && (
<div>
<span className="text-warm-500">EPC potential:</span> {property.potential_energy_rating}
<span className="text-warm-500 dark:text-warm-400">EPC potential:</span> {property.potential_energy_rating}
</div>
)}
</div>