Start time grid #62
18
frontend/package-lock.json
generated
18
frontend/package-lock.json
generated
@ -12,7 +12,8 @@
|
|||||||
"openapi-fetch": "^0.13.5",
|
"openapi-fetch": "^0.13.5",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router": "^7.4.1"
|
"react-router": "^7.4.1",
|
||||||
|
"temporal-polyfill": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.21.0",
|
"@eslint/js": "^9.21.0",
|
||||||
@ -3726,6 +3727,21 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"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": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.14",
|
"version": "0.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
||||||
|
|||||||
@ -16,7 +16,8 @@
|
|||||||
"openapi-fetch": "^0.13.5",
|
"openapi-fetch": "^0.13.5",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router": "^7.4.1"
|
"react-router": "^7.4.1",
|
||||||
|
"temporal-polyfill": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.21.0",
|
"@eslint/js": "^9.21.0",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo } from "react";
|
||||||
|
import { Temporal } from "temporal-polyfill";
|
||||||
|
|
||||||
/** A booking with ISO 8601 datetime strings */
|
/** A booking with ISO 8601 datetime strings */
|
||||||
export interface Booking {
|
export interface Booking {
|
||||||
@ -41,30 +42,30 @@ export interface TimeSlot {
|
|||||||
|
|
||||||
/** Parse ISO 8601 duration (e.g. "PT4H") to hours */
|
/** Parse ISO 8601 duration (e.g. "PT4H") to hours */
|
||||||
function parseDurationToHours(duration: string): number {
|
function parseDurationToHours(duration: string): number {
|
||||||
const match = duration.match(/PT(\d+)H/);
|
return Temporal.Duration.from(duration).hours || 4;
|
||||||
return match ? parseInt(match[1], 10) : 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function timeToMinutes(time: string): number {
|
function timeToMinutes(time: string): number {
|
||||||
const [hours, minutes] = time.split(":").map(Number);
|
const t = Temporal.PlainTime.from(time);
|
||||||
return hours * 60 + minutes;
|
return t.hour * 60 + t.minute;
|
||||||
}
|
}
|
||||||
|
|
||||||
function minutesToTime(minutes: number): string {
|
function minutesToTime(minutes: number): string {
|
||||||
const h = Math.floor(minutes / 60);
|
return Temporal.PlainTime.from({
|
||||||
const m = minutes % 60;
|
hour: Math.floor(minutes / 60),
|
||||||
return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}`;
|
minute: minutes % 60,
|
||||||
|
}).toString().slice(0, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check if a date string is today */
|
/** Check if a date string is today */
|
||||||
function isToday(date: string): boolean {
|
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 */
|
/** Get current time in minutes since midnight */
|
||||||
function getCurrentTimeMinutes(): number {
|
function getCurrentTimeMinutes(): number {
|
||||||
const now = new Date();
|
const now = Temporal.Now.plainTimeISO();
|
||||||
return now.getHours() * 60 + now.getMinutes();
|
return now.hour * 60 + now.minute;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,9 +105,10 @@ function calculateTimeSlots(
|
|||||||
let availableMinutes = Math.min(latestMinutes - mins, maxHours * 60);
|
let availableMinutes = Math.min(latestMinutes - mins, maxHours * 60);
|
||||||
|
|
||||||
for (const booking of room.bookings) {
|
for (const booking of room.bookings) {
|
||||||
if (!booking.start.startsWith(date)) continue;
|
const bookingDateTime = Temporal.PlainDateTime.from(booking.start);
|
||||||
const bookingStart = timeToMinutes(booking.start.split("T")[1]);
|
if (bookingDateTime.toPlainDate().toString() !== date) continue;
|
||||||
const bookingEnd = timeToMinutes(booking.end.split("T")[1]);
|
const bookingStart = timeToMinutes(bookingDateTime.toPlainTime().toString());
|
||||||
|
const bookingEnd = timeToMinutes(Temporal.PlainDateTime.from(booking.end).toPlainTime().toString());
|
||||||
|
|
||||||
if (mins >= bookingStart && mins < bookingEnd) {
|
if (mins >= bookingStart && mins < bookingEnd) {
|
||||||
availableMinutes = 0;
|
availableMinutes = 0;
|
||||||
@ -156,9 +158,10 @@ function findBestRoom(
|
|||||||
let roomAvailable = true;
|
let roomAvailable = true;
|
||||||
|
|
||||||
for (const booking of room.bookings) {
|
for (const booking of room.bookings) {
|
||||||
if (!booking.start.startsWith(date)) continue;
|
const bookingDateTime = Temporal.PlainDateTime.from(booking.start);
|
||||||
const bookingStart = timeToMinutes(booking.start.split("T")[1]);
|
if (bookingDateTime.toPlainDate().toString() !== date) continue;
|
||||||
const bookingEnd = timeToMinutes(booking.end.split("T")[1]);
|
const bookingStart = timeToMinutes(bookingDateTime.toPlainTime().toString());
|
||||||
|
const bookingEnd = timeToMinutes(Temporal.PlainDateTime.from(booking.end).toPlainTime().toString());
|
||||||
|
|
||||||
if (startMinutes >= bookingStart && startMinutes < bookingEnd) {
|
if (startMinutes >= bookingStart && startMinutes < bookingEnd) {
|
||||||
roomAvailable = false;
|
roomAvailable = false;
|
||||||
@ -221,9 +224,10 @@ function calculateEndTimeOptions(
|
|||||||
let maxEndMinutes = Math.min(startMinutes + maxHours * 60, latestMinutes);
|
let maxEndMinutes = Math.min(startMinutes + maxHours * 60, latestMinutes);
|
||||||
|
|
||||||
for (const booking of room.bookings) {
|
for (const booking of room.bookings) {
|
||||||
if (!booking.start.startsWith(date)) continue;
|
const bookingDateTime = Temporal.PlainDateTime.from(booking.start);
|
||||||
const bookingStart = timeToMinutes(booking.start.split("T")[1]);
|
if (bookingDateTime.toPlainDate().toString() !== date) continue;
|
||||||
const bookingEnd = timeToMinutes(booking.end.split("T")[1]);
|
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 start time is during a booking, no options available
|
||||||
if (startMinutes >= bookingStart && startMinutes < bookingEnd) {
|
if (startMinutes >= bookingStart && startMinutes < bookingEnd) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user