feat(compare): limit the basket to five per phase, not five overall
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m14s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 22s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m17s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 12s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 20s
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m14s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 22s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m17s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 12s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 20s
A parent choosing a primary and a secondary school at once hit the old cap of five total. The basket now holds up to five primary and five secondary schools (ten in all), matching the compare page's phase tabs. Schools that could land in either tab (all-through, special schools with phase "Not applicable", unknown phase) count against both groups, so no tab ever exceeds the five-slot chart palette and point styles. - lib/compareLogic: compareGroups, fullGroupFor, fitToGroupLimits - search modal disables only the full group and says which one - rankings rows carry the phase of the tab they are ranked under - shared ?urns= links are trimmed per group - copy: compare metadata, homepage value prop, How it works card now name primary and secondary schools (also better for search intent) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
343b40c645
commit
0a4c051ee5
12 files changed
+145
-25
No files matched your search
@@ -43,7 +43,7 @@ interface ComparisonChartProps {
|
||||
isSecondary?: boolean;
|
||||
}
|
||||
|
||||
// One shape per basket slot (MAX_SCHOOLS = 5) — secondary encoding so
|
||||
// One shape per slot in a phase tab (MAX_PER_GROUP = 5) — secondary encoding so
|
||||
// converging lines stay tellable apart without relying on hue alone.
|
||||
const POINT_STYLES: PointStyle[] = ['circle', 'triangle', 'rect', 'rectRot', 'star'];
|
||||
|
||||
|
||||
@@ -149,8 +149,8 @@ interface ValueProp {
|
||||
* and is backed by the next-deadline bar further down this page.
|
||||
*
|
||||
* Every claim here must name something the product actually does. Two of the
|
||||
* four previously did not: "up to three schools" contradicted MAX_SCHOOLS = 5
|
||||
* in context/ComparisonProvider.tsx (and the card further down the page, which
|
||||
* four previously did not: "up to three schools" contradicted the basket limit
|
||||
* (now MAX_PER_GROUP = 5 per phase, in lib/compareLogic.ts) (and the card further down the page, which
|
||||
* correctly said five), and "class sizes" described data the codebase has never
|
||||
* held — grep for it and this line was the only hit. Both are corrected below
|
||||
* against the real fields, which live in components/school/InclusionSection.tsx.
|
||||
@@ -179,7 +179,7 @@ const VALUE_PROPS: ValueProp[] = [
|
||||
icon: <BarsIcon />,
|
||||
tintClass: styles.propIconCompare,
|
||||
title: 'Easy to compare',
|
||||
body: 'Up to five schools side by side, on the measures that matter.',
|
||||
body: 'Up to five primary and five secondary schools side by side, on the measures that matter.',
|
||||
},
|
||||
{
|
||||
icon: <HeartIcon />,
|
||||
|
||||
@@ -143,7 +143,7 @@ export function HowItWorksSection() {
|
||||
<span className={styles.compareRowVal}>{b}</span>
|
||||
</div>
|
||||
))}
|
||||
<div className={styles.compareFoot}>+ compare up to 5 schools</div>
|
||||
<div className={styles.compareFoot}>+ up to 5 primary & 5 secondary</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.hiwCardBody}>
|
||||
@@ -152,7 +152,7 @@ export function HowItWorksSection() {
|
||||
{/* One verb for one feature. The site previously called this
|
||||
"compare" in the nav, "shortlist" in the footer and "pin" here,
|
||||
which reads as three separate things it does not have. */}
|
||||
<p className={styles.hiwDesc}>Compare up to five schools and every metric aligns in the same columns. It works for primary and secondary alike.</p>
|
||||
<p className={styles.hiwDesc}>Compare primary and secondary school performance side by side: KS2 SATs results for primary schools, GCSE Attainment 8 for secondary schools, and Ofsted ratings for both, lined up in one table so you can see where each school is stronger.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -95,6 +95,9 @@ export function RankingsView({
|
||||
const handleAddToCompare = (ranking: RankingEntry) => {
|
||||
addSchool({
|
||||
...ranking,
|
||||
// Ranking rows carry no phase, but the tab they're ranked under is
|
||||
// theirs. The compare basket's per-phase limit needs it.
|
||||
phase: isPrimary ? 'Primary' : 'Secondary',
|
||||
address: null,
|
||||
postcode: null,
|
||||
latitude: null,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useComparison } from "@/hooks/useComparison";
|
||||
import { debounce } from "@/lib/utils";
|
||||
import { fetchSchools } from "@/lib/api";
|
||||
import { track } from "@/lib/analytics";
|
||||
import { MAX_PER_GROUP } from "@/lib/compareLogic";
|
||||
import type { School } from "@/lib/types";
|
||||
import styles from "./SchoolSearchModal.module.css";
|
||||
|
||||
@@ -20,7 +21,7 @@ interface SchoolSearchModalProps {
|
||||
}
|
||||
|
||||
export function SchoolSearchModal({ isOpen, onClose }: SchoolSearchModalProps) {
|
||||
const { addSchool, selectedSchools, canAddMore } = useComparison();
|
||||
const { addSchool, selectedSchools, fullGroupFor } = useComparison();
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [results, setResults] = useState<School[]>([]);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
@@ -73,6 +74,19 @@ export function SchoolSearchModal({ isOpen, onClose }: SchoolSearchModalProps) {
|
||||
return selectedSchools.some((s) => s.urn === urn);
|
||||
};
|
||||
|
||||
// Limits apply per phase group, so a full primary list must not stop a
|
||||
// parent adding secondary schools.
|
||||
const primaryFull = fullGroupFor({ phase: "Primary" }) !== null;
|
||||
const secondaryFull = fullGroupFor({ phase: "Secondary" }) !== null;
|
||||
const limitWarning =
|
||||
primaryFull && secondaryFull
|
||||
? `You're comparing ${MAX_PER_GROUP} primary and ${MAX_PER_GROUP} secondary schools, the most at once. Remove a school to add another.`
|
||||
: primaryFull
|
||||
? `You're comparing ${MAX_PER_GROUP} primary schools, the most at once. You can still add secondary schools.`
|
||||
: secondaryFull
|
||||
? `You're comparing ${MAX_PER_GROUP} secondary schools, the most at once. You can still add primary schools.`
|
||||
: null;
|
||||
|
||||
const handleClose = () => {
|
||||
setSearchTerm("");
|
||||
setResults([]);
|
||||
@@ -83,10 +97,8 @@ export function SchoolSearchModal({ isOpen, onClose }: SchoolSearchModalProps) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={handleClose} title="Add School to Comparison">
|
||||
<div className={styles.modalContent}>
|
||||
{!canAddMore && (
|
||||
<div className={styles.warning}>
|
||||
Maximum 5 schools can be compared. Remove a school to add another.
|
||||
</div>
|
||||
{limitWarning && (
|
||||
<div className={styles.warning}>{limitWarning}</div>
|
||||
)}
|
||||
|
||||
{/* Search Input */}
|
||||
@@ -112,6 +124,7 @@ export function SchoolSearchModal({ isOpen, onClose }: SchoolSearchModalProps) {
|
||||
|
||||
{results.map((school) => {
|
||||
const alreadySelected = isSchoolSelected(school.urn);
|
||||
const fullGroup = alreadySelected ? null : fullGroupFor(school);
|
||||
|
||||
return (
|
||||
<div key={school.urn} className={styles.resultItem}>
|
||||
@@ -126,12 +139,16 @@ export function SchoolSearchModal({ isOpen, onClose }: SchoolSearchModalProps) {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleAddSchool(school)}
|
||||
disabled={alreadySelected || !canAddMore}
|
||||
disabled={alreadySelected || fullGroup !== null}
|
||||
className={`${styles.resultButton} ${
|
||||
alreadySelected ? "btn btn-active" : "btn btn-secondary"
|
||||
}`}
|
||||
>
|
||||
{alreadySelected ? "✓ Comparing" : "+ Compare"}
|
||||
{alreadySelected
|
||||
? "✓ Comparing"
|
||||
: fullGroup
|
||||
? `${fullGroup === "primary" ? "Primary" : "Secondary"} full`
|
||||
: "+ Compare"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user