Start time grid #62

Merged
stne3960 merged 30 commits from start_time_grid into main 2026-01-16 14:17:09 +01:00
3 changed files with 42 additions and 21 deletions
Showing only changes of commit 0fc2f7c0d8 - Show all commits

View File

@ -12,7 +12,8 @@
"openapi-fetch": "^0.13.5",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.4.1"
"react-router": "^7.4.1",
"temporal-polyfill": "^0.3.0"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
@ -3726,6 +3727,21 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/temporal-polyfill": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.3.0.tgz",
"integrity": "sha512-qNsTkX9K8hi+FHDfHmf22e/OGuXmfBm9RqNismxBrnSmZVJKegQ+HYYXT+R7Ha8F/YSm2Y34vmzD4cxMu2u95g==",
"license": "MIT",
"dependencies": {
"temporal-spec": "0.3.0"
}
},
"node_modules/temporal-spec": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.3.0.tgz",
"integrity": "sha512-n+noVpIqz4hYgFSMOSiINNOUOMFtV5cZQNCmmszA6GiVFVRt3G7AqVyhXjhCSmowvQn+NsGn+jMDMKJYHd3bSQ==",
"license": "ISC"
},
"node_modules/tinyglobby": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",

View File

@ -16,7 +16,8 @@
"openapi-fetch": "^0.13.5",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.4.1"
"react-router": "^7.4.1",
"temporal-polyfill": "^0.3.0"
},
"devDependencies": {
"@eslint/js": "^9.21.0",

View File

@ -1,4 +1,5 @@
import { useState, useMemo } from "react";
import { Temporal } from "temporal-polyfill";
/** A booking with ISO 8601 datetime strings */
export interface Booking {
@ -41,30 +42,30 @@ export interface TimeSlot {
/** Parse ISO 8601 duration (e.g. "PT4H") to hours */
function parseDurationToHours(duration: string): number {
const match = duration.match(/PT(\d+)H/);
return match ? parseInt(match[1], 10) : 4;
return Temporal.Duration.from(duration).hours || 4;
}
function timeToMinutes(time: string): number {
const [hours, minutes] = time.split(":").map(Number);
return hours * 60 + minutes;
const t = Temporal.PlainTime.from(time);
return t.hour * 60 + t.minute;
}
function minutesToTime(minutes: number): string {
const h = Math.floor(minutes / 60);
const m = minutes % 60;
return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}`;
return Temporal.PlainTime.from({
hour: Math.floor(minutes / 60),
minute: minutes % 60,
}).toString().slice(0, 5);
}
/** Check if a date string is today */
function isToday(date: string): boolean {
return date === new Date().toISOString().split("T")[0];
return Temporal.PlainDate.from(date).equals(Temporal.Now.plainDateISO());
}
/** Get current time in minutes since midnight */
function getCurrentTimeMinutes(): number {
const now = new Date();
return now.getHours() * 60 + now.getMinutes();
const now = Temporal.Now.plainTimeISO();
return now.hour * 60 + now.minute;
}
/**
@ -104,9 +105,10 @@ function calculateTimeSlots(
let availableMinutes = Math.min(latestMinutes - mins, maxHours * 60);
for (const booking of room.bookings) {
if (!booking.start.startsWith(date)) continue;
const bookingStart = timeToMinutes(booking.start.split("T")[1]);
const bookingEnd = timeToMinutes(booking.end.split("T")[1]);
const bookingDateTime = Temporal.PlainDateTime.from(booking.start);
if (bookingDateTime.toPlainDate().toString() !== date) continue;
const bookingStart = timeToMinutes(bookingDateTime.toPlainTime().toString());
const bookingEnd = timeToMinutes(Temporal.PlainDateTime.from(booking.end).toPlainTime().toString());
if (mins >= bookingStart && mins < bookingEnd) {
availableMinutes = 0;
@ -156,9 +158,10 @@ function findBestRoom(
let roomAvailable = true;
for (const booking of room.bookings) {
if (!booking.start.startsWith(date)) continue;
const bookingStart = timeToMinutes(booking.start.split("T")[1]);
const bookingEnd = timeToMinutes(booking.end.split("T")[1]);
const bookingDateTime = Temporal.PlainDateTime.from(booking.start);
if (bookingDateTime.toPlainDate().toString() !== date) continue;
const bookingStart = timeToMinutes(bookingDateTime.toPlainTime().toString());
const bookingEnd = timeToMinutes(Temporal.PlainDateTime.from(booking.end).toPlainTime().toString());
if (startMinutes >= bookingStart && startMinutes < bookingEnd) {
roomAvailable = false;
@ -221,9 +224,10 @@ function calculateEndTimeOptions(
let maxEndMinutes = Math.min(startMinutes + maxHours * 60, latestMinutes);
for (const booking of room.bookings) {
if (!booking.start.startsWith(date)) continue;
const bookingStart = timeToMinutes(booking.start.split("T")[1]);
const bookingEnd = timeToMinutes(booking.end.split("T")[1]);
const bookingDateTime = Temporal.PlainDateTime.from(booking.start);
if (bookingDateTime.toPlainDate().toString() !== date) continue;
const bookingStart = timeToMinutes(bookingDateTime.toPlainTime().toString());
const bookingEnd = timeToMinutes(Temporal.PlainDateTime.from(booking.end).toPlainTime().toString());
// If start time is during a booking, no options available
if (startMinutes >= bookingStart && startMinutes < bookingEnd) {