FilterBar was sending radius in km (e.g. 1.6) but the backend expects miles, causing the "Showing schools within X miles" banner to display the wrong value. Change option values to miles (0.5, 1, 3, 5, 10) and default from 1.6 to 1. school.distance from the API is already in miles (backend haversine uses R=3959). SchoolRow was dividing by 1609.34 giving 0.0 mi; CompactSchoolItem was dividing by 1.60934. Both now display school.distance directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
158 lines
5.5 KiB
TypeScript
158 lines
5.5 KiB
TypeScript
/**
|
|
* SchoolRow Component
|
|
* Compact row-based school display for search results list view
|
|
*/
|
|
|
|
import type { School } from '@/lib/types';
|
|
import { formatPercentage, formatProgress, calculateTrend } from '@/lib/utils';
|
|
import { progressBand } from '@/lib/metrics';
|
|
import styles from './SchoolRow.module.css';
|
|
|
|
interface SchoolRowProps {
|
|
school: School;
|
|
isLocationSearch?: boolean;
|
|
isInCompare?: boolean;
|
|
onAddToCompare?: (school: School) => void;
|
|
onRemoveFromCompare?: (urn: number) => void;
|
|
}
|
|
|
|
export function SchoolRow({
|
|
school,
|
|
isLocationSearch,
|
|
isInCompare = false,
|
|
onAddToCompare,
|
|
onRemoveFromCompare,
|
|
}: SchoolRowProps) {
|
|
const trend = calculateTrend(school.rwm_expected_pct, school.prev_rwm_expected_pct);
|
|
|
|
const hasProgress =
|
|
school.reading_progress != null ||
|
|
school.writing_progress != null ||
|
|
school.maths_progress != null;
|
|
|
|
const handleCompareClick = () => {
|
|
if (isInCompare) {
|
|
onRemoveFromCompare?.(school.urn);
|
|
} else {
|
|
onAddToCompare?.(school);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className={`${styles.row} ${isInCompare ? styles.rowInCompare : ''}`}>
|
|
<div className={styles.rowMain}>
|
|
{/* Line 1: Name + score + actions */}
|
|
<div className={styles.rowTop}>
|
|
<a href={`/school/${school.urn}`} className={styles.schoolName}>
|
|
{school.school_name}
|
|
</a>
|
|
|
|
<div className={styles.scoreBlock}>
|
|
{school.rwm_expected_pct != null ? (
|
|
<>
|
|
<strong className={styles.scoreValue}>
|
|
{formatPercentage(school.rwm_expected_pct, 0)}
|
|
</strong>
|
|
{school.prev_rwm_expected_pct != null && (
|
|
<span
|
|
className={`${styles.trend} ${styles[`trend${trend.charAt(0).toUpperCase() + trend.slice(1)}`]}`}
|
|
title={`Previous year: ${formatPercentage(school.prev_rwm_expected_pct)}`}
|
|
>
|
|
{trend === 'up' && (
|
|
<svg viewBox="0 0 16 16" fill="none" width="10" height="10">
|
|
<path d="M8 3L14 10H2L8 3Z" fill="currentColor" />
|
|
</svg>
|
|
)}
|
|
{trend === 'down' && (
|
|
<svg viewBox="0 0 16 16" fill="none" width="10" height="10">
|
|
<path d="M8 13L2 6H14L8 13Z" fill="currentColor" />
|
|
</svg>
|
|
)}
|
|
{trend === 'stable' && (
|
|
<svg viewBox="0 0 16 16" fill="none" width="10" height="10">
|
|
<rect x="2" y="7" width="12" height="2" rx="1" fill="currentColor" />
|
|
</svg>
|
|
)}
|
|
</span>
|
|
)}
|
|
</>
|
|
) : (
|
|
<span className={styles.scoreNA}>—</span>
|
|
)}
|
|
<span className={styles.scoreLabel}>R, W & M</span>
|
|
</div>
|
|
|
|
<div className={styles.rowActions}>
|
|
<a href={`/school/${school.urn}`} className={styles.btnView}>
|
|
View
|
|
</a>
|
|
{(onAddToCompare || onRemoveFromCompare) && (
|
|
<button
|
|
onClick={handleCompareClick}
|
|
className={isInCompare ? styles.btnRemove : styles.btnCompare}
|
|
>
|
|
{isInCompare ? '✓ Remove' : '+ Add'}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Line 2: Meta tags */}
|
|
<div className={styles.rowMeta}>
|
|
{school.local_authority && <span>{school.local_authority}</span>}
|
|
{school.school_type && <span>{school.school_type}</span>}
|
|
{!isLocationSearch &&
|
|
school.religious_denomination &&
|
|
school.religious_denomination !== 'Does not apply' && (
|
|
<span>{school.religious_denomination}</span>
|
|
)}
|
|
{isLocationSearch && school.distance != null && (
|
|
<span className={styles.distanceBadge}>
|
|
{school.distance.toFixed(1)} mi
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{/* Line 3: Progress scores with plain-language bands */}
|
|
{hasProgress && (
|
|
<div className={styles.rowProgress}>
|
|
{school.reading_progress != null && (
|
|
<span className={styles.progressItem}>
|
|
Reading{' '}
|
|
<strong className={styles.progressValue}>
|
|
{formatProgress(school.reading_progress)}
|
|
</strong>
|
|
<em className={styles.progressBand}>
|
|
{progressBand(school.reading_progress)}
|
|
</em>
|
|
</span>
|
|
)}
|
|
{school.writing_progress != null && (
|
|
<span className={styles.progressItem}>
|
|
Writing{' '}
|
|
<strong className={styles.progressValue}>
|
|
{formatProgress(school.writing_progress)}
|
|
</strong>
|
|
<em className={styles.progressBand}>
|
|
{progressBand(school.writing_progress)}
|
|
</em>
|
|
</span>
|
|
)}
|
|
{school.maths_progress != null && (
|
|
<span className={styles.progressItem}>
|
|
Maths{' '}
|
|
<strong className={styles.progressValue}>
|
|
{formatProgress(school.maths_progress)}
|
|
</strong>
|
|
<em className={styles.progressBand}>
|
|
{progressBand(school.maths_progress)}
|
|
</em>
|
|
</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|