import { useMemo, useState } from 'react'; import type { FeatureFilters, FeatureMeta, HexagonStatsResponse, PostcodeData } from '../types'; import type { HexagonLocation } from '../lib/external-search'; import { formatValue, calculateHistogramMean } from '../lib/format'; import { groupFeaturesByCategory } from '../lib/features'; import { STACKED_GROUPS } from '../lib/consts'; import { DualHistogram, LoadingSkeleton } from './DualHistogram'; import EnumBarChart from './EnumBarChart'; import StackedBarChart from './StackedBarChart'; import PriceHistoryChart from './PriceHistoryChart'; import ExternalSearchLinks from './ExternalSearchLinks'; import { InfoIcon, CloseIcon } from './ui/Icons'; import { IconButton } from './ui/IconButton'; import { FeatureInfoPopup } from './FeatureInfoPopup'; import { EmptyState } from './ui/EmptyState'; interface AreaPaneProps { stats: HexagonStatsResponse | null; globalFeatures: FeatureMeta[]; loading: boolean; hexagonId: string | null; isPostcode?: boolean; postcodeData?: PostcodeData | null; onViewProperties: () => void; onClose: () => void; hexagonLocation: HexagonLocation | null; filters: FeatureFilters; onNavigateToSource?: (slug: string, featureName: string) => void; } export default function AreaPane({ stats, globalFeatures, loading, hexagonId, isPostcode = false, postcodeData, onViewProperties, onClose, hexagonLocation, filters, onNavigateToSource, }: AreaPaneProps) { // For postcodes, use local data for count const propertyCount = isPostcode && postcodeData ? postcodeData.count : stats?.count; const featureGroups = useMemo(() => groupFeaturesByCategory(globalFeatures), [globalFeatures]); const [infoFeature, setInfoFeature] = useState(null); const numericByName = useMemo(() => { if (!stats) return new Map(); return new Map(stats.numeric_features.map((feature) => [feature.name, feature])); }, [stats]); const enumByName = useMemo(() => { if (!stats) return new Map(); return new Map(stats.enum_features.map((feature) => [feature.name, feature])); }, [stats]); const globalFeatureByName = useMemo( () => new Map(globalFeatures.map((f) => [f.name, f])), [globalFeatures] ); if (!hexagonId) { return ( } title="No area selected" description="Click a hexagon or postcode to view area statistics" centered /> ); } return (

{isPostcode ? hexagonId : 'Area Statistics'}

{isPostcode && ( Postcode )}
{propertyCount != null && (

{propertyCount.toLocaleString()} properties

)} {!isPostcode && stats && ( )}
{hexagonLocation && stats && ( )}
{loading && !stats ? ( ) : stats ? (
{stats.price_history && stats.price_history.length > 0 && (
Price History
)} {featureGroups.map((group) => { const hasData = group.features.some( (feature) => numericByName.has(feature.name) || enumByName.has(feature.name) ); if (!hasData) return null; const stackedCharts = STACKED_GROUPS[group.name]; return (

{group.name}

{stackedCharts ? // Render stacked charts for this group stackedCharts.map((chart) => { const segments = chart.components .map((name) => ({ name, value: numericByName.get(name)?.mean ?? 0, })) .filter((s) => s.value > 0); // Use aggregate feature stats if available, otherwise sum components const aggregateStats = chart.feature ? numericByName.get(chart.feature) : undefined; const total = aggregateStats ? aggregateStats.mean : segments.reduce((sum, s) => sum + s.value, 0); const featureMeta = chart.feature ? globalFeatureByName.get(chart.feature) : undefined; if (total === 0) return null; return (
{chart.label} {featureMeta?.detail && ( )}
{formatValue(total)} {chart.unit ? ` ${chart.unit}` : ''}
); }) : // Default: render each feature individually group.features.map((feature) => { const numericStats = numericByName.get(feature.name); const enumStats = enumByName.get(feature.name); if (numericStats) { const globalFeature = globalFeatureByName.get(feature.name); const globalHistogram = globalFeature?.histogram; const globalMean = globalHistogram ? calculateHistogramMean(globalHistogram) : undefined; return (
{feature.name} {feature.detail && ( )}
{formatValue(numericStats.mean)}
{numericStats.histogram && ( <>
{formatValue(numericStats.histogram.min)} {formatValue(numericStats.histogram.max)}
{globalHistogram ? ( ) : ( )} )}
); } if (enumStats) { return (
{feature.name} {feature.detail && ( )}
); } return null; })}
); })}
) : null}
{infoFeature && ( setInfoFeature(null)} onNavigateToSource={onNavigateToSource} /> )}
); }