fix(search): offer every filter option, not only those in the results
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m17s
PR Checks / Backend Smoke (pull_request) Successful in 9s
PR Checks / Build Backend (no push) (pull_request) Successful in 19s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m22s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 15s

School type, gender and admissions took their options from the result
set, which the filter had already narrowed: choose "Girls" and only
"Girls" was offered, so switching to "Boys" meant clearing first. They
now offer the full lists from /api/filters, as phase already did. Local
authority stays scoped to the results, so a postcode search offers the
councils nearby rather than all 153.

Whether gender, sixth form and admissions show was also decided by the
results (any secondary school in them). It is now decided by the phase
alone: hidden for Primary, Nursery and Middle deemed primary, shown
otherwise. Choosing one of those phases clears the three filters, which
would otherwise stay applied with no control showing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
TudorandClaude Opus 5.5 committed 2026-10-02 10:22:39 +01:00
1 parent eb13ab0b5e
commit 0cc4f52816
4 files changed
+168 -16

No files matched your search

+31 -16
View File
@@ -66,6 +66,14 @@ const FILTER_KEYS = [
] as const;
type FilterKey = (typeof FILTER_KEYS)[number];
/** Filters that only mean something for schools teaching beyond primary. */
const SECONDARY_ONLY_KEYS = ["gender", "has_sixth_form", "admissions_policy"] as const;
/** Phases with no secondary-age pupils, for which those filters are hidden. */
const PRIMARY_PHASES = new Set(["primary", "nursery", "middle deemed primary"]);
const hasSecondaryFilters = (phase: string) => !PRIMARY_PHASES.has(phase.toLowerCase());
function SlidersIcon() {
return (
<svg
@@ -326,7 +334,13 @@ export function FilterBar({
};
const handleFilterChange = (key: string, value: string) => {
updateURL({ [key]: value });
// A primary phase hides the secondary-only filters, so it clears them
// too: left applied, they would empty the list with no control showing.
const cleared =
key === "phase" && !hasSecondaryFilters(value)
? Object.fromEntries(SECONDARY_ONLY_KEYS.map((k) => [k, ""]))
: {};
updateURL({ ...cleared, [key]: value });
};
// Every filter at once, keeping the search and its distance: what "Clear
@@ -353,20 +367,24 @@ export function FilterBar({
currentAdmissionsPolicy ||
currentHasSixthForm;
// Use result-scoped filter values when available, fall back to global
/*
* A filter's options come from the full lists, not from the results: the
* results have already been narrowed by that filter, so scoping to them
* would leave only the chosen value on offer, and switching (Girls to Boys,
* one school type to another) would need clearing first.
*
* Local authority is the exception, scoped to the results so a postcode
* search offers the councils nearby rather than all of England's.
*/
const laOptions =
resultFilters?.local_authorities ?? filters.local_authorities;
const typeOptions = resultFilters?.school_types ?? filters.school_types;
// Phase is the exception: always the full list. The result set has already
// been narrowed by the phase filter, so scoping to it would leave only the
// chosen phase on offer and switching phase would need "Any phase" first.
const typeOptions = filters.school_types;
const phaseOptions = filters.phases ?? [];
const genderOptions = resultFilters?.genders ?? filters.genders ?? [];
const admissionsPolicyOptions =
resultFilters?.admissions_policies ?? filters.admissions_policies ?? [];
const genderOptions = filters.genders ?? [];
const admissionsPolicyOptions = filters.admissions_policies ?? [];
const isSecondaryMode =
currentPhase === "secondary" || genderOptions.length > 0;
// Set by the phase chosen, never by what the results happen to contain.
const isSecondaryMode = hasSecondaryFilters(currentPhase);
// A select that is narrowing the results carries a sage tint; the class is
// only ever additive, so the control's behaviour is untouched.
@@ -409,11 +427,8 @@ export function FilterBar({
// rest are the names themselves.
const named: Partial<Record<FilterKey, string[]>> = {
phase: phaseOptions,
gender: [...genderOptions, ...(filters.genders ?? [])],
admissions_policy: [
...admissionsPolicyOptions,
...(filters.admissions_policies ?? []),
],
gender: genderOptions,
admissions_policy: admissionsPolicyOptions,
};
return (named[key] ?? []).find((o) => o.toLowerCase() === value.toLowerCase()) ?? value;
};