feat: UI to show the SLA threshold in chat screen (#9146)

- UI will show the breach in the conversation list.
- UI will show the breach in the conversation header.

Fixes: https://linear.app/chatwoot/issue/CW-3146/update-the-ui-to-show-the-breach-in-the-conversation-list
Fixes: https://linear.app/chatwoot/issue/CW-3144/ui-update-to-show-the-breachgoing-to-breach
This commit is contained in:
Sivin Varghese
2024-04-04 15:46:46 +05:30
committed by GitHub
parent e21d7552d3
commit e49ef773d8
21 changed files with 745 additions and 106 deletions

View File

@@ -1,47 +1,63 @@
<template>
<div
class="flex items-center px-2 truncate border min-w-fit border-slate-75 dark:border-slate-700"
:class="showExtendedInfo ? 'py-[5px] rounded-lg' : 'py-0.5 gap-1 rounded'"
v-if="hasSlaThreshold"
class="relative flex items-center border cursor-pointer min-w-fit border-slate-75 dark:border-slate-700"
:class="showExtendedInfo ? 'rounded-lg' : 'rounded'"
>
<div
class="flex items-center gap-1"
:class="
showExtendedInfo &&
'ltr:pr-1.5 rtl:pl-1.5 ltr:border-r rtl:border-l border-solid border-slate-75 dark:border-slate-700'
"
class="flex items-center w-full truncate"
:class="showExtendedInfo ? 'h-[26px] px-1.5' : 'h-5 px-2 gap-1'"
@mouseover="openSlaPopover()"
@mouseleave="closeSlaPopover()"
>
<fluent-icon
size="14"
:icon="slaStatus.icon"
type="outline"
:icon-lib="isSlaMissed ? 'lucide' : 'fluent'"
class="flex-shrink-0"
:class="slaTextStyles"
/>
<span
v-if="showExtendedInfo"
class="text-xs font-medium"
:class="slaTextStyles"
<div
class="flex items-center gap-1"
:class="
showExtendedInfo &&
'ltr:pr-1.5 rtl:pl-1.5 ltr:border-r rtl:border-l border-solid border-slate-75 dark:border-slate-700'
"
>
{{ slaStatusText }}
<fluent-icon
size="14"
:icon="slaStatus.icon"
type="outline"
:icon-lib="isSlaMissed ? 'lucide' : 'fluent'"
class="flex-shrink-0"
:class="slaTextStyles"
/>
<span
v-if="showExtendedInfo"
class="text-xs font-medium"
:class="slaTextStyles"
>
{{ slaStatusText }}
</span>
</div>
<span
class="text-xs font-medium"
:class="[slaTextStyles, showExtendedInfo && 'ltr:pl-1.5 rtl:pr-1.5']"
>
{{ slaStatus.threshold }}
</span>
</div>
<span
class="text-xs font-medium"
:class="[slaTextStyles, showExtendedInfo && 'ltr:pl-1.5 rtl:pr-1.5']"
>
{{ slaStatus.threshold }}
</span>
<SLA-popover-card
v-if="showSlaPopoverCard"
:all-missed-slas="slaEvents"
class="right-0 top-7"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { evaluateSLAStatus } from '../helpers/SLAHelper';
import SLAPopoverCard from './SLAPopoverCard.vue';
// const REFRESH_INTERVAL = 60000;
const REFRESH_INTERVAL = 60000;
export default {
components: {
SLAPopoverCard,
},
props: {
chat: {
type: Object,
@@ -55,19 +71,27 @@ export default {
data() {
return {
timer: null,
slaStatus: {},
showSlaPopover: false,
slaStatus: {
threshold: null,
isSlaMissed: false,
type: null,
icon: null,
},
};
},
computed: {
...mapGetters({
activeSLA: 'sla/getSLAById',
}),
slaPolicyId() {
return this.chat?.sla_policy_id;
},
sla() {
if (!this.slaPolicyId) return null;
return this.activeSLA(this.slaPolicyId);
appliedSLA() {
return this.chat?.applied_sla;
},
slaEvents() {
return this.chat?.sla_events;
},
hasSlaThreshold() {
return this.slaStatus?.threshold;
},
isSlaMissed() {
return this.slaStatus?.isSlaMissed;
@@ -79,12 +103,17 @@ export default {
},
slaStatusText() {
const upperCaseType = this.slaStatus?.type?.toUpperCase(); // FRT, NRT, or RT
const statusKey = this.isSlaMissed ? 'BREACH' : 'DUE';
const statusKey = this.isSlaMissed ? 'MISSED' : 'DUE';
return this.$t(`CONVERSATION.HEADER.SLA_STATUS.${upperCaseType}`, {
status: this.$t(`CONVERSATION.HEADER.SLA_STATUS.${statusKey}`),
});
},
showSlaPopoverCard() {
return (
this.showExtendedInfo && this.showSlaPopover && this.slaEvents.length
);
},
},
watch: {
chat() {
@@ -93,10 +122,29 @@ export default {
},
mounted() {
this.updateSlaStatus();
this.createTimer();
},
beforeDestroy() {
if (this.timer) {
clearTimeout(this.timer);
}
},
methods: {
createTimer() {
this.timer = setTimeout(() => {
this.updateSlaStatus();
this.createTimer();
}, REFRESH_INTERVAL);
},
updateSlaStatus() {
this.slaStatus = evaluateSLAStatus(this.sla, this.chat);
this.slaStatus = evaluateSLAStatus(this.appliedSLA, this.chat);
},
openSlaPopover() {
if (!this.showExtendedInfo) return;
this.showSlaPopover = true;
},
closeSlaPopover() {
this.showSlaPopover = false;
},
},
};

View File

@@ -0,0 +1,45 @@
<script setup>
import { format, fromUnixTime } from 'date-fns';
defineProps({
allMissedSlas: {
type: Array,
default: () => [],
},
});
const formatDate = timestamp => format(fromUnixTime(timestamp), 'PP');
const upperCase = str => str.toUpperCase();
</script>
<template>
<div
class="absolute flex flex-col items-start bg-[#fdfdfd] dark:bg-slate-800 z-50 p-4 border border-solid border-slate-75 dark:border-slate-700 w-[384px] rounded-xl gap-4"
>
<div
v-for="missedSLA in allMissedSlas"
:key="missedSLA.id"
class="flex items-center justify-between w-full"
>
<span
class="text-sm font-normal tracking-[-0.6%] w-[140px] truncate text-slate-900 dark:text-slate-50"
>
{{
$t(
`CONVERSATION.HEADER.SLA_POPOVER.${upperCase(missedSLA.event_type)}`
)
}}
</span>
<span
class="text-sm font-normal tracking-[-0.6%] text-slate-600 dark:text-slate-200"
>
{{ $t('CONVERSATION.HEADER.SLA_POPOVER.MISSED') }}
</span>
<span
class="text-sm font-normal tracking-[-0.6%] text-slate-900 dark:text-slate-50"
>
{{ formatDate(missedSLA.created_at) }}
</span>
</div>
</div>
</template>