diff --git a/web/src/lib/components/CourseDetail.svelte b/web/src/lib/components/CourseDetail.svelte index 71082b5..172685f 100644 --- a/web/src/lib/components/CourseDetail.svelte +++ b/web/src/lib/components/CourseDetail.svelte @@ -9,6 +9,7 @@ import { isTimeTBA, } from "$lib/course"; import { Tooltip } from "bits-ui"; +import SimpleTooltip from "./SimpleTooltip.svelte"; import { Info, Copy, Check } from "@lucide/svelte"; let { course }: { course: CourseResponse } = $props(); @@ -50,7 +51,8 @@ async function copyEmail(email: string, event: MouseEvent) {
{instructor.displayName}
@@ -134,16 +136,9 @@ async function copyEmail(email: string, event: MouseEvent) {

Delivery - - - - - - How the course is taught: in-person, online, hybrid, etc. - - + + +

@@ -168,34 +163,20 @@ async function copyEmail(email: string, event: MouseEvent) {

Attributes - - - - - - Course flags for degree requirements, core curriculum, or special designations - - + + +

{#each course.attributes as attr} - - - - {attr} - - - + - Course attribute code - - + {attr} + + {/each}
@@ -207,19 +188,12 @@ async function copyEmail(email: string, event: MouseEvent) {

Cross-list - - - - - - Cross-listed sections share enrollment across multiple course numbers. Students in any linked section attend the same class. - - + + +

- + @@ -233,7 +207,8 @@ async function copyEmail(email: string, event: MouseEvent) { Group {course.crossList} {#if course.crossListCount != null && course.crossListCapacity != null} diff --git a/web/src/lib/components/CourseTable.svelte b/web/src/lib/components/CourseTable.svelte index b748e8f..12d3bfa 100644 --- a/web/src/lib/components/CourseTable.svelte +++ b/web/src/lib/components/CourseTable.svelte @@ -24,7 +24,8 @@ import { type Updater, } from "@tanstack/table-core"; import { ArrowUp, ArrowDown, ArrowUpDown, Columns3, Check, RotateCcw } from "@lucide/svelte"; -import { DropdownMenu, ContextMenu } from "bits-ui"; +import { DropdownMenu, ContextMenu, Tooltip } from "bits-ui"; +import SimpleTooltip from "./SimpleTooltip.svelte"; import { fade, fly } from "svelte/transition"; let { @@ -33,12 +34,14 @@ let { sorting = [], onSortingChange, manualSorting = false, + subjectMap = {}, }: { courses: CourseResponse[]; loading: boolean; sorting?: SortingState; onSortingChange?: (sorting: SortingState) => void; manualSorting?: boolean; + subjectMap?: Record; } = $props(); let expandedCrn: string | null = $state(null); @@ -301,12 +304,24 @@ const table = createSvelteTable({
- - - View - + + + + + View + + + + Show or hide table columns + + + {course.crn} {:else if colId === "course_code"} + {@const subjectDesc = subjectMap[course.subject]} - {course.subject} {course.courseNumber}{#if course.sequenceNumber}-{course.sequenceNumber}{/if} + + {course.subject} {course.courseNumber}{#if course.sequenceNumber}-{course.sequenceNumber}{/if} + {:else if colId === "title"} - {course.title} + + + {course.title} + + {:else if colId === "instructor"} + {@const primary = getPrimaryInstructor(course.instructors)} - {primaryInstructorDisplay(course)} + + {primaryInstructorDisplay(course)} + {#if primaryRating(course)} {@const r = primaryRating(course)!} - {r.rating.toFixed(1)}★ + + {r.rating.toFixed(1)}★ + {/if} {:else if colId === "time"} @@ -442,11 +468,13 @@ const table = createSvelteTable({ {:else if colId === "seats"} - - - {#if openSeats(course) === 0}Full{:else}{openSeats(course)} open{/if} - {course.enrollment}/{course.maxEnrollment}{#if course.waitCount > 0} · WL {course.waitCount}/{course.waitCapacity}{/if} - + + + + {#if openSeats(course) === 0}Full{:else}{openSeats(course)} open{/if} + {course.enrollment}/{course.maxEnrollment}{#if course.waitCount > 0} · WL {course.waitCount}/{course.waitCapacity}{/if} + + {/if} {/each} diff --git a/web/src/lib/components/SearchFilters.svelte b/web/src/lib/components/SearchFilters.svelte index faf2039..3f121ef 100644 --- a/web/src/lib/components/SearchFilters.svelte +++ b/web/src/lib/components/SearchFilters.svelte @@ -1,5 +1,6 @@ + + + + {@render children()} + + + {text} + + diff --git a/web/src/lib/components/ThemeToggle.svelte b/web/src/lib/components/ThemeToggle.svelte index 8cd7a60..2041472 100644 --- a/web/src/lib/components/ThemeToggle.svelte +++ b/web/src/lib/components/ThemeToggle.svelte @@ -2,6 +2,7 @@ import { tick } from "svelte"; import { Moon, Sun } from "@lucide/svelte"; import { themeStore } from "$lib/stores/theme.svelte"; +import SimpleTooltip from "./SimpleTooltip.svelte"; /** * Theme toggle with View Transitions API circular reveal animation. @@ -42,25 +43,27 @@ async function handleToggle(event: MouseEvent) { } - + + + diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index c298222..4910fac 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -51,6 +51,9 @@ function handleSortingChange(newSorting: SortingState) { // Data state let subjects: Subject[] = $state([]); +let subjectMap: Record = $derived( + Object.fromEntries(subjects.map((s) => [s.code, s.description])) +); let searchResult: SearchResponse | null = $state(null); let loading = $state(false); let error = $state(null); @@ -181,6 +184,7 @@ function handlePageChange(newOffset: number) { {sorting} onSortingChange={handleSortingChange} manualSorting={true} + {subjectMap} /> {#if searchResult} diff --git a/web/src/routes/health/+page.svelte b/web/src/routes/health/+page.svelte index 8e7dd2a..98acf69 100644 --- a/web/src/routes/health/+page.svelte +++ b/web/src/routes/health/+page.svelte @@ -12,7 +12,7 @@ import { WifiOff, XCircle, } from "@lucide/svelte"; -import { Tooltip } from "bits-ui"; +import SimpleTooltip from "$lib/components/SimpleTooltip.svelte"; import { type ServiceStatus, type ServiceInfo, type StatusResponse, client } from "$lib/api"; import { relativeTime } from "$lib/time"; @@ -290,20 +290,13 @@ onMount(() => { Last Updated
- - - - {relativeLastFetch} - - - + - as of {lastFetch.toLocaleTimeString()} - - + {relativeLastFetch} + + {/if} diff --git a/web/src/routes/layout.css b/web/src/routes/layout.css index d7eda81..c1b45e6 100644 --- a/web/src/routes/layout.css +++ b/web/src/routes/layout.css @@ -12,6 +12,8 @@ --muted-foreground: oklch(0.556 0 0); --border: oklch(0.922 0 0); --ring: oklch(0.708 0 0); + --accent: oklch(0.96 0 0); + --accent-foreground: oklch(0.145 0 0); --status-green: oklch(0.65 0.2 145); --status-red: oklch(0.63 0.2 25); @@ -28,6 +30,8 @@ --muted-foreground: oklch(0.708 0 0); --border: oklch(0.269 0 0); --ring: oklch(0.556 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); --status-green: oklch(0.72 0.19 145); --status-red: oklch(0.7 0.19 25); @@ -44,6 +48,8 @@ --color-muted-foreground: var(--muted-foreground); --color-border: var(--border); --color-ring: var(--ring); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); --color-status-green: var(--status-green); --color-status-red: var(--status-red); --color-status-orange: var(--status-orange);