import { ExternalLink, Bed, Maximize2, PoundSterling, Clock, Building, Footprints, Bike, Train } from 'lucide-react'; import { Button } from './ui/button'; import type { PropertyProperties, POIDistanceInfo } from '@/types'; function formatDuration(seconds: number): string { const minutes = Math.round(seconds / 60); if (minutes < 60) return `${minutes}m`; const hours = Math.floor(minutes / 60); const mins = minutes % 60; return mins > 0 ? `${hours}h${mins}m` : `${hours}h`; } function TravelModeIcon({ mode }: { mode: string }) { switch (mode) { case 'WALK': return ; case 'BICYCLE': return ; case 'TRANSIT': return ; default: return null; } } function POIDistanceBadges({ distances }: { distances: POIDistanceInfo[] }) { if (!distances || distances.length === 0) return null; // Group by POI name const byPoi = new Map(); for (const d of distances) { const existing = byPoi.get(d.poi_name) || []; existing.push(d); byPoi.set(d.poi_name, existing); } return (
{Array.from(byPoi.entries()).map(([poiName, dists]) => (
{poiName}: {dists.map(d => ( {formatDuration(d.duration_seconds)} ))}
))}
); } interface PropertyCardProps { property: PropertyProperties; variant?: 'compact' | 'full'; isHighlighted?: boolean; avgPricePerSqm?: number; onClick?: () => void; } export function PropertyCard({ property, variant = 'compact', isHighlighted = false, avgPricePerSqm, onClick, }: PropertyCardProps) { const lastSeenDate = property.last_seen.split('T')[0]; const lastSeenDays = Math.round((Date.now() - new Date(lastSeenDate).getTime()) / (1000 * 60 * 60 * 24)); // Determine if this is a good deal const isGoodDeal = avgPricePerSqm && property.qmprice > 0 && property.qmprice < avgPricePerSqm * 0.9; const isExpensive = avgPricePerSqm && property.qmprice > avgPricePerSqm * 1.1; const priceIndicator = isGoodDeal ? { color: 'text-green-600 bg-green-50', label: 'Good deal' } : isExpensive ? { color: 'text-red-600 bg-red-50', label: 'Above avg' } : null; const handleClick = () => { window.open(property.url, '_blank', 'noopener,noreferrer'); onClick?.(); }; if (variant === 'compact') { return (
{/* Thumbnail */}
{property.photo_thumbnail && ( Property )}
{/* Details */}
£{property.total_price.toLocaleString()} {property.listing_type !== 'BUY' && ( /mo )}
{priceIndicator && ( {priceIndicator.label} )}
{property.rooms} {property.qm} m² £{property.qmprice}/m²
{lastSeenDays}d ago {property.agency}
); } // Full variant (for popup/detail view) return (
{/* Header with image and price */}
{property.photo_thumbnail && ( Property )}
£{property.total_price.toLocaleString()} {property.listing_type !== 'BUY' && ( /mo )}
{priceIndicator && ( {priceIndicator.label} )}
{/* Stats grid */}
{property.rooms} bedrooms
{property.qm}
£{property.qmprice}/m²
{property.listing_type !== 'BUY' && property.available_from && (
Available {property.available_from}
)} {property.listing_type === 'BUY' && (
Seen {lastSeenDays}d ago
)}
{/* Agency and last seen */}
{property.agency} Seen {lastSeenDays} days ago
{/* POI Distances */} {property.poi_distances && property.poi_distances.length > 0 && (
Travel times
)} {/* Price history */} {property.price_history.length > 1 && (
Price history
{property.price_history.slice(0, 5).map((entry) => (
{entry.last_seen.split('T')[0]} £{entry.price.toLocaleString()}
))}
)} {/* Actions */}
); }