Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62fc4186ea | ||
|
|
ea3c12d14f | ||
|
|
427f5bef27 | ||
|
|
d297987bc5 | ||
|
|
0aa5237bbf | ||
|
|
10282fd797 | ||
|
|
f29a86d1f5 | ||
|
|
a795028979 |
@@ -4,7 +4,7 @@ import { setHeader } from 'widget/helpers/axios';
|
|||||||
import addHours from 'date-fns/addHours';
|
import addHours from 'date-fns/addHours';
|
||||||
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
||||||
import configMixin from './mixins/configMixin';
|
import configMixin from './mixins/configMixin';
|
||||||
import availabilityMixin from 'widget/mixins/availability';
|
import { useAvailability } from 'widget/composables/useAvailability';
|
||||||
import { getLocale } from './helpers/urlParamsHelper';
|
import { getLocale } from './helpers/urlParamsHelper';
|
||||||
import { isEmptyObject } from 'widget/helpers/utils';
|
import { isEmptyObject } from 'widget/helpers/utils';
|
||||||
import Spinner from 'shared/components/Spinner.vue';
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
@@ -27,13 +27,17 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
Spinner,
|
Spinner,
|
||||||
},
|
},
|
||||||
mixins: [availabilityMixin, configMixin, routerMixin, darkModeMixin],
|
mixins: [configMixin, routerMixin, darkModeMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isMobile: false,
|
isMobile: false,
|
||||||
campaignsSnoozedTill: undefined,
|
campaignsSnoozedTill: undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
const { isInBusinessHours } = useAvailability();
|
||||||
|
return { isInBusinessHours };
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
activeCampaign: 'campaign/getActiveCampaign',
|
activeCampaign: 'campaign/getActiveCampaign',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import availabilityMixin from 'widget/mixins/availability';
|
import { useAvailability } from 'widget/composables/useAvailability';
|
||||||
import nextAvailabilityTime from 'widget/mixins/nextAvailabilityTime';
|
import nextAvailabilityTime from 'widget/mixins/nextAvailabilityTime';
|
||||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||||
import HeaderActions from './HeaderActions.vue';
|
import HeaderActions from './HeaderActions.vue';
|
||||||
@@ -12,7 +12,7 @@ export default {
|
|||||||
FluentIcon,
|
FluentIcon,
|
||||||
HeaderActions,
|
HeaderActions,
|
||||||
},
|
},
|
||||||
mixins: [nextAvailabilityTime, availabilityMixin, routerMixin, darkMixin],
|
mixins: [nextAvailabilityTime, routerMixin, darkMixin],
|
||||||
props: {
|
props: {
|
||||||
avatarUrl: {
|
avatarUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -35,16 +35,11 @@ export default {
|
|||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
setup(props) {
|
||||||
isOnline() {
|
const { isOnline, replyWaitMessage } = useAvailability(
|
||||||
const { workingHoursEnabled } = this.channelConfig;
|
props.availableAgents
|
||||||
const anyAgentOnline = this.availableAgents.length > 0;
|
);
|
||||||
|
return { isOnline, replyWaitMessage };
|
||||||
if (workingHoursEnabled) {
|
|
||||||
return this.isInBetweenTheWorkingHours;
|
|
||||||
}
|
|
||||||
return anyAgentOnline;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onBackButtonClick() {
|
onBackButtonClick() {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { getContrastingTextColor } from '@chatwoot/utils';
|
import { getContrastingTextColor } from '@chatwoot/utils';
|
||||||
import nextAvailabilityTime from 'widget/mixins/nextAvailabilityTime';
|
|
||||||
import AvailableAgents from 'widget/components/AvailableAgents.vue';
|
import AvailableAgents from 'widget/components/AvailableAgents.vue';
|
||||||
import configMixin from 'widget/mixins/configMixin';
|
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 FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||||
import { IFrameHelper } from 'widget/helpers/utils';
|
import { IFrameHelper } from 'widget/helpers/utils';
|
||||||
import { CHATWOOT_ON_START_CONVERSATION } from '../constants/sdkEvents';
|
import { CHATWOOT_ON_START_CONVERSATION } from '../constants/sdkEvents';
|
||||||
@@ -15,7 +14,7 @@ export default {
|
|||||||
AvailableAgents,
|
AvailableAgents,
|
||||||
FluentIcon,
|
FluentIcon,
|
||||||
},
|
},
|
||||||
mixins: [configMixin, nextAvailabilityTime, availabilityMixin],
|
mixins: [configMixin],
|
||||||
props: {
|
props: {
|
||||||
availableAgents: {
|
availableAgents: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -26,7 +25,12 @@ export default {
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
setup(props) {
|
||||||
|
const { isOnline, replyWaitMessage } = useAvailability(
|
||||||
|
props.availableAgents
|
||||||
|
);
|
||||||
|
return { isOnline, replyWaitMessage };
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
widgetColor: 'appConfig/getWidgetColor',
|
widgetColor: 'appConfig/getWidgetColor',
|
||||||
@@ -34,15 +38,6 @@ export default {
|
|||||||
textColor() {
|
textColor() {
|
||||||
return getContrastingTextColor(this.widgetColor);
|
return getContrastingTextColor(this.widgetColor);
|
||||||
},
|
},
|
||||||
isOnline() {
|
|
||||||
const { workingHoursEnabled } = this.channelConfig;
|
|
||||||
const anyAgentOnline = this.availableAgents.length > 0;
|
|
||||||
|
|
||||||
if (workingHoursEnabled) {
|
|
||||||
return this.isInBetweenTheWorkingHours;
|
|
||||||
}
|
|
||||||
return anyAgentOnline;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
startConversation() {
|
startConversation() {
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import { computed } from 'vue';
|
||||||
|
import { utcToZonedTime } from 'date-fns-tz';
|
||||||
|
import { isTimeAfter } from 'shared/helpers/DateHelper';
|
||||||
|
import { useI18n } from 'dashboard/composables/useI18n';
|
||||||
|
import { useNextAvailability } from './useNextAvailability';
|
||||||
|
/**
|
||||||
|
* Composable for handling availability-related computations.
|
||||||
|
* @param {Object} t - Vue i18n instance for translations.
|
||||||
|
* @returns {Object} An object containing computed properties and methods for availability management.
|
||||||
|
*/
|
||||||
|
export function useAvailability(availableAgents) {
|
||||||
|
const { timeLeftToBackInOnline } = useNextAvailability();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const channelConfig = computed(() => window.chatwootWebChannel);
|
||||||
|
const replyTime = computed(() => window.chatwootWebChannel.replyTime);
|
||||||
|
|
||||||
|
const replyTimeStatus = computed(() => {
|
||||||
|
switch (replyTime.value) {
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 currentDayAvailability = computed(() => {
|
||||||
|
const { utcOffset } = channelConfig.value;
|
||||||
|
const dayOfTheWeek = getDateWithOffset(utcOffset).getDay();
|
||||||
|
const [workingHourConfig = {}] = channelConfig.value.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 isInBusinessHours = computed(() => {
|
||||||
|
const { workingHoursEnabled } = channelConfig.value;
|
||||||
|
return workingHoursEnabled ? isInBetweenTheWorkingHours.value : true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDateWithOffset = utcOffset => {
|
||||||
|
return utcToZonedTime(new Date().toISOString(), utcOffset);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isOnline = computed(() => {
|
||||||
|
if (availableAgents) {
|
||||||
|
const { workingHoursEnabled } = channelConfig.value;
|
||||||
|
const anyAgentOnline = availableAgents.length > 0;
|
||||||
|
if (workingHoursEnabled) {
|
||||||
|
return isInBetweenTheWorkingHours.value || anyAgentOnline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
channelConfig,
|
||||||
|
replyTime,
|
||||||
|
replyTimeStatus,
|
||||||
|
replyWaitMessage,
|
||||||
|
outOfOfficeMessage,
|
||||||
|
currentDayAvailability,
|
||||||
|
isInBetweenTheWorkingHours,
|
||||||
|
isInBusinessHours,
|
||||||
|
isOnline,
|
||||||
|
getDateWithOffset,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useI18n } from 'dashboard/composables/useI18n';
|
||||||
|
import {
|
||||||
|
timeSlotParse,
|
||||||
|
defaultTimeSlot,
|
||||||
|
} from 'dashboard/routes/dashboard/settings/inbox/helpers/businessHour.js';
|
||||||
|
import { utcToZonedTime } from 'date-fns-tz';
|
||||||
|
import { generateRelativeTime } from 'shared/helpers/DateHelper';
|
||||||
|
|
||||||
|
const MINUTE_ROUNDING_FACTOR = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable for handling next availability calculations
|
||||||
|
* @returns {Object} An object containing computed properties and methods for next availability management
|
||||||
|
*/
|
||||||
|
export function useNextAvailability() {
|
||||||
|
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 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 currentDay = Object.keys(dayNames).find(
|
||||||
|
key => dayNames[key] === dayNames[day]
|
||||||
|
);
|
||||||
|
return Number(currentDay);
|
||||||
|
});
|
||||||
|
const timeZoneValue = computed(() => channelConfig.value.timezone);
|
||||||
|
const languageCode = computed(() => window.chatwootWebChannel.locale);
|
||||||
|
|
||||||
|
const currentDayWorkingHours = computed(() =>
|
||||||
|
workingHours.value.find(slot => slot.day_of_week === currentDay.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
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]
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the next day of the week
|
||||||
|
* @param {number} day - Current day of the week
|
||||||
|
* @returns {number} Next day of the week
|
||||||
|
*/
|
||||||
|
function getNextDay(day) {
|
||||||
|
return (day + 1) % 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the next working hour for a given day
|
||||||
|
* @param {number} day - Day of the week
|
||||||
|
* @returns {Object|null} Working hour object for the next working day, or null if not found
|
||||||
|
*/
|
||||||
|
function getNextWorkingHour(day) {
|
||||||
|
const workingHour = workingHours.value.find(
|
||||||
|
slot => slot.day_of_week === day
|
||||||
|
);
|
||||||
|
if (workingHour && !workingHour.closed_all_day) {
|
||||||
|
return workingHour;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates hours and minutes until next day open
|
||||||
|
* @param {number} openHour - Opening hour
|
||||||
|
* @param {number} openMinutes - Opening minutes
|
||||||
|
* @param {number} closeHour - Closing hour
|
||||||
|
* @returns {Object} Object containing hours and minutes left
|
||||||
|
*/
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the time slot
|
||||||
|
*/
|
||||||
|
function setTimeSlot() {
|
||||||
|
const timeSlots = workingHours.value;
|
||||||
|
|
||||||
|
const currentSlot =
|
||||||
|
presentHour.value >= currentDayTimings.value.closeHour
|
||||||
|
? nextDayWorkingHours.value
|
||||||
|
: currentDayWorkingHours.value;
|
||||||
|
|
||||||
|
const slots = timeSlotParse(timeSlots).length
|
||||||
|
? timeSlotParse(timeSlots)
|
||||||
|
: defaultTimeSlot;
|
||||||
|
timeSlots.value = slots;
|
||||||
|
|
||||||
|
timeSlot.value = timeSlots.value.find(
|
||||||
|
slot => slot.day === currentSlot.day_of_week
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call setTimeSlot initially
|
||||||
|
setTimeSlot();
|
||||||
|
|
||||||
|
return {
|
||||||
|
dayNames,
|
||||||
|
timeSlots,
|
||||||
|
timeSlot,
|
||||||
|
channelConfig,
|
||||||
|
workingHours,
|
||||||
|
newDateWithTimeZone,
|
||||||
|
presentHour,
|
||||||
|
presentMinute,
|
||||||
|
currentDay,
|
||||||
|
timeZoneValue,
|
||||||
|
languageCode,
|
||||||
|
currentDayWorkingHours,
|
||||||
|
nextDayWorkingHours,
|
||||||
|
currentDayTimings,
|
||||||
|
nextDayTimings,
|
||||||
|
dayDiff,
|
||||||
|
dayNameOfNextWorkingDay,
|
||||||
|
hoursAndMinutesBackInOnline,
|
||||||
|
exactTimeInAmPm,
|
||||||
|
hoursAndMinutesLeft,
|
||||||
|
hoursAndMinutesToBack,
|
||||||
|
timeLeftToBackInOnline,
|
||||||
|
getNextDay,
|
||||||
|
getNextWorkingHour,
|
||||||
|
getHoursAndMinutesUntilNextDayOpen,
|
||||||
|
setTimeSlot,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useAvailability } from '../../composables/useAvailability';
|
||||||
|
|
||||||
|
vi.mock('dashboard/composables/useI18n', () => ({
|
||||||
|
useI18n: () => ({
|
||||||
|
t: key => key,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('useAvailability', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
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_minutes',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct replyTimeStatus', () => {
|
||||||
|
const availableAgents = ref([]);
|
||||||
|
const { replyTimeStatus } = useAvailability(availableAgents);
|
||||||
|
expect(replyTimeStatus.value).toBe('REPLY_TIME.IN_A_FEW_MINUTES');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct isInBetweenTheWorkingHours when in working hours', () => {
|
||||||
|
vi.setSystemTime(new Date('Wed Apr 13 2022 10:00:00 GMT-0700'));
|
||||||
|
const availableAgents = ref([]);
|
||||||
|
const { isInBetweenTheWorkingHours } = useAvailability(availableAgents);
|
||||||
|
expect(isInBetweenTheWorkingHours.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct isInBetweenTheWorkingHours when outside working hours', () => {
|
||||||
|
vi.setSystemTime(new Date('Wed Apr 13 2022 18:00:00 GMT-0700'));
|
||||||
|
const availableAgents = ref([]);
|
||||||
|
const { isInBetweenTheWorkingHours } = useAvailability(availableAgents);
|
||||||
|
expect(isInBetweenTheWorkingHours.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true for isInBetweenTheWorkingHours when open all day', () => {
|
||||||
|
global.chatwootWebChannel.workingHours = [
|
||||||
|
{ day_of_week: 3, open_all_day: true },
|
||||||
|
];
|
||||||
|
const availableAgents = ref([]);
|
||||||
|
const { isInBetweenTheWorkingHours } = useAvailability(availableAgents);
|
||||||
|
expect(isInBetweenTheWorkingHours.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for isInBetweenTheWorkingHours when closed all day', () => {
|
||||||
|
global.chatwootWebChannel.workingHours = [
|
||||||
|
{ day_of_week: 3, closed_all_day: true },
|
||||||
|
];
|
||||||
|
const availableAgents = ref([]);
|
||||||
|
const { isInBetweenTheWorkingHours } = useAvailability(availableAgents);
|
||||||
|
expect(isInBetweenTheWorkingHours.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct isOnline status when agents are available', () => {
|
||||||
|
const availableAgents = ref([{ id: 1 }]);
|
||||||
|
const { isOnline } = useAvailability(availableAgents);
|
||||||
|
expect(isOnline.value).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct isOnline status when no agents are available and outside working hours', () => {
|
||||||
|
vi.setSystemTime(new Date('Wed Apr 13 2022 18:00:00 GMT-0700'));
|
||||||
|
const availableAgents = ref([]);
|
||||||
|
const { isOnline } = useAvailability(availableAgents);
|
||||||
|
expect(isOnline.value).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct replyWaitMessage when online', () => {
|
||||||
|
const availableAgents = ref([{ id: 1 }]);
|
||||||
|
const { replyWaitMessage } = useAvailability(availableAgents);
|
||||||
|
expect(replyWaitMessage.value).toBe('REPLY_TIME.IN_A_FEW_MINUTES');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct replyWaitMessage when offline', () => {
|
||||||
|
vi.setSystemTime(new Date('Wed Apr 13 2022 18:00:00 GMT-0700'));
|
||||||
|
const availableAgents = ref([]);
|
||||||
|
const { replyWaitMessage } = useAvailability(availableAgents);
|
||||||
|
expect(replyWaitMessage.value).toBe('REPLY_TIME.BACK_IN at 08:30 AM');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user