From 11ae25535a7295b035d4d880e5af3cc2fb38f70a Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Fri, 15 Mar 2024 00:14:36 +0530 Subject: [PATCH] code cleanup --- .../widget/helpers/availabilityHelper.js | 119 +++++--- .../helpers/specs/availabilityHelper.spec.js | 253 ++++++++++++++---- 2 files changed, 283 insertions(+), 89 deletions(-) diff --git a/app/javascript/widget/helpers/availabilityHelper.js b/app/javascript/widget/helpers/availabilityHelper.js index 53c3aab1b..149cc2f6a 100644 --- a/app/javascript/widget/helpers/availabilityHelper.js +++ b/app/javascript/widget/helpers/availabilityHelper.js @@ -1,3 +1,4 @@ +import { utcToZonedTime } from 'date-fns-tz'; // Array defining days of the week const daysOfWeek = [ 'Sunday', @@ -9,6 +10,64 @@ const daysOfWeek = [ 'Saturday', ]; +/** + * Find the next available time for the specified day + * @param {Object[]} workingHours - Array of objects representing working hours for each day + * @param {number} dayOfWeek - Index of the day of the week (0 for Sunday, 1 for Monday, etc.) + * @param {number} currentHour - Current hour of the day (24-hour format) + * @param {number} currentMinutes - Current minutes of the hour + * @returns {Object|null} - Object containing next available time information or null if not available + */ +const findNextAvailableTimeForDay = ( + workingHours, + dayOfWeek, + currentHour, + currentMinutes +) => { + const availableHours = workingHours.find( + hours => + hours.day_of_week === dayOfWeek && + !hours.closed_all_day && + (currentHour < hours.close_hour || + (currentHour === hours.close_hour && + currentMinutes < hours.close_minutes)) + ); + + if (availableHours) { + return { + hour: availableHours.open_hour, + minutes: availableHours.open_minutes, + }; + } + + return null; +}; + +/** + * Find the next available time across days + * @param {Object[]} workingHours - Array of objects representing working hours for each day + * @param {number} currentDayOfWeek - Index of the current day of the week (0 for Sunday, 1 for Monday, etc.) + * @returns {Object|null} - Object containing next available time information or null if not available + */ +const findNextAvailableTimeAcrossDays = (workingHours, currentDayOfWeek) => { + const nextAvailableDays = workingHours.filter(hours => !hours.closed_all_day); + for (let i = 0; i < 7; i += 1) { + const nextDayIndex = (currentDayOfWeek + 1 + i) % 7; + const nextDay = nextAvailableDays.find( + hours => hours.day_of_week === nextDayIndex + ); + if (nextDay) { + return { + hour: nextDay.open_hour, + minutes: nextDay.open_minutes, + day: nextDayIndex, + }; + } + } + + return null; +}; + /** * Function to find the next available time * @param {Array} workingHours - Array of working hours for each day @@ -23,48 +82,17 @@ const findNextAvailableTime = ( currentHour, currentMinutes ) => { - let nextAvailableDays = []; - let nextAvailableTime = null; - - // Find the next available time for today - const nextAvailableTimeToday = workingHours.find( - workingHour => - currentDayOfWeek === workingHour.day_of_week && // Match current day - !workingHour.closed_all_day && // Check if not closed for the whole day - (currentHour < workingHour.close_hour || // Check if current hour is before closing hour - (currentHour === workingHour.close_hour && // or if hours are equal but minutes are before closing minutes - currentMinutes < workingHour.close_minutes)) + const nextAvailableTimeToday = findNextAvailableTimeForDay( + workingHours, + currentDayOfWeek, + currentHour, + currentMinutes ); - // If next available time for today is found + if (nextAvailableTimeToday) { - nextAvailableTime = { - hour: nextAvailableTimeToday.open_hour, - minutes: nextAvailableTimeToday.open_minutes, - day: 'today', - }; + return { ...nextAvailableTimeToday, day: 'today' }; } - - // If no available time for today, find the next available time for the next day(s) - if (nextAvailableTime === null) { - nextAvailableDays = workingHours.filter(hours => !hours.closed_all_day); - - for (let i = 0; i < 7; i += 1) { - const nextDayIndex = (currentDayOfWeek + 1 + i) % 7; - const nextDay = nextAvailableDays.find( - hours => hours.day_of_week === nextDayIndex - ); - if (nextDay) { - nextAvailableTime = { - hour: nextDay.open_hour, - minutes: nextDay.open_minutes, - day: nextDayIndex, - }; - break; - } - } - } - - return nextAvailableTime; + return findNextAvailableTimeAcrossDays(workingHours, currentDayOfWeek); }; /** @@ -73,10 +101,15 @@ const findNextAvailableTime = ( * @param {Date} currentTime - Current time object * @returns {string} - Next availability message */ -export const getNextAvailabilityMessage = (workingHours, currentTime) => { - const currentDayOfWeek = currentTime.getDay(); - const currentHour = currentTime.getHours(); - const currentMinutes = currentTime.getMinutes(); +export const getNextAvailabilityMessage = ( + workingHours, + currentTime, + timezone = 'UTC' +) => { + const utcZonedTime = utcToZonedTime(currentTime, timezone); + const currentDayOfWeek = utcZonedTime.getDay(); + const currentHour = utcZonedTime.getHours(); + const currentMinutes = utcZonedTime.getMinutes(); const nextAvailableTime = findNextAvailableTime( workingHours, currentDayOfWeek, diff --git a/app/javascript/widget/helpers/specs/availabilityHelper.spec.js b/app/javascript/widget/helpers/specs/availabilityHelper.spec.js index 5bcd2fca2..b5a7dd75e 100644 --- a/app/javascript/widget/helpers/specs/availabilityHelper.spec.js +++ b/app/javascript/widget/helpers/specs/availabilityHelper.spec.js @@ -1,6 +1,170 @@ import { getNextAvailabilityMessage } from '../availabilityHelper'; -describe('getNextAvailabilityMessage function', () => { +describe('getNextAvailabilityMessage with out timezone', () => { + const working_hours = [ + { + id: 8, + inbox_id: 2, + account_id: 1, + day_of_week: 0, + closed_all_day: true, + open_hour: null, + open_minutes: null, + close_hour: null, + close_minutes: null, + created_at: '2024-01-08T06:12:53.980Z', + updated_at: '2024-01-08T06:12:53.980Z', + open_all_day: false, + }, + { + id: 9, + inbox_id: 2, + account_id: 1, + day_of_week: 1, + closed_all_day: false, + open_hour: 9, + open_minutes: 0, + close_hour: 17, + close_minutes: 0, + created_at: '2024-01-08T06:12:53.982Z', + updated_at: '2024-01-08T06:12:53.982Z', + open_all_day: false, + }, + { + id: 10, + inbox_id: 2, + account_id: 1, + day_of_week: 2, + closed_all_day: true, + open_hour: 15, + open_minutes: 0, + close_hour: 23, + close_minutes: 30, + created_at: '2024-01-08T06:12:53.983Z', + updated_at: '2024-03-12T06:54:00.313Z', + open_all_day: false, + }, + { + id: 11, + inbox_id: 2, + account_id: 1, + day_of_week: 3, + closed_all_day: true, + open_hour: null, + open_minutes: null, + close_hour: null, + close_minutes: null, + created_at: '2024-01-08T06:12:53.984Z', + updated_at: '2024-03-13T07:15:41.249Z', + open_all_day: false, + }, + { + id: 12, + inbox_id: 2, + account_id: 1, + day_of_week: 4, + closed_all_day: false, + open_hour: 15, + open_minutes: 0, + close_hour: 20, + close_minutes: 0, + created_at: '2024-01-08T06:12:53.985Z', + updated_at: '2024-03-13T07:21:31.430Z', + open_all_day: false, + }, + { + id: 13, + inbox_id: 2, + account_id: 1, + day_of_week: 5, + closed_all_day: false, + open_hour: 9, + open_minutes: 0, + close_hour: 17, + close_minutes: 0, + created_at: '2024-01-08T06:12:53.986Z', + updated_at: '2024-01-08T06:12:53.986Z', + open_all_day: false, + }, + { + id: 14, + inbox_id: 2, + account_id: 1, + day_of_week: 6, + closed_all_day: false, + open_hour: 9, + open_minutes: 0, + close_hour: 7, + close_minutes: 0, + created_at: '2024-01-08T06:12:53.987Z', + updated_at: '2024-01-08T06:12:53.987Z', + open_all_day: false, + }, + ]; + test('returns correct message for less than a minute', () => { + const lessThanMinute = new Date('2024-03-14T14:59:59'); // Current time + 59 seconds + expect(getNextAvailabilityMessage(working_hours, lessThanMinute)).toBe( + 'We will be back online in less than a minute.' + ); + }); + + test('returns correct message for less than 10 minutes', () => { + const lessThan10Minutes = new Date('2024-03-14T14:50:00'); // Current time + 9 minutes + expect(getNextAvailabilityMessage(working_hours, lessThan10Minutes)).toBe( + 'We will be back online in 10 minutes.' + ); + }); + + test('returns minutes message for less than an hour', () => { + const lessThanHour = new Date('2024-03-14T14:45:00'); // Current time + 30 minutes + expect(getNextAvailabilityMessage(working_hours, lessThanHour)).toBe( + 'We will be back online in 15 minutes.' + ); + }); + + test('returns correct message for greater than 2 hours', () => { + const lessThanHour = new Date('2024-03-14T12:30:00'); // Current time + 30 minutes + expect(getNextAvailabilityMessage(working_hours, lessThanHour)).toBe( + 'We will be back online in 2 hours' + ); + }); + + test('returns correct message for next day', () => { + const nextDay = new Date('2024-03-14T00:00:00'); // Next day + working_hours[4].closed_all_day = true; + expect(getNextAvailabilityMessage(working_hours, nextDay)).toBe( + 'We will be back online tomorrow' + ); + }); + + test('returns correct message for specific day', () => { + const specificDay = new Date('2024-03-14T00:00:00'); // Next day + working_hours[4].closed_all_day = true; + working_hours[5].closed_all_day = true; + expect(getNextAvailabilityMessage(working_hours, specificDay)).toBe( + 'We will be back online on Saturday' + ); + }); + + test('returns correct message for next week', () => { + const nextWeek = new Date('2024-03-21T21:00:00'); // Next day + working_hours[5].closed_all_day = true; + working_hours[6].closed_all_day = true; + working_hours[0].closed_all_day = true; + working_hours[1].closed_all_day = true; + working_hours[2].closed_all_day = true; + working_hours[3].closed_all_day = true; + working_hours[4].closed_all_day = false; + expect(getNextAvailabilityMessage(working_hours, nextWeek)).toBe( + 'We will be back online on Thursday' + ); + }); + afterEach(() => { + jest.useRealTimers(); + }); +}); + +describe('getNextAvailabilityMessage with timezone', () => { const working_hours = [ { id: 8, @@ -103,65 +267,62 @@ describe('getNextAvailabilityMessage function', () => { ]; test('returns correct message for less than a minute', () => { - const lessThanMinute = new Date('2024-03-14T14:59:59'); // Current time + 59 seconds - expect(getNextAvailabilityMessage(working_hours, lessThanMinute)).toBe( - 'We will be back online in less than a minute.' - ); + jest.useFakeTimers('modern'); + jest.setSystemTime(new Date('2024-03-14T19:50:00')); + const lessThan10Minutes = new Date('2024-03-14T19:50:00'); + expect( + getNextAvailabilityMessage( + working_hours, + lessThan10Minutes, + 'America/New_York' + ) + ).toBe('We will be back online in less than a minute.'); }); test('returns correct message for less than 10 minutes', () => { - const lessThan10Minutes = new Date('2024-03-14T14:50:00'); // Current time + 9 minutes - expect(getNextAvailabilityMessage(working_hours, lessThan10Minutes)).toBe( - 'We will be back online in 10 minutes.' - ); - }); - - test('returns minutes message for less than an hour', () => { - const lessThanHour = new Date('2024-03-14T14:45:00'); // Current time + 30 minutes - expect(getNextAvailabilityMessage(working_hours, lessThanHour)).toBe( - 'We will be back online in 15 minutes.' - ); + jest.useFakeTimers('modern'); + jest.setSystemTime(new Date('2024-03-14T19:50:00')); + const lessThan10Minutes = new Date('2024-03-14T19:50:00'); + expect( + getNextAvailabilityMessage( + working_hours, + lessThan10Minutes, + 'America/New_York' + ) + ).toBe('We will be back online in less than a minute.'); }); test('returns correct message for greater than 2 hours', () => { - const lessThanHour = new Date('2024-03-14T12:30:00'); // Current time + 30 minutes - expect(getNextAvailabilityMessage(working_hours, lessThanHour)).toBe( - 'We will be back online in 2 hours' - ); + jest.useFakeTimers('modern'); + jest.setSystemTime(new Date('2024-03-14T14:59:59')); + const lessThanMinute = new Date('2024-03-14T14:59:59'); + expect( + getNextAvailabilityMessage( + working_hours, + lessThanMinute, + 'America/New_York' + ) + ).toBe('We will be back online in 4 hours'); }); test('returns correct message for next day', () => { - const nextDay = new Date('2024-03-14T00:00:00'); // Next day + jest.useFakeTimers('modern'); + jest.setSystemTime(new Date('2024-03-14T14:59:59')); + const nextDay = new Date('2024-03-15T00:00:00'); // Next day working_hours[4].closed_all_day = true; - expect(getNextAvailabilityMessage(working_hours, nextDay)).toBe( - 'We will be back online tomorrow' - ); + expect( + getNextAvailabilityMessage(working_hours, nextDay, 'America/New_York') + ).toBe('We will be back online tomorrow'); }); test('returns correct message for specific day', () => { - const specificDay = new Date('2024-03-14T00:00:00'); // Next day + jest.useFakeTimers('modern'); + jest.setSystemTime(new Date('2024-03-14T14:59:59')); + const specificDay = new Date('2024-03-17T00:00:00'); // Next day working_hours[4].closed_all_day = true; working_hours[5].closed_all_day = true; - expect(getNextAvailabilityMessage(working_hours, specificDay)).toBe( - 'We will be back online on Saturday' - ); - }); - - test('returns correct message for next week', () => { - const nextWeek = new Date('2024-03-21T21:00:00'); // Next day - working_hours[5].closed_all_day = true; - working_hours[6].closed_all_day = true; - working_hours[0].closed_all_day = true; - working_hours[1].closed_all_day = true; - working_hours[2].closed_all_day = true; - working_hours[3].closed_all_day = true; - working_hours[4].closed_all_day = false; - expect(getNextAvailabilityMessage(working_hours, nextWeek)).toBe( - 'We will be back online on Thursday' - ); - }); - - afterEach(() => { - jest.useRealTimers(); + expect( + getNextAvailabilityMessage(working_hours, specificDay, 'America/New_York') + ).toBe('We will be back online on Monday'); }); });