@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
- // Get current time in target timezone using reliable cross-browser method
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 nowParts = getDatePartsInTimeZone(now, timeZone);
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
- nowParts.year === year && nowParts.month === month && nowParts.day === day;
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);
@@ -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
- nowParts.year, nowParts.month - 1, nowParts.day,
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 targetDateInTimeZone = new Date(year, month - 1, day);
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 null for selectedZitplaats, then duration
157
- if (isTimeAvailableSync(data, dateStr, time, guests, reservations, null, duration)) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happychef/algorithm",
3
- "version": "1.2.25",
3
+ "version": "1.2.27",
4
4
  "description": "Restaurant and reservation algorithm utilities",
5
5
  "main": "index.js",
6
6
  "scripts": {
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
- restaurantData.floors.forEach(floor => {
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
  };