mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 03:27:52 +00:00
Fix: Update offline message in widget by availability (#1879)
Feat: Display out of office message based on business hours Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
This commit is contained in:
committed by
GitHub
parent
2a28e05a77
commit
33b73451b7
@@ -16,12 +16,14 @@ export const formatDate = ({ date, todayText, yesterdayText }) => {
|
|||||||
return date;
|
return date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildDateFromTime = (hr, min, utcOffset, date = new Date()) => {
|
|
||||||
const today = format(date, 'yyyy-MM-dd');
|
|
||||||
const timeString = `${today}T${hr}:${min}:00${utcOffset}`;
|
|
||||||
return parseISO(timeString);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatDigitToString = val => {
|
export const formatDigitToString = val => {
|
||||||
return val > 9 ? `${val}` : `0${val}`;
|
return val > 9 ? `${val}` : `0${val}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const buildDateFromTime = (hr, min, utcOffset, date = new Date()) => {
|
||||||
|
const today = format(date, 'yyyy-MM-dd');
|
||||||
|
const hour = formatDigitToString(hr);
|
||||||
|
const minute = formatDigitToString(min);
|
||||||
|
const timeString = `${today}T${hour}:${minute}:00${utcOffset}`;
|
||||||
|
return parseISO(timeString);
|
||||||
|
};
|
||||||
|
|||||||
@@ -13,13 +13,13 @@
|
|||||||
<div
|
<div
|
||||||
:class="
|
:class="
|
||||||
`status-view--badge rounded-full leading-4 ${
|
`status-view--badge rounded-full leading-4 ${
|
||||||
availableAgents.length ? 'bg-green-500' : 'hidden'
|
isOnline ? 'bg-green-500' : 'hidden'
|
||||||
}`
|
}`
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs mt-1 text-black-700">
|
<div class="text-xs mt-1 text-black-700">
|
||||||
{{ replyTimeStatus }}
|
{{ replyWaitMeessage }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,14 +30,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import HeaderActions from './HeaderActions';
|
import HeaderActions from './HeaderActions';
|
||||||
import configMixin from 'widget/mixins/configMixin';
|
import availabilityMixin from 'widget/mixins/availability';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChatHeader',
|
name: 'ChatHeader',
|
||||||
components: {
|
components: {
|
||||||
HeaderActions,
|
HeaderActions,
|
||||||
},
|
},
|
||||||
mixins: [configMixin],
|
mixins: [availabilityMixin],
|
||||||
props: {
|
props: {
|
||||||
avatarUrl: {
|
avatarUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -60,6 +60,20 @@ export default {
|
|||||||
...mapGetters({
|
...mapGetters({
|
||||||
widgetColor: 'appConfig/getWidgetColor',
|
widgetColor: 'appConfig/getWidgetColor',
|
||||||
}),
|
}),
|
||||||
|
isOnline() {
|
||||||
|
const { workingHoursEnabled } = this.channelConfig;
|
||||||
|
const anyAgentOnline = this.availableAgents.length > 0;
|
||||||
|
|
||||||
|
if (workingHoursEnabled) {
|
||||||
|
return this.isInBetweenTheWorkingHours;
|
||||||
|
}
|
||||||
|
return anyAgentOnline;
|
||||||
|
},
|
||||||
|
replyWaitMeessage() {
|
||||||
|
return this.isOnline
|
||||||
|
? this.replyTimeStatus
|
||||||
|
: this.$t('TEAM_AVAILABILITY.OFFLINE');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,27 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="px-4">
|
<div class="px-4">
|
||||||
<div
|
<div class="flex items-center justify-between mb-4">
|
||||||
v-if="channelConfig.workingHoursEnabled"
|
|
||||||
class="flex items-center justify-between mb-4"
|
|
||||||
>
|
|
||||||
<div class="text-black-700">
|
<div class="text-black-700">
|
||||||
<div class="text-base leading-5 font-medium mb-1">
|
<div class="text-base leading-5 font-medium mb-1">
|
||||||
{{
|
{{
|
||||||
isInBetweenTheWorkingHours
|
isOnline
|
||||||
? $t('TEAM_AVAILABILITY.ONLINE')
|
? $t('TEAM_AVAILABILITY.ONLINE')
|
||||||
: $t('TEAM_AVAILABILITY.OFFLINE')
|
: $t('TEAM_AVAILABILITY.OFFLINE')
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs leading-4 mt-1">
|
<div class="text-xs leading-4 mt-1">
|
||||||
{{
|
{{ replyWaitMeessage }}
|
||||||
isInBetweenTheWorkingHours ? replyTimeStatus : outOfOfficeMessage
|
|
||||||
}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<available-agents
|
<available-agents v-if="isOnline" :agents="availableAgents" />
|
||||||
v-if="isInBetweenTheWorkingHours"
|
|
||||||
:agents="availableAgents"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<woot-button
|
<woot-button
|
||||||
class="font-medium"
|
class="font-medium"
|
||||||
@@ -39,13 +31,9 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import AvailableAgents from 'widget/components/AvailableAgents.vue';
|
import AvailableAgents from 'widget/components/AvailableAgents.vue';
|
||||||
import { getContrastingTextColor } from 'shared/helpers/ColorHelper';
|
import { getContrastingTextColor } from 'shared/helpers/ColorHelper';
|
||||||
import {
|
|
||||||
buildDateFromTime,
|
|
||||||
formatDigitToString,
|
|
||||||
} from 'shared/helpers/DateHelper';
|
|
||||||
import WootButton from 'shared/components/Button';
|
import WootButton from 'shared/components/Button';
|
||||||
import configMixin from 'widget/mixins/configMixin';
|
import configMixin from 'widget/mixins/configMixin';
|
||||||
import compareAsc from 'date-fns/compareAsc';
|
import availabilityMixin from 'widget/mixins/availability';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TeamAvailability',
|
name: 'TeamAvailability',
|
||||||
@@ -53,7 +41,7 @@ export default {
|
|||||||
AvailableAgents,
|
AvailableAgents,
|
||||||
WootButton,
|
WootButton,
|
||||||
},
|
},
|
||||||
mixins: [configMixin],
|
mixins: [configMixin, availabilityMixin],
|
||||||
props: {
|
props: {
|
||||||
availableAgents: {
|
availableAgents: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -65,52 +53,25 @@ export default {
|
|||||||
textColor() {
|
textColor() {
|
||||||
return getContrastingTextColor(this.widgetColor);
|
return getContrastingTextColor(this.widgetColor);
|
||||||
},
|
},
|
||||||
isInBetweenTheWorkingHours() {
|
isOnline() {
|
||||||
const {
|
const { workingHoursEnabled } = this.channelConfig;
|
||||||
closedAllDay,
|
const anyAgentOnline = this.availableAgents.length > 0;
|
||||||
openHour,
|
|
||||||
openMinute,
|
|
||||||
closeHour,
|
|
||||||
closeMinute,
|
|
||||||
} = this.currentDayAvailability;
|
|
||||||
|
|
||||||
if (closedAllDay) {
|
if (workingHoursEnabled) {
|
||||||
return false;
|
return this.isInBetweenTheWorkingHours;
|
||||||
}
|
}
|
||||||
|
return anyAgentOnline;
|
||||||
const { utcOffset } = this.channelConfig;
|
|
||||||
const startTime = buildDateFromTime(
|
|
||||||
formatDigitToString(openHour),
|
|
||||||
formatDigitToString(openMinute),
|
|
||||||
utcOffset
|
|
||||||
);
|
|
||||||
const endTime = buildDateFromTime(
|
|
||||||
formatDigitToString(closeHour),
|
|
||||||
formatDigitToString(closeMinute),
|
|
||||||
utcOffset
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
compareAsc(new Date(), startTime) === 1 &&
|
|
||||||
compareAsc(endTime, new Date()) === 1
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
currentDayAvailability() {
|
replyWaitMeessage() {
|
||||||
const dayOfTheWeek = new Date().getDay();
|
const { workingHoursEnabled } = this.channelConfig;
|
||||||
const [workingHourConfig = {}] = this.channelConfig.workingHours.filter(
|
|
||||||
workingHour => workingHour.day_of_week === dayOfTheWeek
|
if (this.isOnline) {
|
||||||
);
|
return this.replyTimeStatus;
|
||||||
return {
|
}
|
||||||
closedAllDay: workingHourConfig.closed_all_day,
|
if (workingHoursEnabled) {
|
||||||
openHour: workingHourConfig.open_hour,
|
return this.outOfOfficeMessage;
|
||||||
openMinute: workingHourConfig.open_minutes,
|
}
|
||||||
closeHour: workingHourConfig.close_hour,
|
return this.$t('TEAM_AVAILABILITY.OFFLINE');
|
||||||
closeMinute: workingHourConfig.close_minutes,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
62
app/javascript/widget/mixins/availability.js
Normal file
62
app/javascript/widget/mixins/availability.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import compareAsc from 'date-fns/compareAsc';
|
||||||
|
import { buildDateFromTime } from 'shared/helpers/DateHelper';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
channelConfig() {
|
||||||
|
return window.chatwootWebChannel;
|
||||||
|
},
|
||||||
|
replyTime() {
|
||||||
|
return window.chatwootWebChannel.replyTime;
|
||||||
|
},
|
||||||
|
replyTimeStatus() {
|
||||||
|
switch (this.replyTime) {
|
||||||
|
case 'in_a_few_minutes':
|
||||||
|
return this.$t('REPLY_TIME.IN_A_FEW_MINUTES');
|
||||||
|
case 'in_a_few_hours':
|
||||||
|
return this.$t('REPLY_TIME.IN_A_FEW_HOURS');
|
||||||
|
case 'in_a_day':
|
||||||
|
return this.$t('REPLY_TIME.IN_A_DAY');
|
||||||
|
default:
|
||||||
|
return this.$t('REPLY_TIME.IN_A_FEW_HOURS');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
outOfOfficeMessage() {
|
||||||
|
return this.channelConfig.outOfOfficeMessage;
|
||||||
|
},
|
||||||
|
isInBetweenTheWorkingHours() {
|
||||||
|
const {
|
||||||
|
openHour,
|
||||||
|
openMinute,
|
||||||
|
closeHour,
|
||||||
|
closeMinute,
|
||||||
|
closedAllDay,
|
||||||
|
} = this.currentDayAvailability;
|
||||||
|
const { utcOffset } = this.channelConfig;
|
||||||
|
|
||||||
|
if (closedAllDay) return false;
|
||||||
|
|
||||||
|
const startTime = buildDateFromTime(openHour, openMinute, utcOffset);
|
||||||
|
const endTime = buildDateFromTime(closeHour, closeMinute, utcOffset);
|
||||||
|
const isBetween =
|
||||||
|
compareAsc(new Date(), startTime) === 1 &&
|
||||||
|
compareAsc(endTime, new Date()) === 1;
|
||||||
|
|
||||||
|
if (isBetween) return true;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
currentDayAvailability() {
|
||||||
|
const dayOfTheWeek = new Date().getDay();
|
||||||
|
const [workingHourConfig = {}] = this.channelConfig.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,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -21,9 +21,6 @@ export default {
|
|||||||
hasAttachmentsEnabled() {
|
hasAttachmentsEnabled() {
|
||||||
return this.channelConfig.enabledFeatures.includes('attachments');
|
return this.channelConfig.enabledFeatures.includes('attachments');
|
||||||
},
|
},
|
||||||
replyTime() {
|
|
||||||
return window.chatwootWebChannel.replyTime;
|
|
||||||
},
|
|
||||||
preChatFormEnabled() {
|
preChatFormEnabled() {
|
||||||
return window.chatwootWebChannel.preChatFormEnabled;
|
return window.chatwootWebChannel.preChatFormEnabled;
|
||||||
},
|
},
|
||||||
@@ -34,20 +31,5 @@ export default {
|
|||||||
preChatMessage: options.pre_chat_message,
|
preChatMessage: options.pre_chat_message,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
replyTimeStatus() {
|
|
||||||
switch (this.replyTime) {
|
|
||||||
case 'in_a_few_minutes':
|
|
||||||
return this.$t('REPLY_TIME.IN_A_FEW_MINUTES');
|
|
||||||
case 'in_a_few_hours':
|
|
||||||
return this.$t('REPLY_TIME.IN_A_FEW_HOURS');
|
|
||||||
case 'in_a_day':
|
|
||||||
return this.$t('REPLY_TIME.IN_A_DAY');
|
|
||||||
default:
|
|
||||||
return this.$t('REPLY_TIME.IN_A_FEW_HOURS');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
outOfOfficeMessage() {
|
|
||||||
return this.channelConfig.outOfOfficeMessage;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user