This commit is contained in:
Andras Schmelczer 2026-06-10 21:33:28 +01:00
parent d3418c67cc
commit 5135429d91
13 changed files with 383 additions and 276 deletions

View file

@ -15,6 +15,13 @@ export interface AiTravelTimeFilter {
max?: number;
}
export interface AiMatchBounds {
south: number;
west: number;
north: number;
east: number;
}
export interface AiFiltersResult {
filters: FeatureFilters;
travelTimeFilters: AiTravelTimeFilter[];
@ -23,6 +30,8 @@ export interface AiFiltersResult {
summary: string;
/** Number of properties matching the proposed filters (excludes travel time) */
matchCount: number;
/** Bounding box of the matching properties, if any matched */
matchBounds: AiMatchBounds | null;
}
export type AiFilterErrorType = 'auth' | 'limit' | 'error';
@ -152,6 +161,7 @@ export function useAiFilters(): UseAiFiltersResult {
notes: json.notes || '',
summary: summaryText,
matchCount,
matchBounds: (json.match_bounds as AiMatchBounds | undefined) ?? null,
};
setNotes(result.notes || null);
setSummary(summaryText);

View file

@ -12,8 +12,11 @@ interface FilterCountsResponse {
}
/**
* Fetches per-filter rejection counts: for each active filter,
* how many in-bounds properties that filter removes.
* Single source of truth for the in-view property count. Returns:
* - `total`: exact count of in-bounds properties passing the active filters
* (or the full in-bounds total when there are none); null until first load.
* - `impacts`: per-filter rejection counts how many in-bounds properties
* each active filter removes.
*/
export function useFilterCounts(
filters: FeatureFilters,
@ -23,7 +26,10 @@ export function useFilterCounts(
shareCode?: string
) {
const [impacts, setImpacts] = useState<Record<string, number>>({});
const [total, setTotal] = useState<number>(0);
// null = not loaded yet for the current bounds (hide the counter until the
// first response). The number is the exact count of in-bounds properties
// passing the active filters — and, with no filters, the total in bounds.
const [total, setTotal] = useState<number | null>(null);
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const abortRef = useRef<AbortController | null>(null);
@ -39,16 +45,7 @@ export function useFilterCounts(
if (!bounds) {
abortRef.current?.abort();
setImpacts({});
setTotal(0);
return;
}
const filterCount = Object.keys(filters).length;
const hasTravelFilters = travelTimeEntries.some((e) => e.slug && e.timeRange);
if (filterCount === 0 && !hasTravelFilters) {
abortRef.current?.abort();
setImpacts({});
setTotal(0);
setTotal(null);
return;
}

View file

@ -31,7 +31,9 @@ export function useTutorial(initialLoading: boolean, isMobile: boolean, blocked
target: '[data-tutorial="map"]',
title: t('tutorial.step3Title'),
content: t('tutorial.step3Content'),
placement: 'top' as const,
// The map fills the viewport; directional placements push the tooltip
// off-screen. Center it over the map instead.
placement: 'center' as const,
skipBeacon: true,
},
{