Boring changes

This commit is contained in:
Andras Schmelczer 2026-07-12 15:10:26 +01:00
parent cfaf58dfba
commit 920119ff48
32 changed files with 912 additions and 88 deletions

View file

@ -57,6 +57,14 @@ import {
isTenureFeatureName,
isTenureFilterName,
} from './tenure-filter';
import {
COUNCIL_FILTER_NAME,
createCouncilFilterKey,
getCouncilFeatureName,
getCouncilWindow,
isCouncilFeatureName,
isCouncilFilterName,
} from './council-filter';
import {
POI_DISTANCE_FILTER_NAME,
TRANSPORT_DISTANCE_FILTER_NAME,
@ -77,6 +85,7 @@ import {
colorOpacityToPercent,
normalizeColorOpacity,
} from './color-opacity';
import { DEFAULT_LISTINGS_MODE, LISTINGS_MODES, type ListingsMode } from './listings';
const POI_NONE_PARAM = '__none';
const CRIME_TYPES_NONE_PARAM = '__none';
@ -94,6 +103,8 @@ export interface UrlState {
crimeTypes?: Set<string>;
basemap: BasemapId;
colorOpacity: number;
/** Which slice of the live for-sale listings to render (new-build only, etc.). */
listings: ListingsMode;
tab: 'properties' | 'area';
travelTime?: TravelTimeInitial;
postcode?: string;
@ -109,6 +120,7 @@ function parseFilters(params: URLSearchParams): FeatureFilters {
const ethnicityParams = params.getAll('ethnicity');
const qualificationParams = params.getAll('qualification');
const tenureParams = params.getAll('tenure');
const councilParams = params.getAll('council');
const amenityDistanceParams = params.getAll('amenityDistance');
const transportDistanceParams = params.getAll('transportDistance');
const amenityCount2KmParams = params.getAll('amenityCount2km');
@ -122,6 +134,7 @@ function parseFilters(params: URLSearchParams): FeatureFilters {
ethnicityParams.length === 0 &&
qualificationParams.length === 0 &&
tenureParams.length === 0 &&
councilParams.length === 0 &&
amenityDistanceParams.length === 0 &&
transportDistanceParams.length === 0 &&
amenityCount2KmParams.length === 0 &&
@ -243,6 +256,20 @@ function parseFilters(params: URLSearchParams): FeatureFilters {
filters[createTenureFilterKey(featureName, index)] = [min, max];
});
councilParams.forEach((entry, index) => {
const parts = entry.split(':');
if (parts.length < 3) return;
const featureName = parts.slice(0, -2).join(':');
const min = Number(parts[parts.length - 2]);
const max = Number(parts[parts.length - 1]);
// getCouncilWindow recognises all three pill columns, incl. the shared
// "% Social rent" (which isCouncilFeatureName excludes from bare folding).
if (getCouncilWindow(featureName) == null || isNaN(min) || isNaN(max)) {
return;
}
filters[createCouncilFilterKey(featureName, index)] = [min, max];
});
const parsePoiParams = (entries: string[], filterName: PoiFilterName, startIndex: number) => {
entries.forEach((entry, index) => {
const parts = entry.split(':');
@ -295,6 +322,7 @@ export function parseUrlState(): UrlState {
overlays: new Set(DEFAULT_OVERLAY_IDS),
basemap: 'standard',
colorOpacity: DEFAULT_COLOR_OPACITY,
listings: DEFAULT_LISTINGS_MODE,
tab: 'area',
};
@ -363,6 +391,13 @@ export function parseUrlState(): UrlState {
}
}
// Listings mode: which slice of the for-sale listings is shown. Absent means
// the default ('all'); an explicit value survives a reload.
const listings = params.get('listings');
if (listings && (LISTINGS_MODES as readonly string[]).includes(listings)) {
result.listings = listings as ListingsMode;
}
// Tab: full name
const tab = params.get('tab');
if (tab === 'properties' || tab === 'area') {
@ -436,7 +471,8 @@ export function stateToParams(
basemap?: BasemapId,
selectedCrimeTypes?: Set<string>,
selectedPostcode?: string,
colorOpacity?: number
colorOpacity?: number,
listingsMode?: ListingsMode
): URLSearchParams {
const params = new URLSearchParams();
@ -500,6 +536,15 @@ export function stateToParams(
continue;
}
// After tenure: a "Council housing:" key never matches isTenureFilterName,
// and a bare "% Social rent" is handled by the tenure branch above.
const councilFeatureName = getCouncilFeatureName(name);
if (councilFeatureName && isCouncilFilterName(name)) {
const [min, max] = value as [number, number];
params.append('council', `${councilFeatureName}:${min}:${max}`);
continue;
}
const amenityDistanceFeatureName = getPoiDistanceFeatureName(name);
if (amenityDistanceFeatureName && isPoiDistanceFilterName(name)) {
const [min, max] = value as [number, number];
@ -577,6 +622,12 @@ export function stateToParams(
}
}
// Emit the listings mode only when it deviates from the default, matching the
// "only serialize non-default" convention above (keeps default URLs clean).
if (listingsMode && listingsMode !== DEFAULT_LISTINGS_MODE) {
params.set('listings', listingsMode);
}
// Travel time: repeated `tt` params
if (travelTimeEntries) {
for (const entry of dedupeTravelTimeEntries(travelTimeEntries)) {
@ -612,6 +663,7 @@ export function summarizeParams(queryString: string): string {
const ethnicityParams = params.getAll('ethnicity');
const qualificationParams = params.getAll('qualification');
const tenureParams = params.getAll('tenure');
const councilParams = params.getAll('council');
const amenityDistanceParams = params.getAll('amenityDistance');
const transportDistanceParams = params.getAll('transportDistance');
const amenityCount2KmParams = params.getAll('amenityCount2km');
@ -625,6 +677,7 @@ export function summarizeParams(queryString: string): string {
ethnicityParams.length > 0 ||
qualificationParams.length > 0 ||
tenureParams.length > 0 ||
councilParams.length > 0 ||
amenityDistanceParams.length > 0 ||
transportDistanceParams.length > 0 ||
amenityCount2KmParams.length > 0 ||
@ -640,6 +693,7 @@ export function summarizeParams(queryString: string): string {
if (isEthnicityFeatureName(name)) return ETHNICITIES_FILTER_NAME;
if (isQualificationFeatureName(name)) return QUALIFICATIONS_FILTER_NAME;
if (isTenureFeatureName(name)) return TENURE_FILTER_NAME;
if (isCouncilFeatureName(name)) return COUNCIL_FILTER_NAME;
const poiFilterName = getPoiFilterName(name);
if (poiFilterName) return poiFilterName;
return name;
@ -667,6 +721,9 @@ export function summarizeParams(queryString: string): string {
for (let i = 0; i < tenureParams.length; i++) {
filterNames.push(TENURE_FILTER_NAME);
}
for (let i = 0; i < councilParams.length; i++) {
filterNames.push(COUNCIL_FILTER_NAME);
}
for (let i = 0; i < amenityDistanceParams.length; i++) {
filterNames.push(POI_DISTANCE_FILTER_NAME);
}