chore: Replace nextAvailibility mixing with the new useNextAvailability composable and test

This commit is contained in:
Fayaz Ahmed
2024-08-09 19:21:35 +05:30
parent f29a86d1f5
commit 10282fd797
4 changed files with 293 additions and 12 deletions
@@ -1,7 +1,6 @@
<script>
import { mapGetters } from 'vuex';
import { getContrastingTextColor } from '@chatwoot/utils';
import nextAvailabilityTime from 'widget/mixins/nextAvailabilityTime';
import AvailableAgents from 'widget/components/AvailableAgents.vue';
import configMixin from 'widget/mixins/configMixin';
import { useAvailability } from 'widget/composables/useAvailability';
@@ -15,7 +14,7 @@ export default {
AvailableAgents,
FluentIcon,
},
mixins: [configMixin, nextAvailabilityTime],
mixins: [configMixin],
props: {
availableAgents: {
type: Array,
@@ -2,27 +2,28 @@ 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.
* @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 { t: $t } = useI18n();
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');
return t('REPLY_TIME.IN_A_FEW_MINUTES');
case 'in_a_few_hours':
return $t('REPLY_TIME.IN_A_FEW_HOURS');
return t('REPLY_TIME.IN_A_FEW_HOURS');
case 'in_a_day':
return $t('REPLY_TIME.IN_A_DAY');
return t('REPLY_TIME.IN_A_DAY');
default:
return $t('REPLY_TIME.IN_A_FEW_HOURS');
return t('REPLY_TIME.IN_A_FEW_HOURS');
}
});
@@ -31,11 +32,11 @@ export function useAvailability(availableAgents) {
if (workingHoursEnabled) {
return isOnline.value
? replyTimeStatus.value
: `${$t('REPLY_TIME.BACK_IN')} ${timeLeftToBackInOnline.value}`;
: `${t('REPLY_TIME.BACK_IN')} ${timeLeftToBackInOnline.value}`;
}
return isOnline.value
? replyTimeStatus.value
: $t('TEAM_AVAILABILITY.OFFLINE');
: t('TEAM_AVAILABILITY.OFFLINE');
});
const outOfOfficeMessage = computed(
@@ -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,
};
}
@@ -99,6 +99,6 @@ describe('useAvailability', () => {
vi.setSystemTime(new Date('Wed Apr 13 2022 18:00:00 GMT-0700'));
const availableAgents = ref([]);
const { replyWaitMessage } = useAvailability(availableAgents);
expect(replyWaitMessage.value).toBe('TEAM_AVAILABILITY.OFFLINE');
expect(replyWaitMessage.value).toBe('REPLY_TIME.BACK_IN at 08:30 AM');
});
});