feat(ui): add phase indicators to school list rows
All checks were successful
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 49s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 11s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s

Add coloured left-border and phase label pill to visually differentiate
school phases (Primary, Secondary, All-through, Post-16, Nursery) in
search result lists. Colours are accessible (WCAG AA) and don't clash
with existing Ofsted/trend semantic colours.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tudor Sitaru
2026-04-01 15:47:51 +01:00
parent cacbeeb068
commit 4db36b9099
6 changed files with 92 additions and 6 deletions

View File

@@ -12,10 +12,14 @@
}
.row:hover {
border-left-color: var(--accent-coral, #e07256);
box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06);
}
/* Phase border colours */
.phasePrimary { border-left-color: var(--phase-primary, #5b8cbf); }
.phaseAllThrough { border-left-color: var(--phase-all-through, #7a9a6d); }
.phaseNursery { border-left-color: var(--phase-nursery, #e0a0b0); }
.rowInCompare {
border-left-color: var(--accent-teal, #2d7d7d);
background: var(--bg-secondary, #f3ede4);
@@ -59,6 +63,21 @@
color: var(--accent-coral, #e07256);
}
/* Phase label pill */
.phaseLabel {
display: inline-block;
padding: 0.0625rem 0.375rem;
font-size: 0.6875rem;
font-weight: 600;
border-radius: 3px;
white-space: nowrap;
margin-right: 0.25rem;
}
.phaseLabelPrimary { background: var(--phase-primary-bg); color: var(--phase-primary-text); }
.phaseLabelAllThrough { background: var(--phase-all-through-bg); color: var(--phase-all-through-text); }
.phaseLabelNursery { background: var(--phase-nursery-bg); color: var(--phase-nursery-text); }
/* Line 2: context tags */
.line2 {
display: flex;

View File

@@ -9,7 +9,7 @@
*/
import type { School } from '@/lib/types';
import { formatPercentage, formatProgress, calculateTrend, schoolUrl } from '@/lib/utils';
import { formatPercentage, formatProgress, calculateTrend, getPhaseStyle, schoolUrl } from '@/lib/utils';
import { progressBand } from '@/lib/metrics';
import styles from './SchoolRow.module.css';
@@ -36,6 +36,7 @@ export function SchoolRow({
onRemoveFromCompare,
}: SchoolRowProps) {
const trend = calculateTrend(school.rwm_expected_pct, school.prev_rwm_expected_pct);
const phase = getPhaseStyle(school.phase);
// Use reading progress as representative; fall back to writing, then maths
const progressScore =
@@ -55,7 +56,7 @@ export function SchoolRow({
school.religious_denomination !== 'Does not apply';
return (
<div className={`${styles.row} ${isInCompare ? styles.rowInCompare : ''}`}>
<div className={`${styles.row} ${phase.key ? styles[`phase${phase.key}`] : ''} ${isInCompare ? styles.rowInCompare : ''}`}>
{/* Left: four content lines */}
<div className={styles.rowContent}>
@@ -78,6 +79,11 @@ export function SchoolRow({
{/* Line 2: Context tags */}
<div className={styles.line2}>
{phase.label && (
<span className={`${styles.phaseLabel} ${styles[`phaseLabel${phase.key}`]}`}>
{phase.label}
</span>
)}
{school.school_type && <span>{school.school_type}</span>}
{school.age_range && <span>{school.age_range}</span>}
{showDenomination && <span>{school.religious_denomination}</span>}

View File

@@ -12,10 +12,14 @@
}
.row:hover {
border-left-color: var(--accent-coral, #e07256);
box-shadow: 0 2px 8px rgba(26, 22, 18, 0.06);
}
/* Phase border colours */
.phaseSecondary { border-left-color: var(--phase-secondary, #9b6bb0); }
.phaseAllThrough { border-left-color: var(--phase-all-through, #7a9a6d); }
.phasePost16 { border-left-color: var(--phase-post16, #c4915e); }
.rowInCompare {
border-left-color: var(--accent-teal, #2d7d7d);
background: var(--bg-secondary, #f3ede4);
@@ -144,6 +148,21 @@
border-radius: 3px;
}
/* Phase label pill */
.phaseLabel {
display: inline-block;
padding: 0.0625rem 0.375rem;
font-size: 0.6875rem;
font-weight: 600;
border-radius: 3px;
white-space: nowrap;
margin-right: 0.25rem;
}
.phaseLabelSecondary { background: var(--phase-secondary-bg); color: var(--phase-secondary-text); }
.phaseLabelAllThrough { background: var(--phase-all-through-bg); color: var(--phase-all-through-text); }
.phaseLabelPost16 { background: var(--phase-post16-bg); color: var(--phase-post16-text); }
.provisionTag {
display: inline-block;
padding: 0.0625rem 0.375rem;

View File

@@ -11,7 +11,7 @@
'use client';
import type { School } from '@/lib/types';
import { schoolUrl } from '@/lib/utils';
import { getPhaseStyle, schoolUrl } from '@/lib/utils';
import styles from './SecondarySchoolRow.module.css';
const OFSTED_LABELS: Record<number, string> = {
@@ -58,6 +58,7 @@ export function SecondarySchoolRow({
}
};
const phase = getPhaseStyle(school.phase);
const att8 = school.attainment_8_score ?? null;
const laDelta =
att8 != null && laAvgAttainment8 != null ? att8 - laAvgAttainment8 : null;
@@ -67,7 +68,7 @@ export function SecondarySchoolRow({
const showGender = school.gender && school.gender.toLowerCase() !== 'mixed';
return (
<div className={`${styles.row} ${isInCompare ? styles.rowInCompare : ''}`}>
<div className={`${styles.row} ${phase.key ? styles[`phase${phase.key}`] : ''} ${isInCompare ? styles.rowInCompare : ''}`}>
{/* Left: four content lines */}
<div className={styles.rowContent}>
@@ -90,6 +91,11 @@ export function SecondarySchoolRow({
{/* Line 2: Context tags */}
<div className={styles.line2}>
{phase.label && (
<span className={`${styles.phaseLabel} ${styles[`phaseLabel${phase.key}`]}`}>
{phase.label}
</span>
)}
{school.school_type && <span>{school.school_type}</span>}
{school.age_range && <span>{school.age_range}</span>}
{showGender && (