From 03973bd44d89c99e6fdf310e7b797793ad019155 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 14 Apr 2026 01:59:37 +0200 Subject: [PATCH] Add ProjectSchedulePane component --- src/app/ProjectSchedulePane.tsx | 433 ++++++++++++++++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 src/app/ProjectSchedulePane.tsx diff --git a/src/app/ProjectSchedulePane.tsx b/src/app/ProjectSchedulePane.tsx new file mode 100644 index 00000000..691d04cb --- /dev/null +++ b/src/app/ProjectSchedulePane.tsx @@ -0,0 +1,433 @@ +import type { KeyboardEvent as ReactKeyboardEvent } from "react"; + +import { + HOURS_PER_DAY, + formatTimeOfDayHours +} from "../document/project-time-settings"; +import type { ProjectNpcActorRecord } from "../entities/npc-actor-registry"; +import { + PROJECT_SCHEDULE_WEEKDAYS, + formatProjectScheduleDaySelection, + formatProjectScheduleWeekdayLabel, + getProjectScheduleTimelineSegments, + type ProjectScheduler, + type ProjectScheduleRoutine, + type ProjectScheduleWeekday +} from "../scheduler/project-scheduler"; + +interface ProjectSchedulePaneProps { + actors: ProjectNpcActorRecord[]; + scheduler: ProjectScheduler; + selectedRoutineId: string | null; + onSelectRoutine(routineId: string | null): void; + onAddRoutine(actorId: string): void; + onDeleteRoutine(routineId: string): void; + onClose(): void; + onSetRoutineActor(routineId: string, actorId: string): void; + onSetRoutineTitle(routineId: string, title: string): void; + onSetRoutineEnabled(routineId: string, enabled: boolean): void; + onSetRoutineStartHour(routineId: string, startHour: number): void; + onSetRoutineEndHour(routineId: string, endHour: number): void; + onSetRoutinePriority(routineId: string, priority: number): void; + onSetRoutinePresence(routineId: string, active: boolean): void; + onSetRoutineDays( + routineId: string, + mode: "everyDay" | "selectedDays", + days: ProjectScheduleWeekday[] + ): void; +} + +function handleCommitOnEnter( + event: ReactKeyboardEvent, + commit: () => void +) { + if (event.key !== "Enter") { + return; + } + + event.currentTarget.blur(); + commit(); +} + +function getRoutineSummary(routine: ProjectScheduleRoutine): string { + return `${formatProjectScheduleDaySelection(routine.days)} · ${formatTimeOfDayHours(routine.startHour)}-${formatTimeOfDayHours(routine.endHour)} · P${routine.priority}`; +} + +export function ProjectSchedulePane({ + actors, + scheduler, + selectedRoutineId, + onSelectRoutine, + onAddRoutine, + onDeleteRoutine, + onClose, + onSetRoutineActor, + onSetRoutineTitle, + onSetRoutineEnabled, + onSetRoutineStartHour, + onSetRoutineEndHour, + onSetRoutinePriority, + onSetRoutinePresence, + onSetRoutineDays +}: ProjectSchedulePaneProps) { + const selectedRoutine = + selectedRoutineId === null ? null : scheduler.routines[selectedRoutineId] ?? null; + const hourTicks = Array.from({ length: HOURS_PER_DAY }, (_, hour) => hour); + + return ( +
+
+
+
Schedule
+
+ Scheduler-owned actor orchestration over global project time. +
+
+
+ + +
+
+ +
+
+
+
Actors
+
+ {hourTicks.map((hour) => ( +
+ {String(hour).padStart(2, "0")} +
+ ))} +
+
+ + {actors.length === 0 ? ( +
+ No NPC actors are authored in this project yet. +
+ ) : ( + actors.map((actor) => { + const routines = Object.values(scheduler.routines) + .filter( + (routine) => + routine.target.kind === "actor" && + routine.target.actorId === actor.actorId + ) + .sort((left, right) => left.startHour - right.startHour); + + return ( +
+
+ +
+
{actor.label}
+
+ {actor.actorId} + {actor.usages.length > 1 + ? ` · ${actor.usages.length} usages` + : ""} +
+
+
+
+
+ {routines.map((routine) => + getProjectScheduleTimelineSegments(routine).map( + (segment) => ( + + ) + ) + )} +
+
+ ); + }) + )} +
+ + +
+
+ ); +}