This commit is contained in:
Andras Schmelczer 2026-03-15 17:38:26 +00:00
parent 80c093b7ba
commit f72c43a9fa
101 changed files with 2168 additions and 1177 deletions

View file

@ -70,7 +70,11 @@ export async function shortenUrl(params: string): Promise<string> {
return `${window.location.origin}${data.url}`;
}
export function buildFilterString(filters: FeatureFilters, features: FeatureMeta[], exclude?: string): string {
export function buildFilterString(
filters: FeatureFilters,
features: FeatureMeta[],
exclude?: string
): string {
const entries = Object.entries(filters);
if (entries.length === 0) return '';
return entries

View file

@ -87,12 +87,7 @@ export const POI_GROUP_COLORS: Record<string, [number, number, number]> = {
export const POI_DEFAULT_COLOR: [number, number, number] = [107, 114, 128];
/** Categories only shown when zoomed in past MINOR_POI_ZOOM_THRESHOLD */
export const MINOR_POI_CATEGORIES = new Set([
'Bus stop',
'Taxi rank',
'EV Charging',
'Playground',
]);
export const MINOR_POI_CATEGORIES = new Set(['Bus stop', 'Taxi rank', 'EV Charging', 'Playground']);
/** Zoom level below which minor POI categories are hidden */
export const MINOR_POI_ZOOM_THRESHOLD = 14;
@ -217,16 +212,16 @@ export const STACKED_ENUM_GROUPS: Record<
* 10 colors chosen for perceptual distinctness in both light and dark modes.
*/
export const ENUM_PALETTE: [number, number, number][] = [
[59, 130, 246], // blue-500
[249, 115, 22], // orange-500
[139, 92, 246], // violet-500
[34, 197, 94], // green-500
[239, 68, 68], // red-500
[6, 182, 212], // cyan-500
[236, 72, 153], // pink-500
[245, 158, 11], // amber-500
[20, 184, 166], // teal-500
[107, 114, 128], // gray-500
[59, 130, 246], // blue-500
[249, 115, 22], // orange-500
[139, 92, 246], // violet-500
[34, 197, 94], // green-500
[239, 68, 68], // red-500
[6, 182, 212], // cyan-500
[236, 72, 153], // pink-500
[245, 158, 11], // amber-500
[20, 184, 166], // teal-500
[107, 114, 128], // gray-500
];
/** Colors for stacked bar segments */

View file

@ -42,11 +42,11 @@ const ZOOPLA_RADII = [0.25, 0.5, 1, 3, 5, 10, 15, 20, 25, 30];
// Rightmove only accepts these specific price values
const RIGHTMOVE_PRICES = [
50000, 60000, 70000, 80000, 90000, 100000, 110000, 120000, 125000, 130000, 140000, 150000,
160000, 170000, 175000, 180000, 190000, 200000, 210000, 220000, 230000, 240000, 250000, 260000,
270000, 280000, 290000, 300000, 325000, 350000, 375000, 400000, 425000, 450000, 475000, 500000,
550000, 600000, 650000, 700000, 800000, 900000, 1000000, 1250000, 1500000, 1750000, 2000000,
2500000, 3000000, 4000000, 5000000, 7500000, 10000000, 15000000, 20000000,
50000, 60000, 70000, 80000, 90000, 100000, 110000, 120000, 125000, 130000, 140000, 150000, 160000,
170000, 175000, 180000, 190000, 200000, 210000, 220000, 230000, 240000, 250000, 260000, 270000,
280000, 290000, 300000, 325000, 350000, 375000, 400000, 425000, 450000, 475000, 500000, 550000,
600000, 650000, 700000, 800000, 900000, 1000000, 1250000, 1500000, 1750000, 2000000, 2500000,
3000000, 4000000, 5000000, 7500000, 10000000, 15000000, 20000000,
];
function nearestRadius(target: number, allowed: number[]): number {
@ -99,15 +99,23 @@ export function buildPropertySearchUrls({
const bedroomFilter = filters['Bedrooms'];
const minBedrooms =
Array.isArray(bedroomFilter) && typeof bedroomFilter[0] === 'number' ? bedroomFilter[0] : undefined;
Array.isArray(bedroomFilter) && typeof bedroomFilter[0] === 'number'
? bedroomFilter[0]
: undefined;
const maxBedrooms =
Array.isArray(bedroomFilter) && typeof bedroomFilter[1] === 'number' ? bedroomFilter[1] : undefined;
Array.isArray(bedroomFilter) && typeof bedroomFilter[1] === 'number'
? bedroomFilter[1]
: undefined;
const bathroomFilter = filters['Bathrooms'];
const minBathrooms =
Array.isArray(bathroomFilter) && typeof bathroomFilter[0] === 'number' ? bathroomFilter[0] : undefined;
Array.isArray(bathroomFilter) && typeof bathroomFilter[0] === 'number'
? bathroomFilter[0]
: undefined;
const maxBathrooms =
Array.isArray(bathroomFilter) && typeof bathroomFilter[1] === 'number' ? bathroomFilter[1] : undefined;
Array.isArray(bathroomFilter) && typeof bathroomFilter[1] === 'number'
? bathroomFilter[1]
: undefined;
const tenureFilter = filters['Leasehold/Freehold'];
const selectedTenures =
@ -123,8 +131,10 @@ export function buildPropertySearchUrls({
rmParams.set('useLocationIdentifier', 'true');
rmParams.set('locationIdentifier', rightmoveLocationId);
rmParams.set('radius', String(nearestRadius(radiusMiles, RIGHTMOVE_RADII)));
if (minPrice !== undefined) rmParams.set('minPrice', String(snapRightmovePrice(minPrice, 'floor')));
if (maxPrice !== undefined) rmParams.set('maxPrice', String(snapRightmovePrice(maxPrice, 'ceil')));
if (minPrice !== undefined)
rmParams.set('minPrice', String(snapRightmovePrice(minPrice, 'floor')));
if (maxPrice !== undefined)
rmParams.set('maxPrice', String(snapRightmovePrice(maxPrice, 'ceil')));
if (minBedrooms !== undefined) rmParams.set('minBedrooms', String(Math.floor(minBedrooms)));
if (maxBedrooms !== undefined) rmParams.set('maxBedrooms', String(Math.ceil(maxBedrooms)));
if (minBathrooms !== undefined) rmParams.set('minBathrooms', String(Math.floor(minBathrooms)));

View file

@ -494,10 +494,7 @@ const FEATURE_ICON_PATHS: Record<string, ReactNode> = {
/**
* Returns a complete SVG icon element for a given feature name, or null if unmapped.
*/
export function getFeatureIcon(
featureName: string,
className: string,
): ReactElement | null {
export function getFeatureIcon(featureName: string, className: string): ReactElement | null {
const paths = FEATURE_ICON_PATHS[featureName];
if (!paths) return null;
return (

View file

@ -30,8 +30,18 @@ export function formatDuration(d: string): string {
}
const MONTH_NAMES = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
export function formatTransactionDate(fractionalYear: number): string {

View file

@ -24,8 +24,6 @@ const GROUP_ICONS: Record<string, ComponentType<{ className?: string }>> = {
Property: TagIcon,
};
export function getGroupIcon(
group: string,
): ComponentType<{ className?: string }> | null {
export function getGroupIcon(group: string): ComponentType<{ className?: string }> | null {
return GROUP_ICONS[group] ?? null;
}