Feat: Component for setting business hour availability (#1794)

* Component business hour setting availability
This commit is contained in:
Nithin David Thomas 2021-02-22 16:30:58 +05:30 committed by GitHub
parent 4cbdbbe4bd
commit a3b0de63de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 275 additions and 3 deletions

View file

@ -5,8 +5,7 @@
"LIST": {
"404": "There are no inboxes attached to this account."
},
"CREATE_FLOW": [
{
"CREATE_FLOW": [{
"title": "Choose Channel",
"route": "settings_inbox_new",
"body": "Choose the provider you want to integrate with Chatwoot."
@ -226,7 +225,8 @@
"SETTINGS": "Settings",
"COLLABORATORS": "Collaborators",
"CONFIGURATION": "Configuration",
"PRE_CHAT_FORM": "Pre Chat Form"
"PRE_CHAT_FORM": "Pre Chat Form",
"BUSINESS_HOURS": "Business Hours"
},
"SETTINGS": "Settings",
"FEATURES": {
@ -269,6 +269,18 @@
"REQUIRE_EMAIL": {
"LABEL": "Visitors should provide their name and email address before starting the chat"
}
},
"BUSINESS_HOURS": {
"TITLE": "Set your availability",
"SUBTITLE": "Set your availability on your livechat widget",
"WEEKLY_TITLE": "Set your weekly hours",
"DAY": {
"ENABLE": "Enable availability for this day",
"UNAVAILABLE": "Unavailable",
"HOURS": "hours",
"VALIDATION_ERROR": "Starting time should be before closing time.",
"CHOOSE": "Choose"
}
}
}
}

View file

@ -0,0 +1,223 @@
<template>
<div class="day-wrap">
<div class="checkbox-wrap">
<input
v-model="isDayEnabled"
name="enable-day"
class="enable-day"
type="checkbox"
:title="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.ENABLE')"
/>
</div>
<div class="day">
<span>{{ dayName }}</span>
</div>
<div v-if="isDayEnabled" class="hours-select-wrap">
<div class="hours-range">
<multiselect
v-model="fromTime"
:options="timeSlots"
deselect-label=""
select-label=""
selected-label=""
:placeholder="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.CHOOSE')"
:allow-empty="false"
/>
<div class="separator-icon">
<i class="ion-minus-round" />
</div>
<multiselect
v-model="toTime"
:options="timeSlots"
deselect-label=""
select-label=""
selected-label=""
:placeholder="$t('INBOX_MGMT.BUSINESS_HOURS.DAY.CHOOSE')"
:allow-empty="false"
/>
</div>
<div v-if="hasError" class="date-error">
<span class="error">{{
$t('INBOX_MGMT.BUSINESS_HOURS.DAY.VALIDATION_ERROR')
}}</span>
</div>
</div>
<div v-else class="day-unavailable">
<span>
{{ $t('INBOX_MGMT.BUSINESS_HOURS.DAY.UNAVAILABLE') }}
</span>
</div>
<div>
<span v-if="isDayEnabled && !hasError" class="label">
{{ totalHours }} {{ $t('INBOX_MGMT.BUSINESS_HOURS.DAY.HOURS') }}
</span>
</div>
</div>
</template>
<script>
import parse from 'date-fns/parse';
import differenceInMinutes from 'date-fns/differenceInMinutes';
import { generateTimeSlots } from '../helpers/businessHour';
const timeSlots = generateTimeSlots(30);
export default {
components: {},
props: {
dayName: {
type: String,
default: '',
required: true,
},
timeSlot: {
type: Object,
default: () => ({
from: '',
to: '',
}),
},
},
computed: {
timeSlots() {
return timeSlots;
},
isDayEnabled: {
get() {
return this.timeSlot.from && this.timeSlot.to;
},
set(value) {
const newSlot = value
? {
...this.timeSlot,
from: timeSlots[0],
to: timeSlots[16],
}
: {
...this.timeSlot,
from: '',
to: '',
};
this.$emit('update', newSlot);
},
},
fromTime: {
get() {
return this.timeSlot.from;
},
set(value) {
this.$emit('update', {
...this.timeSlot,
from: value,
});
},
},
toTime: {
get() {
return this.timeSlot.to;
},
set(value) {
this.$emit('update', {
...this.timeSlot,
to: value,
});
},
},
fromDate() {
return parse(this.fromTime, 'hh:mm a', new Date());
},
toDate() {
return parse(this.toTime, 'hh:mm a', new Date());
},
totalHours() {
return differenceInMinutes(this.toDate, this.fromDate) / 60;
},
hasError() {
return this.totalHours < 0;
},
},
};
</script>
<style lang="scss" scoped>
.day-wrap::v-deep .multiselect {
margin: 0;
width: 12rem;
> .multiselect__tags {
padding-left: var(--space-slab);
.multiselect__single {
font-size: var(--font-size-small);
line-height: var(--space-medium);
padding: var(--space-small) 0;
}
}
}
.day-wrap {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-normal);
height: var(--space-larger);
box-sizing: content-box;
border-bottom: 1px solid var(--color-border-light);
}
.enable-day {
margin: 0;
}
.hours-select-wrap {
display: flex;
flex-direction: column;
flex-shrink: 0;
flex-grow: 1;
position: relative;
}
.hours-range,
.day-unavailable {
display: flex;
align-items: center;
flex-shrink: 0;
flex-grow: 1;
}
.day-unavailable {
font-size: var(--font-size-small);
color: var(--s-500);
}
.checkbox-wrap {
display: flex;
align-items: center;
}
.separator-icon,
.day {
display: flex;
align-items: center;
padding: 0 var(--space-slab);
height: 100%;
}
.day {
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
width: 13rem;
}
.label {
font-size: var(--font-size-mini);
color: var(--w-700);
background: var(--w-50);
}
.date-error {
padding: var(--space-small) 0;
}
.error {
font-size: var(--font-size-mini);
color: var(--r-300);
}
</style>

View file

@ -0,0 +1,20 @@
export const generateTimeSlots = (step = 15) => {
/*
Generates a list of time strings from 12:00 AM to next 24 hours. Each new string
will be generated by adding `step` minutes to the previous one.
The list is generated by starting with a random day and adding step minutes till end of the same day.
*/
const date = new Date(1970, 1, 1);
const slots = [];
while (date.getDate() === 1) {
slots.push(
date.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true,
})
);
date.setMinutes(date.getMinutes() + step);
}
return slots;
};

View file

@ -0,0 +1,17 @@
import { generateTimeSlots } from '../businessHour';
describe('#generateTimeSlots', () => {
it('returns correct number of time slots', () => {
expect(generateTimeSlots(15).length).toStrictEqual((60 / 15) * 24);
});
it('returns correct time slots', () => {
expect(generateTimeSlots(240)).toStrictEqual([
'12:00 AM',
'04:00 AM',
'08:00 AM',
'12:00 PM',
'04:00 PM',
'08:00 PM',
]);
});
});