Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bce6707d2 |
@@ -4,7 +4,7 @@ import { setHeader } from 'widget/helpers/axios';
|
||||
import addHours from 'date-fns/addHours';
|
||||
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
||||
import configMixin from './mixins/configMixin';
|
||||
import availabilityMixin from 'widget/mixins/availability';
|
||||
import { useAvailability } from 'widget/composables/useAvailability';
|
||||
import { getLocale } from './helpers/urlParamsHelper';
|
||||
import { isEmptyObject } from 'widget/helpers/utils';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
@@ -27,7 +27,11 @@ export default {
|
||||
components: {
|
||||
Spinner,
|
||||
},
|
||||
mixins: [availabilityMixin, configMixin, routerMixin, darkModeMixin],
|
||||
mixins: [configMixin, routerMixin, darkModeMixin],
|
||||
setup() {
|
||||
const { isInBusinessHours } = useAvailability();
|
||||
return { isInBusinessHours };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isMobile: false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import availabilityMixin from 'widget/mixins/availability';
|
||||
import nextAvailabilityTime from 'widget/mixins/nextAvailabilityTime';
|
||||
import { useAvailability } from 'widget/composables/useAvailability';
|
||||
import { useNextAvailabilityTime } from 'widget/composables/useNextAvailabilityTime';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import HeaderActions from './HeaderActions.vue';
|
||||
import routerMixin from 'widget/mixins/routerMixin';
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
FluentIcon,
|
||||
HeaderActions,
|
||||
},
|
||||
mixins: [nextAvailabilityTime, availabilityMixin, routerMixin, darkMixin],
|
||||
mixins: [routerMixin, darkMixin],
|
||||
props: {
|
||||
avatarUrl: {
|
||||
type: String,
|
||||
@@ -30,21 +30,25 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
availableAgents: {
|
||||
type: Array,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isOnline() {
|
||||
const { workingHoursEnabled } = this.channelConfig;
|
||||
const anyAgentOnline = this.availableAgents.length > 0;
|
||||
|
||||
if (workingHoursEnabled) {
|
||||
return this.isInBetweenTheWorkingHours;
|
||||
}
|
||||
return anyAgentOnline;
|
||||
},
|
||||
setup() {
|
||||
const {
|
||||
isOnline,
|
||||
channelConfig,
|
||||
isInBetweenTheWorkingHours,
|
||||
replyWaitMessage,
|
||||
} = useAvailability();
|
||||
const { setTimeSlot } = useNextAvailabilityTime();
|
||||
return {
|
||||
isOnline,
|
||||
channelConfig,
|
||||
isInBetweenTheWorkingHours,
|
||||
replyWaitMessage,
|
||||
setTimeSlot,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.setTimeSlot();
|
||||
},
|
||||
methods: {
|
||||
onBackButtonClick() {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { getContrastingTextColor } from '@chatwoot/utils';
|
||||
import nextAvailabilityTime from 'widget/mixins/nextAvailabilityTime';
|
||||
import { useNextAvailabilityTime } from 'widget/composables/useNextAvailabilityTime';
|
||||
import AvailableAgents from 'widget/components/AvailableAgents.vue';
|
||||
import configMixin from 'widget/mixins/configMixin';
|
||||
import availabilityMixin from 'widget/mixins/availability';
|
||||
import { useAvailability } from 'widget/composables/useAvailability';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import { IFrameHelper } from 'widget/helpers/utils';
|
||||
import { CHATWOOT_ON_START_CONVERSATION } from '../constants/sdkEvents';
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
AvailableAgents,
|
||||
FluentIcon,
|
||||
},
|
||||
mixins: [configMixin, nextAvailabilityTime, availabilityMixin],
|
||||
mixins: [configMixin],
|
||||
props: {
|
||||
availableAgents: {
|
||||
type: Array,
|
||||
@@ -26,7 +26,11 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
const { isInBetweenTheWorkingHours, replyWaitMessage } = useAvailability();
|
||||
const { setTimeSlot } = useNextAvailabilityTime();
|
||||
return { setTimeSlot, isInBetweenTheWorkingHours, replyWaitMessage };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
@@ -44,6 +48,9 @@ export default {
|
||||
return anyAgentOnline;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setTimeSlot();
|
||||
},
|
||||
methods: {
|
||||
startConversation() {
|
||||
this.$emit('startConversation');
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { ref } from 'vue';
|
||||
import { useAvailability } from '../useAvailability';
|
||||
import * as useNextAvailabilityTime from '../useNextAvailabilityTime';
|
||||
import * as useI18n from 'dashboard/composables/useI18n';
|
||||
import * as useMapGetter from 'dashboard/composables/store';
|
||||
|
||||
vi.mock('../useNextAvailabilityTime');
|
||||
vi.mock('dashboard/composables/useI18n');
|
||||
vi.mock('dashboard/composables/store');
|
||||
|
||||
describe('useAvailability', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
useNextAvailabilityTime.useNextAvailabilityTime.mockReturnValue({
|
||||
timeLeftToBackInOnline: ref('2 hours'),
|
||||
});
|
||||
useI18n.useI18n.mockReturnValue({
|
||||
t: vi.fn(key => key),
|
||||
});
|
||||
useMapGetter.useMapGetter.mockReturnValue(ref([]));
|
||||
|
||||
global.chatwootWebChannel = {
|
||||
workingHoursEnabled: true,
|
||||
workingHours: [
|
||||
{
|
||||
day_of_week: 3,
|
||||
closed_all_day: false,
|
||||
open_hour: 8,
|
||||
open_minutes: 30,
|
||||
close_hour: 17,
|
||||
close_minutes: 35,
|
||||
open_all_day: false,
|
||||
},
|
||||
{
|
||||
day_of_week: 4,
|
||||
closed_all_day: false,
|
||||
open_hour: 8,
|
||||
open_minutes: 30,
|
||||
close_hour: 17,
|
||||
close_minutes: 30,
|
||||
open_all_day: false,
|
||||
},
|
||||
],
|
||||
utcOffset: '-07:00',
|
||||
replyTime: 'in_a_few_hours',
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('returns valid isInBetweenTheWorkingHours if in different timezone', () => {
|
||||
vi.setSystemTime(new Date('Thu Apr 14 2022 06:04:46 GMT+0530'));
|
||||
const { isInBetweenTheWorkingHours } = useAvailability();
|
||||
expect(isInBetweenTheWorkingHours.value).toBe(true);
|
||||
});
|
||||
|
||||
it('returns valid isInBetweenTheWorkingHours if in same timezone', () => {
|
||||
global.chatwootWebChannel.utcOffset = '+05:30';
|
||||
vi.setSystemTime(new Date('Thu Apr 14 2022 09:01:46 GMT+0530'));
|
||||
const { isInBetweenTheWorkingHours } = useAvailability();
|
||||
expect(isInBetweenTheWorkingHours.value).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if closed all day', () => {
|
||||
global.chatwootWebChannel.utcOffset = '-07:00';
|
||||
global.chatwootWebChannel.workingHours = [
|
||||
{ day_of_week: 3, closed_all_day: true },
|
||||
];
|
||||
vi.setSystemTime(new Date('Thu Apr 14 2022 09:01:46 GMT+0530'));
|
||||
const { isInBetweenTheWorkingHours } = useAvailability();
|
||||
expect(isInBetweenTheWorkingHours.value).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if open all day', () => {
|
||||
global.chatwootWebChannel.utcOffset = '-07:00';
|
||||
global.chatwootWebChannel.workingHours = [
|
||||
{ day_of_week: 3, open_all_day: true },
|
||||
];
|
||||
vi.setSystemTime(new Date('Thu Apr 14 2022 09:01:46 GMT+0530'));
|
||||
const { isInBetweenTheWorkingHours } = useAvailability();
|
||||
expect(isInBetweenTheWorkingHours.value).toBe(true);
|
||||
});
|
||||
|
||||
it('returns correct replyTimeStatus', () => {
|
||||
const { replyTimeStatus } = useAvailability();
|
||||
expect(replyTimeStatus.value).toBe('REPLY_TIME.IN_A_FEW_HOURS');
|
||||
});
|
||||
|
||||
it('returns correct replyWaitMessage when online', () => {
|
||||
global.chatwootWebChannel.workingHoursEnabled = true;
|
||||
global.chatwootWebChannel.utcOffset = '+05:30';
|
||||
vi.setSystemTime(new Date('Thu Apr 14 2022 09:01:46 GMT+0530'));
|
||||
const { replyWaitMessage, isInBetweenTheWorkingHours } = useAvailability();
|
||||
expect(isInBetweenTheWorkingHours.value).toBe(true);
|
||||
expect(replyWaitMessage.value).toBe('REPLY_TIME.IN_A_FEW_HOURS');
|
||||
});
|
||||
|
||||
it('returns correct replyWaitMessage when offline', () => {
|
||||
global.chatwootWebChannel.workingHoursEnabled = true;
|
||||
global.chatwootWebChannel.workingHours = [
|
||||
{ day_of_week: 3, closed_all_day: true },
|
||||
];
|
||||
vi.setSystemTime(new Date('Thu Apr 14 2022 09:01:46 GMT+0530'));
|
||||
const { replyWaitMessage } = useAvailability();
|
||||
expect(replyWaitMessage.value).toBe('REPLY_TIME.BACK_IN 2 hours');
|
||||
});
|
||||
|
||||
it('returns correct isInBusinessHours', () => {
|
||||
global.chatwootWebChannel.workingHoursEnabled = true;
|
||||
global.chatwootWebChannel.utcOffset = '+05:30';
|
||||
vi.setSystemTime(new Date('Thu Apr 14 2022 09:01:46 GMT+0530'));
|
||||
const { isInBusinessHours, isInBetweenTheWorkingHours } = useAvailability();
|
||||
expect(isInBetweenTheWorkingHours.value).toBe(true);
|
||||
expect(isInBusinessHours.value).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,275 @@
|
||||
import { useNextAvailabilityTime } from '../useNextAvailabilityTime';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { generateRelativeTime } from 'shared/helpers/DateHelper';
|
||||
import { utcToZonedTime } from 'date-fns-tz';
|
||||
|
||||
vi.mock('dashboard/composables/useI18n');
|
||||
|
||||
describe('useNextAvailabilityTime', () => {
|
||||
const chatwootWebChannel = {
|
||||
workingHoursEnabled: true,
|
||||
workingHours: [
|
||||
{
|
||||
day_of_week: 0,
|
||||
open_hour: 9,
|
||||
closed_all_day: false,
|
||||
open_minutes: 0,
|
||||
close_hour: 17,
|
||||
},
|
||||
{
|
||||
day_of_week: 1,
|
||||
open_hour: 9,
|
||||
closed_all_day: false,
|
||||
open_minutes: 0,
|
||||
close_hour: 17,
|
||||
},
|
||||
{
|
||||
day_of_week: 2,
|
||||
open_hour: 9,
|
||||
closed_all_day: false,
|
||||
open_minutes: 0,
|
||||
close_hour: 17,
|
||||
},
|
||||
{
|
||||
day_of_week: 3,
|
||||
open_hour: 9,
|
||||
closed_all_day: false,
|
||||
open_minutes: 0,
|
||||
close_hour: 17,
|
||||
},
|
||||
{
|
||||
day_of_week: 4,
|
||||
open_hour: 9,
|
||||
closed_all_day: false,
|
||||
open_minutes: 0,
|
||||
close_hour: 17,
|
||||
},
|
||||
{
|
||||
day_of_week: 5,
|
||||
open_hour: 9,
|
||||
closed_all_day: false,
|
||||
open_minutes: 0,
|
||||
close_hour: 17,
|
||||
},
|
||||
{
|
||||
day_of_week: 6,
|
||||
open_hour: 9,
|
||||
closed_all_day: false,
|
||||
open_minutes: 0,
|
||||
close_hour: 17,
|
||||
},
|
||||
],
|
||||
timezone: 'UTC',
|
||||
locale: 'en',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.chatwootWebChannel = chatwootWebChannel;
|
||||
useI18n.mockReturnValue({
|
||||
t: vi.fn(key => {
|
||||
if (key === 'DAY_NAMES') {
|
||||
return [
|
||||
'Sunday',
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday',
|
||||
];
|
||||
}
|
||||
return key;
|
||||
}),
|
||||
});
|
||||
generateRelativeTime.mockImplementation((value, unit) => {
|
||||
if (unit === 'hour') return `in ${value} hour${value > 1 ? 's' : ''}`;
|
||||
if (unit === 'minutes')
|
||||
return `in ${value} minute${value > 1 ? 's' : ''}`;
|
||||
if (unit === 'days') return value === 1 ? 'tomorrow' : `in ${value} days`;
|
||||
return `in ${value} ${unit}`;
|
||||
});
|
||||
utcToZonedTime.mockImplementation(date => new Date(date));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete window.chatwootWebChannel;
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return day names', () => {
|
||||
const { dayNames } = useNextAvailabilityTime();
|
||||
expect(dayNames).toEqual([
|
||||
'Sunday',
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return channelConfig', () => {
|
||||
const { channelConfig } = useNextAvailabilityTime();
|
||||
expect(channelConfig.value).toEqual(chatwootWebChannel);
|
||||
});
|
||||
|
||||
it('should return workingHours', () => {
|
||||
const { workingHours } = useNextAvailabilityTime();
|
||||
expect(workingHours.value).toEqual(chatwootWebChannel.workingHours);
|
||||
});
|
||||
|
||||
it('should return currentDayWorkingHours', () => {
|
||||
const { currentDayWorkingHours } = useNextAvailabilityTime();
|
||||
const currentDay = new Date().getDay();
|
||||
const expectedWorkingHours = chatwootWebChannel.workingHours.find(
|
||||
slot => slot.day_of_week === currentDay
|
||||
);
|
||||
expect(currentDayWorkingHours.value).toEqual(expectedWorkingHours);
|
||||
});
|
||||
|
||||
it('should return nextDayWorkingHours', () => {
|
||||
const { nextDayWorkingHours } = useNextAvailabilityTime();
|
||||
const currentDay = new Date().getDay();
|
||||
const nextDay = currentDay === 6 ? 0 : currentDay + 1;
|
||||
const expectedWorkingHours = chatwootWebChannel.workingHours.find(
|
||||
slot => slot.day_of_week === nextDay
|
||||
);
|
||||
expect(nextDayWorkingHours.value).toEqual(expectedWorkingHours);
|
||||
});
|
||||
|
||||
it('should return presentHour', () => {
|
||||
const { presentHour } = useNextAvailabilityTime();
|
||||
expect(presentHour.value).toBe(new Date().getUTCHours());
|
||||
});
|
||||
|
||||
it('should return presentMinute', () => {
|
||||
const { presentMinute } = useNextAvailabilityTime();
|
||||
expect(presentMinute.value).toBe(new Date().getUTCMinutes());
|
||||
});
|
||||
|
||||
it('should return currentDay', () => {
|
||||
const { currentDay } = useNextAvailabilityTime();
|
||||
const date = new Date();
|
||||
const day = date.getUTCDay();
|
||||
expect(currentDay.value).toBe(day);
|
||||
});
|
||||
|
||||
it('should return currentDayTimings', () => {
|
||||
const { currentDayTimings, currentDayWorkingHours } =
|
||||
useNextAvailabilityTime();
|
||||
const {
|
||||
open_hour: openHour,
|
||||
open_minutes: openMinute,
|
||||
close_hour: closeHour,
|
||||
} = currentDayWorkingHours.value;
|
||||
expect(currentDayTimings.value).toEqual({
|
||||
openHour,
|
||||
openMinute,
|
||||
closeHour,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return nextDayTimings', () => {
|
||||
const { nextDayTimings, nextDayWorkingHours } = useNextAvailabilityTime();
|
||||
const { open_hour: openHour, open_minutes: openMinute } =
|
||||
nextDayWorkingHours.value;
|
||||
expect(nextDayTimings.value).toEqual({
|
||||
openHour,
|
||||
openMinute,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return dayDiff', () => {
|
||||
const { dayDiff, currentDay, nextDayWorkingHours } =
|
||||
useNextAvailabilityTime();
|
||||
const nextDay = nextDayWorkingHours.value.day_of_week;
|
||||
const totalDays = 6;
|
||||
const expectedDayDiff =
|
||||
nextDay > currentDay.value
|
||||
? nextDay - currentDay.value - 1
|
||||
: totalDays - currentDay.value + nextDay;
|
||||
expect(dayDiff.value).toEqual(expectedDayDiff);
|
||||
});
|
||||
|
||||
it('should return dayNameOfNextWorkingDay', () => {
|
||||
const { dayNameOfNextWorkingDay, nextDayWorkingHours, dayNames } =
|
||||
useNextAvailabilityTime();
|
||||
const nextDay = nextDayWorkingHours.value.day_of_week;
|
||||
const expectedDayName = dayNames[nextDay];
|
||||
expect(dayNameOfNextWorkingDay.value).toEqual(expectedDayName);
|
||||
});
|
||||
|
||||
it('should return hoursAndMinutesBackInOnline', () => {
|
||||
const { hoursAndMinutesBackInOnline } = useNextAvailabilityTime();
|
||||
expect(hoursAndMinutesBackInOnline.value).toBeDefined();
|
||||
expect(typeof hoursAndMinutesBackInOnline.value.hoursLeft).toBe('number');
|
||||
expect(typeof hoursAndMinutesBackInOnline.value.minutesLeft).toBe('number');
|
||||
});
|
||||
|
||||
// Time-based tests
|
||||
describe('time-based tests', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should return in 30 minutes', () => {
|
||||
vi.setSystemTime(new Date('2022-04-14T17:30:00.000Z')); // Thursday
|
||||
chatwootWebChannel.workingHours[4].open_hour = 18;
|
||||
chatwootWebChannel.workingHours[4].open_minutes = 0;
|
||||
chatwootWebChannel.workingHours[4].close_hour = 23;
|
||||
|
||||
const { timeLeftToBackInOnline, setTimeSlot } = useNextAvailabilityTime();
|
||||
setTimeSlot();
|
||||
expect(timeLeftToBackInOnline.value).toBe('in 30 minutes');
|
||||
});
|
||||
|
||||
it('should return in 2 hours', () => {
|
||||
vi.setSystemTime(new Date('2022-04-14T17:00:00.000Z')); // Thursday
|
||||
chatwootWebChannel.workingHours[4].open_hour = 19;
|
||||
chatwootWebChannel.workingHours[4].open_minutes = 0;
|
||||
chatwootWebChannel.workingHours[4].close_hour = 23;
|
||||
|
||||
const { timeLeftToBackInOnline, setTimeSlot } = useNextAvailabilityTime();
|
||||
setTimeSlot();
|
||||
expect(timeLeftToBackInOnline.value).toBe('in 2 hours');
|
||||
});
|
||||
|
||||
it('should return at 10:00 AM', () => {
|
||||
vi.setSystemTime(new Date('2022-04-14T22:00:00.000Z')); // Thursday
|
||||
chatwootWebChannel.workingHours[5].open_hour = 10; // Friday
|
||||
chatwootWebChannel.workingHours[5].open_minutes = 0;
|
||||
chatwootWebChannel.workingHours[5].close_hour = 18;
|
||||
|
||||
const { timeLeftToBackInOnline, setTimeSlot } = useNextAvailabilityTime();
|
||||
setTimeSlot();
|
||||
expect(timeLeftToBackInOnline.value).toBe('at 10:00 AM');
|
||||
});
|
||||
|
||||
it('should return tomorrow', () => {
|
||||
vi.setSystemTime(new Date('2022-04-14T22:00:00.000Z')); // Thursday
|
||||
chatwootWebChannel.workingHours[5].open_hour = 9; // Friday
|
||||
chatwootWebChannel.workingHours[5].open_minutes = 0;
|
||||
chatwootWebChannel.workingHours[5].close_hour = 17;
|
||||
|
||||
const { timeLeftToBackInOnline, setTimeSlot } = useNextAvailabilityTime();
|
||||
setTimeSlot();
|
||||
expect(timeLeftToBackInOnline.value).toBe('tomorrow');
|
||||
});
|
||||
|
||||
it('should return on Saturday', () => {
|
||||
vi.setSystemTime(new Date('2022-04-14T22:00:00.000Z')); // Thursday
|
||||
chatwootWebChannel.workingHours[4].open_hour = 9;
|
||||
chatwootWebChannel.workingHours[4].close_hour = 17;
|
||||
chatwootWebChannel.workingHours[5].closed_all_day = true; // Friday closed
|
||||
|
||||
const { timeLeftToBackInOnline, setTimeSlot } = useNextAvailabilityTime();
|
||||
setTimeSlot();
|
||||
expect(timeLeftToBackInOnline.value).toBe('on Saturday');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import { utcToZonedTime } from 'date-fns-tz';
|
||||
import { isTimeAfter } from 'shared/helpers/DateHelper';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { useNextAvailabilityTime } from './useNextAvailabilityTime';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
|
||||
export const useAvailability = () => {
|
||||
const availableAgents = useMapGetter('agent/availableAgents');
|
||||
|
||||
const { t } = useI18n();
|
||||
const { timeLeftToBackInOnline } = useNextAvailabilityTime();
|
||||
const channelConfig = ref(window.chatwootWebChannel);
|
||||
|
||||
const replyTimeStatus = computed(() => {
|
||||
switch (channelConfig.value.replyTime) {
|
||||
case 'in_a_few_minutes':
|
||||
return t('REPLY_TIME.IN_A_FEW_MINUTES');
|
||||
case 'in_a_few_hours':
|
||||
return t('REPLY_TIME.IN_A_FEW_HOURS');
|
||||
case 'in_a_day':
|
||||
return t('REPLY_TIME.IN_A_DAY');
|
||||
default:
|
||||
return t('REPLY_TIME.IN_A_FEW_HOURS');
|
||||
}
|
||||
});
|
||||
|
||||
function getDateWithOffset(utcOffset) {
|
||||
return utcToZonedTime(new Date().toISOString(), utcOffset);
|
||||
}
|
||||
|
||||
const currentDayAvailability = computed(() => {
|
||||
const { utcOffset, workingHours } = channelConfig.value;
|
||||
const dayOfTheWeek = getDateWithOffset(utcOffset).getDay();
|
||||
const [workingHourConfig = {}] = workingHours.filter(
|
||||
workingHour => workingHour.day_of_week === dayOfTheWeek
|
||||
);
|
||||
return {
|
||||
closedAllDay: workingHourConfig.closed_all_day,
|
||||
openHour: workingHourConfig.open_hour,
|
||||
openMinute: workingHourConfig.open_minutes,
|
||||
closeHour: workingHourConfig.close_hour,
|
||||
closeMinute: workingHourConfig.close_minutes,
|
||||
openAllDay: workingHourConfig.open_all_day,
|
||||
};
|
||||
});
|
||||
|
||||
const isInBetweenTheWorkingHours = computed(() => {
|
||||
const {
|
||||
openHour,
|
||||
openMinute,
|
||||
closeHour,
|
||||
closeMinute,
|
||||
closedAllDay,
|
||||
openAllDay,
|
||||
} = currentDayAvailability.value;
|
||||
|
||||
if (openAllDay) return true;
|
||||
if (closedAllDay) return false;
|
||||
|
||||
const { utcOffset } = channelConfig.value;
|
||||
const today = getDateWithOffset(utcOffset);
|
||||
const currentHours = today.getHours();
|
||||
const currentMinutes = today.getMinutes();
|
||||
|
||||
const isAfterStartTime = isTimeAfter(
|
||||
currentHours,
|
||||
currentMinutes,
|
||||
openHour,
|
||||
openMinute
|
||||
);
|
||||
const isBeforeEndTime = isTimeAfter(
|
||||
closeHour,
|
||||
closeMinute,
|
||||
currentHours,
|
||||
currentMinutes
|
||||
);
|
||||
return isAfterStartTime && isBeforeEndTime;
|
||||
});
|
||||
|
||||
const isOnline = computed(() => {
|
||||
const { workingHoursEnabled } = channelConfig.value;
|
||||
const anyAgentOnline = availableAgents.value.length > 0;
|
||||
|
||||
if (workingHoursEnabled) {
|
||||
return isInBetweenTheWorkingHours.value;
|
||||
}
|
||||
return anyAgentOnline;
|
||||
});
|
||||
|
||||
const replyWaitMessage = computed(() => {
|
||||
const { workingHoursEnabled } = channelConfig.value;
|
||||
if (workingHoursEnabled) {
|
||||
return isOnline.value
|
||||
? replyTimeStatus.value
|
||||
: `${t('REPLY_TIME.BACK_IN')} ${timeLeftToBackInOnline.value}`;
|
||||
}
|
||||
return isOnline.value
|
||||
? replyTimeStatus.value
|
||||
: t('TEAM_AVAILABILITY.OFFLINE');
|
||||
});
|
||||
|
||||
const outOfOfficeMessage = computed(
|
||||
() => channelConfig.value.outOfOfficeMessage
|
||||
);
|
||||
|
||||
const isInBusinessHours = computed(() => {
|
||||
const { workingHoursEnabled } = channelConfig.value;
|
||||
return workingHoursEnabled ? isInBetweenTheWorkingHours.value : true;
|
||||
});
|
||||
|
||||
return {
|
||||
channelConfig,
|
||||
replyTimeStatus,
|
||||
currentDayAvailability,
|
||||
isInBetweenTheWorkingHours,
|
||||
isOnline,
|
||||
replyWaitMessage,
|
||||
outOfOfficeMessage,
|
||||
isInBusinessHours,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,245 @@
|
||||
import { ref, computed } from 'vue';
|
||||
import {
|
||||
timeSlotParse,
|
||||
defaultTimeSlot,
|
||||
} from 'dashboard/routes/dashboard/settings/inbox/helpers/businessHour.js';
|
||||
import { utcToZonedTime } from 'date-fns-tz';
|
||||
import { generateRelativeTime } from 'shared/helpers/DateHelper';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
|
||||
const MINUTE_ROUNDING_FACTOR = 5;
|
||||
|
||||
export const useNextAvailabilityTime = () => {
|
||||
const { t } = useI18n();
|
||||
const dayNames = t('DAY_NAMES');
|
||||
const timeSlots = ref([...defaultTimeSlot]);
|
||||
const timeSlot = ref({});
|
||||
|
||||
const channelConfig = computed(() => window.chatwootWebChannel);
|
||||
const workingHours = computed(() => channelConfig.value.workingHours);
|
||||
const timeZoneValue = computed(() => channelConfig.value.timezone);
|
||||
const languageCode = computed(() => window.chatwootWebChannel.locale);
|
||||
|
||||
const newDateWithTimeZone = computed(() =>
|
||||
utcToZonedTime(new Date(), timeZoneValue.value)
|
||||
);
|
||||
const presentHour = computed(() => newDateWithTimeZone.value.getHours());
|
||||
const presentMinute = computed(() => newDateWithTimeZone.value.getMinutes());
|
||||
|
||||
const currentDay = computed(() => {
|
||||
const date = newDateWithTimeZone.value;
|
||||
const day = date.getDay();
|
||||
const getCurrentDay = Object.keys(dayNames).find(
|
||||
key => dayNames[key] === dayNames[day]
|
||||
);
|
||||
return Number(getCurrentDay);
|
||||
});
|
||||
|
||||
const currentDayWorkingHours = computed(() =>
|
||||
workingHours.value.find(slot => slot.day_of_week === currentDay.value)
|
||||
);
|
||||
|
||||
function getNextDay(day) {
|
||||
return (day + 1) % 7;
|
||||
}
|
||||
|
||||
function getNextWorkingHour(day) {
|
||||
const workingHour = workingHours.value.find(
|
||||
slot => slot.day_of_week === day
|
||||
);
|
||||
if (workingHour && !workingHour.closed_all_day) {
|
||||
return workingHour;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const nextDayWorkingHours = computed(() => {
|
||||
let nextDay = getNextDay(currentDay.value);
|
||||
let nextWorkingHour = getNextWorkingHour(nextDay);
|
||||
|
||||
while (!nextWorkingHour) {
|
||||
nextDay = getNextDay(nextDay);
|
||||
nextWorkingHour = getNextWorkingHour(nextDay);
|
||||
}
|
||||
return nextWorkingHour;
|
||||
});
|
||||
|
||||
const currentDayTimings = computed(() => {
|
||||
const {
|
||||
open_hour: openHour,
|
||||
open_minutes: openMinute,
|
||||
close_hour: closeHour,
|
||||
} = currentDayWorkingHours.value ?? {};
|
||||
return { openHour, openMinute, closeHour };
|
||||
});
|
||||
|
||||
const nextDayTimings = computed(() => {
|
||||
const { open_hour: openHour, open_minutes: openMinute } =
|
||||
nextDayWorkingHours.value ?? {};
|
||||
return { openHour, openMinute };
|
||||
});
|
||||
|
||||
const dayDiff = computed(() => {
|
||||
const nextDay = nextDayWorkingHours.value.day_of_week;
|
||||
const totalDays = 6;
|
||||
return nextDay > currentDay.value
|
||||
? nextDay - currentDay.value - 1
|
||||
: totalDays - currentDay.value + nextDay;
|
||||
});
|
||||
|
||||
const dayNameOfNextWorkingDay = computed(
|
||||
() => dayNames[nextDayWorkingHours.value.day_of_week]
|
||||
);
|
||||
|
||||
function getHoursAndMinutesUntilNextDayOpen(
|
||||
openHour,
|
||||
openMinutes,
|
||||
closeHour
|
||||
) {
|
||||
if (closeHour < openHour) {
|
||||
openHour += 24;
|
||||
}
|
||||
let diffMinutes =
|
||||
openHour * 60 +
|
||||
openMinutes -
|
||||
(presentHour.value * 60 + presentMinute.value);
|
||||
diffMinutes = diffMinutes < 0 ? diffMinutes + 24 * 60 : diffMinutes;
|
||||
const [hoursLeft, minutesLeft] = [
|
||||
Math.floor(diffMinutes / 60),
|
||||
diffMinutes % 60,
|
||||
];
|
||||
|
||||
return { hoursLeft, minutesLeft };
|
||||
}
|
||||
|
||||
const hoursAndMinutesBackInOnline = computed(() => {
|
||||
if (presentHour.value >= currentDayTimings.value.closeHour) {
|
||||
return getHoursAndMinutesUntilNextDayOpen(
|
||||
nextDayWorkingHours.value.open_all_day
|
||||
? 0
|
||||
: nextDayTimings.value.openHour,
|
||||
nextDayTimings.value.openMinute,
|
||||
currentDayTimings.value.closeHour
|
||||
);
|
||||
}
|
||||
return getHoursAndMinutesUntilNextDayOpen(
|
||||
currentDayTimings.value.openHour,
|
||||
currentDayTimings.value.openMinute,
|
||||
currentDayTimings.value.closeHour
|
||||
);
|
||||
});
|
||||
|
||||
const exactTimeInAmPm = computed(
|
||||
() =>
|
||||
`${
|
||||
timeSlot.value.day === currentDay.value
|
||||
? `at ${timeSlot.value.from}`
|
||||
: ''
|
||||
}`
|
||||
);
|
||||
|
||||
const hoursAndMinutesLeft = computed(() => {
|
||||
const { hoursLeft, minutesLeft } = hoursAndMinutesBackInOnline.value;
|
||||
const timeLeftChars = [];
|
||||
|
||||
if (hoursLeft > 0) {
|
||||
const roundedUpHoursLeft = minutesLeft > 0 ? hoursLeft + 1 : hoursLeft;
|
||||
const hourRelative = generateRelativeTime(
|
||||
roundedUpHoursLeft,
|
||||
'hour',
|
||||
languageCode.value
|
||||
);
|
||||
timeLeftChars.push(`${hourRelative}`);
|
||||
}
|
||||
|
||||
if (minutesLeft > 0 && hoursLeft === 0) {
|
||||
const roundedUpMinLeft =
|
||||
Math.ceil(minutesLeft / MINUTE_ROUNDING_FACTOR) *
|
||||
MINUTE_ROUNDING_FACTOR;
|
||||
const minRelative = generateRelativeTime(
|
||||
roundedUpMinLeft,
|
||||
'minutes',
|
||||
languageCode.value
|
||||
);
|
||||
timeLeftChars.push(`${minRelative}`);
|
||||
}
|
||||
|
||||
return timeLeftChars.join(' ');
|
||||
});
|
||||
|
||||
const hoursAndMinutesToBack = computed(() => {
|
||||
const { hoursLeft, minutesLeft } = hoursAndMinutesBackInOnline.value;
|
||||
if (hoursLeft >= 3) {
|
||||
return exactTimeInAmPm.value;
|
||||
}
|
||||
if (hoursLeft > 0 || minutesLeft > 0) {
|
||||
return hoursAndMinutesLeft.value;
|
||||
}
|
||||
return 'in some time';
|
||||
});
|
||||
|
||||
const timeLeftToBackInOnline = computed(() => {
|
||||
if (
|
||||
hoursAndMinutesBackInOnline.value.hoursLeft >= 24 ||
|
||||
(timeSlot.value.day !== currentDay.value && dayDiff.value === 0)
|
||||
) {
|
||||
const hourRelative = generateRelativeTime(
|
||||
dayDiff.value + 1,
|
||||
'days',
|
||||
languageCode.value
|
||||
);
|
||||
return `${hourRelative}`;
|
||||
}
|
||||
if (
|
||||
dayDiff.value >= 1 &&
|
||||
presentHour.value >= currentDayTimings.value.closeHour
|
||||
) {
|
||||
return `on ${dayNameOfNextWorkingDay.value}`;
|
||||
}
|
||||
return hoursAndMinutesToBack.value;
|
||||
});
|
||||
|
||||
function setTimeSlot() {
|
||||
const workingTimeSlots = workingHours.value;
|
||||
|
||||
const currentSlot =
|
||||
presentHour.value >= currentDayTimings.value.closeHour
|
||||
? nextDayWorkingHours.value
|
||||
: currentDayWorkingHours.value;
|
||||
|
||||
const slots = timeSlotParse(workingTimeSlots).length
|
||||
? timeSlotParse(workingTimeSlots)
|
||||
: defaultTimeSlot;
|
||||
timeSlots.value = slots;
|
||||
|
||||
timeSlot.value = timeSlots.value.find(
|
||||
slot => slot.day === currentSlot.day_of_week
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
dayNames,
|
||||
timeSlots,
|
||||
timeSlot,
|
||||
channelConfig,
|
||||
workingHours,
|
||||
newDateWithTimeZone,
|
||||
presentHour,
|
||||
presentMinute,
|
||||
currentDay,
|
||||
timeZoneValue,
|
||||
languageCode,
|
||||
currentDayWorkingHours,
|
||||
nextDayWorkingHours,
|
||||
currentDayTimings,
|
||||
nextDayTimings,
|
||||
dayDiff,
|
||||
dayNameOfNextWorkingDay,
|
||||
hoursAndMinutesBackInOnline,
|
||||
exactTimeInAmPm,
|
||||
hoursAndMinutesLeft,
|
||||
hoursAndMinutesToBack,
|
||||
timeLeftToBackInOnline,
|
||||
setTimeSlot,
|
||||
};
|
||||
};
|
||||
@@ -26,7 +26,7 @@ export default {
|
||||
if (workingHoursEnabled) {
|
||||
return this.isOnline
|
||||
? this.replyTimeStatus
|
||||
: `${this.$t('REPLY_TIME.BACK_IN')} ${this.timeLeftToBackInOnline}`;
|
||||
: `${this.$t('REPLY_TIME.BACK_IN')} ${this.timeLeftToBackInOnline}`; // timeLeftToBackInOnline should be pulled in from the useNextAvailabilityTime composable
|
||||
}
|
||||
return this.isOnline
|
||||
? this.replyTimeStatus
|
||||
|
||||
Reference in New Issue
Block a user