Move schedule data to JSON file

This commit is contained in:
Hornwitser 2025-02-26 23:56:19 +01:00
parent cb2ad42915
commit 99d04f4b43
5 changed files with 136 additions and 122 deletions

View file

@ -1,109 +0,0 @@
export interface ScheduleEvent {
name: string,
id: string,
host?: string,
cancelled?: boolean,
description?: string,
slots: TimeSlot[],
}
export interface ScheduleLocation {
name: string,
id: string,
description?: string,
}
export interface TimeSlot {
id: string,
start: string,
end: string,
locations: string[],
}
export const locations: ScheduleLocation[] = [
{
name: "House",
id: "house",
description: "Blue building east of the camping",
},
{
name: "Common House",
id: "common-house",
description: "That big red building in the middle",
},
{
name: "Info Desk",
id: "info-desk",
},
{
name: "Camping Fireplace",
id: "camping-fireplace",
},
];
export const events: ScheduleEvent[] = [
{
name: "Arcade",
id: "arcade",
description: "Play retro games!",
slots: [
{
id: "arcade-1",
start: "2025-07-18T10:00Z",
end: "2025-07-19T01:30Z",
locations: ["house"],
},
{
id: "arcade-2",
start: "2025-07-19T10:00Z",
end: "2025-07-20T01:00Z",
locations: ["house"],
},
{
id: "arcade-3",
start: "2025-07-20T10:00Z",
end: "2025-07-20T18:00Z",
locations: ["house"],
},
],
},
{
name: "Bonfire Stories",
description: "Share your stories as we sit cosily around the bonfire.",
id: "bonfire",
slots: [
{
id: "bonfire-1",
start: "2025-07-19T20:00Z",
end: "2025-07-20T01:00Z",
locations: ["camping-fireplace"],
},
],
},
{
name: "Fursuit Games",
description: "Playful time for the suiters.",
id: "fursuit-games",
slots: [
{
id: "fursuit-games-1",
start: "2025-07-19T19:00Z",
end: "2025-07-19T20:00Z",
locations: ["common-house"],
},
],
},
{
name: "Late Stragglers",
description: "Wait a minute, why are you still here?.",
id: "too-late",
slots: [
{
id: "too-late-1",
start: "2025-07-22T20:00Z",
end: "2025-07-23T01:00Z",
locations: ["camping-fireplace"],
},
],
},
];

View file

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

26
app/schedule/types.ts Normal file
View file

@ -0,0 +1,26 @@
export interface ScheduleEvent {
name: string,
id: string,
host?: string,
cancelled?: boolean,
description?: string,
slots: TimeSlot[],
}
export interface ScheduleLocation {
name: string,
id: string,
description?: string,
}
export interface TimeSlot {
id: string,
start: string,
end: string,
locations: string[],
}
export interface Schedule {
locations: ScheduleLocation[],
events: ScheduleEvent[],
}

90
schedule.json Normal file
View file

@ -0,0 +1,90 @@
{
"locations": [
{
"name": "House",
"id": "house",
"description": "Blue building east of the camping"
},
{
"name": "Common House",
"id": "common-house",
"description": "That big red building in the middle"
},
{
"name": "Info Desk",
"id": "info-desk",
"description": "Found at the entrance"
},
{
"name": "Camping Fireplace",
"id": "camping-fireplace",
"description": "Next to the big tree"
}
],
"events": [
{
"name": "Arcade",
"id": "arcade",
"description": "Play retro games!",
"slots": [
{
"id": "arcade-1",
"start": "2025-07-18T10:00Z",
"end": "2025-07-19T01:30Z",
"locations": ["house"]
},
{
"id": "arcade-2",
"start": "2025-07-19T10:00Z",
"end": "2025-07-20T01:00Z",
"locations": ["house"]
},
{
"id": "arcade-3",
"start": "2025-07-20T10:00Z",
"end": "2025-07-20T18:00Z",
"locations": ["house"]
}
]
},
{
"name": "Bonfire Stories",
"description": "Share your stories as we sit cosily around the bonfire.",
"id": "bonfire",
"slots": [
{
"id": "bonfire-1",
"start": "2025-07-19T20:00Z",
"end": "2025-07-20T01:00Z",
"locations": ["camping-fireplace"]
}
]
},
{
"name": "Fursuit Games",
"description": "Playful time for the suiters.",
"id": "fursuit-games",
"slots": [
{
"id": "fursuit-games-1",
"start": "2025-07-19T19:00Z",
"end": "2025-07-19T20:00Z",
"locations": ["common-house"]
}
]
},
{
"name": "Late Stragglers",
"description": "Wait a minute, why are you still here?.",
"id": "too-late",
"slots": [
{
"id": "too-late-1",
"start": "2025-07-22T20:00Z",
"end": "2025-07-23T01:00Z",
"locations": ["camping-fireplace"]
}
]
}
]
}

View file

@ -1,4 +1,4 @@
import { ScheduleEvent, TimeSlot, locations } from "@/app/schedule/events"; import { Schedule, ScheduleEvent, ScheduleLocation, TimeSlot } from "@/app/schedule/types";
import styles from "./timetable.module.css"; import styles from "./timetable.module.css";
const oneDayMs = 24 * 60 * 60 * 1000; const oneDayMs = 24 * 60 * 60 * 1000;
@ -100,7 +100,9 @@ function junctionsFromEdges(edges: Iterable<Edge>) {
return keys.map(key => junctions.get(key)!); return keys.map(key => junctions.get(key)!);
} }
function* spansFromJunctions(junctions: Iterable<Junction>): Generator<Span> { function* spansFromJunctions(
junctions: Iterable<Junction>, locations: ScheduleLocation[]
): Generator<Span> {
const activeLocations = new Map( const activeLocations = new Map(
locations.map(location => [location.id, new Set<TimeSlot>()]) locations.map(location => [location.id, new Set<TimeSlot>()])
); );
@ -211,7 +213,9 @@ function* cutSpansByHours(span: Span): Generator<Span> {
} }
} }
function tableElementsFromStretches(stretches: Iterable<Stretch>) { function tableElementsFromStretches(
stretches: Iterable<Stretch>, locations: ScheduleLocation[]
) {
type Col = { minutes?: number }; type Col = { minutes?: number };
type DayHead = { span: number, content?: string } type DayHead = { span: number, content?: string }
type HourHead = { span: number, content?: string } type HourHead = { span: number, content?: string }
@ -308,17 +312,18 @@ function tableElementsFromStretches(stretches: Iterable<Stretch>) {
}; };
} }
export default function Timetable(props: { events: ScheduleEvent[] }) { export default function Timetable(props: { schedule: Schedule }) {
const junctions = junctionsFromEdges(edgesFromEvents(props.events)); const { locations, events } = props.schedule;
const stretches = [...stretchesFromSpans(spansFromJunctions(junctions), oneHourMs * 5)]; const junctions = junctionsFromEdges(edgesFromEvents(events));
const stretches = [...stretchesFromSpans(spansFromJunctions(junctions, locations), oneHourMs * 5)];
const { const {
columnGroups, columnGroups,
dayHeaders, dayHeaders,
hourHeaders, hourHeaders,
locationRows, locationRows,
} = tableElementsFromStretches(stretches); } = tableElementsFromStretches(stretches, locations);
const eventBySlotId = new Map( const eventBySlotId = new Map(
props.events.flatMap( events.flatMap(
event => event.slots.map(slot => [slot.id, event]) event => event.slots.map(slot => [slot.id, event])
) )
); );