mirror of
https://github.com/Xevion/banner.git
synced 2026-01-31 08:23:35 -06:00
feat(course): distinguish async from synchronous online courses
Add logic to detect and label asynchronous online sections (INT building with TBA times) separately from synchronous online courses. Update table rendering to show "Async" instead of "TBA" for these sections.
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
formatTimeRange,
|
||||
getDeliveryConcern,
|
||||
getPrimaryInstructor,
|
||||
isAsyncOnline,
|
||||
isMeetingTimeTBA,
|
||||
isTimeTBA,
|
||||
openSeats,
|
||||
@@ -611,7 +612,12 @@ const table = createSvelteTable({
|
||||
)}
|
||||
passthrough
|
||||
>
|
||||
{#if timeIsTBA(course)}
|
||||
{#if isAsyncOnline(course)}
|
||||
<span
|
||||
class="text-xs text-muted-foreground/60"
|
||||
>Async</span
|
||||
>
|
||||
{:else if timeIsTBA(course)}
|
||||
<span
|
||||
class="text-xs text-muted-foreground/60"
|
||||
>TBA</span
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
formatCreditHours,
|
||||
formatDate,
|
||||
formatDateShort,
|
||||
formatLocationDisplay,
|
||||
formatMeetingDays,
|
||||
formatMeetingDaysLong,
|
||||
formatMeetingDaysVerbose,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
formatTime,
|
||||
formatTimeRange,
|
||||
getPrimaryInstructor,
|
||||
isAsyncOnline,
|
||||
isMeetingTimeTBA,
|
||||
isTimeTBA,
|
||||
} from "$lib/course";
|
||||
@@ -411,3 +413,92 @@ describe("formatMeetingTimesTooltip", () => {
|
||||
expect(result).toContain("\n\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe("isAsyncOnline", () => {
|
||||
it("returns true for INT building with no times", () => {
|
||||
const course = {
|
||||
meetingTimes: [
|
||||
makeMeetingTime({
|
||||
building: "INT",
|
||||
building_description: "Internet Class",
|
||||
begin_time: null,
|
||||
end_time: null,
|
||||
}),
|
||||
],
|
||||
} as CourseResponse;
|
||||
expect(isAsyncOnline(course)).toBe(true);
|
||||
});
|
||||
it("returns false for INT building with meeting times", () => {
|
||||
const course = {
|
||||
meetingTimes: [
|
||||
makeMeetingTime({
|
||||
building: "INT",
|
||||
building_description: "Internet Class",
|
||||
tuesday: true,
|
||||
thursday: true,
|
||||
begin_time: "1000",
|
||||
end_time: "1115",
|
||||
}),
|
||||
],
|
||||
} as CourseResponse;
|
||||
expect(isAsyncOnline(course)).toBe(false);
|
||||
});
|
||||
it("returns false for non-INT building", () => {
|
||||
const course = {
|
||||
meetingTimes: [
|
||||
makeMeetingTime({
|
||||
building: "MH",
|
||||
building_description: "Main Hall",
|
||||
begin_time: null,
|
||||
end_time: null,
|
||||
}),
|
||||
],
|
||||
} as CourseResponse;
|
||||
expect(isAsyncOnline(course)).toBe(false);
|
||||
});
|
||||
it("returns false for empty meeting times", () => {
|
||||
const course = { meetingTimes: [] } as unknown as CourseResponse;
|
||||
expect(isAsyncOnline(course)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatLocationDisplay", () => {
|
||||
it("returns 'Online' for INT building", () => {
|
||||
const course = {
|
||||
meetingTimes: [
|
||||
makeMeetingTime({
|
||||
building: "INT",
|
||||
building_description: "Internet Class",
|
||||
}),
|
||||
],
|
||||
campus: "9",
|
||||
} as CourseResponse;
|
||||
expect(formatLocationDisplay(course)).toBe("Online");
|
||||
});
|
||||
it("returns building and room for physical location", () => {
|
||||
const course = {
|
||||
meetingTimes: [
|
||||
makeMeetingTime({
|
||||
building: "MH",
|
||||
building_description: "Main Hall",
|
||||
room: "2.206",
|
||||
}),
|
||||
],
|
||||
campus: "11",
|
||||
} as CourseResponse;
|
||||
expect(formatLocationDisplay(course)).toBe("MH 2.206");
|
||||
});
|
||||
it("returns building only when no room", () => {
|
||||
const course = {
|
||||
meetingTimes: [
|
||||
makeMeetingTime({
|
||||
building: "MH",
|
||||
building_description: "Main Hall",
|
||||
room: null,
|
||||
}),
|
||||
],
|
||||
campus: "11",
|
||||
} as CourseResponse;
|
||||
expect(formatLocationDisplay(course)).toBe("MH");
|
||||
});
|
||||
});
|
||||
|
||||
+17
-2
@@ -152,6 +152,13 @@ export function isTimeTBA(mt: DbMeetingTime): boolean {
|
||||
return !mt.begin_time || mt.begin_time.length !== 4;
|
||||
}
|
||||
|
||||
/** Check if course is asynchronous online (INT building with no meeting times) */
|
||||
export function isAsyncOnline(course: CourseResponse): boolean {
|
||||
if (course.meetingTimes.length === 0) return false;
|
||||
const mt = course.meetingTimes[0];
|
||||
return mt.building === "INT" && isTimeTBA(mt);
|
||||
}
|
||||
|
||||
/** Format a date string to "January 20, 2026". Accepts YYYY-MM-DD or MM/DD/YYYY. */
|
||||
export function formatDate(dateStr: string): string {
|
||||
let year: number, month: number, day: number;
|
||||
@@ -170,6 +177,8 @@ export function formatDate(dateStr: string): string {
|
||||
/** Short location string from first meeting time: "MH 2.206" or campus fallback */
|
||||
export function formatLocation(course: CourseResponse): string | null {
|
||||
for (const mt of course.meetingTimes) {
|
||||
// Skip INT building - handled by formatLocationDisplay
|
||||
if (mt.building === "INT") continue;
|
||||
if (mt.building && mt.room) return `${mt.building} ${mt.room}`;
|
||||
if (mt.building) return mt.building;
|
||||
}
|
||||
@@ -307,13 +316,19 @@ export function concernAccentColor(concern: DeliveryConcern): string | null {
|
||||
|
||||
/**
|
||||
* Location display text for the table cell.
|
||||
* Falls back to "Online" for online courses instead of showing a dash.
|
||||
* Shows "Online" for internet class (INT building) or other online courses.
|
||||
*/
|
||||
export function formatLocationDisplay(course: CourseResponse): string | null {
|
||||
// Check for Internet Class building first
|
||||
const hasIntBuilding = course.meetingTimes.some((mt) => mt.building === "INT");
|
||||
if (hasIntBuilding) return "Online";
|
||||
|
||||
const loc = formatLocation(course);
|
||||
if (loc) return loc;
|
||||
|
||||
const concern = getDeliveryConcern(course);
|
||||
if (concern === "online") return "Online";
|
||||
if (concern === "online" || concern === "internet") return "Online";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user