feat(ux): implement comprehensive UX audit fixes across all pages
Addresses 28 issues identified in UX audit (P0–P3 severity): P0 — Critical: - Fix compare URL sharing: seed ComparisonContext from SSR initialData when localStorage is empty, making /compare?urns=... links shareable - Remove permanently broken "Avg. Scaled Score" column from school detail historical data table P1 — High priority: - Add radius selector (0.5–10 mi) to postcode search in FilterBar - Make Add to Compare a toggle (remove) on SchoolCards - Hide hero title/description once a search is active - Show school count + quick-search prompts on empty landing page - Compare empty state opens in-page school search modal directly - Remove URN from school detail header (irrelevant to end users) - Move map above performance chart in school detail page - Add ← Back navigation to school detail page - Add sort controls to search results (RWM%, distance, A–Z) - Show metric descriptions below metric selector - Expand ComparisonToast to list school names with per-school remove - Add progress score explainer (0 = national average) throughout P2 — Medium: - Remove console.log statements from ComparisonView - Colour-code comparison school cards to match chart line colours - Replace plain loading text with LoadingSkeleton in ComparisonView - Rankings empty state uses shared EmptyState component - Rankings year filter shows actual year e.g. "2023 (Latest)" - Rankings subtitle shows top-N count - Add View link alongside Add button in rankings table - Remove placeholder Privacy Policy / Terms links from footer - Replace untappable 10px info icons with visible metric hint text - Show active filter chips in search results header P3 — Polish: - Remove redundant "Home" nav link (logo already links home) - Add / and Ctrl+K keyboard shortcut to focus search input - Add Share button to compare page (copies URL to clipboard) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
||||
import { useComparison } from '@/hooks/useComparison';
|
||||
import type { RankingEntry, Filters, MetricDefinition } from '@/lib/types';
|
||||
import { formatPercentage, formatProgress } from '@/lib/utils';
|
||||
import { EmptyState } from './EmptyState';
|
||||
import styles from './RankingsView.module.css';
|
||||
|
||||
interface RankingsViewProps {
|
||||
@@ -83,9 +84,17 @@ export function RankingsView({
|
||||
<h1>School Rankings</h1>
|
||||
<p className={styles.subtitle}>
|
||||
Top-performing schools by {metricLabel.toLowerCase()}
|
||||
{!selectedArea && <span className={styles.limitNote}> — showing top {rankings.length}</span>}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{currentMetricDef?.description && (
|
||||
<p className={styles.metricDescription}>{currentMetricDef.description}</p>
|
||||
)}
|
||||
{isProgressScore && (
|
||||
<p className={styles.progressHint}>Progress scores: 0 = national average. Positive = above average.</p>
|
||||
)}
|
||||
|
||||
{/* Filters */}
|
||||
<section className={styles.filters}>
|
||||
<div className={styles.filterGroup}>
|
||||
@@ -170,7 +179,9 @@ export function RankingsView({
|
||||
onChange={(e) => handleYearChange(e.target.value)}
|
||||
className={styles.filterSelect}
|
||||
>
|
||||
<option value="">Latest</option>
|
||||
<option value="">
|
||||
{filters.years.length > 0 ? `${Math.max(...filters.years)} (Latest)` : 'Latest'}
|
||||
</option>
|
||||
{filters.years.map((year) => (
|
||||
<option key={year} value={year}>
|
||||
{year}
|
||||
@@ -183,9 +194,14 @@ export function RankingsView({
|
||||
{/* Rankings Table */}
|
||||
<section className={styles.rankingsSection}>
|
||||
{rankings.length === 0 ? (
|
||||
<div className={styles.noResults}>
|
||||
<p>No rankings available for the selected filters.</p>
|
||||
</div>
|
||||
<EmptyState
|
||||
title="No rankings found"
|
||||
message="Try selecting a different metric, area, or year."
|
||||
action={{
|
||||
label: 'Clear filters',
|
||||
onClick: () => router.push(pathname),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className={styles.tableWrapper}>
|
||||
<table className={styles.rankingsTable}>
|
||||
@@ -242,6 +258,7 @@ export function RankingsView({
|
||||
<strong>{displayValue}</strong>
|
||||
</td>
|
||||
<td className={styles.actionCell}>
|
||||
<a href={`/school/${ranking.urn}`} className={styles.viewButton}>View</a>
|
||||
<button
|
||||
onClick={() => handleAddToCompare(ranking)}
|
||||
disabled={alreadyInComparison}
|
||||
|
||||
Reference in New Issue
Block a user