@happychef/algorithm 1.2.26 → 1.2.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci-cd.yml +1 -1
- package/changes/2026/January/PR14_add__change.md +20 -0
- package/getAvailableTimeblocks.js +6 -49
- package/isDateAvailable.js +5 -42
- package/isDateAvailableWithTableCheck.js +3 -3
- package/nul +0 -13
- package/package.json +1 -1
- package/simulateTableAssignment.js +3 -3
- package/tableHelpers.js +33 -2
|
@@ -47,7 +47,7 @@ jobs:
|
|
|
47
47
|
- name: Publish to NPM
|
|
48
48
|
if: steps.version-check.outputs.version_changed == 'true'
|
|
49
49
|
run: |
|
|
50
|
-
npm publish
|
|
50
|
+
npm publish --access public
|
|
51
51
|
echo "## 📦 NPM Package Published" >> $GITHUB_STEP_SUMMARY
|
|
52
52
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
53
53
|
echo "✅ Successfully published @happychef/algorithm@$(node -p "require('./package.json').version")" >> $GITHUB_STEP_SUMMARY
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# PR 14 - Fix cross-browser date parsing inconsistency
|
|
2
|
+
|
|
3
|
+
**Actions:**
|
|
4
|
+
|
|
5
|
+
## Changes Summary
|
|
6
|
+
ADDED:
|
|
7
|
+
- New function `parseDateString(dateString)` added to handle cross-browser date parsing inconsistencies for YYYY-MM-DD formatted strings.
|
|
8
|
+
- New import of `parseDateString` function into the main module for consistent date handling across different browser environments.
|
|
9
|
+
|
|
10
|
+
NO_REMOVALS
|
|
11
|
+
|
|
12
|
+
CHANGED:
|
|
13
|
+
- Modified the `getDayOfWeek` function in the codebase to replace direct `new Date(dateString)` construction with a call to a new `parseDateString` function for consistent cross-browser date parsing.
|
|
14
|
+
- Updated the date parsing logic to handle YYYY-MM-DD format strings uniformly, addressing the inconsistency where Chrome interprets them as local midnight and Firefox as UTC.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
**Author:** thibaultvandesompele2
|
|
19
|
+
**Date:** 2026-01-26
|
|
20
|
+
**PR Link:** https://github.com/thibaultvandesompele2/15-happy-algorithm/pull/14
|
|
@@ -3,42 +3,6 @@
|
|
|
3
3
|
const { timeblocksAvailable } = require('./processing/timeblocksAvailable');
|
|
4
4
|
const { parseTime, getMealTypeByTime } = require('./tableHelpers');
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Gets the current date/time components in the specified timezone.
|
|
8
|
-
* Uses Intl.DateTimeFormat for reliable cross-browser timezone conversion.
|
|
9
|
-
* @param {Date} date - The date to convert.
|
|
10
|
-
* @param {string} timeZone - The IANA timezone identifier.
|
|
11
|
-
* @returns {Object} Object with year, month, day, hour, minute, second components.
|
|
12
|
-
*/
|
|
13
|
-
function getDatePartsInTimeZone(date, timeZone) {
|
|
14
|
-
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
15
|
-
timeZone,
|
|
16
|
-
year: 'numeric',
|
|
17
|
-
month: '2-digit',
|
|
18
|
-
day: '2-digit',
|
|
19
|
-
hour: '2-digit',
|
|
20
|
-
minute: '2-digit',
|
|
21
|
-
second: '2-digit',
|
|
22
|
-
hourCycle: 'h23', // Use 'h23' instead of hour12:false for consistent 0-23 range
|
|
23
|
-
});
|
|
24
|
-
const parts = formatter.formatToParts(date);
|
|
25
|
-
const values = {};
|
|
26
|
-
for (const part of parts) {
|
|
27
|
-
values[part.type] = part.value;
|
|
28
|
-
}
|
|
29
|
-
// Handle edge case: Chrome may return "24" for midnight with some hourCycle settings
|
|
30
|
-
let hour = parseInt(values.hour, 10);
|
|
31
|
-
if (hour === 24) hour = 0;
|
|
32
|
-
return {
|
|
33
|
-
year: parseInt(values.year, 10),
|
|
34
|
-
month: parseInt(values.month, 10),
|
|
35
|
-
day: parseInt(values.day, 10),
|
|
36
|
-
hour: hour,
|
|
37
|
-
minute: parseInt(values.minute, 10),
|
|
38
|
-
second: parseInt(values.second, 10),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
6
|
/**
|
|
43
7
|
* Parses a time string in "HH:MM" format into a Date object on a specific date.
|
|
44
8
|
* @param {string} dateStr - The date string in "YYYY-MM-DD" format.
|
|
@@ -90,13 +54,10 @@ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlot
|
|
|
90
54
|
// Time zone for CEST/CET (Europe/Amsterdam)
|
|
91
55
|
const timeZone = 'Europe/Amsterdam';
|
|
92
56
|
|
|
93
|
-
//
|
|
57
|
+
// Current date/time in local timezone (system time)
|
|
58
|
+
// Note: We assume the server is running in the correct timezone
|
|
94
59
|
const now = new Date();
|
|
95
|
-
const
|
|
96
|
-
const currentTimeInTimeZone = new Date(
|
|
97
|
-
nowParts.year, nowParts.month - 1, nowParts.day,
|
|
98
|
-
nowParts.hour, nowParts.minute, nowParts.second
|
|
99
|
-
);
|
|
60
|
+
const currentTimeInTimeZone = now;
|
|
100
61
|
|
|
101
62
|
// Calculate the maximum allowed date
|
|
102
63
|
const maxAllowedDate = new Date(currentTimeInTimeZone.getTime());
|
|
@@ -114,9 +75,8 @@ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlot
|
|
|
114
75
|
}
|
|
115
76
|
|
|
116
77
|
// Check if the target date is today in the specified time zone
|
|
117
|
-
// Compare year, month, day components to determine if it's today
|
|
118
78
|
const isToday =
|
|
119
|
-
|
|
79
|
+
currentTimeInTimeZone.toDateString() === targetDateInTimeZone.toDateString();
|
|
120
80
|
|
|
121
81
|
// Get available time blocks or shifts
|
|
122
82
|
const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin, duration);
|
|
@@ -155,14 +115,11 @@ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlot
|
|
|
155
115
|
timeZone: 'Europe/Brussels',
|
|
156
116
|
hour: '2-digit',
|
|
157
117
|
minute: '2-digit',
|
|
158
|
-
|
|
118
|
+
hour12: false,
|
|
159
119
|
});
|
|
160
120
|
const parts = formatter.formatToParts(now);
|
|
161
121
|
const timeParts = Object.fromEntries(parts.map(p => [p.type, p.value]));
|
|
162
|
-
|
|
163
|
-
let hourValue = parseInt(timeParts.hour, 10);
|
|
164
|
-
if (hourValue === 24) hourValue = 0;
|
|
165
|
-
const currentTimeMinutes = hourValue * 60 + parseInt(timeParts.minute, 10);
|
|
122
|
+
const currentTimeMinutes = parseInt(timeParts.hour, 10) * 60 + parseInt(timeParts.minute, 10);
|
|
166
123
|
|
|
167
124
|
// Check if current time has passed any stop times
|
|
168
125
|
const breakfastStopMinutes = breakfastStop ? parseTime(breakfastStop) : null;
|
package/isDateAvailable.js
CHANGED
|
@@ -12,42 +12,6 @@ function parseTime(timeStr) {
|
|
|
12
12
|
return hours * 60 + minutes;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* Gets the current date/time components in the specified timezone.
|
|
17
|
-
* Uses Intl.DateTimeFormat for reliable cross-browser timezone conversion.
|
|
18
|
-
* @param {Date} date - The date to convert.
|
|
19
|
-
* @param {string} timeZone - The IANA timezone identifier.
|
|
20
|
-
* @returns {Object} Object with year, month, day, hour, minute, second components.
|
|
21
|
-
*/
|
|
22
|
-
function getDatePartsInTimeZone(date, timeZone) {
|
|
23
|
-
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
24
|
-
timeZone,
|
|
25
|
-
year: 'numeric',
|
|
26
|
-
month: '2-digit',
|
|
27
|
-
day: '2-digit',
|
|
28
|
-
hour: '2-digit',
|
|
29
|
-
minute: '2-digit',
|
|
30
|
-
second: '2-digit',
|
|
31
|
-
hourCycle: 'h23', // Use 'h23' instead of hour12:false for consistent 0-23 range
|
|
32
|
-
});
|
|
33
|
-
const parts = formatter.formatToParts(date);
|
|
34
|
-
const values = {};
|
|
35
|
-
for (const part of parts) {
|
|
36
|
-
values[part.type] = part.value;
|
|
37
|
-
}
|
|
38
|
-
// Handle edge case: Chrome may return "24" for midnight with some hourCycle settings
|
|
39
|
-
let hour = parseInt(values.hour, 10);
|
|
40
|
-
if (hour === 24) hour = 0;
|
|
41
|
-
return {
|
|
42
|
-
year: parseInt(values.year, 10),
|
|
43
|
-
month: parseInt(values.month, 10),
|
|
44
|
-
day: parseInt(values.day, 10),
|
|
45
|
-
hour: hour,
|
|
46
|
-
minute: parseInt(values.minute, 10),
|
|
47
|
-
second: parseInt(values.second, 10),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
15
|
/**
|
|
52
16
|
* Checks if a date is within the allowed future range defined by dagenInToekomst.
|
|
53
17
|
* @param {Object} data - The main data object (to access general settings).
|
|
@@ -67,21 +31,20 @@ function isDateWithinAllowedRange(data, dateStr) {
|
|
|
67
31
|
|
|
68
32
|
const timeZone = 'Europe/Amsterdam';
|
|
69
33
|
|
|
70
|
-
// Get current time in the target timezone using reliable cross-browser method
|
|
71
34
|
const now = new Date();
|
|
72
|
-
const nowParts = getDatePartsInTimeZone(now, timeZone);
|
|
73
35
|
const currentTimeInTimeZone = new Date(
|
|
74
|
-
|
|
75
|
-
nowParts.hour, nowParts.minute, nowParts.second
|
|
36
|
+
now.toLocaleString('en-US', { timeZone: timeZone })
|
|
76
37
|
);
|
|
77
38
|
|
|
78
39
|
const maxAllowedDate = new Date(currentTimeInTimeZone.getTime());
|
|
79
40
|
maxAllowedDate.setDate(maxAllowedDate.getDate() + dagenInToekomst);
|
|
80
41
|
maxAllowedDate.setHours(23, 59, 59, 999);
|
|
81
42
|
|
|
82
|
-
// Parse target date - already in local date format, no timezone conversion needed
|
|
83
43
|
const [year, month, day] = dateStr.split('-').map(Number);
|
|
84
|
-
const
|
|
44
|
+
const targetDate = new Date(Date.UTC(year, month - 1, day));
|
|
45
|
+
const targetDateInTimeZone = new Date(
|
|
46
|
+
targetDate.toLocaleString('en-US', { timeZone: timeZone })
|
|
47
|
+
);
|
|
85
48
|
|
|
86
49
|
return targetDateInTimeZone <= maxAllowedDate;
|
|
87
50
|
}
|
|
@@ -74,7 +74,7 @@ function timeHasGiftcard(data, dateStr, timeStr, giftcard) {
|
|
|
74
74
|
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
75
75
|
* @returns {boolean} - Returns true if the date passes all checks to be available.
|
|
76
76
|
*/
|
|
77
|
-
function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, blockedSlots = [], selectedGiftcard = null, isAdmin = false, duration = null) {
|
|
77
|
+
function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, blockedSlots = [], selectedGiftcard = null, isAdmin = false, duration = null, selectedZitplaats = null) {
|
|
78
78
|
// 0) Optionally filter by selected menu item's date range (only when normalOpeningTimes = false)
|
|
79
79
|
const currentItem = typeof window !== 'undefined' ? window.currentReservationTicketItem : null;
|
|
80
80
|
if (
|
|
@@ -153,8 +153,8 @@ function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, bloc
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
console.log(`Checking table availability for ${time} on ${dateStr} for ${guests} guests`);
|
|
156
|
-
// Pass
|
|
157
|
-
if (isTimeAvailableSync(data, dateStr, time, guests, reservations,
|
|
156
|
+
// Pass selectedZitplaats so floor link constraints are respected
|
|
157
|
+
if (isTimeAvailableSync(data, dateStr, time, guests, reservations, selectedZitplaats, duration)) {
|
|
158
158
|
console.log(`Found available time ${time} with table assignment!`);
|
|
159
159
|
atLeastOneAvailable = true;
|
|
160
160
|
break;
|
package/nul
CHANGED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
Envoi d'une requ�te 'Ping' 127.0.0.1 avec 32 octets de donn�es�:
|
|
3
|
-
R�ponse de 127.0.0.1�: octets=32 temps<1ms TTL=128
|
|
4
|
-
R�ponse de 127.0.0.1�: octets=32 temps<1ms TTL=128
|
|
5
|
-
R�ponse de 127.0.0.1�: octets=32 temps<1ms TTL=128
|
|
6
|
-
R�ponse de 127.0.0.1�: octets=32 temps<1ms TTL=128
|
|
7
|
-
R�ponse de 127.0.0.1�: octets=32 temps<1ms TTL=128
|
|
8
|
-
R�ponse de 127.0.0.1�: octets=32 temps<1ms TTL=128
|
|
9
|
-
|
|
10
|
-
Statistiques Ping pour 127.0.0.1:
|
|
11
|
-
Paquets�: envoy�s = 6, re�us = 6, perdus = 0 (perte 0%),
|
|
12
|
-
Dur�e approximative des boucles en millisecondes :
|
|
13
|
-
Minimum = 0ms, Maximum = 0ms, Moyenne = 0ms
|
package/package.json
CHANGED
|
@@ -503,7 +503,7 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
|
|
|
503
503
|
} else {
|
|
504
504
|
// ... simulation log ...
|
|
505
505
|
console.log(`[isTimeAvailableSync] No actual table data, simulating assignment for reservation`);
|
|
506
|
-
assignedTables = assignTablesForGivenTime(restaurantData, r.date, r.time, r.guests, tableOccupiedSlots, null, rDuration); // Simulation
|
|
506
|
+
assignedTables = assignTablesForGivenTime(restaurantData, r.date, r.time, r.guests, tableOccupiedSlots, r.zitplaats || null, rDuration); // Simulation - use reservation's own zitplaats
|
|
507
507
|
}
|
|
508
508
|
|
|
509
509
|
// Update the occupancy map based on the actual or simulated assignment
|
|
@@ -679,9 +679,9 @@ function getAvailableTablesForTime(restaurantData, date, time, guests, reservati
|
|
|
679
679
|
if (actualTables.length > 0) {
|
|
680
680
|
assignedTables = actualTables;
|
|
681
681
|
} else {
|
|
682
|
-
assignedTables = assignTablesForGivenTime(restaurantData, r.date, r.time, r.guests, tableOccupiedSlots, null, rDuration);
|
|
682
|
+
assignedTables = assignTablesForGivenTime(restaurantData, r.date, r.time, r.guests, tableOccupiedSlots, r.zitplaats || null, rDuration);
|
|
683
683
|
}
|
|
684
|
-
|
|
684
|
+
|
|
685
685
|
if (assignedTables.length > 0) {
|
|
686
686
|
const rSlots = computeRequiredSlots(r.time, rDuration, intervalReservatie);
|
|
687
687
|
if (!rSlots || rSlots.length === 0) continue;
|
package/tableHelpers.js
CHANGED
|
@@ -49,16 +49,46 @@ function getMealTypeByTime(timeStr) {
|
|
|
49
49
|
|
|
50
50
|
// --- Table Fetching ---
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Gets the floor ID linked to a seat place from seatAreaFloorLinks.
|
|
54
|
+
* Mirrors the server-side getFloorIdForSeatPlace in assignTables.js.
|
|
55
|
+
* @param {Object} restaurantData - The restaurant data object.
|
|
56
|
+
* @param {string} seatPlace - The seat place identifier (zitplaats).
|
|
57
|
+
* @returns {string|null} The floor ID or null if not found.
|
|
58
|
+
*/
|
|
59
|
+
function getFloorIdForSeatPlace(restaurantData, seatPlace) {
|
|
60
|
+
if (!seatPlace || !restaurantData) return null;
|
|
61
|
+
const seatAreaFloorLinks = restaurantData["general-settings"]?.seatAreaFloorLinks;
|
|
62
|
+
if (!seatAreaFloorLinks || typeof seatAreaFloorLinks !== 'object') return null;
|
|
63
|
+
return seatAreaFloorLinks[seatPlace] || null;
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
/**
|
|
53
67
|
* Extracts and processes table data from the restaurantData object.
|
|
54
68
|
* Includes temporary table properties and sorts tables.
|
|
69
|
+
* When selectedZitplaats is provided and has a linked floor via seatAreaFloorLinks,
|
|
70
|
+
* only tables from that linked floor are returned (matching server-side behavior).
|
|
55
71
|
* @param {Object} restaurantData - The main restaurant data object.
|
|
72
|
+
* @param {string|null} selectedZitplaats - Optional seat place to filter by linked floor.
|
|
56
73
|
* @returns {Array} An array of processed table objects.
|
|
57
74
|
*/
|
|
58
|
-
function getAllTables(restaurantData) {
|
|
75
|
+
function getAllTables(restaurantData, selectedZitplaats) {
|
|
59
76
|
let allTables = [];
|
|
60
77
|
if (restaurantData?.floors && Array.isArray(restaurantData.floors)) {
|
|
61
|
-
|
|
78
|
+
// If a zitplaats is specified and has a floor link, only use that floor
|
|
79
|
+
let floorsToUse = restaurantData.floors;
|
|
80
|
+
if (selectedZitplaats) {
|
|
81
|
+
const linkedFloorId = getFloorIdForSeatPlace(restaurantData, selectedZitplaats);
|
|
82
|
+
if (linkedFloorId) {
|
|
83
|
+
const linkedFloor = restaurantData.floors.find(f => f.id === linkedFloorId);
|
|
84
|
+
if (linkedFloor) {
|
|
85
|
+
floorsToUse = [linkedFloor];
|
|
86
|
+
console.log(`[getAllTables] Floor link found: zitplaats '${selectedZitplaats}' -> floor '${linkedFloorId}'`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
floorsToUse.forEach(floor => {
|
|
62
92
|
if (floor?.tables && Array.isArray(floor.tables)) {
|
|
63
93
|
floor.tables.forEach(tbl => {
|
|
64
94
|
// Ensure table number, capacities, priority exist before parsing
|
|
@@ -174,5 +204,6 @@ module.exports = {
|
|
|
174
204
|
parseTime,
|
|
175
205
|
getMealTypeByTime,
|
|
176
206
|
getAllTables,
|
|
207
|
+
getFloorIdForSeatPlace,
|
|
177
208
|
isTemporaryTableValid
|
|
178
209
|
};
|