Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5343b3f8ae | ||
|
|
11ae25535a | ||
|
|
d8708e5ee5 | ||
|
|
3efdd446a9 | ||
|
|
85d970754b | ||
|
|
7cc6adbd9e | ||
|
|
6cad9c683a |
@@ -0,0 +1,150 @@
|
|||||||
|
import { utcToZonedTime } from 'date-fns-tz';
|
||||||
|
// Array defining days of the week
|
||||||
|
const daysOfWeek = [
|
||||||
|
'Sunday',
|
||||||
|
'Monday',
|
||||||
|
'Tuesday',
|
||||||
|
'Wednesday',
|
||||||
|
'Thursday',
|
||||||
|
'Friday',
|
||||||
|
'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
|
||||||
|
* @param {number} currentDayOfWeek - Index of the current 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 findNextAvailableTime = (
|
||||||
|
workingHours,
|
||||||
|
currentDayOfWeek,
|
||||||
|
currentHour,
|
||||||
|
currentMinutes
|
||||||
|
) => {
|
||||||
|
const nextAvailableTimeToday = findNextAvailableTimeForDay(
|
||||||
|
workingHours,
|
||||||
|
currentDayOfWeek,
|
||||||
|
currentHour,
|
||||||
|
currentMinutes
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nextAvailableTimeToday) {
|
||||||
|
return { ...nextAvailableTimeToday, day: 'today' };
|
||||||
|
}
|
||||||
|
return findNextAvailableTimeAcrossDays(workingHours, currentDayOfWeek);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to get the next availability message
|
||||||
|
* @param {Array} workingHours - Array of working hours for each day
|
||||||
|
* @param {Date} currentTime - Current time object
|
||||||
|
* @returns {string} - Next availability message
|
||||||
|
*/
|
||||||
|
export const getNextAvailabilityMessage = (
|
||||||
|
workingHours,
|
||||||
|
currentTime,
|
||||||
|
timezone
|
||||||
|
) => {
|
||||||
|
const utcZonedTime = utcToZonedTime(currentTime, timezone);
|
||||||
|
const currentDayOfWeek = utcZonedTime.getDay();
|
||||||
|
const currentHour = utcZonedTime.getHours();
|
||||||
|
const currentMinutes = utcZonedTime.getMinutes();
|
||||||
|
const nextAvailableTime = findNextAvailableTime(
|
||||||
|
workingHours,
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Check if the next available time is tomorrow
|
||||||
|
if (nextAvailableTime.day === (currentDayOfWeek + 1) % 7) {
|
||||||
|
return `We will be back online tomorrow`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the next available time is on a specific day
|
||||||
|
if (nextAvailableTime.day !== 'today') {
|
||||||
|
return `We will be back online on ${daysOfWeek[nextAvailableTime.day]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate appropriate message based on time difference
|
||||||
|
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.';
|
||||||
|
};
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import { getNextAvailabilityMessage } from '../availabilityHelper';
|
||||||
|
|
||||||
|
const workingHoursCommon = [
|
||||||
|
{ id: 8, inbox_id: 2, account_id: 1, day_of_week: 0, closed_all_day: true },
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
{ id: 11, inbox_id: 2, account_id: 1, day_of_week: 3, closed_all_day: true },
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('getNextAvailabilityMessage without timezone', () => {
|
||||||
|
let workingHours;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
workingHours = JSON.parse(JSON.stringify(workingHoursCommon));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('less than a minute', () =>
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(workingHours, new Date('2024-03-14T14:59:59'))
|
||||||
|
).toBe('We will be back online in less than a minute.'));
|
||||||
|
test('less than 10 minutes', () =>
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(workingHours, new Date('2024-03-14T14:50:00'))
|
||||||
|
).toBe('We will be back online in 10 minutes.'));
|
||||||
|
test('minutes message for less than an hour', () =>
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(workingHours, new Date('2024-03-14T14:45:00'))
|
||||||
|
).toBe('We will be back online in 15 minutes.'));
|
||||||
|
test('greater than 2 hours', () =>
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(workingHours, new Date('2024-03-14T12:30:00'))
|
||||||
|
).toBe('We will be back online in 2 hours'));
|
||||||
|
test('next day', () => {
|
||||||
|
workingHours[4].closed_all_day = true;
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(workingHours, new Date('2024-03-14T00:00:00'))
|
||||||
|
).toBe('We will be back online tomorrow');
|
||||||
|
});
|
||||||
|
test('specific day', () => {
|
||||||
|
workingHours[4].closed_all_day = true;
|
||||||
|
workingHours[5].closed_all_day = true;
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(workingHours, new Date('2024-03-14T00:00:00'))
|
||||||
|
).toBe('We will be back online on Saturday');
|
||||||
|
});
|
||||||
|
test('next week', () => {
|
||||||
|
workingHours[5].closed_all_day = true;
|
||||||
|
workingHours[6].closed_all_day = true;
|
||||||
|
workingHours[0].closed_all_day = true;
|
||||||
|
workingHours[1].closed_all_day = true;
|
||||||
|
workingHours[2].closed_all_day = true;
|
||||||
|
workingHours[3].closed_all_day = true;
|
||||||
|
workingHours[4].closed_all_day = false;
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(workingHours, new Date('2024-03-21T21:00:00'))
|
||||||
|
).toBe('We will be back online on Thursday');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getNextAvailabilityMessage with timezone', () => {
|
||||||
|
let workingHours;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
workingHours = JSON.parse(JSON.stringify(workingHoursCommon));
|
||||||
|
jest.useFakeTimers('modern');
|
||||||
|
jest.setSystemTime(new Date('2024-03-14T19:50:00'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => jest.useRealTimers());
|
||||||
|
|
||||||
|
test('less than a minute', () =>
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(
|
||||||
|
workingHours,
|
||||||
|
new Date('2024-03-14T19:50:00'),
|
||||||
|
'America/New_York'
|
||||||
|
)
|
||||||
|
).toBe('We will be back online in less than a minute.'));
|
||||||
|
test('less than 10 minutes', () =>
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(
|
||||||
|
workingHours,
|
||||||
|
new Date('2024-03-14T19:50:00'),
|
||||||
|
'America/New_York'
|
||||||
|
)
|
||||||
|
).toBe('We will be back online in less than a minute.'));
|
||||||
|
test('greater than 2 hours', () =>
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(
|
||||||
|
workingHours,
|
||||||
|
new Date('2024-03-14T14:59:59'),
|
||||||
|
'America/New_York'
|
||||||
|
)
|
||||||
|
).toBe('We will be back online in 4 hours'));
|
||||||
|
test('next day', () => {
|
||||||
|
workingHours[4].closed_all_day = true;
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(
|
||||||
|
workingHours,
|
||||||
|
new Date('2024-03-15T00:00:00'),
|
||||||
|
'America/New_York'
|
||||||
|
)
|
||||||
|
).toBe('We will be back online tomorrow');
|
||||||
|
});
|
||||||
|
test('specific day', () => {
|
||||||
|
workingHours[4].closed_all_day = true;
|
||||||
|
workingHours[5].closed_all_day = true;
|
||||||
|
expect(
|
||||||
|
getNextAvailabilityMessage(
|
||||||
|
workingHours,
|
||||||
|
new Date('2024-03-17T00:00:00'),
|
||||||
|
'America/New_York'
|
||||||
|
)
|
||||||
|
).toBe('We will be back online on Monday');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { utcToZonedTime } from 'date-fns-tz';
|
import { utcToZonedTime } from 'date-fns-tz';
|
||||||
import { isTimeAfter } from 'shared/helpers/DateHelper';
|
import { isTimeAfter } from 'shared/helpers/DateHelper';
|
||||||
|
import { getNextAvailabilityMessage } from 'widget/helpers/availabilityHelper';
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
channelConfig() {
|
channelConfig() {
|
||||||
@@ -23,10 +23,13 @@ export default {
|
|||||||
},
|
},
|
||||||
replyWaitMessage() {
|
replyWaitMessage() {
|
||||||
const { workingHoursEnabled } = this.channelConfig;
|
const { workingHoursEnabled } = this.channelConfig;
|
||||||
|
const nextAvailabilityMessage = getNextAvailabilityMessage(
|
||||||
|
this.channelConfig.workingHours,
|
||||||
|
new Date(),
|
||||||
|
this.channelConfig.timezone
|
||||||
|
);
|
||||||
if (workingHoursEnabled) {
|
if (workingHoursEnabled) {
|
||||||
return this.isOnline
|
return this.isOnline ? this.replyTimeStatus : nextAvailabilityMessage;
|
||||||
? this.replyTimeStatus
|
|
||||||
: `${this.$t('REPLY_TIME.BACK_IN')} ${this.timeLeftToBackInOnline}`;
|
|
||||||
}
|
}
|
||||||
return this.isOnline
|
return this.isOnline
|
||||||
? this.replyTimeStatus
|
? this.replyTimeStatus
|
||||||
|
|||||||
Reference in New Issue
Block a user