feat(ux): implement UX audit recommendations
All checks were successful
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 1m10s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s

- Redesign landing page with unified Omnibox search

- Add ComparisonToast for better comparison flow visibility

- Add visual 'Added' state to SchoolCard

- Add info tooltips to educational metrics

- Optimize mobile map view with Bottom Sheet

- Standardize distance display to miles
This commit is contained in:
Tudor
2026-03-05 09:33:47 +00:00
parent 6a95445f5e
commit ad7380dba5
9 changed files with 430 additions and 279 deletions

View File

@@ -13,14 +13,15 @@ interface SchoolCardProps {
onAddToCompare?: (school: School) => void;
showDistance?: boolean;
distance?: number;
isInCompare?: boolean;
}
export function SchoolCard({ school, onAddToCompare, showDistance, distance }: SchoolCardProps) {
export function SchoolCard({ school, onAddToCompare, showDistance, distance, isInCompare = false }: SchoolCardProps) {
const trend = calculateTrend(school.rwm_expected_pct, school.prev_rwm_expected_pct);
const trendColor = getTrendColor(trend);
return (
<div className={styles.card}>
<div className={`${styles.card} ${isInCompare ? styles.cardInCompare : ''}`}>
<div className={styles.header}>
<h3 className={styles.title}>
<Link href={`/school/${school.urn}`}>
@@ -29,7 +30,7 @@ export function SchoolCard({ school, onAddToCompare, showDistance, distance }: S
</h3>
{showDistance && distance !== undefined && (
<span className={styles.distance}>
{distance.toFixed(1)} km away
{(distance / 1.60934).toFixed(1)} miles away
</span>
)}
</div>
@@ -50,13 +51,16 @@ export function SchoolCard({ school, onAddToCompare, showDistance, distance }: S
<div className={styles.metrics}>
{school.rwm_expected_pct !== null && (
<div className={styles.metric}>
<span className={styles.metricLabel}>RWM Expected</span>
<span className={styles.metricLabel} title="Percentage of pupils achieving the expected standard in Reading, Writing, and Maths">
RWM Expected
<svg className="info-icon" style={{ marginLeft: '4px', width: '10px', height: '10px', verticalAlign: 'middle', color: 'var(--text-muted)' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
</span>
<div className={styles.metricValue}>
<strong>{formatPercentage(school.rwm_expected_pct)}</strong>
{school.prev_rwm_expected_pct !== null && (
<span
className={`${styles.trend} ${styles[`trend${trend.charAt(0).toUpperCase() + trend.slice(1)}`]}`}
title={`Previous: ${formatPercentage(school.prev_rwm_expected_pct)}`}
title={`Previous year: ${formatPercentage(school.prev_rwm_expected_pct)}`}
>
{trend === 'up' && (
<svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon}>
@@ -87,21 +91,30 @@ export function SchoolCard({ school, onAddToCompare, showDistance, distance }: S
{school.reading_progress !== null && (
<div className={styles.metric}>
<span className={styles.metricLabel}>Reading Progress</span>
<span className={styles.metricLabel} title="Progress score from KS1 to KS2 in Reading. >0 is above average.">
Reading
<svg className="info-icon" style={{ marginLeft: '4px', width: '10px', height: '10px', verticalAlign: 'middle', color: 'var(--text-muted)' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
</span>
<strong>{formatProgress(school.reading_progress)}</strong>
</div>
)}
{school.writing_progress !== null && (
<div className={styles.metric}>
<span className={styles.metricLabel}>Writing Progress</span>
<span className={styles.metricLabel} title="Progress score from KS1 to KS2 in Writing. >0 is above average.">
Writing
<svg className="info-icon" style={{ marginLeft: '4px', width: '10px', height: '10px', verticalAlign: 'middle', color: 'var(--text-muted)' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
</span>
<strong>{formatProgress(school.writing_progress)}</strong>
</div>
)}
{school.maths_progress !== null && (
<div className={styles.metric}>
<span className={styles.metricLabel}>Maths Progress</span>
<span className={styles.metricLabel} title="Progress score from KS1 to KS2 in Maths. >0 is above average.">
Maths
<svg className="info-icon" style={{ marginLeft: '4px', width: '10px', height: '10px', verticalAlign: 'middle', color: 'var(--text-muted)' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
</span>
<strong>{formatProgress(school.maths_progress)}</strong>
</div>
)}
@@ -115,9 +128,10 @@ export function SchoolCard({ school, onAddToCompare, showDistance, distance }: S
{onAddToCompare && (
<button
onClick={() => onAddToCompare(school)}
className={styles.btnPrimary}
className={`${styles.btnPrimary} ${isInCompare ? styles.btnAdded : ''}`}
disabled={isInCompare}
>
Add to Compare
{isInCompare ? 'Added ✓' : 'Add to Compare'}
</button>
)}
</div>