Fix crime & add actual listings
This commit is contained in:
parent
017902b8e6
commit
ebe7bbb51d
34 changed files with 2014 additions and 172754 deletions
|
|
@ -15,6 +15,7 @@ import type {
|
|||
FeatureMeta,
|
||||
Bounds,
|
||||
MapFlyToOptions,
|
||||
ActualListing,
|
||||
} from '../../types';
|
||||
|
||||
import {
|
||||
|
|
@ -41,6 +42,7 @@ interface MapProps {
|
|||
postcodeData: PostcodeFeature[];
|
||||
usePostcodeView: boolean;
|
||||
pois: POI[];
|
||||
actualListings?: ActualListing[];
|
||||
onViewChange: (params: ViewChangeParams) => void;
|
||||
viewFeature: string | null;
|
||||
colorRange: [number, number] | null;
|
||||
|
|
@ -77,6 +79,20 @@ interface MapProps {
|
|||
}
|
||||
|
||||
const EMPTY_TRAVEL_ENTRIES: TravelTimeEntry[] = [];
|
||||
const EMPTY_ACTUAL_LISTINGS: ActualListing[] = [];
|
||||
|
||||
function formatListingPrice(price: number): string {
|
||||
return `£${price.toLocaleString()}`;
|
||||
}
|
||||
|
||||
function formatListingHeadline(listing: ActualListing): string | null {
|
||||
const parts: string[] = [];
|
||||
if (listing.bedrooms != null) parts.push(`${listing.bedrooms} bed`);
|
||||
if (listing.bathrooms != null) parts.push(`${listing.bathrooms} bath`);
|
||||
if (listing.property_sub_type) parts.push(listing.property_sub_type);
|
||||
else if (listing.property_type) parts.push(listing.property_type);
|
||||
return parts.length > 0 ? parts.join(' · ') : null;
|
||||
}
|
||||
|
||||
interface Dimensions {
|
||||
width: number;
|
||||
|
|
@ -263,6 +279,7 @@ export default memo(function Map({
|
|||
postcodeData,
|
||||
usePostcodeView,
|
||||
pois,
|
||||
actualListings = EMPTY_ACTUAL_LISTINGS,
|
||||
onViewChange,
|
||||
viewFeature,
|
||||
colorRange,
|
||||
|
|
@ -442,6 +459,8 @@ export default memo(function Map({
|
|||
layers,
|
||||
popupInfo,
|
||||
clearPopupInfo,
|
||||
listingPopup,
|
||||
clearListingPopup,
|
||||
hoverPosition,
|
||||
countRange,
|
||||
postcodeCountRange,
|
||||
|
|
@ -453,6 +472,7 @@ export default memo(function Map({
|
|||
usePostcodeView,
|
||||
zoom: viewState.zoom,
|
||||
pois,
|
||||
actualListings,
|
||||
viewFeature,
|
||||
colorRange,
|
||||
filterRange,
|
||||
|
|
@ -677,6 +697,77 @@ export default memo(function Map({
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
{listingPopup && (
|
||||
<div
|
||||
className="pointer-events-auto absolute bg-white dark:bg-warm-800 rounded-lg shadow-lg text-sm dark:text-white max-w-[280px]"
|
||||
style={{
|
||||
left: listingPopup.x,
|
||||
top: listingPopup.y - 12,
|
||||
transform: 'translate(-50%, -100%)',
|
||||
zIndex: 9999,
|
||||
}}
|
||||
onMouseLeave={clearListingPopup}
|
||||
>
|
||||
<button
|
||||
className="pointer-events-auto absolute -top-2 -right-2 w-5 h-5 flex items-center justify-center rounded-full bg-warm-200 dark:bg-warm-700 text-warm-500 dark:text-warm-400 hover:text-warm-700 dark:hover:text-warm-300 shadow-sm"
|
||||
onClick={clearListingPopup}
|
||||
>
|
||||
<CloseIcon className="w-3 h-3" />
|
||||
</button>
|
||||
<a
|
||||
href={listingPopup.listing.listing_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block px-3 py-2"
|
||||
>
|
||||
{listingPopup.listing.asking_price != null && (
|
||||
<div className="text-base font-bold text-teal-600 dark:text-teal-400">
|
||||
{formatListingPrice(listingPopup.listing.asking_price)}
|
||||
{listingPopup.listing.price_qualifier ? (
|
||||
<span className="ml-1 text-xs font-medium text-warm-500 dark:text-warm-400">
|
||||
{listingPopup.listing.price_qualifier}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
{formatListingHeadline(listingPopup.listing) && (
|
||||
<div className="text-xs text-warm-700 dark:text-warm-200 mt-0.5">
|
||||
{formatListingHeadline(listingPopup.listing)}
|
||||
</div>
|
||||
)}
|
||||
{listingPopup.listing.address && (
|
||||
<div className="text-xs text-warm-500 dark:text-warm-400 mt-0.5 line-clamp-2">
|
||||
{listingPopup.listing.address}
|
||||
</div>
|
||||
)}
|
||||
{listingPopup.listing.postcode && (
|
||||
<div className="text-[11px] text-warm-400 dark:text-warm-500 mt-0.5">
|
||||
{listingPopup.listing.postcode}
|
||||
</div>
|
||||
)}
|
||||
{listingPopup.listing.floor_area_sqm != null && (
|
||||
<div className="text-[11px] text-warm-500 dark:text-warm-400 mt-0.5">
|
||||
{Math.round(listingPopup.listing.floor_area_sqm)} sqm
|
||||
{listingPopup.listing.asking_price_per_sqm != null
|
||||
? ` · £${Math.round(listingPopup.listing.asking_price_per_sqm).toLocaleString()}/sqm`
|
||||
: ''}
|
||||
</div>
|
||||
)}
|
||||
{listingPopup.listing.features.length > 0 && (
|
||||
<ul className="mt-1.5 text-[11px] text-warm-600 dark:text-warm-300 list-disc pl-4 space-y-0.5">
|
||||
{listingPopup.listing.features.slice(0, 3).map((feature, idx) => (
|
||||
<li key={idx} className="line-clamp-1">
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<div className="mt-1.5 text-[11px] text-teal-600 dark:text-teal-400 font-medium">
|
||||
Open listing ↗
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{hoverPosition && hoveredHexagonId && hoveredHexagonId !== selectedHexagonId && (
|
||||
<HoverCard
|
||||
x={hoverPosition.x}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue