Files
school_compare/nextjs-app/components/SchoolRow.tsx
Tudor 6ddfcadbde
All checks were successful
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 32s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m4s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 0s
fix(search): correct radius units and distance display for postcode search
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>
2026-03-23 22:39:50 +00:00

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 &amp; 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>
);
}