Fix: Improve UX with empty state, miles, and metric labels
1. Show empty state by default on home page
- Don't fetch or display schools until user searches
- Show helpful message prompting users to search
- Only fetch schools when search params are present
2. Change distance search to miles
- Display 0.5, 1, and 2 mile options instead of km
- Convert miles to km when sending to API (backend expects km)
- Convert km back to miles for display in location banner
- Maintains backend compatibility while improving UX
3. Fix metric labels in rankings dropdown
- Backend returns 'name' and 'type' fields
- Frontend expects 'label' and 'format' fields
- Added transformation in fetchMetrics to map fields
- Dropdown now shows proper labels like "RWM Combined %"
instead of technical codes like "rwm_expected_pct"
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,12 +31,24 @@ export default async function HomePage({ searchParams }: HomePageProps) {
|
||||
|
||||
// Parse search params
|
||||
const page = parseInt(params.page || '1');
|
||||
const radius = params.radius ? parseInt(params.radius) : undefined;
|
||||
const radius = params.radius ? parseFloat(params.radius) : undefined;
|
||||
|
||||
// Check if user has performed a search
|
||||
const hasSearchParams = !!(
|
||||
params.search ||
|
||||
params.local_authority ||
|
||||
params.school_type ||
|
||||
params.postcode
|
||||
);
|
||||
|
||||
// Fetch data on server with error handling
|
||||
try {
|
||||
const [schoolsData, filtersData] = await Promise.all([
|
||||
fetchSchools({
|
||||
const filtersData = await fetchFilters();
|
||||
|
||||
// Only fetch schools if there are search parameters
|
||||
let schoolsData;
|
||||
if (hasSearchParams) {
|
||||
schoolsData = await fetchSchools({
|
||||
search: params.search,
|
||||
local_authority: params.local_authority,
|
||||
school_type: params.school_type,
|
||||
@@ -44,9 +56,11 @@ export default async function HomePage({ searchParams }: HomePageProps) {
|
||||
radius,
|
||||
page,
|
||||
page_size: 50,
|
||||
}),
|
||||
fetchFilters(),
|
||||
]);
|
||||
});
|
||||
} else {
|
||||
// Empty state by default
|
||||
schoolsData = { schools: [], page: 1, page_size: 50, total: 0, total_pages: 0 };
|
||||
}
|
||||
|
||||
return (
|
||||
<HomeView
|
||||
|
||||
@@ -29,7 +29,9 @@ export function FilterBar({ filters, showLocationSearch = true }: FilterBarProps
|
||||
const currentLA = searchParams.get('local_authority') || '';
|
||||
const currentType = searchParams.get('school_type') || '';
|
||||
const currentPostcode = searchParams.get('postcode') || '';
|
||||
const currentRadius = searchParams.get('radius') || '5';
|
||||
const currentRadiusKm = searchParams.get('radius') || '0.8';
|
||||
// Convert km back to miles for display
|
||||
const currentRadiusMiles = (parseFloat(currentRadiusKm) / 1.60934).toFixed(1);
|
||||
|
||||
const updateURL = useCallback((updates: Record<string, string>) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
@@ -77,7 +79,7 @@ export function FilterBar({ filters, showLocationSearch = true }: FilterBarProps
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target as HTMLFormElement);
|
||||
const postcode = formData.get('postcode') as string;
|
||||
const radius = formData.get('radius') as string;
|
||||
const radiusMiles = formData.get('radius') as string;
|
||||
|
||||
if (!postcode.trim()) {
|
||||
alert('Please enter a postcode');
|
||||
@@ -89,7 +91,10 @@ export function FilterBar({ filters, showLocationSearch = true }: FilterBarProps
|
||||
return;
|
||||
}
|
||||
|
||||
updateURL({ postcode, radius, search: '' });
|
||||
// Convert miles to km for API (backend expects km)
|
||||
const radiusKm = (parseFloat(radiusMiles) * 1.60934).toFixed(1);
|
||||
|
||||
updateURL({ postcode, radius: radiusKm, search: '' });
|
||||
};
|
||||
|
||||
const handleClearFilters = () => {
|
||||
@@ -138,12 +143,10 @@ export function FilterBar({ filters, showLocationSearch = true }: FilterBarProps
|
||||
className={styles.postcodeInput}
|
||||
required
|
||||
/>
|
||||
<select name="radius" defaultValue={currentRadius} className={styles.radiusSelect}>
|
||||
<option value="1">1 km</option>
|
||||
<option value="2">2 km</option>
|
||||
<option value="5">5 km</option>
|
||||
<option value="10">10 km</option>
|
||||
<option value="20">20 km</option>
|
||||
<select name="radius" defaultValue={currentRadiusMiles} className={styles.radiusSelect}>
|
||||
<option value="0.5">0.5 miles</option>
|
||||
<option value="1">1 mile</option>
|
||||
<option value="2">2 miles</option>
|
||||
</select>
|
||||
<button type="submit" className={styles.searchButton}>
|
||||
Search
|
||||
|
||||
@@ -46,7 +46,7 @@ export function HomeView({ initialSchools, filters }: HomeViewProps) {
|
||||
<div className={styles.locationBanner}>
|
||||
<span className={styles.locationIcon}>📍</span>
|
||||
<span>
|
||||
Showing schools within {initialSchools.location_info.radius}km of{' '}
|
||||
Showing schools within {(initialSchools.location_info.radius / 1.60934).toFixed(1)} miles of{' '}
|
||||
<strong>{initialSchools.location_info.postcode}</strong>
|
||||
</span>
|
||||
</div>
|
||||
@@ -74,14 +74,22 @@ export function HomeView({ initialSchools, filters }: HomeViewProps) {
|
||||
|
||||
{initialSchools.schools.length === 0 ? (
|
||||
<EmptyState
|
||||
title="No schools found"
|
||||
message="Try adjusting your search criteria or filters to find schools."
|
||||
action={{
|
||||
title={hasSearch ? "No schools found" : "Search for Schools"}
|
||||
message={
|
||||
hasSearch
|
||||
? "Try adjusting your search criteria or filters to find schools."
|
||||
: "Use the search above to find schools by name or search by location to discover schools near a postcode."
|
||||
}
|
||||
action={
|
||||
hasSearch
|
||||
? {
|
||||
label: 'Clear Filters',
|
||||
onClick: () => {
|
||||
window.location.href = '/';
|
||||
},
|
||||
}}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -225,7 +225,20 @@ export async function fetchMetrics(
|
||||
},
|
||||
});
|
||||
|
||||
return handleResponse<MetricsResponse>(response);
|
||||
const data = await handleResponse<any>(response);
|
||||
|
||||
// Transform backend response to match our TypeScript types
|
||||
// Backend uses 'name' and 'type', we use 'label' and 'format'
|
||||
return {
|
||||
metrics: data.metrics.map((metric: any) => ({
|
||||
key: metric.key,
|
||||
label: metric.name, // Map 'name' to 'label'
|
||||
description: metric.description,
|
||||
category: metric.category,
|
||||
format: metric.type, // Map 'type' to 'format'
|
||||
hasNationalAverage: metric.hasNationalAverage,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user