Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9e64679
fix(incoming-task): add-uts-for-incoming-task
akulakum Jul 15, 2025
7c9c889
Merge branch 'ccwidgets' into INCOMING_TASK_UTs
akulakum Jul 15, 2025
7bb85bb
Merge branch 'ccwidgets' into INCOMING_TASK_UTs
akulakum Jul 15, 2025
575b9bf
fix(task-list-uts): add-uts-for-task-list
akulakum Jul 15, 2025
889566d
fix(task-uts): add-uts-for-task-component
akulakum Jul 16, 2025
16d5dfd
Merge branch 'ccwidgets' into INCOMING_TASK_UTs
akulakum Jul 16, 2025
25dc2ae
Merge branch 'ccwidgets' of github.com:webex/widgets into INCOMING_TA…
akulakum Jul 17, 2025
61c44cb
fix(task-uts): remove-unnessary-comments
akulakum Jul 21, 2025
0ade9d2
fix(task-uts): address-review-comments
akulakum Jul 22, 2025
dfe8c3e
fix(task-uts): remove-jest-mock
akulakum Jul 23, 2025
6d26d32
fix(task-uts): added-default-props
akulakum Jul 23, 2025
cb8b902
fix(task-list-uts): address-review-comments
akulakum Jul 24, 2025
9caeeeb
fix(task-list-uts): address-review-comments
akulakum Jul 24, 2025
861e91d
Merge branch 'ccwidgets' into INCOMING_TASK_UTs
akulakum Jul 24, 2025
eaccfe4
Merge branch 'ccwidgets' into INCOMING_TASK_UTs
akulakum Jul 24, 2025
89e9b2e
fix(incoming-task-uts): address-review-comments
akulakum Jul 24, 2025
5d1af39
fix(task-uts): address-review-comments
akulakum Jul 25, 2025
7e8464d
Merge branch 'ccwidgets' of github.com:webex/widgets into INCOMING_TA…
akulakum Jul 28, 2025
5ffd23f
fix(task-incoming-uts): address-review-comments
akulakum Jul 29, 2025
8b0f439
fix(types) jest spy type error
Shreyas281299 Jul 29, 2025
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
@@ -1,66 +1,38 @@
import React from 'react';
import {IncomingTaskComponentProps, MEDIA_CHANNEL} from '../task.types';
import Task from '../Task';
import {extractIncomingTaskData} from './incoming-task.utils';

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

//@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762
const callAssociationDetails = incomingTask?.data?.interaction?.callAssociatedDetails;
const ani = callAssociationDetails?.ani;
const customerName = callAssociationDetails?.customerName;
const virtualTeamName = callAssociationDetails?.virtualTeamName;
const ronaTimeout = callAssociationDetails?.ronaTimeout ? Number(callAssociationDetails?.ronaTimeout) : null;
const startTimeStamp = incomingTask?.data?.interaction?.createdTimestamp;
const mediaType = incomingTask.data.interaction.mediaType;
const mediaChannel = incomingTask.data.interaction.mediaChannel;
const isTelephony = mediaType === MEDIA_CHANNEL.TELEPHONY;
const isSocial = mediaType === MEDIA_CHANNEL.SOCIAL;
const acceptText = !incomingTask.data.wrapUpRequired
? isTelephony && !isBrowser
? 'Ringing...'
: 'Accept'
: undefined;
const declineText = !incomingTask.data.wrapUpRequired && isTelephony && isBrowser ? 'Decline' : undefined;
// Extract all task data using the utility function
const taskData = extractIncomingTaskData(incomingTask, isBrowser);

return (
<Task
interactionId={incomingTask.data.interactionId}
title={isSocial ? customerName : ani}
title={taskData.title}
state=""
startTimeStamp={startTimeStamp}
startTimeStamp={taskData.startTimeStamp}
isIncomingTask={true}
queue={virtualTeamName}
queue={taskData.virtualTeamName}
acceptTask={() => {
logger.info(
`CC-Widgets: IncomingTask: accept clicked for task with interactionID: ${incomingTask.data.interactionId}`,
{
module: 'incoming-task.tsx',
method: 'acceptTask',
}
);
accept(incomingTask);
}}
declineTask={() => {
logger.info(
`CC-Widgets: IncomingTask: decline clicked for task with interactionID: ${incomingTask.data.interactionId}`,
{
module: 'incoming-task.tsx',
method: 'declineTask',
}
);
reject(incomingTask);
}}
ronaTimeout={ronaTimeout}
acceptText={acceptText}
disableAccept={isTelephony && !isBrowser}
declineText={declineText}
ronaTimeout={taskData.ronaTimeout}
acceptText={taskData.acceptText}
disableAccept={taskData.disableAccept}
declineText={taskData.declineText}
styles="task-list-hover"
mediaType={mediaType}
mediaChannel={mediaChannel}
mediaType={taskData.mediaType as MEDIA_CHANNEL}
mediaChannel={taskData.mediaChannel as MEDIA_CHANNEL}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {MEDIA_CHANNEL} from '../task.types';
import {ITask} from '@webex/cc-store';

export interface IncomingTaskData {
ani: string;
customerName: string;
virtualTeamName: string;
ronaTimeout: number | null;
startTimeStamp: number;
mediaType: string;
mediaChannel: string;
isTelephony: boolean;
isSocial: boolean;
acceptText: string | undefined;
declineText: string | undefined;
title: string;
disableAccept: boolean;
}

/**
* Extracts and processes all data needed for rendering an incoming task
* @param incomingTask - The incoming task object
* @param isBrowser - Whether the device type is browser
* @returns Processed task data with computed values
*/
export const extractIncomingTaskData = (incomingTask: ITask, isBrowser: boolean): IncomingTaskData => {
// Extract basic data from task
//@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762
const callAssociationDetails = incomingTask?.data?.interaction?.callAssociatedDetails;
const ani = callAssociationDetails?.ani;
const customerName = callAssociationDetails?.customerName;
const virtualTeamName = callAssociationDetails?.virtualTeamName;
const ronaTimeout = callAssociationDetails?.ronaTimeout ? Number(callAssociationDetails?.ronaTimeout) : null;
const startTimeStamp = incomingTask?.data?.interaction?.createdTimestamp;
const mediaType = incomingTask.data.interaction.mediaType;
const mediaChannel = incomingTask.data.interaction.mediaChannel;

// Compute media type flags
const isTelephony = mediaType === MEDIA_CHANNEL.TELEPHONY;
const isSocial = mediaType === MEDIA_CHANNEL.SOCIAL;

// Compute button text based on conditions
const acceptText = !incomingTask.data.wrapUpRequired
? isTelephony && !isBrowser
? 'Ringing...'
: 'Accept'
: undefined;

const declineText = !incomingTask.data.wrapUpRequired && isTelephony && isBrowser ? 'Decline' : undefined;

// Compute title based on media type
const title = isSocial ? customerName : ani;

// Compute disable state for accept button
const disableAccept = isTelephony && !isBrowser;

return {
ani,
customerName,
virtualTeamName,
ronaTimeout,
startTimeStamp,
mediaType,
mediaChannel,
isTelephony,
isSocial,
acceptText,
declineText,
title,
disableAccept,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import {ButtonPill, ListItemBase, ListItemBaseSection, Text} from '@momentum-ui/
import {Avatar, Brandvisual, Tooltip} from '@momentum-design/components/dist/react';
import {PressEvent} from '@react-types/shared';
import TaskTimer from '../TaskTimer';
import {getMediaTypeInfo} from '../../../utils';
import type {MEDIA_CHANNEL as MediaChannelType} from '../task.types';
import {extractTaskComponentData, getTaskListItemClasses} from './task.utils';
import './styles.scss';

interface TaskProps {
export interface TaskProps {
interactionId?: string;
title?: string;
state?: string;
Expand Down Expand Up @@ -46,50 +46,44 @@ const Task: React.FC<TaskProps> = ({
mediaType,
mediaChannel,
}) => {
const capitalizeFirstWord = (str: string) => {
return str.replace(/^\s*(\w)/, (match, firstLetter) => firstLetter.toUpperCase());
};
const currentMediaType = getMediaTypeInfo(mediaType, mediaChannel);
const isNonVoiceMedia = currentMediaType.labelName !== 'Call';
// Create unique IDs for tooltip trigger and tooltip
const tooltipTriggerId = `tooltip-trigger-${interactionId}`;
const tooltipId = `tooltip-${interactionId}`;
// Helper function to get the correct CSS class
const getTitleClassName = () => {
if (isNonVoiceMedia && isIncomingTask) {
return 'incoming-digital-task-title';
}
if (isNonVoiceMedia && !isIncomingTask) {
return 'task-digital-title';
}
return 'task-title';
};
// Extract all computed data using the utility function
const taskData = extractTaskComponentData({
mediaType,
mediaChannel,
isIncomingTask,
interactionId,
state,
queue,
ronaTimeout,
startTimeStamp,
});

const renderTitle = () => {
if (!title) return null;

const textComponent = (
<Text
tagName="span"
type={selected ? 'body-large-bold' : 'body-large-medium'}
className={getTitleClassName()}
id={isNonVoiceMedia ? tooltipTriggerId : undefined}
className={taskData.titleClassName}
id={taskData.isNonVoiceMedia ? taskData.tooltipTriggerId : undefined}
>
{title}
</Text>
);

if (isNonVoiceMedia) {
if (taskData.isNonVoiceMedia) {
return (
<>
{textComponent}
<Tooltip
color="contrast"
delay="0,0"
id={tooltipId}
id={taskData.tooltipId}
placement="top-start"
offset={4}
tooltip-type="description"
triggerID={tooltipTriggerId}
triggerID={taskData.tooltipTriggerId}
className="task-tooltip"
>
{title}
Expand All @@ -103,48 +97,66 @@ const Task: React.FC<TaskProps> = ({

return (
<ListItemBase
className={`task-list-item ${selected ? 'task-list-item--selected' : ''} ${styles}`}
className={getTaskListItemClasses(selected, styles)}
onPress={onTaskSelect ? onTaskSelect : undefined}
id={interactionId}
>
<ListItemBaseSection position="start">
{currentMediaType.isBrandVisual ? (
{taskData.currentMediaType.isBrandVisual ? (
<div className="brand-visual-background">
<Brandvisual name={currentMediaType.iconName} className={currentMediaType.className} />
<Brandvisual name={taskData.currentMediaType.iconName} className={taskData.currentMediaType.className} />
</div>
) : (
<Avatar icon-name={currentMediaType.iconName} className={currentMediaType.className} />
<Avatar icon-name={taskData.currentMediaType.iconName} className={taskData.currentMediaType.className} />
)}
</ListItemBaseSection>

<ListItemBaseSection position="fill">
<section className="task-details">
{renderTitle()}
{state && !isIncomingTask && (
<Text tagName="span" type="body-midsize-regular" className="task-text">
{capitalizeFirstWord(state)}
{taskData.shouldShowState && (
<Text
tagName="span"
type="body-midsize-regular"
className="task-text"
data-testid={`${interactionId}-state`}
>
{taskData.capitalizedState}
</Text>
)}

{queue && isIncomingTask && (
<Text tagName="span" type="body-midsize-regular" className="task-text">
{capitalizeFirstWord(queue)}
{taskData.shouldShowQueue && (
<Text
tagName="span"
type="body-midsize-regular"
className="task-text"
data-testid={`${interactionId}-queue`}
>
{taskData.capitalizedQueue}
</Text>
)}

{/* Handle Time should render if it's an incoming call without ronaTimeout OR if it's not an incoming call */}
{(isIncomingTask && !ronaTimeout) || !isIncomingTask
? startTimeStamp && (
<Text tagName="span" type="body-midsize-regular" className="task-text">
Handle Time: {' '}
<TaskTimer startTimeStamp={startTimeStamp} />
</Text>
)
: null}
{taskData.shouldShowHandleTime && (
<Text
tagName="span"
type="body-midsize-regular"
className="task-text"
data-testid={`${interactionId}-handle-time`}
>
Handle Time: {' '}
<TaskTimer startTimeStamp={startTimeStamp} />
</Text>
)}

{/* Time Left should render if it's an incoming call with ronaTimeout */}
{isIncomingTask && ronaTimeout && (
<Text tagName="span" type="body-midsize-regular" className="task-text">
{taskData.shouldShowTimeLeft && (
<Text
tagName="span"
type="body-midsize-regular"
className="task-text"
data-testid={`${interactionId}-time-left`}
>
Time Left: {' '}
<TaskTimer countdown={true} ronaTimeout={ronaTimeout} />
</Text>
Expand Down
Loading
Loading