Skip to content

Commit 6273e60

Browse files
authored
Merge branch 'next' into CC_DIGITAL_CHANNEL
2 parents 598aa3f + ab03c0c commit 6273e60

File tree

12 files changed

+1192
-42
lines changed

12 files changed

+1192
-42
lines changed

packages/contact-center/cc-components/src/components/task/task.types.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ import {
1616

1717
type Enum<T extends Record<string, unknown>> = T[keyof T];
1818

19+
/**
20+
* Target types for consult/transfer operations
21+
*/
22+
export const TARGET_TYPE = {
23+
AGENT: 'agent',
24+
QUEUE: 'queue',
25+
ENTRY_POINT: 'entryPoint',
26+
DIAL_NUMBER: 'dialNumber',
27+
} as const;
28+
29+
export type TargetType = (typeof TARGET_TYPE)[keyof typeof TARGET_TYPE];
30+
1931
/**
2032
* Interface representing the TaskProps of a user.
2133
*/
@@ -414,12 +426,12 @@ export interface ControlProps {
414426
/**
415427
* Function to set the last target type
416428
*/
417-
lastTargetType: 'queue' | 'agent';
429+
lastTargetType: TargetType;
418430

419431
/**
420432
* Function to set the last target type
421433
*/
422-
setLastTargetType: (targetType: 'queue' | 'agent') => void;
434+
setLastTargetType: (targetType: TargetType) => void;
423435

424436
controlVisibility: ControlVisibility;
425437

packages/contact-center/cc-components/tests/components/task/CallControl/call-control.snapshot.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import '@testing-library/jest-dom';
33
import {render, fireEvent, act} from '@testing-library/react';
44
import CallControlComponent from '../../../../src/components/task/CallControl/call-control';
5-
import {CallControlComponentProps} from '../../../../src/components/task/task.types';
5+
import {CallControlComponentProps, TARGET_TYPE} from '../../../../src/components/task/task.types';
66
import {mockTask, mockAgents, mockProfile, mockCC} from '@webex/test-fixtures';
77
import {BuddyDetails, IWrapupCode} from '@webex/cc-store';
88

@@ -99,7 +99,7 @@ describe('CallControlComponent Snapshots', () => {
9999
consultTimerLabel: 'Consulting',
100100
consultTimerTimestamp: 0,
101101
allowConsultToQueue: mockProfile.allowConsultToQueue,
102-
lastTargetType: 'agent',
102+
lastTargetType: TARGET_TYPE.AGENT,
103103
setLastTargetType: jest.fn(),
104104
controlVisibility: {
105105
accept: {isVisible: true, isEnabled: true},

packages/contact-center/cc-components/tests/components/task/CallControl/call-control.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import {render, fireEvent} from '@testing-library/react';
33
import '@testing-library/jest-dom';
44
import CallControlComponent from '../../../../src/components/task/CallControl/call-control';
5-
import {CallControlComponentProps, CallControlMenuType} from '../../../../src/components/task/task.types';
5+
import {CallControlComponentProps, CallControlMenuType, TARGET_TYPE} from '../../../../src/components/task/task.types';
66
import * as callControlUtils from '../../../../src/components/task/CallControl/call-control.utils';
77
import {mockTask} from '@webex/test-fixtures';
88

@@ -121,7 +121,7 @@ describe('CallControlComponent', () => {
121121
consultTimerLabel: 'Consulting',
122122
consultTimerTimestamp: 0,
123123
allowConsultToQueue: true,
124-
lastTargetType: 'agent',
124+
lastTargetType: TARGET_TYPE.AGENT,
125125
setLastTargetType: jest.fn(),
126126
controlVisibility: mockControlVisibility,
127127
logger: mockLogger,

packages/contact-center/cc-components/tests/components/task/CallControlCAD/call-control-cad.snapshot.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import {render} from '@testing-library/react';
33
import CallControlCADComponent from '../../../../src/components/task/CallControlCAD/call-control-cad';
4-
import {CallControlComponentProps} from '../../../../src/components/task/task.types';
4+
import {CallControlComponentProps, TARGET_TYPE} from '../../../../src/components/task/task.types';
55
import {mockTask} from '@webex/test-fixtures';
66
import {BuddyDetails} from '@webex/cc-store';
77
import '@testing-library/jest-dom';
@@ -131,7 +131,7 @@ describe('CallControlCADComponent Snapshots', () => {
131131
consultTimerLabel: 'Consulting',
132132
consultTimerTimestamp: 0,
133133
allowConsultToQueue: true,
134-
lastTargetType: 'agent',
134+
lastTargetType: TARGET_TYPE.AGENT,
135135
setLastTargetType: jest.fn(),
136136
controlVisibility: {
137137
accept: {isVisible: true, isEnabled: true},

packages/contact-center/cc-components/tests/components/task/CallControlCAD/call-control-cad.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import {render} from '@testing-library/react';
33
import CallControlCADComponent from '../../../../src/components/task/CallControlCAD/call-control-cad';
4-
import {CallControlComponentProps} from '../../../../src/components/task/task.types';
4+
import {CallControlComponentProps, TARGET_TYPE} from '../../../../src/components/task/task.types';
55
import {mockTask} from '@webex/test-fixtures';
66
import {BuddyDetails} from '@webex/cc-store';
77
import '@testing-library/jest-dom';
@@ -141,7 +141,7 @@ describe('CallControlCADComponent', () => {
141141
consultTimerLabel: 'Consulting',
142142
consultTimerTimestamp: 0,
143143
allowConsultToQueue: true,
144-
lastTargetType: 'agent',
144+
lastTargetType: TARGET_TYPE.AGENT,
145145
setLastTargetType: jest.fn(),
146146
controlVisibility: mockControlVisibility,
147147
logger: mockLogger,

packages/contact-center/task/src/Utils/task-util.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,14 @@ export function getEndButtonVisibility(
8686
isConsultInitiatedOrAcceptedOrBeingConsulted: boolean,
8787
isConferenceInProgress: boolean,
8888
isConsultCompleted: boolean,
89-
isHeld: boolean
89+
isHeld: boolean,
90+
consultCallHeld: boolean
9091
): Visibility {
9192
const isVisible = isBrowser || (isEndCallEnabled && isCall) || !isCall;
92-
// Disable if: held (except when in conference and consult not completed) OR consult in progress
93+
// Disable if: held (except when in conference and consult not completed) OR consult in progress (unless consult call is held - meaning we're back on main)
9394
const isEnabled =
94-
(!isHeld || (isConferenceInProgress && !isConsultCompleted)) && !isConsultInitiatedOrAcceptedOrBeingConsulted;
95+
(!isHeld || (isConferenceInProgress && !isConsultCompleted)) &&
96+
(!isConsultInitiatedOrAcceptedOrBeingConsulted || consultCallHeld);
9597

9698
return {isVisible, isEnabled};
9799
}
@@ -441,7 +443,8 @@ export function getControlsVisibility(
441443
isConsultInitiatedOrAcceptedOrBeingConsulted,
442444
isConferenceInProgress,
443445
isConsultCompleted,
444-
isHeld
446+
isHeld,
447+
consultCallHeld
445448
),
446449
muteUnmute: getMuteUnmuteButtonVisibility(isBrowser, webRtcEnabled, isCall, isBeingConsulted),
447450
holdResume: getHoldResumeButtonVisibility(

packages/contact-center/task/src/helper.ts

Lines changed: 112 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import {useEffect, useCallback, useState, useMemo} from 'react';
22
import {AddressBookEntriesResponse, AddressBookEntrySearchParams, ITask} from '@webex/contact-center';
3-
import {useCallControlProps, UseTaskListProps, UseTaskProps, useOutdialCallProps} from './task.types';
3+
import {
4+
useCallControlProps,
5+
UseTaskListProps,
6+
UseTaskProps,
7+
useOutdialCallProps,
8+
TargetType,
9+
TARGET_TYPE,
10+
} from './task.types';
411
import store, {
512
TASK_EVENTS,
613
BuddyDetails,
@@ -302,7 +309,7 @@ export const useCallControl = (props: useCallControlProps) => {
302309
// Consult timer labels and timestamps
303310
const [consultTimerLabel, setConsultTimerLabel] = useState<string>(TIMER_LABEL_CONSULTING);
304311
const [consultTimerTimestamp, setConsultTimerTimestamp] = useState<number>(0);
305-
const [lastTargetType, setLastTargetType] = useState<'agent' | 'queue'>('agent');
312+
const [lastTargetType, setLastTargetType] = useState<TargetType>(TARGET_TYPE.AGENT);
306313
const [conferenceParticipants, setConferenceParticipants] = useState<Participant[]>([]);
307314

308315
// Use custom hook for hold timer management
@@ -322,21 +329,111 @@ export const useCallControl = (props: useCallControlProps) => {
322329
const {interaction} = currentTask.data;
323330
const myAgentId = store.cc.agentConfig?.agentId;
324331

325-
// Find all agent participants except the current agent
326-
const otherAgents = Object.values(interaction.participants || {}).filter(
327-
(participant): participant is Participant =>
328-
(participant as Participant).pType === 'Agent' && (participant as Participant).id !== myAgentId
329-
);
332+
// For Entry Point or Dial Number consults, check if destination agent has joined
333+
if (lastTargetType === TARGET_TYPE.ENTRY_POINT || lastTargetType === TARGET_TYPE.DIAL_NUMBER) {
334+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
335+
const consultDestinationAgentName = (interaction as any).callProcessingDetails?.consultDestinationAgentName;
336+
337+
if (consultDestinationAgentName) {
338+
// Destination agent has joined, show their name
339+
setConsultAgentName(consultDestinationAgentName);
340+
logger.info(`${lastTargetType} consult answered - showing agent name: ${consultDestinationAgentName}`, {
341+
module: 'widget-cc-task#helper.ts',
342+
method: 'useCallControl#extractConsultingAgent',
343+
});
344+
} else {
345+
// Still ringing - find the EP/DN participant in the consult media
346+
const consultMediaResourceId = findMediaResourceId(currentTask, 'consult');
347+
348+
if (consultMediaResourceId && interaction.media?.[consultMediaResourceId]) {
349+
const consultMedia = interaction.media[consultMediaResourceId];
350+
// Find the participant in consult media who is not the current agent
351+
const consultParticipantId = consultMedia.participants?.find(
352+
(participantId: string) => participantId !== myAgentId
353+
);
354+
355+
if (consultParticipantId && interaction.participants[consultParticipantId]) {
356+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
357+
const participant = interaction.participants[consultParticipantId] as any;
358+
const phoneNumber = participant.dn || participant.id;
359+
360+
if (phoneNumber && phoneNumber !== consultAgentName) {
361+
setConsultAgentName(phoneNumber);
362+
logger.info(`${lastTargetType} consult ringing - showing phone number: ${phoneNumber}`, {
363+
module: 'widget-cc-task#helper.ts',
364+
method: 'useCallControl#extractConsultingAgent',
365+
});
366+
}
367+
}
368+
}
369+
}
370+
return;
371+
}
330372

331-
// Pick the first other agent (should only be one in a consult)
332-
const foundAgent = otherAgents.length > 0 ? {id: otherAgents[0].id, name: otherAgents[0].name} : null;
373+
// For regular agent consults, find the agent in the consult media
374+
const consultMediaResourceId = findMediaResourceId(currentTask, 'consult');
333375

334-
if (foundAgent) {
335-
setConsultAgentName(foundAgent.name);
336-
logger.info(`Consulting agent detected: ${foundAgent.name} ${foundAgent.id}`, {
337-
module: 'widget-cc-task#helper.ts',
338-
method: 'useCallControl#extractConsultingAgent',
376+
if (consultMediaResourceId && interaction.media?.[consultMediaResourceId]) {
377+
const consultMedia = interaction.media[consultMediaResourceId];
378+
// Find the agent participant in consult media who is not the current agent
379+
const consultParticipantId = consultMedia.participants?.find((participantId: string) => {
380+
const participant = interaction.participants[participantId];
381+
return participant && participant.id !== myAgentId && participant.pType === 'Agent';
339382
});
383+
384+
if (consultParticipantId && interaction.participants[consultParticipantId]) {
385+
const consultAgent = interaction.participants[consultParticipantId];
386+
setConsultAgentName(consultAgent.name || consultAgent.id);
387+
logger.info(`Consulting agent detected: ${consultAgent.name} ${consultAgent.id}`, {
388+
module: 'widget-cc-task#helper.ts',
389+
method: 'useCallControl#extractConsultingAgent',
390+
});
391+
}
392+
} else {
393+
// Fallback: Use old logic if consult media not found
394+
const otherAgents = Object.values(interaction.participants || {}).filter(
395+
(participant): participant is Participant =>
396+
(participant as Participant).pType === 'Agent' && (participant as Participant).id !== myAgentId
397+
);
398+
399+
// In a conference with multiple agents, find the agent currently being consulted
400+
// Priority: 1) consultState="consulting" 2) most recent consultTimestamp
401+
let foundAgent: {id: string; name: string} | null = null;
402+
403+
if (otherAgents.length > 0) {
404+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
405+
const consultingAgent = otherAgents.find((agent: any) => agent.consultState === 'consulting');
406+
407+
if (consultingAgent) {
408+
foundAgent = {
409+
id: consultingAgent.id,
410+
name: consultingAgent.name,
411+
};
412+
} else {
413+
// Fallback: Find agent with most recent consultTimestamp
414+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
415+
const agentWithMostRecentTimestamp = otherAgents.reduce((latest: any, current: any) => {
416+
const currentTimestamp = current.consultTimestamp || current.joinTimestamp || 0;
417+
const latestTimestamp = latest ? latest.consultTimestamp || latest.joinTimestamp || 0 : 0;
418+
return currentTimestamp >= latestTimestamp ? current : latest;
419+
}, null);
420+
421+
if (agentWithMostRecentTimestamp) {
422+
foundAgent = {
423+
id: agentWithMostRecentTimestamp.id,
424+
name: agentWithMostRecentTimestamp.name,
425+
};
426+
}
427+
}
428+
}
429+
430+
if (foundAgent) {
431+
setConsultAgentName(foundAgent.name);
432+
logger.info(`Consulting agent detected (fallback): ${foundAgent.name} ${foundAgent.id}`, {
433+
module: 'widget-cc-task#helper.ts',
434+
method: 'useCallControl#extractConsultingAgent',
435+
});
436+
}
340437
}
341438
} catch (error) {
342439
console.log('error', error);
@@ -345,7 +442,7 @@ export const useCallControl = (props: useCallControlProps) => {
345442
method: 'extractConsultingAgent',
346443
});
347444
}
348-
}, [currentTask, logger]);
445+
}, [currentTask, logger, lastTargetType, consultAgentName, setConsultAgentName]);
349446

350447
// Extract main call timestamp whenever currentTask changes
351448
useEffect(() => {

packages/contact-center/task/src/task.types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,15 @@ export interface DeviceTypeFlags {
4848
isAgentDN: boolean;
4949
isExtension: boolean;
5050
}
51+
52+
/**
53+
* Target types for consult/transfer operations
54+
*/
55+
export const TARGET_TYPE = {
56+
AGENT: 'agent',
57+
QUEUE: 'queue',
58+
ENTRY_POINT: 'entryPoint',
59+
DIAL_NUMBER: 'dialNumber',
60+
} as const;
61+
62+
export type TargetType = (typeof TARGET_TYPE)[keyof typeof TARGET_TYPE];

packages/contact-center/task/tests/CallControl/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as helper from '../../src/helper';
44
import {CallControl} from '../../src';
55
import store from '@webex/cc-store';
66
import {mockTask} from '@webex/test-fixtures';
7+
import {TARGET_TYPE} from '../../src/task.types';
78
import '@testing-library/jest-dom';
89

910
const onHoldResumeCb = jest.fn();
@@ -46,7 +47,7 @@ describe('CallControl Component', () => {
4647
setConsultAgentName: jest.fn(),
4748
holdTime: 0,
4849
startTimestamp: 0,
49-
lastTargetType: 'agent' as const,
50+
lastTargetType: TARGET_TYPE.AGENT,
5051
setLastTargetType: jest.fn(),
5152
controlVisibility: {
5253
accept: defaultVisibility,

packages/contact-center/task/tests/CallControlCAD/index.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as helper from '../../src/helper';
44
import {CallControlCAD} from '../../src';
55
import store from '@webex/cc-store';
66
import {mockTask} from '@webex/test-fixtures';
7+
import {TARGET_TYPE} from '../../src/task.types';
78
import '@testing-library/jest-dom';
89

910
const onHoldResumeCb = jest.fn();
@@ -42,7 +43,7 @@ describe('CallControlCAD Component', () => {
4243
setConsultAgentName: jest.fn(),
4344
holdTime: 0,
4445
startTimestamp: 0,
45-
lastTargetType: 'agent' as const,
46+
lastTargetType: TARGET_TYPE.AGENT,
4647
setLastTargetType: jest.fn(),
4748
controlVisibility: {
4849
accept: {isVisible: false, isEnabled: false},
@@ -139,7 +140,7 @@ describe('CallControlCAD Component', () => {
139140
setConsultAgentName: jest.fn(),
140141
holdTime: 0,
141142
startTimestamp: 0,
142-
lastTargetType: 'agent' as const,
143+
lastTargetType: TARGET_TYPE.AGENT,
143144
setLastTargetType: jest.fn(),
144145
controlVisibility: {
145146
accept: {isVisible: false, isEnabled: false},
@@ -217,7 +218,7 @@ describe('CallControlCAD Component', () => {
217218
setConsultAgentName: jest.fn(),
218219
holdTime: 0,
219220
startTimestamp: 0,
220-
lastTargetType: 'agent' as const,
221+
lastTargetType: TARGET_TYPE.AGENT,
221222
setLastTargetType: jest.fn(),
222223
controlVisibility: {
223224
accept: {isVisible: false, isEnabled: false},
@@ -297,7 +298,7 @@ describe('CallControlCAD Component', () => {
297298
setConsultAgentName: jest.fn(),
298299
holdTime: 0,
299300
startTimestamp: 0,
300-
lastTargetType: 'agent' as const,
301+
lastTargetType: TARGET_TYPE.AGENT,
301302
setLastTargetType: jest.fn(),
302303
controlVisibility: {
303304
accept: {isVisible: false, isEnabled: false},

0 commit comments

Comments
 (0)