Implement EventStream for live schedule updates

This commit is contained in:
Hornwitser 2025-02-27 15:42:59 +01:00
parent e5aac858e4
commit cdad188233
7 changed files with 172 additions and 36 deletions

41
app/schedule/context.tsx Normal file
View file

@ -0,0 +1,41 @@
"use client";
import { createContext, useContext, useEffect, useState } from "react";
import { Schedule } from "./types";
const ScheduleContext = createContext<Schedule | null>(null);
interface ScheduleProviderProps {
children: React.ReactElement;
schedule: Schedule;
}
export function ScheduleProvider(props: ScheduleProviderProps) {
const [schedule, setSchedule] = useState(props.schedule);
useEffect(() => {
console.log("Opening event source")
const source = new EventSource("/api/events");
source.addEventListener("message", (message) => {
console.log("Message", message.data);
setSchedule(old => {
const copy: Schedule = JSON.parse(JSON.stringify(old));
const ts = copy.events[0].slots[0].start;
copy.events[0].slots[0].start = new Date(Date.parse(ts) + 36e5).toISOString();
return copy;
})
});
source.addEventListener("update", (message) => console.log("Update", message.data));
return () => {
console.log("Closing event source")
source.close();
}
}, [])
return (
<ScheduleContext.Provider value={schedule}>
{props.children}
</ScheduleContext.Provider>
);
}
export function useSchedule() {
return useContext(ScheduleContext);
}

View file

@ -1,11 +0,0 @@
.event {
background: color-mix(in oklab, var(--background), grey 20%);
padding: 0.5rem;
border-radius: 0.5rem;
}
.event h3 {
margin: 0;
}
.event + .event {
margin-block-start: 0.5rem;
}

View file

@ -1,38 +1,26 @@
import Timetable from "@/ui/timetable"
import styles from "./page.module.css"
import { Schedule, ScheduleEvent } from "./types"
import { Schedule } from "./types"
import { readFile } from "fs/promises"
import { ScheduleProvider } from "./context"
import { Events } from "@/ui/events";
import { Locations } from "@/ui/locations";
function EventInfo(props: { event: ScheduleEvent }) {
return <section className={styles.event}>
<h3>{props.event.name}</h3>
<p>{props.event.description ?? "No description provided"}</p>
<h4>Timeslots</h4>
<ul>
{props.event.slots.map(slot => <li key={slot.id}>
{slot.start} - {slot.end}
</li>)}
</ul>
</section>
}
export default async function schedule() {
export default async function page() {
const schedule: Schedule = JSON.parse(await readFile("schedule.json", "utf-8"));
return <main className={styles.schedule}>
<h1>Schedule & Events</h1>
<p>
Study carefully, we only hold these events once a year.
</p>
<h2>Schedule</h2>
<Timetable schedule={schedule} />
<h2>Events</h2>
{schedule.events.map(event => <EventInfo event={event} key={event.id}/>)}
<h2>Locations</h2>
<ul>
{schedule.locations.map(location => <li key={location.id}>
<h3>{location.name}</h3>
{location.description ?? "No description provided"}
</li>)}
</ul>
</main>
return (
<ScheduleProvider schedule={schedule}>
<main>
<h1>Schedule & Events</h1>
<p>
Study carefully, we only hold these events once a year.
</p>
<h2>Schedule</h2>
<Timetable />
<h2>Events</h2>
<Events />
<h2>Locations</h2>
<Locations />
</main>
</ScheduleProvider>
);
}