feat(shangwutong): sync classifications and update conversations
This commit is contained in:
@@ -15,6 +15,7 @@ import ConversationAction from './ConversationAction.vue';
|
||||
import ConversationParticipant from './ConversationParticipant.vue';
|
||||
import ContactInfo from './contact/ContactInfo.vue';
|
||||
import ContactNotes from './contact/ContactNotes.vue';
|
||||
import ShangwutongClassifications from './ShangwutongClassifications.vue';
|
||||
import ConversationInfo from './ConversationInfo.vue';
|
||||
import CustomAttributes from './customAttributes/CustomAttributes.vue';
|
||||
import SharedFiles from './SharedFiles.vue';
|
||||
@@ -138,6 +139,12 @@ onMounted(() => {
|
||||
@close="closeContactPanel"
|
||||
/>
|
||||
<ContactInfo :contact="contact" :channel-type="channelType" />
|
||||
<ShangwutongClassifications
|
||||
:conversation-id="conversationId"
|
||||
:inbox-id="inboxId"
|
||||
:channel-type="channelType"
|
||||
:conversation-attributes="conversationAdditionalAttributes"
|
||||
/>
|
||||
<div class="px-2 pb-8 list-group">
|
||||
<Draggable
|
||||
:list="conversationSidebarItems"
|
||||
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import ShangwutongClassificationsAPI from 'dashboard/api/shangwutongClassifications';
|
||||
|
||||
const props = defineProps({
|
||||
conversationId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
inboxId: {
|
||||
type: [Number, String],
|
||||
default: undefined,
|
||||
},
|
||||
channelType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
conversationAttributes: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const currentChat = useMapGetter('getSelectedChat');
|
||||
const catalog = ref({
|
||||
conversation_kinds: [],
|
||||
customer_color_kinds: [],
|
||||
sync_status: 'never',
|
||||
});
|
||||
const selectedChatKind = ref('');
|
||||
const selectedCustomerColor = ref('');
|
||||
const isLoading = ref(false);
|
||||
const saving = ref('');
|
||||
|
||||
const isShangwutong = computed(
|
||||
() =>
|
||||
props.channelType === 'Channel::Shangwutong' ||
|
||||
currentChat.value?.meta?.channel === 'Channel::Shangwutong'
|
||||
);
|
||||
const isReady = computed(() => catalog.value.sync_status === 'succeeded');
|
||||
const attributes = computed(() => {
|
||||
if (Object.keys(props.conversationAttributes || {}).length > 0) {
|
||||
return props.conversationAttributes;
|
||||
}
|
||||
return currentChat.value?.meta?.additional_attributes || {};
|
||||
});
|
||||
|
||||
const resetSelections = () => {
|
||||
selectedChatKind.value =
|
||||
attributes.value.swt_chat_kind || attributes.value.swt_chat_kind_id || '';
|
||||
selectedCustomerColor.value = '';
|
||||
const currentColor = attributes.value.swt_label_color;
|
||||
if (currentColor !== undefined && currentColor !== null) {
|
||||
const match = catalog.value.customer_color_kinds.find(
|
||||
item =>
|
||||
item.id === String(currentColor) || item.name === String(currentColor)
|
||||
);
|
||||
selectedCustomerColor.value = match?.id || String(currentColor);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchClassifications = async () => {
|
||||
if (!isShangwutong.value || !props.inboxId) return;
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const response = await ShangwutongClassificationsAPI.get(props.inboxId);
|
||||
catalog.value = response.data;
|
||||
resetSelections();
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
error.message ||
|
||||
t('CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.LOAD_ERROR')
|
||||
);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const conversationAttributesFrom = response =>
|
||||
response.data?.conversation?.additional_attributes ||
|
||||
response.data?.additional_attributes ||
|
||||
response.data?.meta?.additional_attributes ||
|
||||
{};
|
||||
|
||||
const waitForConfirmation = async (type, value, eventID, attempt = 0) => {
|
||||
if (attempt >= 10) return null;
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
try {
|
||||
const response = await ShangwutongClassificationsAPI.getConversation(
|
||||
props.conversationId
|
||||
);
|
||||
const updated = conversationAttributesFrom(response);
|
||||
if (updated.swt_classification_event_id === eventID) {
|
||||
if (updated.swt_classification_status !== 'succeeded') return false;
|
||||
return type === 'chat'
|
||||
? String(updated.swt_chat_kind) === String(value)
|
||||
: String(updated.swt_label_color) === String(value);
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
return waitForConfirmation(type, value, eventID, attempt + 1);
|
||||
};
|
||||
|
||||
const updateClassification = async (type, value) => {
|
||||
if (!value || saving.value || !isReady.value) return;
|
||||
saving.value = type;
|
||||
const payload =
|
||||
type === 'chat' ? { chat_kind_id: value } : { customer_color_id: value };
|
||||
try {
|
||||
const response = await ShangwutongClassificationsAPI.updateConversation(
|
||||
props.conversationId,
|
||||
payload
|
||||
);
|
||||
const confirmation = await waitForConfirmation(
|
||||
type,
|
||||
value,
|
||||
response.data?.sync_id
|
||||
);
|
||||
if (confirmation === true) {
|
||||
useAlert(
|
||||
t('CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.UPDATE_CONFIRMED')
|
||||
);
|
||||
} else if (confirmation === false) {
|
||||
resetSelections();
|
||||
useAlert(
|
||||
t('CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.UPDATE_ERROR')
|
||||
);
|
||||
} else {
|
||||
useAlert(
|
||||
t('CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.UPDATE_PENDING')
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
resetSelections();
|
||||
useAlert(
|
||||
error.message ||
|
||||
t('CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.UPDATE_ERROR')
|
||||
);
|
||||
} finally {
|
||||
saving.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
[() => props.inboxId, () => props.channelType, () => props.conversationId],
|
||||
fetchClassifications,
|
||||
{ immediate: true }
|
||||
);
|
||||
watch(() => props.conversationAttributes, resetSelections, { deep: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="isShangwutong">
|
||||
<div class="px-4 py-3 border-b border-n-weak">
|
||||
<h4 class="text-heading-3 text-n-slate-12">
|
||||
{{ $t('CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.TITLE') }}
|
||||
</h4>
|
||||
<p v-if="!isReady" class="mt-2 text-body-small text-n-slate-10">
|
||||
{{
|
||||
catalog.sync_status === 'pending'
|
||||
? $t(
|
||||
'CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.SYNC_PENDING'
|
||||
)
|
||||
: catalog.sync_status === 'failed'
|
||||
? $t(
|
||||
'CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.SYNC_FAILED'
|
||||
)
|
||||
: $t(
|
||||
'CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.NOT_SYNCED'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<div v-else class="mt-3 space-y-3">
|
||||
<label class="block text-body-small text-n-slate-11">
|
||||
{{
|
||||
$t(
|
||||
'CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.CONVERSATION_KIND'
|
||||
)
|
||||
}}
|
||||
<select
|
||||
v-model="selectedChatKind"
|
||||
class="w-full mt-1"
|
||||
:disabled="isLoading || !!saving"
|
||||
@change="updateClassification('chat', selectedChatKind)"
|
||||
>
|
||||
<option value="">
|
||||
{{
|
||||
$t('CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.SELECT')
|
||||
}}
|
||||
</option>
|
||||
<option
|
||||
v-for="item in catalog.conversation_kinds"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="block text-body-small text-n-slate-11">
|
||||
{{
|
||||
$t(
|
||||
'CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.CUSTOMER_COLOR'
|
||||
)
|
||||
}}
|
||||
<select
|
||||
v-model="selectedCustomerColor"
|
||||
class="w-full mt-1"
|
||||
:disabled="isLoading || !!saving"
|
||||
@change="updateClassification('color', selectedCustomerColor)"
|
||||
>
|
||||
<option value="">
|
||||
{{
|
||||
$t('CONVERSATION_SIDEBAR.SHANGWUTONG_CLASSIFICATIONS.SELECT')
|
||||
}}
|
||||
</option>
|
||||
<option
|
||||
v-for="item in catalog.customer_color_kinds"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else />
|
||||
</template>
|
||||
+278
-215
@@ -1,231 +1,294 @@
|
||||
<script>
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import InboxHealthAPI from 'dashboard/api/inboxHealth';
|
||||
import ShangwutongClassificationsAPI from 'dashboard/api/shangwutongClassifications';
|
||||
import SettingsFieldSection from 'dashboard/components-next/Settings/SettingsFieldSection.vue';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
export default {
|
||||
components: { SettingsFieldSection, NextButton },
|
||||
props: {
|
||||
inbox: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
desiredPresence: 'online',
|
||||
password: '',
|
||||
webhookUrl: '',
|
||||
health: null,
|
||||
isSaving: false,
|
||||
isLoadingHealth: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
inbox: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.desiredPresence = value.desired_presence || 'online';
|
||||
this.webhookUrl = value.webhook_url || '';
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchHealth();
|
||||
},
|
||||
methods: {
|
||||
statusLabel(type, value, fallback) {
|
||||
const status = value || fallback;
|
||||
const key = `INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_VALUES.${type}.${status.toUpperCase()}`;
|
||||
return this.$te(key)
|
||||
? this.$t(key)
|
||||
: this.$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_VALUES.UNKNOWN');
|
||||
},
|
||||
async fetchHealth() {
|
||||
this.isLoadingHealth = true;
|
||||
try {
|
||||
const response = await InboxHealthAPI.getHealthStatus(
|
||||
this.inbox.id
|
||||
);
|
||||
this.health = response.data;
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
error.message ||
|
||||
this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE')
|
||||
);
|
||||
} finally {
|
||||
this.isLoadingHealth = false;
|
||||
}
|
||||
},
|
||||
async save() {
|
||||
this.isSaving = true;
|
||||
const channel = {
|
||||
desired_presence: this.desiredPresence,
|
||||
webhook_url: this.webhookUrl.trim(),
|
||||
};
|
||||
if (this.password) channel.password = this.password;
|
||||
try {
|
||||
await this.$store.dispatch('inboxes/updateInbox', {
|
||||
id: this.inbox.id,
|
||||
formData: false,
|
||||
channel,
|
||||
});
|
||||
this.password = '';
|
||||
useAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
|
||||
await this.fetchHealth();
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
error.message ||
|
||||
this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE')
|
||||
);
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
components: { SettingsFieldSection, NextButton },
|
||||
props: {
|
||||
inbox: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
desiredPresence: 'online',
|
||||
password: '',
|
||||
webhookUrl: '',
|
||||
health: null,
|
||||
isSaving: false,
|
||||
isLoadingHealth: false,
|
||||
classifications: null,
|
||||
isSyncingClassifications: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
inbox: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
this.desiredPresence = value.desired_presence || 'online';
|
||||
this.webhookUrl = value.webhook_url || '';
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchHealth();
|
||||
this.fetchClassifications();
|
||||
},
|
||||
methods: {
|
||||
statusLabel(type, value, fallback) {
|
||||
const status = value || fallback;
|
||||
const key = `INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_VALUES.${type}.${status.toUpperCase()}`;
|
||||
return this.$te(key)
|
||||
? this.$t(key)
|
||||
: this.$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_VALUES.UNKNOWN');
|
||||
},
|
||||
async fetchHealth() {
|
||||
this.isLoadingHealth = true;
|
||||
try {
|
||||
const response = await InboxHealthAPI.getHealthStatus(this.inbox.id);
|
||||
this.health = response.data;
|
||||
} catch (error) {
|
||||
useAlert(error.message || this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE'));
|
||||
} finally {
|
||||
this.isLoadingHealth = false;
|
||||
}
|
||||
},
|
||||
async fetchClassifications() {
|
||||
try {
|
||||
const response = await ShangwutongClassificationsAPI.get(this.inbox.id);
|
||||
this.classifications = response.data;
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
error.message ||
|
||||
this.$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.CLASSIFICATION_LOAD_ERROR')
|
||||
);
|
||||
}
|
||||
},
|
||||
async waitForClassificationSync(attempt = 0) {
|
||||
if (attempt >= 10) return;
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
await this.fetchClassifications();
|
||||
if (this.classifications?.sync_status === 'pending') {
|
||||
await this.waitForClassificationSync(attempt + 1);
|
||||
}
|
||||
},
|
||||
async syncClassifications() {
|
||||
this.isSyncingClassifications = true;
|
||||
try {
|
||||
await ShangwutongClassificationsAPI.sync(this.inbox.id);
|
||||
await this.waitForClassificationSync();
|
||||
if (this.classifications?.sync_status === 'succeeded') {
|
||||
useAlert(
|
||||
this.$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.CLASSIFICATION_SYNCED', {
|
||||
conversation: this.classifications.conversation_kinds.length,
|
||||
customer: this.classifications.customer_color_kinds.length,
|
||||
})
|
||||
);
|
||||
} else if (this.classifications?.sync_status !== 'failed') {
|
||||
useAlert(
|
||||
this.$t(
|
||||
'INBOX_MGMT.SHANGWUTONG_SETTINGS.CLASSIFICATION_SYNC_QUEUED'
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
useAlert(
|
||||
error.message ||
|
||||
this.$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.CLASSIFICATION_SYNC_ERROR')
|
||||
);
|
||||
} finally {
|
||||
this.isSyncingClassifications = false;
|
||||
}
|
||||
},
|
||||
async save() {
|
||||
this.isSaving = true;
|
||||
const channel = {
|
||||
desired_presence: this.desiredPresence,
|
||||
webhook_url: this.webhookUrl.trim(),
|
||||
};
|
||||
if (this.password) channel.password = this.password;
|
||||
try {
|
||||
await this.$store.dispatch('inboxes/updateInbox', {
|
||||
id: this.inbox.id,
|
||||
formData: false,
|
||||
channel,
|
||||
});
|
||||
this.password = '';
|
||||
useAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
|
||||
await this.fetchHealth();
|
||||
} catch (error) {
|
||||
useAlert(error.message || this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE'));
|
||||
} finally {
|
||||
this.isSaving = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.RUNTIME_STATUS')"
|
||||
:help-text="
|
||||
$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.RUNTIME_STATUS_HELP')
|
||||
"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
{{
|
||||
$t(
|
||||
'INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_FIELDS.CONNECTION_STATUS'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{
|
||||
statusLabel(
|
||||
'CONNECTION',
|
||||
health?.connection_status || inbox.connection_status,
|
||||
'pending'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
{{
|
||||
$t(
|
||||
'INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_FIELDS.ACTUAL_PRESENCE'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{
|
||||
statusLabel(
|
||||
'PRESENCE',
|
||||
health?.actual_presence || inbox.actual_presence,
|
||||
'offline'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
{{
|
||||
$t(
|
||||
'INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_FIELDS.CREDENTIAL_STATUS'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{
|
||||
statusLabel(
|
||||
'CREDENTIAL',
|
||||
health?.credential_status || inbox.credential_status,
|
||||
'pending'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
{{
|
||||
$t(
|
||||
'INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_FIELDS.LAST_HEARTBEAT_AT'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{
|
||||
health?.last_heartbeat_at ||
|
||||
inbox.last_heartbeat_at ||
|
||||
'—'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="health?.last_error_code"
|
||||
class="mt-3 text-sm text-n-ruby-11"
|
||||
>
|
||||
{{ health.last_error_code }}
|
||||
</p>
|
||||
<NextButton
|
||||
class="mt-3"
|
||||
:is-loading="isLoadingHealth"
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.REFRESH')"
|
||||
@click="fetchHealth"
|
||||
/>
|
||||
</SettingsFieldSection>
|
||||
<div class="space-y-6">
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.RUNTIME_STATUS')"
|
||||
:help-text="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.RUNTIME_STATUS_HELP')"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
{{
|
||||
$t(
|
||||
'INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_FIELDS.CONNECTION_STATUS'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{
|
||||
statusLabel(
|
||||
'CONNECTION',
|
||||
health?.connection_status || inbox.connection_status,
|
||||
'pending'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
{{
|
||||
$t(
|
||||
'INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_FIELDS.ACTUAL_PRESENCE'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{
|
||||
statusLabel(
|
||||
'PRESENCE',
|
||||
health?.actual_presence || inbox.actual_presence,
|
||||
'offline'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
{{
|
||||
$t(
|
||||
'INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_FIELDS.CREDENTIAL_STATUS'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{
|
||||
statusLabel(
|
||||
'CREDENTIAL',
|
||||
health?.credential_status || inbox.credential_status,
|
||||
'pending'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-n-weak p-3">
|
||||
<p class="text-label-small text-n-slate-10">
|
||||
{{
|
||||
$t(
|
||||
'INBOX_MGMT.SHANGWUTONG_SETTINGS.STATUS_FIELDS.LAST_HEARTBEAT_AT'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p class="text-body-main text-n-slate-12">
|
||||
{{ health?.last_heartbeat_at || inbox.last_heartbeat_at || '—' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="health?.last_error_code" class="mt-3 text-sm text-n-ruby-11">
|
||||
{{ health.last_error_code }}
|
||||
</p>
|
||||
<NextButton
|
||||
class="mt-3"
|
||||
:is-loading="isLoadingHealth"
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.REFRESH')"
|
||||
@click="fetchHealth"
|
||||
/>
|
||||
</SettingsFieldSection>
|
||||
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.DESIRED_PRESENCE')"
|
||||
:help-text="
|
||||
$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.DESIRED_PRESENCE_HELP')
|
||||
"
|
||||
>
|
||||
<select v-model="desiredPresence" class="w-full">
|
||||
<option value="online">
|
||||
{{ $t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PRESENCE.ONLINE') }}
|
||||
</option>
|
||||
<option value="busy">
|
||||
{{ $t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PRESENCE.BUSY') }}
|
||||
</option>
|
||||
<option value="away">
|
||||
{{ $t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PRESENCE.AWAY') }}
|
||||
</option>
|
||||
<option value="offline">
|
||||
{{ $t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PRESENCE.OFFLINE') }}
|
||||
</option>
|
||||
</select>
|
||||
</SettingsFieldSection>
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.CLASSIFICATIONS')"
|
||||
:help-text="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.CLASSIFICATIONS_HELP')"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<NextButton
|
||||
:is-loading="isSyncingClassifications"
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.SYNC_CLASSIFICATIONS')"
|
||||
@click="syncClassifications"
|
||||
/>
|
||||
<span class="text-body-small text-n-slate-10">
|
||||
{{
|
||||
classifications?.sync_status === 'succeeded'
|
||||
? $t('INBOX_MGMT.SHANGWUTONG_SETTINGS.CLASSIFICATION_SYNCED', {
|
||||
conversation: classifications.conversation_kinds.length,
|
||||
customer: classifications.customer_color_kinds.length,
|
||||
})
|
||||
: classifications?.sync_status === 'pending'
|
||||
? $t(
|
||||
'INBOX_MGMT.SHANGWUTONG_SETTINGS.CLASSIFICATION_SYNC_PENDING'
|
||||
)
|
||||
: $t(
|
||||
'INBOX_MGMT.SHANGWUTONG_SETTINGS.CLASSIFICATION_NOT_SYNCED'
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</SettingsFieldSection>
|
||||
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PASSWORD')"
|
||||
:help-text="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PASSWORD_HELP')"
|
||||
>
|
||||
<input
|
||||
v-model="password"
|
||||
type="password"
|
||||
maxlength="4096"
|
||||
autocomplete="new-password"
|
||||
class="w-full"
|
||||
/>
|
||||
</SettingsFieldSection>
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.DESIRED_PRESENCE')"
|
||||
:help-text="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.DESIRED_PRESENCE_HELP')"
|
||||
>
|
||||
<select v-model="desiredPresence" class="w-full">
|
||||
<option value="online">
|
||||
{{ $t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PRESENCE.ONLINE') }}
|
||||
</option>
|
||||
<option value="busy">
|
||||
{{ $t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PRESENCE.BUSY') }}
|
||||
</option>
|
||||
<option value="away">
|
||||
{{ $t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PRESENCE.AWAY') }}
|
||||
</option>
|
||||
<option value="offline">
|
||||
{{ $t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PRESENCE.OFFLINE') }}
|
||||
</option>
|
||||
</select>
|
||||
</SettingsFieldSection>
|
||||
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.WEBHOOK_URL')"
|
||||
:help-text="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.WEBHOOK_URL_HELP')"
|
||||
>
|
||||
<input v-model="webhookUrl" type="url" class="w-full" />
|
||||
</SettingsFieldSection>
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PASSWORD')"
|
||||
:help-text="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.PASSWORD_HELP')"
|
||||
>
|
||||
<input
|
||||
v-model="password"
|
||||
type="password"
|
||||
maxlength="4096"
|
||||
autocomplete="new-password"
|
||||
class="w-full"
|
||||
/>
|
||||
</SettingsFieldSection>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<NextButton
|
||||
:is-loading="isSaving"
|
||||
:label="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
|
||||
@click="save"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsFieldSection
|
||||
:label="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.WEBHOOK_URL')"
|
||||
:help-text="$t('INBOX_MGMT.SHANGWUTONG_SETTINGS.WEBHOOK_URL_HELP')"
|
||||
>
|
||||
<input v-model="webhookUrl" type="url" class="w-full" />
|
||||
</SettingsFieldSection>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<NextButton
|
||||
:is-loading="isSaving"
|
||||
:label="$t('INBOX_MGMT.SETTINGS_POPUP.UPDATE')"
|
||||
@click="save"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user