@happychef/algorithm 1.2.25 → 1.2.27
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.
|
@@ -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,39 +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
|
-
hour12: false,
|
|
23
|
-
});
|
|
24
|
-
const parts = formatter.formatToParts(date);
|
|
25
|
-
const values = {};
|
|
26
|
-
for (const part of parts) {
|
|
27
|
-
values[part.type] = part.value;
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
year: parseInt(values.year, 10),
|
|
31
|
-
month: parseInt(values.month, 10),
|
|
32
|
-
day: parseInt(values.day, 10),
|
|
33
|
-
hour: parseInt(values.hour, 10),
|
|
34
|
-
minute: parseInt(values.minute, 10),
|
|
35
|
-
second: parseInt(values.second, 10),
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
6
|
/**
|
|
40
7
|
* Parses a time string in "HH:MM" format into a Date object on a specific date.
|
|
41
8
|
* @param {string} dateStr - The date string in "YYYY-MM-DD" format.
|
|
@@ -87,13 +54,10 @@ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlot
|
|
|
87
54
|
// Time zone for CEST/CET (Europe/Amsterdam)
|
|
88
55
|
const timeZone = 'Europe/Amsterdam';
|
|
89
56
|
|
|
90
|
-
//
|
|
57
|
+
// Current date/time in local timezone (system time)
|
|
58
|
+
// Note: We assume the server is running in the correct timezone
|
|
91
59
|
const now = new Date();
|
|
92
|
-
const
|
|
93
|
-
const currentTimeInTimeZone = new Date(
|
|
94
|
-
nowParts.year, nowParts.month - 1, nowParts.day,
|
|
95
|
-
nowParts.hour, nowParts.minute, nowParts.second
|
|
96
|
-
);
|
|
60
|
+
const currentTimeInTimeZone = now;
|
|
97
61
|
|
|
98
62
|
// Calculate the maximum allowed date
|
|
99
63
|
const maxAllowedDate = new Date(currentTimeInTimeZone.getTime());
|
|
@@ -111,9 +75,8 @@ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlot
|
|
|
111
75
|
}
|
|
112
76
|
|
|
113
77
|
// Check if the target date is today in the specified time zone
|
|
114
|
-
// Compare year, month, day components to determine if it's today
|
|
115
78
|
const isToday =
|
|
116
|
-
|
|
79
|
+
currentTimeInTimeZone.toDateString() === targetDateInTimeZone.toDateString();
|
|
117
80
|
|
|
118
81
|
// Get available time blocks or shifts
|
|
119
82
|
const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin, duration);
|
package/isDateAvailable.js
CHANGED
|
@@ -12,39 +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
|
-
hour12: false,
|
|
32
|
-
});
|
|
33
|
-
const parts = formatter.formatToParts(date);
|
|
34
|
-
const values = {};
|
|
35
|
-
for (const part of parts) {
|
|
36
|
-
values[part.type] = part.value;
|
|
37
|
-
}
|
|
38
|
-
return {
|
|
39
|
-
year: parseInt(values.year, 10),
|
|
40
|
-
month: parseInt(values.month, 10),
|
|
41
|
-
day: parseInt(values.day, 10),
|
|
42
|
-
hour: parseInt(values.hour, 10),
|
|
43
|
-
minute: parseInt(values.minute, 10),
|
|
44
|
-
second: parseInt(values.second, 10),
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
15
|
/**
|
|
49
16
|
* Checks if a date is within the allowed future range defined by dagenInToekomst.
|
|
50
17
|
* @param {Object} data - The main data object (to access general settings).
|
|
@@ -64,21 +31,20 @@ function isDateWithinAllowedRange(data, dateStr) {
|
|
|
64
31
|
|
|
65
32
|
const timeZone = 'Europe/Amsterdam';
|
|
66
33
|
|
|
67
|
-
// Get current time in the target timezone using reliable cross-browser method
|
|
68
34
|
const now = new Date();
|
|
69
|
-
const nowParts = getDatePartsInTimeZone(now, timeZone);
|
|
70
35
|
const currentTimeInTimeZone = new Date(
|
|
71
|
-
|
|
72
|
-
nowParts.hour, nowParts.minute, nowParts.second
|
|
36
|
+
now.toLocaleString('en-US', { timeZone: timeZone })
|
|
73
37
|
);
|
|
74
38
|
|
|
75
39
|
const maxAllowedDate = new Date(currentTimeInTimeZone.getTime());
|
|
76
40
|
maxAllowedDate.setDate(maxAllowedDate.getDate() + dagenInToekomst);
|
|
77
41
|
maxAllowedDate.setHours(23, 59, 59, 999);
|
|
78
42
|
|
|
79
|
-
// Parse target date - already in local date format, no timezone conversion needed
|
|
80
43
|
const [year, month, day] = dateStr.split('-').map(Number);
|
|
81
|
-
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
|
+
);
|
|
82
48
|
|
|
83
49
|
return targetDateInTimeZone <= maxAllowedDate;
|
|
84
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/package.json
CHANGED
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
|
};
|