Improve UI

This commit is contained in:
Andras Schmelczer 2026-02-05 21:19:19 +00:00
parent 5fe192d25a
commit ae29662c92
14 changed files with 221 additions and 313 deletions

View file

@ -5,10 +5,10 @@ import type { ViewState } from '../types';
// =============================================================================
/** Geographic bounds constraining map panning [west, south, east, north] */
export const MAP_BOUNDS: [number, number, number, number] = [-12, 49, 4, 62];
export const MAP_BOUNDS: [number, number, number, number] = [-9.5, 49, 5, 57];
/** Minimum zoom level (can't zoom out further) */
export const MAP_MIN_ZOOM = 5;
export const MAP_MIN_ZOOM = 5.5;
/** Maximum zoom level for tile fetching (map extrapolates beyond this) */
export const TILE_MAX_ZOOM = 15;
@ -74,3 +74,43 @@ export const TWEMOJI_BASE = 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/
/** OpenStreetMap attribution HTML */
export const OSM_ATTRIBUTION = '© <a href="https://openstreetmap.org">OpenStreetMap</a>';
// =============================================================================
// Crime Category Breakdowns
// =============================================================================
/** Component crimes that make up each aggregate crime metric */
export const CRIME_BREAKDOWNS: Record<string, string[]> = {
'Serious crime (avg/yr)': [
'Violence and sexual offences (avg/yr)',
'Robbery (avg/yr)',
'Burglary (avg/yr)',
'Possession of weapons (avg/yr)',
],
'Minor crime (avg/yr)': [
'Anti-social behaviour (avg/yr)',
'Criminal damage and arson (avg/yr)',
'Shoplifting (avg/yr)',
'Bicycle theft (avg/yr)',
'Theft from the person (avg/yr)',
'Other theft (avg/yr)',
'Vehicle crime (avg/yr)',
'Public order (avg/yr)',
'Drugs (avg/yr)',
'Other crime (avg/yr)',
],
};
/** Colors for crime breakdown segments (designed for 10 distinct categories) */
export const CRIME_SEGMENT_COLORS = [
'#ef4444', // red-500
'#f97316', // orange-500
'#eab308', // yellow-500
'#22c55e', // green-500
'#14b8a6', // teal-500
'#06b6d4', // cyan-500
'#3b82f6', // blue-500
'#8b5cf6', // violet-500
'#d946ef', // fuchsia-500
'#ec4899', // pink-500
];

View file

@ -22,15 +22,3 @@ export function groupFeaturesByCategory(features: FeatureMeta[]): FeatureGroup[]
return groups;
}
// Feature lookup utilities
export function getFeatureByName(
name: string,
features: FeatureMeta[]
): FeatureMeta | undefined {
return features.find((f) => f.name === name);
}
export function createFeatureMap(features: FeatureMeta[]): Map<string, FeatureMeta> {
return new Map(features.map((f) => [f.name, f]));
}

View file

@ -1,33 +1,5 @@
import type { Property } from '../types';
// Field aliases: maps human-readable names to snake_case names
// The server may return either depending on source
const FIELD_ALIASES: Record<string, string[]> = {
price: ['Last known price', 'latest_price'],
pricePerSqm: ['Price per sqm', 'price_per_sqm'],
floorArea: ['Total floor area (sqm)', 'total_floor_area'],
rooms: ['Rooms (including bedrooms & bathrooms)', 'number_habitable_rooms'],
constructionAge: ['Approximate construction age', 'construction_age_band'],
councilTax: ['Council tax (£/yr)'],
councilTaxD: ['Council tax Band D (£/yr)'],
};
export function getPropertyNumber(
property: Property,
field: keyof typeof FIELD_ALIASES
): number | undefined {
const keys = FIELD_ALIASES[field];
if (!keys) return undefined;
for (const key of keys) {
const v = property[key];
if (v !== undefined && v !== null && typeof v === 'number') {
return v;
}
}
return undefined;
}
// Generic getter for any field names (for dynamic lookups)
export function getNum(property: Property, ...keys: string[]): number | undefined {
for (const key of keys) {