import { ExternalLink, Heart, X, Bed, Maximize2, PoundSterling, Building, Clock, MapPin, Footprints, Bike, Train } from 'lucide-react'; import { Button } from './ui/button'; import { Tabs, TabsList, TabsTrigger, TabsContent } from './ui/tabs'; import { PhotoCarousel } from './PhotoCarousel'; import type { ListingDetailData, DecisionType, POIDistanceInfo } from '@/types'; import { formatDate, formatDuration } from '@/utils/format'; interface ListingDetailProps { detail: ListingDetailData; onDecide: (decision: DecisionType) => void; onClearDecision: () => void; } function TravelModeLabel({ mode }: { mode: string }) { switch (mode) { case 'WALK': return 'Walk'; case 'BICYCLE': return 'Cycle'; case 'TRANSIT': return 'Transit'; default: return mode; } } export function ListingDetail({ detail, onDecide, onClearDecision }: ListingDetailProps) { const allPhotos = [ ...detail.photos, ...detail.floorplans.map(fp => ({ url: fp.url, caption: fp.caption || 'Floorplan', type: 'FLOORPLAN' as string | null })), ]; const pricePerSqm = detail.square_meters ? Math.round(detail.price / detail.square_meters) : null; // Group POI distances by POI name for the travel table const poiGroups = new Map>(); for (const d of detail.poi_distances) { if (!poiGroups.has(d.poi_name)) { poiGroups.set(d.poi_name, new Map()); } poiGroups.get(d.poi_name)!.set(d.travel_mode, d); } // Check which tabs have content const hasOverview = detail.key_features.length > 0 || !!detail.description; const hasTravel = detail.poi_distances.length > 0; const hasPriceHistory = detail.price_history.length > 1; const hasDetails = !!(detail.property_sub_type || detail.furnish_type || detail.council_tax_band || detail.available_from || detail.service_charge != null || detail.lease_left != null); return (
{/* Photo carousel - always visible above tabs */}
{/* Price header */}
£{detail.price.toLocaleString()} {detail.listing_type !== 'BUY' && ( /mo )}
{/* Key metrics */}
{detail.number_of_bedrooms} bed · {detail.square_meters ?? '\u2014'} m² {pricePerSqm && ( <> · £{pricePerSqm}/m² )}
{detail.display_address && (
{detail.display_address}
)}
{/* Action buttons */}
{/* Tabbed sections */} Overview {hasTravel && Travel} {hasPriceHistory && Price} {hasDetails && Details} {/* Overview tab */} {detail.key_features.length > 0 && (

Key Features

    {detail.key_features.map((f, i) => (
  • {f}
  • ))}
)} {detail.description && (

Description

{detail.description}

)} {/* Floorplans */} {detail.floorplans.length > 0 && (

Floorplans

{detail.floorplans.map((fp, i) => ( {fp.caption ))}
)} {/* Agency — wrapped in a labelled block so the Overview tab doesn't collapse to just "Foxtons" when description/key_features/floorplans are empty. */} {detail.agency && (

Listed by

{detail.agency}
)} {!hasOverview && !detail.floorplans.length && !detail.agency && (

No overview information available.

)}
{/* Travel tab */} {hasTravel && (
{Array.from(poiGroups.entries()).map(([poiName, modes]) => ( {(['WALK', 'BICYCLE', 'TRANSIT'] as const).map(mode => { const d = modes.get(mode); return ( ); })} ))}
Destination
{poiName} {d ? formatDuration(d.duration_seconds) : '\u2014'}
)} {/* Price History tab */} {hasPriceHistory && (
{detail.price_history.map((entry) => (
{typeof entry.last_seen === 'string' ? entry.last_seen.split('T')[0] : '—'} £{typeof entry.price === 'number' && Number.isFinite(entry.price) ? entry.price.toLocaleString() : '—'}
))}
)} {/* Details tab */} {hasDetails && (
{detail.property_sub_type && (
Property Type
{detail.property_sub_type}
)} {detail.furnish_type && (
Furnishing
{detail.furnish_type}
)} {detail.council_tax_band && (
Council Tax
Band {detail.council_tax_band}
)} {detail.lease_left != null && (
Lease Remaining
{detail.lease_left} years
)} {detail.service_charge != null && (
Service Charge
£{detail.service_charge.toLocaleString()}
)} {detail.available_from && (
Available From
{formatDate(detail.available_from)}
)}
)}
); }