Merge branch 'develop' into extending-lock-to-single-conversation-to-meta-inbox

This commit is contained in:
Jaideep Guntupalli
2024-03-21 20:29:44 +05:30
committed by GitHub
101 changed files with 2438 additions and 1014 deletions
@@ -243,7 +243,7 @@ export default {
attr.attribute_display_type === 'checkbox'
);
});
if (isCustomAttributeCheckbox) {
if (isCustomAttributeCheckbox || type === 'blocked') {
return [
{
id: true,
@@ -76,6 +76,14 @@ const filterTypes = [
filterOperators: OPERATOR_TYPES_5,
attributeModel: 'standard',
},
{
attributeKey: 'blocked',
attributeI18nKey: 'BLOCKED',
inputType: 'search_select',
dataType: 'text',
filterOperators: OPERATOR_TYPES_1,
attributeModel: 'standard',
},
];
export const filterAttributeGroups = [
@@ -115,6 +123,10 @@ export const filterAttributeGroups = [
key: 'last_activity_at',
i18nKey: 'LAST_ACTIVITY',
},
{
key: 'blocked',
i18nKey: 'BLOCKED',
},
],
},
];
@@ -0,0 +1,6 @@
<template>
<div class="flex flex-col w-full h-full gap-10 font-inter">
<slot name="header" />
<slot name="body" />
</div>
</template>
@@ -0,0 +1,21 @@
<script setup>
defineProps({
keepAlive: {
type: Boolean,
default: true,
},
});
</script>
<template>
<div
class="flex flex-col w-full h-full px-5 pt-8 pb-3 m-0 overflow-auto bg-white sm:px-16 sm:pt-16 dark:bg-slate-900"
>
<div class="flex items-start max-w-[900px] w-full">
<keep-alive v-if="keepAlive">
<router-view />
</keep-alive>
<router-view v-else />
</div>
</div>
</template>
@@ -0,0 +1,102 @@
<script setup>
defineProps({
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
iconName: {
type: String,
required: true,
},
href: {
type: String,
default: '',
},
linkText: {
type: String,
default: '',
},
});
const openInNewTab = url => {
if (!url) return;
window.open(url, '_blank', 'noopener noreferrer');
};
</script>
<template>
<div class="flex flex-col items-start w-full gap-4">
<!-- Header section with icon, title and action button -->
<div class="flex items-center justify-between w-full gap-4">
<!-- Icon and title container -->
<div class="flex items-center gap-3">
<div
class="flex items-center w-10 h-10 p-1 rounded-full bg-woot-25/60 dark:bg-woot-900/60"
>
<div
class="flex items-center justify-center w-full h-full rounded-full bg-woot-75/70 dark:bg-woot-800/40"
>
<fluent-icon
size="14"
:icon="iconName"
type="outline"
class="flex-shrink-0 text-woot-500 dark:text-woot-500"
/>
</div>
</div>
<h1
class="text-2xl font-medium tracking-[-1.5%] text-slate-900 dark:text-slate-25"
>
{{ title }}
</h1>
</div>
<!-- Slot for additional actions on larger screens -->
<div class="hidden gap-2 sm:flex">
<slot name="actions" />
</div>
</div>
<!-- Description and optional link -->
<div
class="flex flex-col gap-2 text-slate-600 dark:text-slate-300 max-w-[721px] w-full"
>
<p
class="mb-0 text-sm font-normal tracking-[0.5%] line-clamp-5 sm:line-clamp-none"
>
<slot name="description">{{ description }}</slot>
</p>
<!-- Conditional link -->
<a
v-if="href && linkText"
:href="href"
target="_blank"
rel="noopener noreferrer"
class="sm:inline-flex hidden tracking-[-0.6%] gap-1 w-fit items-center text-woot-500 dark:text-woot-500 text-sm font-medium tracking=[-0.6%] hover:underline"
>
{{ linkText }}
<fluent-icon
size="16"
icon="chevron-right"
type="outline"
class="flex-shrink-0 text-woot-500 dark:text-woot-500"
/>
</a>
</div>
<!-- Mobile view for actions and link -->
<div class="flex items-start justify-start w-full gap-3 sm:hidden">
<slot name="actions" />
<woot-button
v-if="href && linkText"
color-scheme="secondary"
icon="arrow-outwards"
class="flex-row-reverse rounded-xl min-w-0 !bg-slate-50 !text-slate-900 dark:!text-white dark:!bg-slate-800"
@click="openInNewTab(href)"
>
{{ linkText }}
</woot-button>
</div>
</div>
</template>
@@ -0,0 +1,53 @@
<script setup>
defineProps({
title: {
type: String,
default: '',
},
description: {
type: String,
default: '',
},
});
</script>
<template>
<div
class="flex relative flex-col sm:flex-row p-4 gap-4 sm:p-6 justify-between shadow-sm sm:divide-x sm:divide-slate-75 sm:dark:divide-slate-700/50 group bg-white border border-solid rounded-xl dark:bg-slate-800 border-slate-75 dark:border-slate-700/50 max-w-[900px] w-full"
>
<!-- left side section -->
<slot name="leftSection">
<div class="flex flex-col min-w-0 items-start gap-3 max-w-[480px] w-full">
<div
class="flex items-center justify-between w-full gap-3 sm:justify-normal whitespace-nowrap"
>
<h3
class="justify-between text-sm tracking-[-0.6%] font-medium truncate w-fit sm:justify-normal text-slate-900 dark:text-slate-25"
>
<slot name="title">
{{ title }}
</slot>
</h3>
<slot name="label" />
</div>
<p
class="text-sm text-slate-600 tracking-[0.5%] dark:text-slate-300 max-w-[400px] w-full line-clamp-2"
>
<slot name="description">
{{ description }}
</slot>
</p>
</div>
</slot>
<!-- right side section -->
<slot name="rightSection" />
<!-- actions section -->
<div
v-if="$slots.actions"
class="absolute flex-col items-center hidden gap-1 border-none ltr:-right-3 rtl:-left-3 top-3 group-hover:flex"
>
<slot name="actions" />
</div>
</div>
</template>
@@ -74,6 +74,7 @@
import { required, minLength } from 'vuelidate/lib/validators';
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
export default {
mixins: [alertMixin],
@@ -125,10 +126,9 @@ export default {
});
this.errorMessage = this.$t('PROFILE_SETTINGS.PASSWORD_UPDATE_SUCCESS');
} catch (error) {
this.errorMessage = this.$t('RESET_PASSWORD.API.ERROR_MESSAGE');
if (error?.response?.data?.message) {
this.errorMessage = error.response.data.message;
}
this.errorMessage =
parseAPIErrorResponse(error) ||
this.$t('RESET_PASSWORD.API.ERROR_MESSAGE');
} finally {
this.isPasswordChanging = false;
this.showAlert(this.errorMessage);
@@ -0,0 +1,106 @@
<template>
<div class="flex-1 overflow-auto p-4">
<report-filter-selector
:show-agents-filter="false"
:show-group-by-filter="true"
:show-business-hours-switch="false"
@filter-change="onFilterChange"
/>
<bot-metrics :filters="requestPayload" />
<report-container
:group-by="groupBy"
:report-keys="reportKeys"
:account-summary-key="'getBotSummary'"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import BotMetrics from './components/BotMetrics.vue';
import ReportFilterSelector from './components/FilterSelector.vue';
import { GROUP_BY_FILTER } from './constants';
import reportMixin from 'dashboard/mixins/reportMixin';
import ReportContainer from './ReportContainer.vue';
import { REPORTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
export default {
name: 'BotReports',
components: {
BotMetrics,
ReportFilterSelector,
ReportContainer,
},
mixins: [reportMixin],
data() {
return {
from: 0,
to: 0,
groupBy: GROUP_BY_FILTER[1],
reportKeys: {
BOT_RESOLUTION_COUNT: 'bot_resolutions_count',
BOT_HANDOFF_COUNT: 'bot_handoffs_count',
},
businessHours: false,
};
},
computed: {
...mapGetters({
accountReport: 'getAccountReports',
}),
requestPayload() {
return {
from: this.from,
to: this.to,
};
},
},
methods: {
fetchAllData() {
this.fetchBotSummary();
this.fetchChartData();
},
fetchBotSummary() {
try {
this.$store.dispatch('fetchBotSummary', this.getRequestPayload());
} catch {
this.showAlert(this.$t('REPORT.SUMMARY_FETCHING_FAILED'));
}
},
fetchChartData() {
Object.keys(this.reportKeys).forEach(async key => {
try {
await this.$store.dispatch('fetchAccountReport', {
metric: this.reportKeys[key],
...this.getRequestPayload(),
});
} catch {
this.showAlert(this.$t('REPORT.DATA_FETCHING_FAILED'));
}
});
},
getRequestPayload() {
const { from, to, groupBy, businessHours } = this;
return {
from,
to,
groupBy: groupBy?.period,
businessHours,
};
},
onFilterChange({ from, to, groupBy, businessHours }) {
this.from = from;
this.to = to;
this.groupBy = groupBy;
this.businessHours = businessHours;
this.fetchAllData();
this.$track(REPORTS_EVENTS.FILTER_REPORT, {
filterValue: { from, to, groupBy, businessHours },
reportType: 'bots',
});
},
},
};
</script>
@@ -7,7 +7,7 @@
:key="metric.KEY"
class="p-4 rounded-md mb-3"
>
<chart-stats :metric="metric" />
<chart-stats :metric="metric" :account-summary-key="accountSummaryKey" />
<div class="mt-4 h-72">
<woot-loading-state
v-if="accountReport.isFetching[metric.KEY]"
@@ -0,0 +1,68 @@
<script setup>
import { ref, watch, onMounted } from 'vue';
import ReportMetricCard from './ReportMetricCard.vue';
import ReportsAPI from 'dashboard/api/reports';
const props = defineProps({
filters: {
type: Object,
required: true,
},
});
const conversationCount = ref('0');
const messageCount = ref('0');
const resolutionRate = ref('0');
const handoffRate = ref('0');
const formatToPercent = value => {
return value ? `${value}%` : '--';
};
const fetchMetrics = () => {
if (!props.filters.to || !props.filters.from) {
return;
}
ReportsAPI.getBotMetrics(props.filters).then(response => {
conversationCount.value = response.data.conversation_count.toLocaleString();
messageCount.value = response.data.message_count.toLocaleString();
resolutionRate.value = response.data.resolution_rate.toString();
handoffRate.value = response.data.handoff_rate.toString();
});
};
watch(() => props.filters, fetchMetrics, { deep: true });
onMounted(fetchMetrics);
</script>
<template>
<div
class="flex flex-wrap mx-0 bg-white dark:bg-slate-800 rounded-[4px] p-4 mb-5 border border-solid border-slate-75 dark:border-slate-700"
>
<report-metric-card
:label="$t('BOT_REPORTS.METRIC.TOTAL_CONVERSATIONS.LABEL')"
:info-text="$t('BOT_REPORTS.METRIC.TOTAL_CONVERSATIONS.TOOLTIP')"
:value="conversationCount"
class="flex-1"
/>
<report-metric-card
:label="$t('BOT_REPORTS.METRIC.TOTAL_RESPONSES.LABEL')"
:info-text="$t('BOT_REPORTS.METRIC.TOTAL_RESPONSES.TOOLTIP')"
:value="messageCount"
class="flex-1"
/>
<report-metric-card
:label="$t('BOT_REPORTS.METRIC.RESOLUTION_RATE.LABEL')"
:info-text="$t('BOT_REPORTS.METRIC.RESOLUTION_RATE.TOOLTIP')"
:value="formatToPercent(resolutionRate)"
class="flex-1"
/>
<report-metric-card
:label="$t('BOT_REPORTS.METRIC.HANDOFF_RATE.LABEL')"
:info-text="$t('BOT_REPORTS.METRIC.HANDOFF_RATE.TOOLTIP')"
:value="formatToPercent(handoffRate)"
class="flex-1"
/>
</div>
</template>
@@ -1,6 +1,8 @@
<template>
<div>
<span class="text-sm">{{ metric.NAME }}</span>
<div class="text-slate-900 dark:text-slate-100">
<span class="text-sm">
{{ metric.NAME }}
</span>
<div class="flex items-end">
<div class="font-medium text-xl">
{{ displayMetric(metric.KEY) }}
@@ -6,17 +6,20 @@
:label="$t('CSAT_REPORTS.METRIC.TOTAL_RESPONSES.LABEL')"
:info-text="$t('CSAT_REPORTS.METRIC.TOTAL_RESPONSES.TOOLTIP')"
:value="responseCount"
class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"
/>
<csat-metric-card
:disabled="ratingFilterEnabled"
:label="$t('CSAT_REPORTS.METRIC.SATISFACTION_SCORE.LABEL')"
:info-text="$t('CSAT_REPORTS.METRIC.SATISFACTION_SCORE.TOOLTIP')"
:value="ratingFilterEnabled ? '--' : formatToPercent(satisfactionScore)"
class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"
/>
<csat-metric-card
:label="$t('CSAT_REPORTS.METRIC.RESPONSE_RATE.LABEL')"
:info-text="$t('CSAT_REPORTS.METRIC.RESPONSE_RATE.TOOLTIP')"
:value="formatToPercent(responseRate)"
class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"
/>
<div
@@ -21,7 +21,7 @@ defineProps({
<template>
<div
ref="reportMetricContainer"
class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%] m-0 p-4"
class="m-0 p-4"
:class="{
'grayscale pointer-events-none opacity-30': disabled,
}"
@@ -2,9 +2,9 @@
exports[`CsatMetrics.vue computes response count correctly 1`] = `
<div class="flex-col lg:flex-row flex flex-wrap mx-0 bg-white dark:bg-slate-800 rounded-[4px] p-4 mb-5 border border-solid border-slate-75 dark:border-slate-700">
<csat-metric-card-stub label="CSAT_REPORTS.METRIC.TOTAL_RESPONSES.LABEL" value="100" infotext="CSAT_REPORTS.METRIC.TOTAL_RESPONSES.TOOLTIP"></csat-metric-card-stub>
<csat-metric-card-stub label="CSAT_REPORTS.METRIC.SATISFACTION_SCORE.LABEL" value="--" infotext="CSAT_REPORTS.METRIC.SATISFACTION_SCORE.TOOLTIP" disabled="true"></csat-metric-card-stub>
<csat-metric-card-stub label="CSAT_REPORTS.METRIC.RESPONSE_RATE.LABEL" value="90%" infotext="CSAT_REPORTS.METRIC.RESPONSE_RATE.TOOLTIP"></csat-metric-card-stub>
<csat-metric-card-stub label="CSAT_REPORTS.METRIC.TOTAL_RESPONSES.LABEL" value="100" infotext="CSAT_REPORTS.METRIC.TOTAL_RESPONSES.TOOLTIP" class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"></csat-metric-card-stub>
<csat-metric-card-stub label="CSAT_REPORTS.METRIC.SATISFACTION_SCORE.LABEL" value="--" infotext="CSAT_REPORTS.METRIC.SATISFACTION_SCORE.TOOLTIP" disabled="true" class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"></csat-metric-card-stub>
<csat-metric-card-stub label="CSAT_REPORTS.METRIC.RESPONSE_RATE.LABEL" value="90%" infotext="CSAT_REPORTS.METRIC.RESPONSE_RATE.TOOLTIP" class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"></csat-metric-card-stub>
<!---->
</div>
`;
@@ -194,6 +194,8 @@ export const METRIC_CHART = {
reply_time: TIME_CHART_CONFIG,
avg_resolution_time: TIME_CHART_CONFIG,
resolutions_count: DEFAULT_CHART,
bot_resolutions_count: DEFAULT_CHART,
bot_handoffs_count: DEFAULT_CHART,
};
export const OVERVIEW_METRICS = {
@@ -7,6 +7,7 @@ const LabelReports = () => import('./LabelReports.vue');
const InboxReports = () => import('./InboxReports.vue');
const TeamReports = () => import('./TeamReports.vue');
const CsatResponses = () => import('./CsatResponses.vue');
const BotReports = () => import('./BotReports.vue');
const LiveReports = () => import('./LiveReports.vue');
export default {
@@ -66,6 +67,23 @@ export default {
},
],
},
{
path: frontendURL('accounts/:accountId/reports'),
component: SettingsContent,
props: {
headerTitle: 'BOT_REPORTS.HEADER',
icon: 'bot',
keepAlive: false,
},
children: [
{
path: 'bot',
name: 'bot_reports',
roles: ['administrator'],
component: BotReports,
},
],
},
{
path: frontendURL('accounts/:accountId/reports'),
component: SettingsContent,
@@ -1,60 +0,0 @@
<template>
<div class="h-auto overflow-auto flex flex-col">
<woot-modal-header :header-title="pageTitle" />
<sla-form
:submit-label="$t('SLA.FORM.EDIT')"
:selected-response="selectedResponse"
@submit="editSLA"
@close="onClose"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import validationMixin from './validationMixin';
import validations from './validations';
import SlaForm from './SlaForm.vue';
export default {
components: {
SlaForm,
},
mixins: [alertMixin, validationMixin],
props: {
selectedResponse: {
type: Object,
default: () => {},
},
},
validations,
computed: {
...mapGetters({
uiFlags: 'sla/getUIFlags',
}),
pageTitle() {
return `${this.$t('SLA.EDIT.TITLE')} - ${this.selectedResponse.name}`;
},
},
methods: {
onClose() {
this.$emit('close');
},
async editSLA(payload) {
try {
await this.$store.dispatch('sla/update', {
id: this.selectedResponse.id,
...payload,
});
this.showAlert(this.$t('SLA.EDIT.API.SUCCESS_MESSAGE'));
this.onClose();
} catch (error) {
const errorMessage =
error.message || this.$t('SLA.EDIT.API.ERROR_MESSAGE');
this.showAlert(errorMessage);
}
},
},
};
</script>
@@ -58,14 +58,14 @@
</td>
<td class="button-wrapper">
<woot-button
v-tooltip.top="$t('SLA.FORM.EDIT')"
v-tooltip.top="$t('SLA.FORM.DELETE')"
variant="smooth"
color-scheme="alert"
size="tiny"
color-scheme="secondary"
icon="dismiss-circle"
class-names="grey-btn"
:is-loading="loading[sla.id]"
icon="edit"
@click="openEditPopup(sla)"
@click="openDeletePopup(sla)"
/>
</td>
</tr>
@@ -81,9 +81,16 @@
<add-SLA @close="hideAddPopup" />
</woot-modal>
<woot-modal :show.sync="showEditPopup" :on-close="hideEditPopup">
<edit-SLA :selected-response="selectedResponse" @close="hideEditPopup" />
</woot-modal>
<woot-delete-modal
:show.sync="showDeleteConfirmationPopup"
:on-close="closeDeletePopup"
:on-confirm="confirmDeletion"
:title="$t('SLA.DELETE.CONFIRM.TITLE')"
:message="$t('SLA.DELETE.CONFIRM.MESSAGE')"
:message-value="deleteMessage"
:confirm-text="deleteConfirmText"
:reject-text="deleteRejectText"
/>
</div>
</template>
<script>
@@ -91,20 +98,18 @@ import { mapGetters } from 'vuex';
import { convertSecondsToTimeUnit } from '@chatwoot/utils';
import AddSLA from './AddSLA.vue';
import EditSLA from './EditSLA.vue';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
AddSLA,
EditSLA,
},
mixins: [alertMixin],
data() {
return {
loading: {},
showAddPopup: false,
showEditPopup: false,
showDeleteConfirmationPopup: false,
selectedResponse: {},
};
},
@@ -113,6 +118,15 @@ export default {
records: 'sla/getSLA',
uiFlags: 'sla/getUIFlags',
}),
deleteConfirmText() {
return this.$t('SLA.DELETE.CONFIRM.YES');
},
deleteRejectText() {
return this.$t('SLA.DELETE.CONFIRM.NO');
},
deleteMessage() {
return ` ${this.selectedResponse.name}`;
},
},
mounted() {
this.$store.dispatch('sla/get');
@@ -124,12 +138,30 @@ export default {
hideAddPopup() {
this.showAddPopup = false;
},
openEditPopup(response) {
this.showEditPopup = true;
openDeletePopup(response) {
this.showDeleteConfirmationPopup = true;
this.selectedResponse = response;
},
hideEditPopup() {
this.showEditPopup = false;
closeDeletePopup() {
this.showDeleteConfirmationPopup = false;
},
confirmDeletion() {
this.loading[this.selectedResponse.id] = true;
this.closeDeletePopup();
this.deleteSla(this.selectedResponse.id);
},
deleteSla(id) {
this.$store
.dispatch('sla/delete', id)
.then(() => {
this.showAlert(this.$t('SLA.DELETE.API.SUCCESS_MESSAGE'));
})
.catch(() => {
this.showAlert(this.$t('SLA.DELETE.API.ERROR_MESSAGE'));
})
.finally(() => {
this.loading[this.selectedResponse.id] = false;
});
},
displayTime(threshold) {
const { time, unit } = convertSecondsToTimeUnit(threshold, {
@@ -0,0 +1,39 @@
<script setup>
defineProps({
hasBusinessHours: {
type: Boolean,
required: true,
},
});
</script>
<template>
<div
class="inline-flex items-center min-w-0 gap-1 px-1.5 sm:px-2 py-1 border border-solid rounded-lg border-slate-75 dark:border-slate-700/50"
>
<fluent-icon
size="14"
:icon="hasBusinessHours ? 'alarm-on' : 'alarm-off'"
type="outline"
class="flex-shrink-0"
:class="
hasBusinessHours
? 'text-slate-600 dark:text-slate-400'
: 'text-slate-300 dark:text-slate-700'
"
/>
<span
class="hidden text-xs tracking-[0.2%] font-normal truncate sm:block"
:class="
hasBusinessHours
? 'text-slate-600 dark:text-slate-400'
: 'text-slate-300 dark:text-slate-700'
"
>
{{
hasBusinessHours
? $t('SLA.LIST.BUSINESS_HOURS_ON')
: $t('SLA.LIST.BUSINESS_HOURS_OFF')
}}
</span>
</div>
</template>
@@ -0,0 +1,23 @@
<script setup>
import BaseSettingsHeader from '../../components/BaseSettingsHeader.vue';
</script>
<template>
<base-settings-header
:title="$t('SLA.HEADER')"
:description="$t('SLA.DESCRIPTION')"
:link-text="$t('SLA.LEARN_MORE')"
href="/"
icon-name="document-list-clock"
>
<template #actions>
<woot-button
color-scheme="primary"
icon="plus-sign"
class="rounded-xl"
@click="$emit('click')"
>
{{ $t('SLA.ADD_ACTION') }}
</woot-button>
</template>
</base-settings-header>
</template>
@@ -0,0 +1,64 @@
<script setup>
import BaseSettingsListItem from '../../components/BaseSettingsListItem.vue';
import SLAResponseTime from './SLAResponseTime.vue';
import SLABusinessHoursLabel from './SLABusinessHoursLabel.vue';
defineProps({
slaName: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
firstResponse: {
type: String,
required: true,
},
nextResponse: {
type: String,
required: true,
},
resolutionTime: {
type: String,
required: true,
},
hasBusinessHours: {
type: Boolean,
required: true,
},
isLoading: {
type: Boolean,
default: false,
},
});
</script>
<template>
<base-settings-list-item :title="slaName" :description="description">
<template #label>
<SLA-business-hours-label :has-business-hours="hasBusinessHours" />
</template>
<template #rightSection>
<div
class="flex items-center divide-x rtl:divide-x-reverse sm:rtl:!border-l-0 sm:rtl:!border-r sm:rtl:border-solid sm:rtl:border-slate-75 sm:rtl:dark:border-slate-700/50 gap-1.5 w-fit sm:w-full sm:gap-0 sm:justify-between divide-slate-75 dark:divide-slate-700/50"
>
<SLA-response-time response-type="FRT" :response-time="firstResponse" />
<SLA-response-time response-type="NRT" :response-time="nextResponse" />
<SLA-response-time response-type="RT" :response-time="resolutionTime" />
</div>
</template>
<template #actions>
<woot-button
v-tooltip.top="$t('SLA.FORM.DELETE')"
variant="smooth"
color-scheme="alert"
size="tiny"
icon="delete"
class-names="grey-btn"
:is-loading="isLoading"
@click="$emit('click')"
/>
</template>
</base-settings-list-item>
</template>
@@ -0,0 +1,36 @@
<script setup>
defineProps({
responseType: {
type: String,
default: '',
},
responseTime: {
type: String,
default: '',
},
});
</script>
<template>
<div
class="flex flex-row items-start w-full h-full gap-1 sm:items-end sm:px-6 sm:py-2 sm:gap-2 sm:flex-col"
>
<span
class="inline-flex items-center gap-1 tracking-[-0.6%] text-sm ltr:pl-1.5 sm:ltr:pl-0 rtl:pr-1.5 sm:rtl:pr-0 text-slate-600 dark:text-slate-300"
>
<fluent-icon
v-tooltip.left="$t(`SLA.LIST.RESPONSE_TYPES.${responseType}`)"
size="14"
icon="information"
type="outline"
class="flex-shrink-0 hidden text-sm font-normal sm:flex sm:font-medium text-slate-500 dark:text-slate-500"
/>
{{ $t(`SLA.LIST.RESPONSE_TYPES.SHORT_HAND.${responseType}`) }}
<span class="flex sm:hidden">:</span>
</span>
<span
class="text-sm sm:text-2xl font-medium tracking-[-1.5%] text-slate-900 dark:text-slate-25"
>
{{ responseTime }}
</span>
</div>
</template>