From a28827c12f3c8da9e3dcaf949323f0c36a6cecfa Mon Sep 17 00:00:00 2001 From: calebyhan Date: Tue, 30 Jun 2026 11:04:20 -0400 Subject: [PATCH] Show finance hearing timeslots on public calendars (#167) Finance hearings fetched via getFinanceHearings() were never passed to CalendarWidget on the meetings page or homepage, so they never appeared on the public calendar. Add a shared mapper (financeHearingsToCalendarEvents) that converts FinanceHearingDate entries into synthetic CalendarEvent objects (assuming a 30-minute slot, titled "Finance Hearing (Full)" when is_full is true) and merge them into the events passed to CalendarWidget on both pages. --- frontend/src/app/meetings/page.tsx | 23 ++++++++++++++++++--- frontend/src/app/page.tsx | 10 +++++++++- frontend/src/lib/calendar.ts | 32 ++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 frontend/src/lib/calendar.ts diff --git a/frontend/src/app/meetings/page.tsx b/frontend/src/app/meetings/page.tsx index 1e0d024..95c0c40 100644 --- a/frontend/src/app/meetings/page.tsx +++ b/frontend/src/app/meetings/page.tsx @@ -1,12 +1,15 @@ import CalendarWidget from "@/components/calendar/CalendarWidget"; import EmptyState from "@/components/ui/EmptyState"; import ErrorMessage from "@/components/ui/ErrorMessage"; -import { getEvents } from "@/lib/api"; +import { getEvents, getFinanceHearings } from "@/lib/api"; +import { financeHearingsToCalendarEvents } from "@/lib/calendar"; export const dynamic = "force-dynamic"; export default async function MeetingsPage() { let events: Awaited> = []; + let financeHearings: Awaited> | null = + null; let hasError = false; try { @@ -15,6 +18,13 @@ export default async function MeetingsPage() { hasError = true; } + try { + financeHearings = await getFinanceHearings(); + } catch { + // Finance hearings are optional — tolerate failure and fall back to + // showing only regular events. + } + if (hasError) { return (
@@ -25,16 +35,23 @@ export default async function MeetingsPage() { ); } + const calendarEvents = [ + ...events, + ...(financeHearings + ? financeHearingsToCalendarEvents(financeHearings.dates) + : []), + ]; + return (
- {events.length === 0 ? ( + {calendarEvents.length === 0 ? ( ) : ( - + )}
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 591ac44..246fce3 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -6,6 +6,7 @@ import RecentNews from "@/components/home/RecentNews"; import EmptyState from "@/components/ui/EmptyState"; import ErrorMessage from "@/components/ui/ErrorMessage"; import { ApiError, getCarousel, getEvents, getFinanceHearings } from "@/lib/api"; +import { financeHearingsToCalendarEvents } from "@/lib/calendar"; export const dynamic = "force-dynamic"; @@ -40,6 +41,13 @@ export default async function Home() { } } + const calendarEvents = [ + ...events, + ...(financeHearings + ? financeHearingsToCalendarEvents(financeHearings.dates) + : []), + ]; + return (
{carouselError ? ( @@ -71,7 +79,7 @@ export default async function Home() { description="Check back soon for the latest calendar updates." /> ) : ( - + )}
diff --git a/frontend/src/lib/calendar.ts b/frontend/src/lib/calendar.ts new file mode 100644 index 0000000..c059b76 --- /dev/null +++ b/frontend/src/lib/calendar.ts @@ -0,0 +1,32 @@ +import type { CalendarEvent, FinanceHearingDate } from "@/types"; + +const FINANCE_HEARING_ID_OFFSET = 1000000; +const DEFAULT_DURATION_MINUTES = 30; + +/** + * Converts a finance hearing date into a synthetic CalendarEvent so it can + * be rendered alongside regular events in CalendarWidget. + */ +export function financeHearingToCalendarEvent( + hearing: FinanceHearingDate, +): CalendarEvent { + const start = new Date(`${hearing.hearing_date}T${hearing.hearing_time}`); + const end = new Date(start.getTime() + DEFAULT_DURATION_MINUTES * 60 * 1000); + + return { + id: FINANCE_HEARING_ID_OFFSET + hearing.id, + title: `Finance Hearing${hearing.is_full ? " (Full)" : ""}`, + description: hearing.description, + start_datetime: start.toISOString(), + end_datetime: end.toISOString(), + location: hearing.location, + event_type: "Finance Hearing", + is_published: true, + }; +} + +export function financeHearingsToCalendarEvents( + hearings: FinanceHearingDate[], +): CalendarEvent[] { + return hearings.map(financeHearingToCalendarEvent); +}