good changes

This commit is contained in:
Andras Schmelczer 2026-03-25 08:04:48 +00:00
parent 160283f1a1
commit c997ea46a5
26 changed files with 991 additions and 288 deletions

View file

@ -19,6 +19,8 @@ export interface AiFiltersResult {
summary: string;
/** The listing mode used (historical/buy/rent) */
listingType: string;
/** Number of properties matching the proposed filters (excludes travel time) */
matchCount: number;
}
export type AiFilterErrorType = 'auth' | 'limit' | 'error';
@ -43,7 +45,11 @@ interface UseAiFiltersResult {
}
/** Build a human-readable summary of the AI result. */
function buildSummary(filters: FeatureFilters, travelTimeFilters: AiTravelTimeFilter[]): string {
function buildSummary(
filters: FeatureFilters,
travelTimeFilters: AiTravelTimeFilter[],
matchCount: number
): string {
const parts: string[] = [];
for (const [name, value] of Object.entries(filters)) {
@ -63,7 +69,8 @@ function buildSummary(filters: FeatureFilters, travelTimeFilters: AiTravelTimeFi
}
if (parts.length === 0) return 'No filters set';
return `Set ${parts.length} filter${parts.length > 1 ? 's' : ''}: ${parts.join(', ')}`;
const countStr = matchCount.toLocaleString();
return `${countStr} properties match · Set ${parts.length} filter${parts.length > 1 ? 's' : ''}: ${parts.join(', ')}`;
}
export function useAiFilters(): UseAiFiltersResult {
@ -137,13 +144,15 @@ export function useAiFilters(): UseAiFiltersResult {
})
);
const filters = json.filters as FeatureFilters;
const summaryText = buildSummary(filters, travelTimeFilters);
const matchCount: number = json.match_count ?? 0;
const summaryText = buildSummary(filters, travelTimeFilters, matchCount);
const result: AiFiltersResult = {
filters,
travelTimeFilters,
notes: json.notes || '',
summary: summaryText,
listingType: json.listing_type || 'historical',
matchCount,
};
setNotes(result.notes || null);
setSummary(summaryText);

View file

@ -1,14 +1,24 @@
import { useState, useCallback } from 'react';
export function useCollapsibleGroups(): [
Set<string>,
/**
* Manages collapsible group state.
* @param defaultCollapsed When true, groups start collapsed (tracks expanded groups).
* When false (default), groups start expanded (tracks collapsed groups).
*/
export function useCollapsibleGroups(defaultCollapsed = false): [
(name: string) => boolean,
(name: string) => void,
(name: string) => void,
] {
const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
const [toggled, setToggled] = useState<Set<string>>(new Set());
const isExpanded = useCallback(
(name: string) => (defaultCollapsed ? toggled.has(name) : !toggled.has(name)),
[toggled, defaultCollapsed]
);
const toggle = useCallback((name: string) => {
setCollapsed((prev) => {
setToggled((prev) => {
const next = new Set(prev);
if (next.has(name)) next.delete(name);
else next.add(name);
@ -16,14 +26,24 @@ export function useCollapsibleGroups(): [
});
}, []);
const expand = useCallback((name: string) => {
setCollapsed((prev) => {
if (!prev.has(name)) return prev;
const next = new Set(prev);
next.delete(name);
return next;
});
}, []);
const expand = useCallback(
(name: string) => {
setToggled((prev) => {
if (defaultCollapsed) {
if (prev.has(name)) return prev;
const next = new Set(prev);
next.add(name);
return next;
} else {
if (!prev.has(name)) return prev;
const next = new Set(prev);
next.delete(name);
return next;
}
});
},
[defaultCollapsed]
);
return [collapsed, toggle, expand];
return [isExpanded, toggle, expand];
}

View file

@ -321,7 +321,7 @@ export function useDeckLayers({
ttVal as number,
ttVal as number,
clr,
null,
fr,
0,
densityGradientRef.current,
dark,
@ -422,7 +422,7 @@ export function useDeckLayers({
ttVal as number,
ttVal as number,
clr,
null,
fr,
0,
densityGradientRef.current,
dark,

View file

@ -119,11 +119,14 @@ export function useMapData({
const boundsStr = `${bounds.south},${bounds.west},${bounds.north},${bounds.east}`;
const isTravelTimeDrag = activeFeature.startsWith('tt_');
const dragTravelParam = isTravelTimeDrag ? buildTravelParam(activeFeature) : travelParam;
// Travel time fields are computed from the travel param, not regular feature columns.
// Sending a tt_* name as fields would cause a 400 (unknown field). Use empty string instead.
const fieldsParam = isTravelTimeDrag ? '' : activeFeature;
if (usePostcodeView) {
const params = new URLSearchParams({ bounds: boundsStr });
if (filtersStr) params.set('filters', filtersStr);
params.set('fields', activeFeature);
params.set('fields', fieldsParam);
if (dragTravelParam) params.set('travel', dragTravelParam);
fetch(apiUrl('postcodes', params), authHeaders({ signal: dragAbortRef.current.signal }))
@ -140,7 +143,7 @@ export function useMapData({
bounds: boundsStr,
});
if (filtersStr) params.set('filters', filtersStr);
params.set('fields', activeFeature);
params.set('fields', fieldsParam);
if (dragTravelParam) params.set('travel', dragTravelParam);
fetch(apiUrl('hexagons', params), authHeaders({ signal: dragAbortRef.current.signal }))