Feat: Show working hours on widget (#1823)
Feat: Display out of office message based on business hours Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com> Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
parent
1d2e1a2823
commit
6c87001a0e
8 changed files with 119 additions and 21 deletions
|
@ -2,6 +2,7 @@ import fromUnixTime from 'date-fns/fromUnixTime';
|
||||||
import format from 'date-fns/format';
|
import format from 'date-fns/format';
|
||||||
import isToday from 'date-fns/isToday';
|
import isToday from 'date-fns/isToday';
|
||||||
import isYesterday from 'date-fns/isYesterday';
|
import isYesterday from 'date-fns/isYesterday';
|
||||||
|
import parseISO from 'date-fns/parseISO';
|
||||||
|
|
||||||
export const formatUnixDate = (date, dateFormat = 'MMM dd, yyyy') => {
|
export const formatUnixDate = (date, dateFormat = 'MMM dd, yyyy') => {
|
||||||
const unixDate = fromUnixTime(date);
|
const unixDate = fromUnixTime(date);
|
||||||
|
@ -14,3 +15,13 @@ export const formatDate = ({ date, todayText, yesterdayText }) => {
|
||||||
if (isYesterday(dateValue)) return yesterdayText;
|
if (isYesterday(dateValue)) return 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 => {
|
||||||
|
return val > 9 ? `${val}` : `0${val}`;
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import { formatDate, formatUnixDate } from '../DateHelper';
|
import {
|
||||||
|
formatDate,
|
||||||
|
formatUnixDate,
|
||||||
|
buildDateFromTime,
|
||||||
|
formatDigitToString,
|
||||||
|
} from '../DateHelper';
|
||||||
|
|
||||||
describe('#DateHelper', () => {
|
describe('#DateHelper', () => {
|
||||||
it('should format unix date correctly without dateFormat', () => {
|
it('should format unix date correctly without dateFormat', () => {
|
||||||
|
@ -39,4 +44,27 @@ describe('#DateHelper', () => {
|
||||||
})
|
})
|
||||||
).toEqual('Yesterday');
|
).toEqual('Yesterday');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#buildDate', () => {
|
||||||
|
it('returns correctly parsed date', () => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setFullYear(2021);
|
||||||
|
date.setMonth(2);
|
||||||
|
date.setDate(5);
|
||||||
|
|
||||||
|
const result = buildDateFromTime(12, 15, '.465Z', date);
|
||||||
|
expect(result + '').toEqual(
|
||||||
|
'Fri Mar 05 2021 12:15:00 GMT+0000 (Coordinated Universal Time)'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#formatDigitToString', () => {
|
||||||
|
it('returns date compatabile string from number is less than 9', () => {
|
||||||
|
expect(formatDigitToString(8)).toEqual('08');
|
||||||
|
});
|
||||||
|
it('returns date compatabile string from number is greater than 9', () => {
|
||||||
|
expect(formatDigitToString(11)).toEqual('11');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,14 +31,13 @@
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import HeaderActions from './HeaderActions';
|
import HeaderActions from './HeaderActions';
|
||||||
import configMixin from 'widget/mixins/configMixin';
|
import configMixin from 'widget/mixins/configMixin';
|
||||||
import teamAvailabilityMixin from 'widget/mixins/teamAvailabilityMixin';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChatHeader',
|
name: 'ChatHeader',
|
||||||
components: {
|
components: {
|
||||||
HeaderActions,
|
HeaderActions,
|
||||||
},
|
},
|
||||||
mixins: [configMixin, teamAvailabilityMixin],
|
mixins: [configMixin],
|
||||||
props: {
|
props: {
|
||||||
avatarUrl: {
|
avatarUrl: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
|
@ -1,15 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="px-4">
|
<div class="px-4">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div
|
||||||
|
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">
|
||||||
{{ teamAvailabilityStatus }}
|
{{
|
||||||
|
isInBetweenTheWorkingHours
|
||||||
|
? $t('TEAM_AVAILABILITY.ONLINE')
|
||||||
|
: $t('TEAM_AVAILABILITY.OFFLINE')
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs leading-4 mt-1">
|
<div class="text-xs leading-4 mt-1">
|
||||||
{{ replyTimeStatus }}
|
{{
|
||||||
|
isInBetweenTheWorkingHours ? replyTimeStatus : outOfOfficeMessage
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<available-agents :agents="availableAgents" />
|
<available-agents
|
||||||
|
v-if="isInBetweenTheWorkingHours"
|
||||||
|
:agents="availableAgents"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<woot-button
|
<woot-button
|
||||||
class="font-medium"
|
class="font-medium"
|
||||||
|
@ -27,9 +39,13 @@
|
||||||
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 teamAvailabilityMixin from 'widget/mixins/teamAvailabilityMixin';
|
import compareAsc from 'date-fns/compareAsc';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TeamAvailability',
|
name: 'TeamAvailability',
|
||||||
|
@ -37,7 +53,7 @@ export default {
|
||||||
AvailableAgents,
|
AvailableAgents,
|
||||||
WootButton,
|
WootButton,
|
||||||
},
|
},
|
||||||
mixins: [configMixin, teamAvailabilityMixin],
|
mixins: [configMixin],
|
||||||
props: {
|
props: {
|
||||||
availableAgents: {
|
availableAgents: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -49,6 +65,53 @@ export default {
|
||||||
textColor() {
|
textColor() {
|
||||||
return getContrastingTextColor(this.widgetColor);
|
return getContrastingTextColor(this.widgetColor);
|
||||||
},
|
},
|
||||||
|
isInBetweenTheWorkingHours() {
|
||||||
|
const {
|
||||||
|
closedAllDay,
|
||||||
|
openHour,
|
||||||
|
openMinute,
|
||||||
|
closeHour,
|
||||||
|
closeMinute,
|
||||||
|
} = this.currentDayAvailability;
|
||||||
|
|
||||||
|
if (closedAllDay) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
startConversation() {
|
startConversation() {
|
||||||
|
|
|
@ -46,5 +46,8 @@ export default {
|
||||||
return this.$t('REPLY_TIME.IN_A_FEW_HOURS');
|
return this.$t('REPLY_TIME.IN_A_FEW_HOURS');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
outOfOfficeMessage() {
|
||||||
|
return this.channelConfig.outOfOfficeMessage;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
export default {
|
|
||||||
computed: {
|
|
||||||
teamAvailabilityStatus() {
|
|
||||||
if (this.availableAgents.length) {
|
|
||||||
return this.$t('TEAM_AVAILABILITY.ONLINE');
|
|
||||||
}
|
|
||||||
return this.$t('TEAM_AVAILABILITY.OFFLINE');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -21,7 +21,11 @@
|
||||||
enabledLanguages: <%= available_locales_with_name.to_json.html_safe %>,
|
enabledLanguages: <%= available_locales_with_name.to_json.html_safe %>,
|
||||||
replyTime: '<%= @web_widget.reply_time %>',
|
replyTime: '<%= @web_widget.reply_time %>',
|
||||||
preChatFormEnabled: <%= @web_widget.pre_chat_form_enabled %>,
|
preChatFormEnabled: <%= @web_widget.pre_chat_form_enabled %>,
|
||||||
preChatFormOptions: <%= @web_widget.pre_chat_form_options.to_json.html_safe %>
|
preChatFormOptions: <%= @web_widget.pre_chat_form_options.to_json.html_safe %>,
|
||||||
|
workingHoursEnabled: <%= @web_widget.inbox.working_hours_enabled %>,
|
||||||
|
workingHours: <%= @web_widget.inbox.working_hours.to_json.html_safe %>,
|
||||||
|
outOfOfficeMessage: <%= @web_widget.inbox.out_of_office_message.to_json.html_safe %>,
|
||||||
|
utcOffset: '<%= ActiveSupport::TimeZone[@web_widget.inbox.timezone].formatted_offset %>'
|
||||||
}
|
}
|
||||||
window.chatwootWidgetDefaults = {
|
window.chatwootWidgetDefaults = {
|
||||||
useInboxAvatarForBot: <%= ActiveModel::Type::Boolean.new.cast(ENV.fetch('USE_INBOX_AVATAR_FOR_BOT', false)) %>,
|
useInboxAvatarForBot: <%= ActiveModel::Type::Boolean.new.cast(ENV.fetch('USE_INBOX_AVATAR_FOR_BOT', false)) %>,
|
||||||
|
|
|
@ -18,7 +18,7 @@ RSpec.describe WorkingHour do
|
||||||
context 'when on sunday 1pm' do
|
context 'when on sunday 1pm' do
|
||||||
before do
|
before do
|
||||||
Time.zone = 'UTC'
|
Time.zone = 'UTC'
|
||||||
create(:working_hour, day_of_week: 7, closed_all_day: true)
|
create(:working_hour, day_of_week: 0, closed_all_day: true)
|
||||||
travel_to '01.11.2020 13:00'.to_datetime
|
travel_to '01.11.2020 13:00'.to_datetime
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue