Skip to content

Commit d1240d7

Browse files
authored
fix(spillover-of-outdial): fix-pending-oudial-items (webex#563)
1 parent 8bb5fbb commit d1240d7

21 files changed

Lines changed: 1818 additions & 466 deletions

File tree

packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import {withMetrics} from '@webex/cc-ui-logging';
55
import {extractIncomingTaskData} from './incoming-task.utils';
66

77
const IncomingTaskComponent: React.FunctionComponent<IncomingTaskComponentProps> = (props) => {
8-
const {incomingTask, isBrowser, accept, reject, logger} = props;
8+
const {incomingTask, isBrowser, accept, reject, logger, isDeclineButtonEnabled} = props;
99
if (!incomingTask) {
1010
return <></>; // hidden component
1111
}
1212

1313
// Extract all task data using the utility function
14-
const taskData = extractIncomingTaskData(incomingTask, isBrowser, logger);
14+
const taskData = extractIncomingTaskData(incomingTask, isBrowser, logger, isDeclineButtonEnabled);
1515

1616
return (
1717
<Task
@@ -30,6 +30,7 @@ const IncomingTaskComponent: React.FunctionComponent<IncomingTaskComponentProps>
3030
ronaTimeout={taskData.ronaTimeout}
3131
acceptText={taskData.acceptText}
3232
disableAccept={taskData.disableAccept}
33+
disableDecline={taskData.disableDecline}
3334
declineText={taskData.declineText}
3435
styles="task-list-hover"
3536
mediaType={taskData.mediaType as MEDIA_CHANNEL}

packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.utils.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface IncomingTaskData {
1515
declineText: string | undefined;
1616
title: string;
1717
disableAccept: boolean;
18+
disableDecline: boolean;
1819
}
1920

2021
/**
@@ -23,7 +24,12 @@ export interface IncomingTaskData {
2324
* @param isBrowser - Whether the device type is browser
2425
* @returns Processed task data with computed values
2526
*/
26-
export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean, logger?): IncomingTaskData => {
27+
export const extractIncomingTaskData = (
28+
incomingTask: ITask,
29+
isBrowser: boolean,
30+
logger?,
31+
isDeclineButtonEnabled?: boolean
32+
): IncomingTaskData => {
2733
try {
2834
// Extract basic data from task
2935
//@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762
@@ -52,8 +58,12 @@ export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean,
5258
// Compute title based on media type
5359
const title = isSocial ? customerName : ani;
5460

61+
// Compute disable state for accept button when auto-answering
62+
const isAutoAnswering = incomingTask.data.isAutoAnswering || false;
5563
// Compute disable state for accept button
56-
const disableAccept = isTelephony && !isBrowser;
64+
const disableAccept = (isTelephony && !isBrowser) || isAutoAnswering;
65+
66+
const disableDecline = (isTelephony && !isBrowser) || (isAutoAnswering && !isDeclineButtonEnabled);
5767

5868
return {
5969
ani,
@@ -69,6 +79,7 @@ export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean,
6979
declineText,
7080
title,
7181
disableAccept,
82+
disableDecline,
7283
};
7384
} catch (error) {
7485
logger?.error('CC-Widgets: IncomingTask: Error in extractIncomingTaskData', {
@@ -91,6 +102,7 @@ export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean,
91102
declineText: undefined,
92103
title: '',
93104
disableAccept: false,
105+
disableDecline: false,
94106
};
95107
}
96108
};

packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.style.scss

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,32 @@
1313
.outdial-input {
1414
width: 100%;
1515
}
16+
17+
.outdial-ani-select-container {
18+
position: relative;
19+
width: 100%;
20+
}
21+
22+
.outDialCallButton {
23+
margin-top: 1.5rem;
24+
}
25+
26+
.outdial-select-arrow-icon {
27+
position: absolute;
28+
right: 0;
29+
top: 83%;
30+
transform: translateY(-50%);
31+
display: flex;
32+
align-items: center;
33+
pointer-events: none;
34+
z-index: 1;
35+
}
36+
37+
.outdial-ani-option-name {
38+
display: flex;
39+
align-items: center;
40+
gap: 0.25rem;
41+
}
1642

1743
.keys {
1844
display: grid;

packages/contact-center/cc-components/src/components/task/OutdialCall/outdial-call.tsx

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import React, {useEffect, useMemo, useState} from 'react';
22
import {OutdialAniEntry, OutdialCallComponentProps} from '../task.types';
33
import './outdial-call.style.scss';
44
import {withMetrics} from '@webex/cc-ui-logging';
5-
import {Input, Button, Option, Select} from '@momentum-design/components/dist/react';
5+
import {Input, Button, Icon} from '@momentum-design/components/dist/react';
6+
// Migrate from @momentum-ui/react-collaboration to @momentum-design/components
7+
// Currently using SelectNext for controlled selection behavior with proper onSelectionChange and onOpenChange support
8+
// bug ticket: https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6990
9+
import {SelectNext} from '@momentum-ui/react-collaboration';
10+
import {Item} from '@react-stately/collections';
611
import {OutdialStrings, KEY_LIST} from './constants';
712

813
/**
@@ -14,15 +19,17 @@ import {OutdialStrings, KEY_LIST} from './constants';
1419
*
1520
* @param props - Properties for the OutdialCallComponent.
1621
* @property startOutdial - Function to initiate the outdial call with the entered destination number.
22+
* @property isTelephonyTaskActive - Boolean indicating if there's an active telephony task.
1723
*/
1824
const OutdialCallComponent: React.FunctionComponent<OutdialCallComponentProps> = (props) => {
19-
const {logger, startOutdial, getOutdialANIEntries} = props;
25+
const {logger, startOutdial, getOutdialANIEntries, isTelephonyTaskActive} = props;
2026

2127
// State Hooks
2228
const [destination, setDestination] = useState('');
2329
const [isValidNumber, setIsValidNumber] = useState('');
24-
const [selectedANI, setSelectedANI] = useState(undefined);
30+
const [selectedANI, setSelectedANI] = useState<string | undefined>(undefined);
2531
const [outdialANIList, setOutdialANIList] = useState<OutdialAniEntry[]>([]);
32+
const [isSelectOpen, setIsSelectOpen] = useState(false);
2633

2734
// Validate the input format using regex from agent desktop
2835
const regExForDnSpecialChars = useMemo(
@@ -82,7 +89,7 @@ const OutdialCallComponent: React.FunctionComponent<OutdialCallComponentProps> =
8289
helpTextType={isValidNumber ? 'error' : 'default'}
8390
placeholder={OutdialStrings.DN_PLACEHOLDER}
8491
value={destination}
85-
onChange={(e: unknown) => {
92+
onInput={(e: unknown) => {
8693
const inputValue = (e as React.ChangeEvent<HTMLInputElement>).target.value;
8794
setDestination(inputValue);
8895
validateOutboundNumber(inputValue);
@@ -97,36 +104,53 @@ const OutdialCallComponent: React.FunctionComponent<OutdialCallComponentProps> =
97104
</li>
98105
))}
99106
</ul>
100-
<Select
101-
className="outdial-input"
102-
label={OutdialStrings.ANI_SELECT_LABEL}
103-
id="outdial-ani-option-select"
104-
name="outdial-ani-option-select"
105-
data-testid="outdial-ani-option-select"
106-
placeholder={OutdialStrings.ANI_SELECT_PLACEHOLDER}
107-
onChange={(event: CustomEvent) => {
108-
setSelectedANI(event.detail.value);
109-
}}
110-
>
111-
{outdialANIList.map((option: OutdialAniEntry, index: number) => {
112-
return (
113-
<Option
114-
selected={option.number === selectedANI}
115-
key={index}
116-
value={option.number}
117-
name={`outdial-ani-option-${index}`}
118-
data-testid={`outdial-ani-option-${index}`}
119-
>
120-
{option.name}
121-
</Option>
122-
);
123-
})}
124-
</Select>
107+
<div className="outdial-ani-select-container">
108+
<Icon
109+
className="outdial-select-arrow-icon"
110+
name={isSelectOpen ? 'arrow-up-bold' : 'arrow-down-bold'}
111+
title=""
112+
data-testid="select-arrow-icon"
113+
/>
114+
115+
<SelectNext
116+
className="outdial-input"
117+
label={OutdialStrings.ANI_SELECT_LABEL}
118+
id="outdial-ani-option-select"
119+
data-testid="outdial-ani-option-select"
120+
placeholder={OutdialStrings.ANI_SELECT_PLACEHOLDER}
121+
selectedKey={selectedANI || null}
122+
onSelectionChange={(key: React.Key) => {
123+
const value = key as string;
124+
// Set to undefined if key is 'none' or null
125+
const newANI = !value || value === 'none' ? undefined : value;
126+
setSelectedANI(newANI);
127+
}}
128+
onOpenChange={(isOpen: boolean) => setIsSelectOpen(isOpen)}
129+
items={[
130+
{id: 'none', name: OutdialStrings.ANI_SELECT_PLACEHOLDER},
131+
...outdialANIList.map((ani) => ({id: ani.number, name: ani.name})),
132+
]}
133+
direction="bottom"
134+
showBorder
135+
>
136+
{(item: {id: string; name: string}) => (
137+
<Item key={item.id} textValue={item.name} data-testid={`outdial-ani-option-${item.id}`}>
138+
<div className="outdial-ani-option-name">{item.name}</div>
139+
</Item>
140+
)}
141+
</SelectNext>
142+
</div>
125143
<Button
126144
data-testid="outdial-call-button"
127145
prefixIcon={'handset-regular'}
128-
onClick={() => startOutdial(destination, selectedANI)}
129-
disabled={!!isValidNumber || !destination}
146+
className="outDialCallButton"
147+
onClick={() => {
148+
startOutdial(destination, selectedANI);
149+
// Clear input field after initiating the call
150+
setDestination('');
151+
setIsValidNumber('');
152+
}}
153+
disabled={!!isValidNumber || !destination || !!isTelephonyTaskActive}
130154
size={40}
131155
/>
132156
</article>

packages/contact-center/cc-components/src/components/task/Task/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface TaskProps {
2222
acceptText?: string;
2323
declineText?: string;
2424
disableAccept?: boolean;
25+
disableDecline?: boolean;
2526
styles?: string;
2627
mediaType?: MediaChannelType;
2728
mediaChannel?: MediaChannelType;
@@ -42,6 +43,7 @@ const Task: React.FC<TaskProps> = ({
4243
onTaskSelect,
4344
acceptText,
4445
disableAccept = false,
46+
disableDecline = false,
4547
declineText,
4648
mediaType,
4749
mediaChannel,
@@ -173,7 +175,12 @@ const Task: React.FC<TaskProps> = ({
173175
</ButtonPill>
174176
) : null}
175177
{declineText ? (
176-
<ButtonPill onPress={declineTask} color="cancel" data-testid="task:decline-button">
178+
<ButtonPill
179+
onPress={declineTask}
180+
color="cancel"
181+
disabled={disableDecline}
182+
data-testid="task:decline-button"
183+
>
177184
{declineText}
178185
</ButtonPill>
179186
) : null}

packages/contact-center/cc-components/src/components/task/TaskList/task-list.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const TaskListComponent: React.FunctionComponent<TaskListComponentProps> = (prop
4848
onTaskSelect={createTaskSelectHandler(task, currentTask, onTaskSelect, agentId)}
4949
acceptText={taskData.acceptText}
5050
disableAccept={taskData.disableAccept}
51+
disableDecline={taskData.disableDecline}
5152
declineText={taskData.declineText}
5253
mediaType={taskData.mediaType as MEDIA_CHANNEL}
5354
mediaChannel={taskData.mediaChannel as MEDIA_CHANNEL}

packages/contact-center/cc-components/src/components/task/TaskList/task-list.utils.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {MEDIA_CHANNEL, TaskListItemData} from '../task.types';
2-
import {ILogger, ITask} from '@webex/cc-store';
3-
import {isIncomingTask} from '@webex/cc-store';
2+
import store, {isIncomingTask, ILogger, ITask} from '@webex/cc-store';
43
/**
54
* Extracts and processes data from a task for rendering in the task list
65
* @param task - The task object
@@ -42,8 +41,13 @@ export const extractTaskListItemData = (
4241
// Compute title based on media type
4342
const title = isSocial ? customerName : ani;
4443

44+
const isAutoAnswering = task.data.isAutoAnswering || false;
45+
4546
// Compute disable state for accept button
46-
const disableAccept = isTaskIncoming && isTelephony && !isBrowser;
47+
const disableAccept = (isTaskIncoming && isTelephony && !isBrowser) || isAutoAnswering;
48+
49+
const disableDecline =
50+
(isTaskIncoming && isTelephony && !isBrowser) || (isAutoAnswering && !store.isDeclineButtonEnabled);
4751

4852
const ronaTimeout = isTaskIncoming ? rawRonaTimeout : null;
4953

@@ -66,6 +70,7 @@ export const extractTaskListItemData = (
6670
declineText,
6771
title,
6872
disableAccept,
73+
disableDecline,
6974
displayState,
7075
};
7176
} catch (error) {
@@ -91,6 +96,7 @@ export const extractTaskListItemData = (
9196
declineText: undefined,
9297
title: '',
9398
disableAccept: false,
99+
disableDecline: false,
94100
displayState: '',
95101
};
96102
}

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,14 @@ export interface TaskProps {
124124
* Agent ID of the logged-in user
125125
*/
126126
agentId: string;
127+
/**
128+
* Flag to enable decline button on incoming task component
129+
*/
130+
isDeclineButtonEnabled?: boolean;
127131
}
128132

129133
export type IncomingTaskComponentProps = Pick<TaskProps, 'isBrowser' | 'accept' | 'reject' | 'logger'> &
130-
Partial<Pick<TaskProps, 'incomingTask'>>;
134+
Partial<Pick<TaskProps, 'incomingTask' | 'isDeclineButtonEnabled'>>;
131135

132136
export type TaskListComponentProps = Pick<
133137
TaskProps,
@@ -516,9 +520,18 @@ export interface OutdialCallProps {
516520
* Logger instance for logging purpose.
517521
*/
518522
logger: ILogger;
523+
524+
/**
525+
* Boolean indicating if there's an active telephony task.
526+
* Used to disable the outdial button when a telephony task is in progress.
527+
*/
528+
isTelephonyTaskActive?: boolean;
519529
}
520530

521-
export type OutdialCallComponentProps = Pick<OutdialCallProps, 'logger' | 'startOutdial' | 'getOutdialANIEntries'>;
531+
export type OutdialCallComponentProps = Pick<
532+
OutdialCallProps,
533+
'logger' | 'startOutdial' | 'getOutdialANIEntries' | 'isTelephonyTaskActive'
534+
>;
522535

523536
/**
524537
* Interface representing the properties for CallControlListItem component.
@@ -697,6 +710,7 @@ export interface TaskListItemData {
697710
declineText: string | undefined;
698711
title: string;
699712
disableAccept: boolean;
713+
disableDecline: boolean;
700714
displayState: string;
701715
}
702716

packages/contact-center/cc-components/tests/components/task/IncomingTask/incoming-task.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,16 @@ describe('IncomingTaskComponent', () => {
216216

217217
// Verify utility function was called correctly for each task
218218
expect(extractIncomingTaskDataSpy).toHaveBeenCalledTimes(4);
219-
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(1, telephonyWebRTCTask, true, loggerMock);
220-
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(2, telephonyExtensionTask, false, loggerMock);
221-
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(3, chatTask, true, loggerMock);
222-
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(4, socialTask, true, loggerMock);
219+
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(1, telephonyWebRTCTask, true, loggerMock, undefined);
220+
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(
221+
2,
222+
telephonyExtensionTask,
223+
false,
224+
loggerMock,
225+
undefined
226+
);
227+
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(3, chatTask, true, loggerMock, undefined);
228+
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(4, socialTask, true, loggerMock, undefined);
223229

224230
// === WebRTC Telephony Task Assertions ===
225231
const webRTCListItem = webRTCContainer.querySelector('li.task-list-item');

0 commit comments

Comments
 (0)