Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import {withMetrics} from '@webex/cc-ui-logging';
import {extractIncomingTaskData} from './incoming-task.utils';

const IncomingTaskComponent: React.FunctionComponent<IncomingTaskComponentProps> = (props) => {
const {incomingTask, isBrowser, accept, reject, logger} = props;
const {incomingTask, isBrowser, accept, reject, logger, isDeclineButtonEnabled} = props;
if (!incomingTask) {
return <></>; // hidden component
}

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

return (
<Task
Expand All @@ -30,6 +30,7 @@ const IncomingTaskComponent: React.FunctionComponent<IncomingTaskComponentProps>
ronaTimeout={taskData.ronaTimeout}
acceptText={taskData.acceptText}
disableAccept={taskData.disableAccept}
disableDecline={taskData.disableDecline}
declineText={taskData.declineText}
styles="task-list-hover"
mediaType={taskData.mediaType as MEDIA_CHANNEL}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface IncomingTaskData {
declineText: string | undefined;
title: string;
disableAccept: boolean;
disableDecline: boolean;
}

/**
Expand All @@ -23,7 +24,12 @@ export interface IncomingTaskData {
* @param isBrowser - Whether the device type is browser
* @returns Processed task data with computed values
*/
export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean, logger?): IncomingTaskData => {
export const extractIncomingTaskData = (
incomingTask: ITask,
isBrowser: boolean,
logger?,
isDeclineButtonEnabled?: boolean
): IncomingTaskData => {
try {
// Extract basic data from task
//@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762
Expand Down Expand Up @@ -52,8 +58,12 @@ export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean,
// Compute title based on media type
const title = isSocial ? customerName : ani;

// Compute disable state for accept button when auto-answering
const isAutoAnswering = incomingTask.data.isAutoAnswering || false;
// Compute disable state for accept button
const disableAccept = isTelephony && !isBrowser;
const disableAccept = (isTelephony && !isBrowser) || isAutoAnswering;

const disableDecline = (isTelephony && !isBrowser) || (isAutoAnswering && !isDeclineButtonEnabled);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did u test with digital task? I feel it will show. just cross check

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I have tested the digital task scenarios. When we receive a digital task, only the Accept button is displayed. For digital tasks, we do not show the Decline button, so the behavior is correct.


return {
ani,
Expand All @@ -69,6 +79,7 @@ export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean,
declineText,
title,
disableAccept,
disableDecline,
};
} catch (error) {
logger?.error('CC-Widgets: IncomingTask: Error in extractIncomingTaskData', {
Expand All @@ -91,6 +102,7 @@ export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean,
declineText: undefined,
title: '',
disableAccept: false,
disableDecline: false,
};
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,32 @@
.outdial-input {
width: 100%;
}

.outdial-ani-select-container {
position: relative;
width: 100%;
}

.outDialCallButton {
margin-top: 1.5rem;
}

.outdial-select-arrow-icon {
position: absolute;
right: 0;
top: 83%;
transform: translateY(-50%);
display: flex;
align-items: center;
pointer-events: none;
z-index: 1;
}

.outdial-ani-option-name {
display: flex;
align-items: center;
gap: 0.25rem;
}

.keys {
display: grid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import React, {useEffect, useMemo, useState} from 'react';
import {OutdialAniEntry, OutdialCallComponentProps} from '../task.types';
import './outdial-call.style.scss';
import {withMetrics} from '@webex/cc-ui-logging';
import {Input, Button, Option, Select} from '@momentum-design/components/dist/react';
import {Input, Button, Icon} from '@momentum-design/components/dist/react';
// Migrate from @momentum-ui/react-collaboration to @momentum-design/components
// Currently using SelectNext for controlled selection behavior with proper onSelectionChange and onOpenChange support
// bug ticket: https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6990
import {SelectNext} from '@momentum-ui/react-collaboration';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6990
Lets add this as a todo. Please mention the details about this in the above ticket as well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import {Item} from '@react-stately/collections';
import {OutdialStrings, KEY_LIST} from './constants';

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

// State Hooks
const [destination, setDestination] = useState('');
const [isValidNumber, setIsValidNumber] = useState('');
const [selectedANI, setSelectedANI] = useState(undefined);
const [selectedANI, setSelectedANI] = useState<string | undefined>(undefined);
const [outdialANIList, setOutdialANIList] = useState<OutdialAniEntry[]>([]);
const [isSelectOpen, setIsSelectOpen] = useState(false);

// Validate the input format using regex from agent desktop
const regExForDnSpecialChars = useMemo(
Expand Down Expand Up @@ -82,7 +89,7 @@ const OutdialCallComponent: React.FunctionComponent<OutdialCallComponentProps> =
helpTextType={isValidNumber ? 'error' : 'default'}
placeholder={OutdialStrings.DN_PLACEHOLDER}
value={destination}
onChange={(e: unknown) => {
onInput={(e: unknown) => {
const inputValue = (e as React.ChangeEvent<HTMLInputElement>).target.value;
setDestination(inputValue);
validateOutboundNumber(inputValue);
Expand All @@ -97,36 +104,53 @@ const OutdialCallComponent: React.FunctionComponent<OutdialCallComponentProps> =
</li>
))}
</ul>
<Select
className="outdial-input"
label={OutdialStrings.ANI_SELECT_LABEL}
id="outdial-ani-option-select"
name="outdial-ani-option-select"
data-testid="outdial-ani-option-select"
placeholder={OutdialStrings.ANI_SELECT_PLACEHOLDER}
onChange={(event: CustomEvent) => {
setSelectedANI(event.detail.value);
}}
>
{outdialANIList.map((option: OutdialAniEntry, index: number) => {
return (
<Option
selected={option.number === selectedANI}
key={index}
value={option.number}
name={`outdial-ani-option-${index}`}
data-testid={`outdial-ani-option-${index}`}
>
{option.name}
</Option>
);
})}
</Select>
<div className="outdial-ani-select-container">
<Icon
className="outdial-select-arrow-icon"
name={isSelectOpen ? 'arrow-up-bold' : 'arrow-down-bold'}
title=""
data-testid="select-arrow-icon"
/>

<SelectNext
className="outdial-input"
label={OutdialStrings.ANI_SELECT_LABEL}
id="outdial-ani-option-select"
data-testid="outdial-ani-option-select"
placeholder={OutdialStrings.ANI_SELECT_PLACEHOLDER}
selectedKey={selectedANI || null}
onSelectionChange={(key: React.Key) => {
const value = key as string;
// Set to undefined if key is 'none' or null
const newANI = !value || value === 'none' ? undefined : value;
setSelectedANI(newANI);
}}
onOpenChange={(isOpen: boolean) => setIsSelectOpen(isOpen)}
items={[
{id: 'none', name: OutdialStrings.ANI_SELECT_PLACEHOLDER},
...outdialANIList.map((ani) => ({id: ani.number, name: ani.name})),
]}
direction="bottom"
showBorder
>
{(item: {id: string; name: string}) => (
<Item key={item.id} textValue={item.name} data-testid={`outdial-ani-option-${item.id}`}>
<div className="outdial-ani-option-name">{item.name}</div>
</Item>
)}
</SelectNext>
</div>
<Button
data-testid="outdial-call-button"
prefixIcon={'handset-regular'}
onClick={() => startOutdial(destination, selectedANI)}
disabled={!!isValidNumber || !destination}
className="outDialCallButton"
onClick={() => {
startOutdial(destination, selectedANI);
// Clear input field after initiating the call
setDestination('');
setIsValidNumber('');
}}
disabled={!!isValidNumber || !destination || !!isTelephonyTaskActive}
size={40}
/>
</article>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface TaskProps {
acceptText?: string;
declineText?: string;
disableAccept?: boolean;
disableDecline?: boolean;
styles?: string;
mediaType?: MediaChannelType;
mediaChannel?: MediaChannelType;
Expand All @@ -42,6 +43,7 @@ const Task: React.FC<TaskProps> = ({
onTaskSelect,
acceptText,
disableAccept = false,
disableDecline = false,
declineText,
mediaType,
mediaChannel,
Expand Down Expand Up @@ -173,7 +175,12 @@ const Task: React.FC<TaskProps> = ({
</ButtonPill>
) : null}
{declineText ? (
<ButtonPill onPress={declineTask} color="cancel" data-testid="task:decline-button">
<ButtonPill
onPress={declineTask}
color="cancel"
disabled={disableDecline}
data-testid="task:decline-button"
>
{declineText}
</ButtonPill>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const TaskListComponent: React.FunctionComponent<TaskListComponentProps> = (prop
onTaskSelect={createTaskSelectHandler(task, currentTask, onTaskSelect, agentId)}
acceptText={taskData.acceptText}
disableAccept={taskData.disableAccept}
disableDecline={taskData.disableDecline}
declineText={taskData.declineText}
mediaType={taskData.mediaType as MEDIA_CHANNEL}
mediaChannel={taskData.mediaChannel as MEDIA_CHANNEL}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {MEDIA_CHANNEL, TaskListItemData} from '../task.types';
import {ILogger, ITask} from '@webex/cc-store';
import {isIncomingTask} from '@webex/cc-store';
import store, {isIncomingTask, ILogger, ITask} from '@webex/cc-store';
/**
* Extracts and processes data from a task for rendering in the task list
* @param task - The task object
Expand Down Expand Up @@ -42,8 +41,13 @@ export const extractTaskListItemData = (
// Compute title based on media type
const title = isSocial ? customerName : ani;

const isAutoAnswering = task.data.isAutoAnswering || false;

// Compute disable state for accept button
const disableAccept = isTaskIncoming && isTelephony && !isBrowser;
const disableAccept = (isTaskIncoming && isTelephony && !isBrowser) || isAutoAnswering;

const disableDecline =
(isTaskIncoming && isTelephony && !isBrowser) || (isAutoAnswering && !store.isDeclineButtonEnabled);

const ronaTimeout = isTaskIncoming ? rawRonaTimeout : null;

Expand All @@ -66,6 +70,7 @@ export const extractTaskListItemData = (
declineText,
title,
disableAccept,
disableDecline,
displayState,
};
} catch (error) {
Expand All @@ -91,6 +96,7 @@ export const extractTaskListItemData = (
declineText: undefined,
title: '',
disableAccept: false,
disableDecline: false,
displayState: '',
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,14 @@ export interface TaskProps {
* Agent ID of the logged-in user
*/
agentId: string;
/**
* Flag to enable decline button on incoming task component
*/
isDeclineButtonEnabled?: boolean;
}

export type IncomingTaskComponentProps = Pick<TaskProps, 'isBrowser' | 'accept' | 'reject' | 'logger'> &
Partial<Pick<TaskProps, 'incomingTask'>>;
Partial<Pick<TaskProps, 'incomingTask' | 'isDeclineButtonEnabled'>>;

export type TaskListComponentProps = Pick<
TaskProps,
Expand Down Expand Up @@ -516,9 +520,18 @@ export interface OutdialCallProps {
* Logger instance for logging purpose.
*/
logger: ILogger;

/**
* Boolean indicating if there's an active telephony task.
* Used to disable the outdial button when a telephony task is in progress.
*/
isTelephonyTaskActive?: boolean;
}

export type OutdialCallComponentProps = Pick<OutdialCallProps, 'logger' | 'startOutdial' | 'getOutdialANIEntries'>;
export type OutdialCallComponentProps = Pick<
OutdialCallProps,
'logger' | 'startOutdial' | 'getOutdialANIEntries' | 'isTelephonyTaskActive'
>;

/**
* Interface representing the properties for CallControlListItem component.
Expand Down Expand Up @@ -697,6 +710,7 @@ export interface TaskListItemData {
declineText: string | undefined;
title: string;
disableAccept: boolean;
disableDecline: boolean;
displayState: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,16 @@ describe('IncomingTaskComponent', () => {

// Verify utility function was called correctly for each task
expect(extractIncomingTaskDataSpy).toHaveBeenCalledTimes(4);
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(1, telephonyWebRTCTask, true, loggerMock);
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(2, telephonyExtensionTask, false, loggerMock);
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(3, chatTask, true, loggerMock);
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(4, socialTask, true, loggerMock);
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(1, telephonyWebRTCTask, true, loggerMock, undefined);
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(
2,
telephonyExtensionTask,
false,
loggerMock,
undefined
);
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(3, chatTask, true, loggerMock, undefined);
expect(extractIncomingTaskDataSpy).toHaveBeenNthCalledWith(4, socialTask, true, loggerMock, undefined);

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