mirror of
https://github.com/Xevion/banner.git
synced 2026-01-31 08:23:35 -06:00
refactor: use friendly term codes in URL query parameters
This commit is contained in:
@@ -0,0 +1,64 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { termToFriendly, termToBanner } from "./term-format";
|
||||||
|
|
||||||
|
describe("termToFriendly", () => {
|
||||||
|
it("converts spring term correctly", () => {
|
||||||
|
expect(termToFriendly("202610")).toBe("spring-26");
|
||||||
|
expect(termToFriendly("202510")).toBe("spring-25");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("converts summer term correctly", () => {
|
||||||
|
expect(termToFriendly("202620")).toBe("summer-26");
|
||||||
|
expect(termToFriendly("202520")).toBe("summer-25");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("converts fall term correctly", () => {
|
||||||
|
expect(termToFriendly("202630")).toBe("fall-26");
|
||||||
|
expect(termToFriendly("202530")).toBe("fall-25");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for invalid codes", () => {
|
||||||
|
expect(termToFriendly("20261")).toBe(null);
|
||||||
|
expect(termToFriendly("2026100")).toBe(null);
|
||||||
|
expect(termToFriendly("202640")).toBe(null); // Invalid semester code
|
||||||
|
expect(termToFriendly("")).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("termToBanner", () => {
|
||||||
|
it("converts spring term correctly", () => {
|
||||||
|
expect(termToBanner("spring-26")).toBe("202610");
|
||||||
|
expect(termToBanner("spring-25")).toBe("202510");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("converts summer term correctly", () => {
|
||||||
|
expect(termToBanner("summer-26")).toBe("202620");
|
||||||
|
expect(termToBanner("summer-25")).toBe("202520");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("converts fall term correctly", () => {
|
||||||
|
expect(termToBanner("fall-26")).toBe("202630");
|
||||||
|
expect(termToBanner("fall-25")).toBe("202530");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for invalid formats", () => {
|
||||||
|
expect(termToBanner("winter-26")).toBe(null);
|
||||||
|
expect(termToBanner("spring26")).toBe(null);
|
||||||
|
expect(termToBanner("spring-2026")).toBe(null);
|
||||||
|
expect(termToBanner("26-spring")).toBe(null);
|
||||||
|
expect(termToBanner("")).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("round-trip conversion", () => {
|
||||||
|
it("converts back and forth correctly", () => {
|
||||||
|
const bannerCodes = ["202610", "202620", "202630", "202510"];
|
||||||
|
|
||||||
|
for (const code of bannerCodes) {
|
||||||
|
const friendly = termToFriendly(code);
|
||||||
|
expect(friendly).not.toBeNull();
|
||||||
|
const backToBanner = termToBanner(friendly!);
|
||||||
|
expect(backToBanner).toBe(code);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Convert between Banner's internal term codes (e.g., "202620") and human-friendly format (e.g., "summer-26")
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type SemesterName = "spring" | "summer" | "fall";
|
||||||
|
|
||||||
|
const SEMESTER_CODES: Record<string, SemesterName> = {
|
||||||
|
"10": "spring",
|
||||||
|
"20": "summer",
|
||||||
|
"30": "fall",
|
||||||
|
};
|
||||||
|
|
||||||
|
const SEMESTER_TO_CODE: Record<SemesterName, string> = {
|
||||||
|
spring: "10",
|
||||||
|
summer: "20",
|
||||||
|
fall: "30",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Banner term code (e.g., "202620") to friendly format (e.g., "summer-26")
|
||||||
|
*/
|
||||||
|
export function termToFriendly(bannerCode: string): string | null {
|
||||||
|
if (bannerCode.length !== 6) return null;
|
||||||
|
|
||||||
|
const year = bannerCode.substring(0, 4);
|
||||||
|
const semesterCode = bannerCode.substring(4, 6);
|
||||||
|
const semester = SEMESTER_CODES[semesterCode];
|
||||||
|
|
||||||
|
if (!semester) return null;
|
||||||
|
|
||||||
|
const shortYear = year.substring(2, 4);
|
||||||
|
return `${semester}-${shortYear}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert friendly format (e.g., "summer-26") to Banner term code (e.g., "202620")
|
||||||
|
*/
|
||||||
|
export function termToBanner(friendly: string): string | null {
|
||||||
|
const match = friendly.match(/^(spring|summer|fall)-(\d{2})$/);
|
||||||
|
if (!match) return null;
|
||||||
|
|
||||||
|
const [, semester, shortYear] = match;
|
||||||
|
const semesterCode = SEMESTER_TO_CODE[semester as SemesterName];
|
||||||
|
if (!semesterCode) return null;
|
||||||
|
|
||||||
|
const fullYear = `20${shortYear}`;
|
||||||
|
return `${fullYear}${semesterCode}`;
|
||||||
|
}
|
||||||
@@ -14,14 +14,17 @@ import SearchStatus, { type SearchMeta } from "$lib/components/SearchStatus.svel
|
|||||||
import CourseTable from "$lib/components/CourseTable.svelte";
|
import CourseTable from "$lib/components/CourseTable.svelte";
|
||||||
import Pagination from "$lib/components/Pagination.svelte";
|
import Pagination from "$lib/components/Pagination.svelte";
|
||||||
import Footer from "$lib/components/Footer.svelte";
|
import Footer from "$lib/components/Footer.svelte";
|
||||||
|
import { termToBanner, termToFriendly } from "$lib/term-format";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
// Read initial state from URL params (intentionally captured once)
|
// Read initial state from URL params (intentionally captured once)
|
||||||
const initialParams = untrack(() => new URLSearchParams(data.url.search));
|
const initialParams = untrack(() => new URLSearchParams(data.url.search));
|
||||||
|
|
||||||
// Filter state
|
// Filter state - only set term from URL if present (no auto-default)
|
||||||
let selectedTerm = $state(untrack(() => initialParams.get("term") ?? data.terms[0]?.code ?? ""));
|
const urlTerm = initialParams.get("term");
|
||||||
|
const bannerTerm = urlTerm ? (termToBanner(urlTerm) ?? "") : "";
|
||||||
|
let selectedTerm = $state(bannerTerm);
|
||||||
let selectedSubjects: string[] = $state(untrack(() => initialParams.getAll("subject")));
|
let selectedSubjects: string[] = $state(untrack(() => initialParams.getAll("subject")));
|
||||||
let query = $state(initialParams.get("q") ?? "");
|
let query = $state(initialParams.get("q") ?? "");
|
||||||
let openOnly = $state(initialParams.get("open") === "true");
|
let openOnly = $state(initialParams.get("open") === "true");
|
||||||
@@ -160,7 +163,10 @@ async function performSearch(
|
|||||||
sort.length > 0 ? (sort[0].desc ? "desc" : "asc") : undefined;
|
sort.length > 0 ? (sort[0].desc ? "desc" : "asc") : undefined;
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.set("term", term);
|
const friendlyTerm = termToFriendly(term);
|
||||||
|
if (friendlyTerm) {
|
||||||
|
params.set("term", friendlyTerm);
|
||||||
|
}
|
||||||
for (const s of subjects) {
|
for (const s of subjects) {
|
||||||
params.append("subject", s);
|
params.append("subject", s);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user