chore(voice): align FE files to incoming-pipeline base (UI changes consolidated to PR #14346)
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStore } from 'vuex';
|
||||
import { useMessageContext } from '../provider.js';
|
||||
import { VOICE_CALL_STATUS } from '../constants';
|
||||
|
||||
@@ -13,6 +11,11 @@ const LABEL_MAP = {
|
||||
[VOICE_CALL_STATUS.COMPLETED]: 'CONVERSATION.VOICE_CALL.CALL_ENDED',
|
||||
};
|
||||
|
||||
const SUBTEXT_MAP = {
|
||||
[VOICE_CALL_STATUS.RINGING]: 'CONVERSATION.VOICE_CALL.NOT_ANSWERED_YET',
|
||||
[VOICE_CALL_STATUS.COMPLETED]: 'CONVERSATION.VOICE_CALL.CALL_ENDED',
|
||||
};
|
||||
|
||||
const ICON_MAP = {
|
||||
[VOICE_CALL_STATUS.IN_PROGRESS]: 'i-ph-phone-call',
|
||||
[VOICE_CALL_STATUS.NO_ANSWER]: 'i-ph-phone-x',
|
||||
@@ -27,40 +30,13 @@ const BG_COLOR_MAP = {
|
||||
[VOICE_CALL_STATUS.FAILED]: 'bg-n-ruby-9',
|
||||
};
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
const { call, conversationId, currentUserId } = useMessageContext();
|
||||
const { call } = useMessageContext();
|
||||
|
||||
const status = computed(() => call.value?.status);
|
||||
const isOutbound = computed(() => call.value?.direction === 'outgoing');
|
||||
const isFailed = computed(() =>
|
||||
[VOICE_CALL_STATUS.NO_ANSWER, VOICE_CALL_STATUS.FAILED].includes(status.value)
|
||||
);
|
||||
const acceptedByAgentId = computed(() => call.value?.accepted_by_agent_id);
|
||||
const didCurrentUserAnswer = computed(
|
||||
() =>
|
||||
!!acceptedByAgentId.value && acceptedByAgentId.value === currentUserId.value
|
||||
);
|
||||
// Pickup auto-assigns the conversation, so the assignee is a safe display proxy
|
||||
// for the answerer when the Call payload lacks accepted_by_agent_id (e.g.,
|
||||
// Twilio's call-status webhook flipped the call to in-progress before the
|
||||
// participant-join webhook claimed it).
|
||||
const conversationAssignee = computed(() => {
|
||||
const conversation = store.getters.getConversationById?.(
|
||||
conversationId?.value
|
||||
);
|
||||
return conversation?.meta?.assignee || null;
|
||||
});
|
||||
const displayAgentName = computed(() => {
|
||||
if (call.value?.accepted_by_agent_name)
|
||||
return call.value.accepted_by_agent_name;
|
||||
if (acceptedByAgentId.value) {
|
||||
const agent = store.getters['agents/getAgentById'](acceptedByAgentId.value);
|
||||
if (agent?.available_name) return agent.available_name;
|
||||
if (agent?.name) return agent.name;
|
||||
}
|
||||
return conversationAssignee.value?.name || null;
|
||||
});
|
||||
|
||||
const labelKey = computed(() => {
|
||||
if (LABEL_MAP[status.value]) return LABEL_MAP[status.value];
|
||||
@@ -74,28 +50,16 @@ const labelKey = computed(() => {
|
||||
: 'CONVERSATION.VOICE_CALL.INCOMING_CALL';
|
||||
});
|
||||
|
||||
const subtext = computed(() => {
|
||||
if (status.value === VOICE_CALL_STATUS.RINGING) {
|
||||
return t('CONVERSATION.VOICE_CALL.NOT_ANSWERED_YET');
|
||||
}
|
||||
if (status.value === VOICE_CALL_STATUS.COMPLETED) {
|
||||
return t('CONVERSATION.VOICE_CALL.CALL_ENDED');
|
||||
}
|
||||
const subtextKey = computed(() => {
|
||||
if (SUBTEXT_MAP[status.value]) return SUBTEXT_MAP[status.value];
|
||||
if (status.value === VOICE_CALL_STATUS.IN_PROGRESS) {
|
||||
if (isOutbound.value) return t('CONVERSATION.VOICE_CALL.THEY_ANSWERED');
|
||||
if (didCurrentUserAnswer.value) {
|
||||
return t('CONVERSATION.VOICE_CALL.YOU_ANSWERED');
|
||||
}
|
||||
if (displayAgentName.value) {
|
||||
return t('CONVERSATION.VOICE_CALL.AGENT_ANSWERED', {
|
||||
agentName: displayAgentName.value,
|
||||
});
|
||||
}
|
||||
return t('CONVERSATION.VOICE_CALL.THEY_ANSWERED');
|
||||
return isOutbound.value
|
||||
? 'CONVERSATION.VOICE_CALL.THEY_ANSWERED'
|
||||
: 'CONVERSATION.VOICE_CALL.YOU_ANSWERED';
|
||||
}
|
||||
return isFailed.value
|
||||
? t('CONVERSATION.VOICE_CALL.NO_ANSWER')
|
||||
: t('CONVERSATION.VOICE_CALL.NOT_ANSWERED_YET');
|
||||
? 'CONVERSATION.VOICE_CALL.NO_ANSWER'
|
||||
: 'CONVERSATION.VOICE_CALL.NOT_ANSWERED_YET';
|
||||
});
|
||||
|
||||
const iconName = computed(() => {
|
||||
@@ -129,7 +93,7 @@ const bgColor = computed(() => BG_COLOR_MAP[status.value] || 'bg-n-teal-9');
|
||||
{{ $t(labelKey) }}
|
||||
</span>
|
||||
<span class="text-xs text-n-slate-11">
|
||||
{{ subtext }}
|
||||
{{ $t(subtextKey) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { computed, ref, watch, onUnmounted, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VoiceAPI from 'dashboard/api/channel/voice/voiceAPIClient';
|
||||
import TwilioVoiceClient from 'dashboard/api/channel/voice/twilioVoiceClient';
|
||||
import { useCallsStore } from 'dashboard/stores/calls';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import Timer from 'dashboard/helper/Timer';
|
||||
|
||||
export function useCallSession() {
|
||||
const callsStore = useCallsStore();
|
||||
const { t } = useI18n();
|
||||
const isJoining = ref(false);
|
||||
const callDuration = ref(0);
|
||||
const durationTimer = new Timer(elapsed => {
|
||||
@@ -77,11 +74,6 @@ export function useCallSession() {
|
||||
|
||||
return { conferenceSid: joinResponse?.conference_sid };
|
||||
} catch (error) {
|
||||
useAlert(error?.response?.data?.error || t('CONTACT_PANEL.CALL_FAILED'));
|
||||
if (error?.response?.status === 409) {
|
||||
TwilioVoiceClient.endClientCall();
|
||||
callsStore.dismissCall(callSid);
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to join call:', error);
|
||||
return null;
|
||||
|
||||
@@ -22,29 +22,6 @@ const shouldSkipCall = (callDirection, senderId, currentUserId) => {
|
||||
return callDirection === 'outbound' && senderId !== currentUserId;
|
||||
};
|
||||
|
||||
const extractAssigneeId = conversation => {
|
||||
return conversation?.assignee_id || conversation?.meta?.assignee?.id || null;
|
||||
};
|
||||
|
||||
const isAssignedToAnotherAgent = (assigneeId, currentUserId) => {
|
||||
if (currentUserId == null) return false;
|
||||
return !!assigneeId && assigneeId !== currentUserId;
|
||||
};
|
||||
|
||||
const shouldShowCall = ({
|
||||
callDirection,
|
||||
senderId,
|
||||
assigneeId,
|
||||
currentUserId,
|
||||
}) => {
|
||||
if (shouldSkipCall(callDirection, senderId, currentUserId)) return false;
|
||||
// Outbound calls are scoped to the initiator via shouldSkipCall; the
|
||||
// conversation may be auto-assigned to a different agent on creation, so
|
||||
// skip the assignee filter for outbound to avoid hiding the caller's own widget.
|
||||
if (callDirection === 'outbound') return true;
|
||||
return !isAssignedToAnotherAgent(assigneeId, currentUserId);
|
||||
};
|
||||
|
||||
function extractCallData(message) {
|
||||
const call = message?.call || {};
|
||||
return {
|
||||
@@ -52,7 +29,6 @@ function extractCallData(message) {
|
||||
status: call.status,
|
||||
callDirection: call.direction === 'outgoing' ? 'outbound' : 'inbound',
|
||||
conversationId: message?.conversation_id,
|
||||
assigneeId: extractAssigneeId(message?.conversation),
|
||||
senderId: message?.sender?.id,
|
||||
};
|
||||
}
|
||||
@@ -60,19 +36,10 @@ function extractCallData(message) {
|
||||
export function handleVoiceCallCreated(message, currentUserId) {
|
||||
if (!isVoiceCallMessage(message)) return;
|
||||
|
||||
const { callSid, callDirection, conversationId, assigneeId, senderId } =
|
||||
const { callSid, callDirection, conversationId, senderId } =
|
||||
extractCallData(message);
|
||||
|
||||
if (
|
||||
!shouldShowCall({
|
||||
callDirection,
|
||||
senderId,
|
||||
assigneeId,
|
||||
currentUserId,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (shouldSkipCall(callDirection, senderId, currentUserId)) return;
|
||||
|
||||
const callsStore = useCallsStore();
|
||||
callsStore.addCall({
|
||||
@@ -86,14 +53,8 @@ export function handleVoiceCallCreated(message, currentUserId) {
|
||||
export function handleVoiceCallUpdated(commit, message, currentUserId) {
|
||||
if (!isVoiceCallMessage(message)) return;
|
||||
|
||||
const {
|
||||
callSid,
|
||||
status,
|
||||
callDirection,
|
||||
conversationId,
|
||||
assigneeId,
|
||||
senderId,
|
||||
} = extractCallData(message);
|
||||
const { callSid, status, callDirection, conversationId, senderId } =
|
||||
extractCallData(message);
|
||||
|
||||
const callsStore = useCallsStore();
|
||||
|
||||
@@ -105,19 +66,11 @@ export function handleVoiceCallUpdated(commit, message, currentUserId) {
|
||||
callSid,
|
||||
});
|
||||
|
||||
if (
|
||||
!shouldShowCall({
|
||||
callDirection,
|
||||
senderId,
|
||||
assigneeId,
|
||||
currentUserId,
|
||||
})
|
||||
) {
|
||||
callsStore.removeCall(callSid);
|
||||
return;
|
||||
}
|
||||
const isNewCall =
|
||||
status === 'ringing' &&
|
||||
!shouldSkipCall(callDirection, senderId, currentUserId);
|
||||
|
||||
if (status === 'ringing') {
|
||||
if (isNewCall) {
|
||||
callsStore.addCall({
|
||||
callSid,
|
||||
conversationId,
|
||||
@@ -126,11 +79,3 @@ export function handleVoiceCallUpdated(commit, message, currentUserId) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function syncConversationCallVisibility(conversation, currentUserId) {
|
||||
const assigneeId = extractAssigneeId(conversation);
|
||||
if (!isAssignedToAnotherAgent(assigneeId, currentUserId)) return;
|
||||
|
||||
const callsStore = useCallsStore();
|
||||
callsStore.removeCallsForConversation(conversation.id);
|
||||
}
|
||||
|
||||
@@ -83,8 +83,7 @@
|
||||
"CALL_ENDED": "Call ended",
|
||||
"NOT_ANSWERED_YET": "Not answered yet",
|
||||
"THEY_ANSWERED": "They answered",
|
||||
"YOU_ANSWERED": "You answered",
|
||||
"AGENT_ANSWERED": "{agentName} answered"
|
||||
"YOU_ANSWERED": "You answered"
|
||||
},
|
||||
"HEADER": {
|
||||
"RESOLVE_ACTION": "Resolve",
|
||||
|
||||
@@ -55,19 +55,5 @@ export const useCallsStore = defineStore('calls', {
|
||||
dismissCall(callSid) {
|
||||
this.calls = this.calls.filter(call => call.callSid !== callSid);
|
||||
},
|
||||
|
||||
removeCallsForConversation(conversationId) {
|
||||
const callsToRemove = this.calls.filter(
|
||||
call => call.conversationId === conversationId
|
||||
);
|
||||
|
||||
if (callsToRemove.some(call => call.isActive)) {
|
||||
TwilioVoiceClient.endClientCall();
|
||||
}
|
||||
|
||||
this.calls = this.calls.filter(
|
||||
call => call.conversationId !== conversationId
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user