Fix: Improve UX with empty state, miles, and metric labels
All checks were successful
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s

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:
Tudor
2026-02-02 22:21:55 +00:00
parent b3fc55faf6
commit 1c0e6298f2
4 changed files with 63 additions and 25 deletions

View File

@@ -31,12 +31,24 @@ export default async function HomePage({ searchParams }: HomePageProps) {
// Parse search params // Parse search params
const page = parseInt(params.page || '1'); 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 // Fetch data on server with error handling
try { try {
const [schoolsData, filtersData] = await Promise.all([ const filtersData = await fetchFilters();
fetchSchools({
// Only fetch schools if there are search parameters
let schoolsData;
if (hasSearchParams) {
schoolsData = await fetchSchools({
search: params.search, search: params.search,
local_authority: params.local_authority, local_authority: params.local_authority,
school_type: params.school_type, school_type: params.school_type,
@@ -44,9 +56,11 @@ export default async function HomePage({ searchParams }: HomePageProps) {
radius, radius,
page, page,
page_size: 50, page_size: 50,
}), });
fetchFilters(), } else {
]); // Empty state by default
schoolsData = { schools: [], page: 1, page_size: 50, total: 0, total_pages: 0 };
}
return ( return (
<HomeView <HomeView

View File

@@ -29,7 +29,9 @@ export function FilterBar({ filters, showLocationSearch = true }: FilterBarProps
const currentLA = searchParams.get('local_authority') || ''; const currentLA = searchParams.get('local_authority') || '';
const currentType = searchParams.get('school_type') || ''; const currentType = searchParams.get('school_type') || '';
const currentPostcode = searchParams.get('postcode') || ''; 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 updateURL = useCallback((updates: Record<string, string>) => {
const params = new URLSearchParams(searchParams); const params = new URLSearchParams(searchParams);
@@ -77,7 +79,7 @@ export function FilterBar({ filters, showLocationSearch = true }: FilterBarProps
e.preventDefault(); e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement); const formData = new FormData(e.target as HTMLFormElement);
const postcode = formData.get('postcode') as string; const postcode = formData.get('postcode') as string;
const radius = formData.get('radius') as string; const radiusMiles = formData.get('radius') as string;
if (!postcode.trim()) { if (!postcode.trim()) {
alert('Please enter a postcode'); alert('Please enter a postcode');
@@ -89,7 +91,10 @@ export function FilterBar({ filters, showLocationSearch = true }: FilterBarProps
return; 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 = () => { const handleClearFilters = () => {
@@ -138,12 +143,10 @@ export function FilterBar({ filters, showLocationSearch = true }: FilterBarProps
className={styles.postcodeInput} className={styles.postcodeInput}
required required
/> />
<select name="radius" defaultValue={currentRadius} className={styles.radiusSelect}> <select name="radius" defaultValue={currentRadiusMiles} className={styles.radiusSelect}>
<option value="1">1 km</option> <option value="0.5">0.5 miles</option>
<option value="2">2 km</option> <option value="1">1 mile</option>
<option value="5">5 km</option> <option value="2">2 miles</option>
<option value="10">10 km</option>
<option value="20">20 km</option>
</select> </select>
<button type="submit" className={styles.searchButton}> <button type="submit" className={styles.searchButton}>
Search Search

View File

@@ -46,7 +46,7 @@ export function HomeView({ initialSchools, filters }: HomeViewProps) {
<div className={styles.locationBanner}> <div className={styles.locationBanner}>
<span className={styles.locationIcon}>📍</span> <span className={styles.locationIcon}>📍</span>
<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> <strong>{initialSchools.location_info.postcode}</strong>
</span> </span>
</div> </div>
@@ -74,14 +74,22 @@ export function HomeView({ initialSchools, filters }: HomeViewProps) {
{initialSchools.schools.length === 0 ? ( {initialSchools.schools.length === 0 ? (
<EmptyState <EmptyState
title="No schools found" title={hasSearch ? "No schools found" : "Search for Schools"}
message="Try adjusting your search criteria or filters to find schools." message={
action={{ 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', label: 'Clear Filters',
onClick: () => { onClick: () => {
window.location.href = '/'; window.location.href = '/';
}, },
}} }
: undefined
}
/> />
) : ( ) : (
<> <>

View File

@@ -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,
})),
};
} }
/** /**