Skip to content

Commit

Permalink
feat: Update the input for the SLA threshold selection (#8974)
Browse files Browse the repository at this point in the history
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
  • Loading branch information
5 people committed Feb 28, 2024
1 parent dca14ef commit 9f905ce
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 72 deletions.
15 changes: 9 additions & 6 deletions app/javascript/dashboard/i18n/locale/en/sla.json
Expand Up @@ -24,21 +24,24 @@
"PLACEHOLDER": "SLA for premium customers"
},
"FIRST_RESPONSE_TIME": {
"LABEL": "First Response Time(Seconds)",
"PLACEHOLDER": "300 for 5 minutes"
"LABEL": "First Response Time",
"PLACEHOLDER": "5"
},
"NEXT_RESPONSE_TIME": {
"LABEL": "Next Response Time(Seconds)",
"PLACEHOLDER": "600 for 10 minutes"
"LABEL": "Next Response Time",
"PLACEHOLDER": "5"
},
"RESOLUTION_TIME": {
"LABEL": "Resolution Time(Seconds)",
"PLACEHOLDER": "86400 for 1 day"
"LABEL": "Resolution Time",
"PLACEHOLDER": "60"
},
"BUSINESS_HOURS": {
"LABEL": "Business Hours",
"PLACEHOLDER": "Only during business hours"
},
"THRESHOLD_TIME": {
"INVALID_FORMAT_ERROR": "Threshold should be a number and greater than zero"
},
"EDIT": "Edit",
"CREATE": "Create",
"DELETE": "Delete",
Expand Down
32 changes: 14 additions & 18 deletions app/javascript/dashboard/routes/dashboard/settings/sla/Index.vue
Expand Up @@ -38,17 +38,17 @@
<td>{{ sla.description }}</td>
<td>
<span class="flex items-center">
{{ sla.first_response_time_threshold }}
{{ displayTime(sla.first_response_time_threshold) }}
</span>
</td>
<td>
<span class="flex items-center">
{{ sla.next_response_time_threshold }}
{{ displayTime(sla.next_response_time_threshold) }}
</span>
</td>
<td>
<span class="flex items-center">
{{ sla.resolution_time_threshold }}
{{ displayTime(sla.resolution_time_threshold) }}
</span>
</td>
<td>
Expand Down Expand Up @@ -88,6 +88,7 @@
</template>
<script>
import { mapGetters } from 'vuex';
import { convertSecondsToTimeUnit } from '@chatwoot/utils';
import AddSLA from './AddSLA.vue';
import EditSLA from './EditSLA.vue';
Expand All @@ -111,26 +112,12 @@ export default {
...mapGetters({
records: 'sla/getSLA',
uiFlags: 'sla/getUIFlags',
accountId: 'getCurrentAccountId',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
isSLAEnabled() {
return this.isFeatureEnabledonAccount(this.accountId, 'sla');
},
},
mounted() {
this.isSLAfeatureEnabled();
this.$store.dispatch('sla/get');
},
methods: {
isSLAfeatureEnabled() {
if (!this.isSLAEnabled) {
this.$router.push({
name: 'general_settings_index',
});
} else {
this.$store.dispatch('sla/get');
}
},
openAddPopup() {
this.showAddPopup = true;
},
Expand All @@ -144,6 +131,15 @@ export default {
hideEditPopup() {
this.showEditPopup = false;
},
displayTime(threshold) {
const { time, unit } = convertSecondsToTimeUnit(threshold, {
minute: 'm',
hour: 'h',
day: 'd',
});
if (!time) return '-';
return `${time}${unit}`;
},
},
};
</script>
136 changes: 94 additions & 42 deletions app/javascript/dashboard/routes/dashboard/settings/sla/SlaForm.vue
Expand Up @@ -2,44 +2,31 @@
<div class="h-auto overflow-auto flex flex-col">
<form class="mx-0 flex flex-wrap" @submit.prevent="onSubmit">
<woot-input
v-model.trim="name"
v-model="name"
:class="{ error: $v.name.$error }"
class="w-full"
:sla="$t('SLA.FORM.NAME.LABEL')"
:label="$t('SLA.FORM.NAME.LABEL')"
:placeholder="$t('SLA.FORM.NAME.PLACEHOLDER')"
:error="getSlaNameErrorMessage"
@input="$v.name.$touch"
/>
<woot-input
v-model.trim="description"
v-model="description"
class="w-full"
:label="$t('SLA.FORM.DESCRIPTION.LABEL')"
:placeholder="$t('SLA.FORM.DESCRIPTION.PLACEHOLDER')"
data-testid="sla-description"
/>

<woot-input
v-model.trim="firstResponseTimeThreshold"
class="w-full"
:label="$t('SLA.FORM.FIRST_RESPONSE_TIME.LABEL')"
:placeholder="$t('SLA.FORM.FIRST_RESPONSE_TIME.PLACEHOLDER')"
data-testid="sla-firstResponseTimeThreshold"
/>

<woot-input
v-model.trim="nextResponseTimeThreshold"
class="w-full"
:label="$t('SLA.FORM.NEXT_RESPONSE_TIME.LABEL')"
:placeholder="$t('SLA.FORM.NEXT_RESPONSE_TIME.PLACEHOLDER')"
data-testid="sla-nextResponseTimeThreshold"
/>

<woot-input
v-model.trim="resolutionTimeThreshold"
class="w-full"
:label="$t('SLA.FORM.RESOLUTION_TIME.LABEL')"
:placeholder="$t('SLA.FORM.RESOLUTION_TIME.PLACEHOLDER')"
data-testid="sla-resolutionTimeThreshold"
<sla-time-input
v-for="(input, index) in slaTimeInputs"
:key="index"
:threshold="input.threshold"
:threshold-unit="input.unit"
:label="$t(input.label)"
:placeholder="$t(input.placeholder)"
@input="updateThreshold(index, $event)"
@unit="updateUnit(index, $event)"
@isInValid="handleIsInvalid(index, $event)"
/>

<div class="w-full">
Expand All @@ -51,7 +38,7 @@

<div class="flex justify-end items-center py-2 px-0 gap-2 w-full">
<woot-button
:is-disabled="$v.name.$invalid || uiFlags.isUpdating"
:is-disabled="isSubmitDisabled"
:is-loading="uiFlags.isUpdating"
>
{{ submitLabel }}
Expand All @@ -66,10 +53,15 @@

<script>
import { mapGetters } from 'vuex';
import { convertSecondsToTimeUnit } from '@chatwoot/utils';
import validationMixin from './validationMixin';
import validations from './validations';
import SlaTimeInput from './SlaTimeInput.vue';
export default {
components: {
SlaTimeInput,
},
mixins: [validationMixin],
props: {
selectedResponse: {
Expand All @@ -85,9 +77,28 @@ export default {
return {
name: '',
description: '',
firstResponseTimeThreshold: '',
nextResponseTimeThreshold: '',
resolutionTimeThreshold: '',
isSlaTimeInputsInvalid: false,
slaTimeInputsValidation: {},
slaTimeInputs: [
{
threshold: null,
unit: 'Minutes',
label: 'SLA.FORM.FIRST_RESPONSE_TIME.LABEL',
placeholder: 'SLA.FORM.FIRST_RESPONSE_TIME.PLACEHOLDER',
},
{
threshold: null,
unit: 'Minutes',
label: 'SLA.FORM.NEXT_RESPONSE_TIME.LABEL',
placeholder: 'SLA.FORM.NEXT_RESPONSE_TIME.PLACEHOLDER',
},
{
threshold: null,
unit: 'Minutes',
label: 'SLA.FORM.RESOLUTION_TIME.LABEL',
placeholder: 'SLA.FORM.RESOLUTION_TIME.PLACEHOLDER',
},
],
onlyDuringBusinessHours: false,
};
},
Expand All @@ -96,10 +107,12 @@ export default {
...mapGetters({
uiFlags: 'sla/getUIFlags',
}),
pageTitle() {
return `${this.$t('SLA.EDIT.TITLE')} - ${
this.selectedResponse?.name || ''
}`;
isSubmitDisabled() {
return (
this.$v.name.$invalid ||
this.isSlaTimeInputsInvalid ||
this.uiFlags.isUpdating
);
},
},
mounted() {
Expand All @@ -121,20 +134,59 @@ export default {
this.name = name;
this.description = description;
this.firstResponseTimeThreshold = firstResponseTimeThreshold;
this.nextResponseTimeThreshold = nextResponseTimeThreshold;
this.resolutionTimeThreshold = resolutionTimeThreshold;
this.onlyDuringBusinessHours = onlyDuringBusinessHours;
const thresholds = [
firstResponseTimeThreshold,
nextResponseTimeThreshold,
resolutionTimeThreshold,
];
this.slaTimeInputs.forEach((input, index) => {
const converted = convertSecondsToTimeUnit(thresholds[index], {
minute: 'Minutes',
hour: 'Hours',
day: 'Days',
});
input.threshold = converted.time;
input.unit = converted.unit;
});
},
updateThreshold(index, value) {
this.slaTimeInputs[index].threshold = value;
},
updateUnit(index, unit) {
this.slaTimeInputs[index].unit = unit;
},
onSubmit() {
this.$emit('submit', {
const payload = {
name: this.name,
description: this.description,
first_response_time_threshold: this.firstResponseTimeThreshold,
next_response_time_threshold: this.nextResponseTimeThreshold,
resolution_time_threshold: this.resolutionTimeThreshold,
first_response_time_threshold: this.convertToSeconds(0),
next_response_time_threshold: this.convertToSeconds(1),
resolution_time_threshold: this.convertToSeconds(2),
only_during_business_hours: this.onlyDuringBusinessHours,
});
};
this.$emit('submit', payload);
},
convertToSeconds(index) {
const { threshold, unit } = this.slaTimeInputs[index];
if (threshold === null || threshold === 0) return null;
const unitsToSeconds = { Minutes: 60, Hours: 3600, Days: 86400 };
return Number(threshold * (unitsToSeconds[unit] || 1));
},
handleIsInvalid(index, isInvalid) {
this.slaTimeInputsValidation = {
...this.slaTimeInputsValidation,
[index]: isInvalid,
};
this.checkValidationState();
},
checkValidationState() {
const isAnyInvalid = Object.values(this.slaTimeInputsValidation).some(
isInvalid => isInvalid
);
this.isSlaTimeInputsInvalid = isAnyInvalid;
},
},
};
Expand Down
@@ -0,0 +1,94 @@
<template>
<div class="relative mt-2 w-full">
<woot-input
v-model="thresholdTime"
:class="{ error: $v.thresholdTime.$error }"
class="w-full [&>input]:pr-24"
:label="label"
:placeholder="placeholder"
:error="getThresholdTimeErrorMessage"
@input="onThresholdTimeChange"
/>
<div class="absolute right-px h-9 top-[27px] flex items-center">
<select
v-model="thresholdUnitValue"
class="h-full rounded-[4px] hover:cursor-pointer font-medium border-1 border-solid bg-transparent border-transparent dark:border-transparent mb-0 py-0 pl-2 pr-7 text-slate-600 dark:text-slate-300 dark:focus:border-woot-500 focus:border-woot-500 text-sm"
@change="onThresholdUnitChange"
>
<option
v-for="(option, index) in options"
:key="index"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</div>
</div>
</template>

<script>
import validationMixin from './validationMixin';
import validations from './validations';
export default {
mixins: [validationMixin],
props: {
threshold: {
type: Number,
default: null,
},
thresholdUnit: {
type: String,
default: 'Minutes',
},
label: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
},
data() {
return {
thresholdTime: this.threshold || '',
thresholdUnitValue: this.thresholdUnit,
options: [
{ value: 'Minutes', label: 'Minutes' },
{ value: 'Hours', label: 'Hours' },
{ value: 'Days', label: 'Days' },
],
};
},
validations,
watch: {
threshold: {
immediate: true,
handler(value) {
if (!Number.isNaN(value)) {
this.thresholdTime = value;
}
},
},
thresholdUnit: {
immediate: true,
handler(value) {
this.thresholdUnitValue = value;
},
},
},
methods: {
onThresholdUnitChange() {
this.$emit('unit', this.thresholdUnitValue);
},
onThresholdTimeChange() {
this.$v.thresholdTime.$touch();
const isInvalid = this.$v.thresholdTime.$invalid;
this.$emit('isInValid', isInvalid);
this.$emit('input', Number(this.thresholdTime));
},
},
};
</script>

0 comments on commit 9f905ce

Please sign in to comment.