@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.
@@ -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
- // 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
94
59
  const now = new Date();
95
- const nowParts = getDatePartsInTimeZone(now, timeZone);
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
- nowParts.year === year && nowParts.month === month && nowParts.day === day;
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
- hourCycle: 'h23', // Use 'h23' for consistent 0-23 hour range across browsers
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
- // Handle edge case: some browsers may return "24" for midnight
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;
@@ -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
- nowParts.year, nowParts.month - 1, nowParts.day,
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 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
+ );
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 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/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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happychef/algorithm",
3
- "version": "1.2.26",
3
+ "version": "1.2.28",
4
4
  "description": "Restaurant and reservation algorithm utilities",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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
- 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
  };