diff --git a/app/javascript/widget/helpers/availabilityHelper.js b/app/javascript/widget/helpers/availabilityHelper.js new file mode 100644 index 000000000..fb8ca8408 --- /dev/null +++ b/app/javascript/widget/helpers/availabilityHelper.js @@ -0,0 +1,116 @@ +const daysOfWeek = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', +]; + +const findNextAvailableTime = ( + working_hours, + currentDayOfWeek, + currentHour, + currentMinutes +) => { + let nextAvailableTime = null; + + // Find the next available time for today + const todayHours = working_hours.filter( + hours => + hours.day_of_week === currentDayOfWeek && + !hours.closed_all_day && + (hours.open_hour > currentHour || + (hours.open_hour === currentHour && + hours.open_minutes > currentMinutes)) + ); + + todayHours.forEach(hours => { + if ( + nextAvailableTime === null || + hours.open_hour < nextAvailableTime.hour || + (hours.open_hour === nextAvailableTime.hour && + hours.open_minutes < nextAvailableTime.minutes) + ) { + nextAvailableTime = { + hour: hours.open_hour, + minutes: hours.open_minutes, + day: 'today', + }; + } + }); + + // If not found, find the next available time for the next day(s) + if (nextAvailableTime === null) { + let nextDaysHours = working_hours.filter( + hours => hours.day_of_week > currentDayOfWeek && !hours.closed_all_day + ); + + // If there are no available hours for the next day, consider hours for the following days + if (nextDaysHours.length === 0) { + nextDaysHours = working_hours.filter( + hours => hours.day_of_week < currentDayOfWeek && !hours.closed_all_day + ); + } + + let nextDay = null; + + for (let i = 0; i < 7; i++) { + nextDay = nextDaysHours.find(hours => hours.day_of_week === i); + if (nextDay) { + break; + } + } + + if (nextDay) { + nextAvailableTime = { + hour: nextDay.open_hour, + minutes: nextDay.open_minutes, + day: nextDay.day_of_week, + }; + } + } + + return nextAvailableTime; +}; + +export const getNextAvailabilityMessage = (working_hours, currentTime) => { + const currentDayOfWeek = currentTime.getDay(); // 0 for Sunday, 1 for Monday, ... + const currentHour = currentTime.getHours(); + const currentMinutes = currentTime.getMinutes(); + const nextAvailableTime = findNextAvailableTime( + working_hours, + currentDayOfWeek, + currentHour, + currentMinutes + ); + + // Calculate the time difference + if (nextAvailableTime !== null) { + const differenceInMinutes = + nextAvailableTime.hour * 60 + + nextAvailableTime.minutes - + (currentHour * 60 + currentMinutes); + const differenceInHours = Math.floor(differenceInMinutes / 60); + + const nextDayOfWeek = nextAvailableTime.day; + if (nextDayOfWeek === currentDayOfWeek + 1) { + return `We will be back online tomorrow`; + } + + if (nextAvailableTime.day !== 'today') { + return `We will be back online on ${daysOfWeek[nextDayOfWeek]}`; + } + if (differenceInMinutes <= 1) { + return 'We will be back online in less than a minute.'; + } + if (differenceInMinutes < 60) { + return `We will be back online in ${differenceInMinutes} minutes.`; + } + if (differenceInMinutes < 1440) { + return `We will be back online in ${differenceInHours} hours`; + } + } + return 'No available time found in working hours.'; +}; diff --git a/app/javascript/widget/helpers/specs/availabilityHelper.spec.js b/app/javascript/widget/helpers/specs/availabilityHelper.spec.js new file mode 100644 index 000000000..1a5a8d840 --- /dev/null +++ b/app/javascript/widget/helpers/specs/availabilityHelper.spec.js @@ -0,0 +1,151 @@ +import { getNextAvailabilityMessage } from '../availabilityHelper'; + +describe('getNextAvailabilityMessage function', () => { + 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: false, + 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', () => { + jest.useFakeTimers('modern').setSystemTime(new Date('2024-03-14T14:59:00')); + 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 minutes message for less than an hour', () => { + jest.useFakeTimers('modern').setSystemTime(new Date('2024-03-14T14:45:00')); + 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 an hour', () => { + jest.useFakeTimers('modern').setSystemTime(new Date('2024-03-14T12:00:00')); + 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', () => { + jest.useFakeTimers('modern').setSystemTime(new Date('2024-03-14T00:00:00')); + 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', () => { + jest.useFakeTimers('modern').setSystemTime(new Date('2024-03-14T00:00:00')); + 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' + ); + }); + + afterEach(() => { + jest.useRealTimers(); + }); +}); diff --git a/app/javascript/widget/mixins/availability.js b/app/javascript/widget/mixins/availability.js index 746a5e095..7f29bb6d9 100644 --- a/app/javascript/widget/mixins/availability.js +++ b/app/javascript/widget/mixins/availability.js @@ -1,6 +1,6 @@ import { utcToZonedTime } from 'date-fns-tz'; import { isTimeAfter } from 'shared/helpers/DateHelper'; - +import { getNextAvailabilityMessage } from 'widget/helpers/availabilityHelper'; export default { computed: { channelConfig() { @@ -23,10 +23,12 @@ export default { }, replyWaitMessage() { const { workingHoursEnabled } = this.channelConfig; + const nextAvailabilityMessage = getNextAvailabilityMessage( + this.channelConfig.workingHours, + new Date() + ); if (workingHoursEnabled) { - return this.isOnline - ? this.replyTimeStatus - : `${this.$t('REPLY_TIME.BACK_IN')} ${this.timeLeftToBackInOnline}`; + return this.isOnline ? this.replyTimeStatus : nextAvailabilityMessage; } return this.isOnline ? this.replyTimeStatus