Skip to content

Commit bc41a9f

Browse files
cmullenxChristina Mullen
andauthored
feat(cc): add campaign preview (#684)
Co-authored-by: Christina Mullen <chrmulle@cisco.com>
1 parent 6be4607 commit bc41a9f

41 files changed

Lines changed: 3244 additions & 222 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/contact-center/cc-components/src/components/task/CallControlCAD/call-control-cad.styles.scss

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,8 @@
2222
}
2323
}
2424

25-
.global-variables {
26-
display: flex;
27-
flex-flow: row wrap;
28-
justify-content: space-between;
29-
width: 100%;
30-
max-height: 11.25rem;
31-
overflow-y: auto;
32-
margin-top: 0.75rem;
33-
34-
.global-variable-item {
35-
flex-basis: 50%;
36-
min-width: 0;
37-
box-sizing: border-box;
38-
padding: 0.125rem 0;
39-
line-height: 1.75rem;
40-
}
25+
.cad-global-variables {
26+
margin-top: 0.5rem;
4127
}
4228

4329
/* On-hold chip styling */
@@ -192,6 +178,11 @@
192178
.media-icon.chat {
193179
color: var(--mds-color-theme-indicator-secure);
194180
}
181+
182+
.campaign-call-avatar {
183+
--mdc-avatar-default-background-color: var(--mds-color-theme-avatar-campaign);
184+
--mdc-avatar-default-foreground-color: var(--mds-color-theme-indicator-stable);
185+
}
195186
}
196187
.call-control-task-tooltip::part(popover-content) {
197188
white-space: normal;

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

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import React from 'react';
1+
import React, {useRef} from 'react';
22
import CallControlComponent from '../CallControl/call-control';
33
import {Text, PopoverNext} from '@momentum-ui/react-collaboration';
4-
import {Brandvisual, Icon, Tooltip, Button} from '@momentum-design/components/dist/react';
4+
import {Avatar, Brandvisual, Icon, Tooltip, Button} from '@momentum-design/components/dist/react';
55
import './call-control-cad.styles.scss';
66
import TaskTimer from '../TaskTimer/index';
77
import CallControlConsultComponent from '../CallControl/CallControlCustom/call-control-consult';
@@ -12,6 +12,7 @@ import {
1212
CallAssociatedDataMap,
1313
} from '../task.types';
1414
import {getAgentViewableGlobalVariables} from '../Task/task.utils';
15+
import GlobalVariablesPanel from '../GlobalVariablesPanel/global-variables-panel';
1516

1617
import {getMediaTypeInfo} from '../../../utils';
1718
import {
@@ -23,6 +24,7 @@ import {
2324
QUEUE,
2425
PHONE_NUMBER,
2526
CUSTOMER_NAME,
27+
CAMPAIGN_CALL,
2628
} from '../constants';
2729
import {withMetrics} from '@webex/cc-ui-logging';
2830

@@ -48,6 +50,7 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
4850
isMuted,
4951
toggleMute,
5052
conferenceParticipants,
53+
isCampaignCall = false,
5154
} = props;
5255

5356
const formatTime = (time: number): string => {
@@ -77,7 +80,26 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
7780

7881
//@ts-expect-error To be fixed in SDK - https://jira-eng-sjc12.cisco.com/jira/browse/CAI-6762
7982
const callAssociatedData = currentTask?.data?.interaction?.callAssociatedData as CallAssociatedDataMap | undefined;
80-
const globalVariables = getAgentViewableGlobalVariables(callAssociatedData);
83+
const latestGlobalVariables = getAgentViewableGlobalVariables(callAssociatedData);
84+
85+
// Persist global variables across task updates — some store refreshes
86+
// replace currentTask with a snapshot that omits callAssociatedData,
87+
// which causes getAgentViewableGlobalVariables to return [].
88+
// We intentionally keep the previous values when length === 0 because
89+
// an empty array indicates missing data, not a legitimate clearing of
90+
// variables. Variables are never cleared mid-call by the backend.
91+
// Reset when the interaction changes so stale CAD from a previous task
92+
// is never shown on a new call.
93+
const interactionId = currentTask.data.interaction.interactionId;
94+
const globalVariablesRef = useRef(latestGlobalVariables);
95+
const prevInteractionIdRef = useRef(interactionId);
96+
if (prevInteractionIdRef.current !== interactionId) {
97+
prevInteractionIdRef.current = interactionId;
98+
globalVariablesRef.current = latestGlobalVariables;
99+
} else if (latestGlobalVariables.length > 0) {
100+
globalVariablesRef.current = latestGlobalVariables;
101+
}
102+
const globalVariables = globalVariablesRef.current;
81103

82104
// Create unique IDs for tooltips
83105
const customerNameTriggerId = `customer-name-trigger-${currentTask.data.interaction.interactionId}`;
@@ -178,7 +200,9 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
178200
{/* Caller Information */}
179201
<div className="caller-info">
180202
<div className="call-icon-background">
181-
{currentMediaType.isBrandVisual ? (
203+
{isCampaignCall ? (
204+
<Avatar icon-name="campaign-management-bold" className="campaign-call-avatar" />
205+
) : currentMediaType.isBrandVisual ? (
182206
<Brandvisual name={currentMediaType.iconName} className={`media-icon ${currentMediaType.className}`} />
183207
) : (
184208
<Icon name={currentMediaType.iconName} size={1} className={`media-icon ${currentMediaType.className}`} />
@@ -190,7 +214,8 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
190214
<div className="call-details">
191215
<div className="call-details-row">
192216
<Text className="call-timer" type="body-secondary" tagName={'small'} data-testid="cc-cad:call-timer">
193-
{currentMediaType.labelName} - <TaskTimer startTimeStamp={startTimestamp} />
217+
{isCampaignCall ? CAMPAIGN_CALL : currentMediaType.labelName} -{' '}
218+
<TaskTimer startTimeStamp={startTimestamp} />
194219
{stateTimerLabel && stateTimerTimestamp && (
195220
<>
196221
{' '}
@@ -285,24 +310,7 @@ const CallControlCADComponent: React.FC<CallControlComponentProps> = (props) =>
285310
</Text>
286311
{renderPhoneNumber()}
287312
</div>
288-
{globalVariables.length > 0 && (
289-
<div className="global-variables" data-testid="cc-cad:global-variables">
290-
{globalVariables.map((variable) => (
291-
<div
292-
key={variable.name}
293-
className="global-variable-item"
294-
data-testid={`cc-cad:global-var-${variable.name}`}
295-
>
296-
<Text type="body-secondary" tagName={'small'}>
297-
{variable.displayName || variable.name}
298-
</Text>
299-
<Text type="body-secondary" tagName={'small'}>
300-
{variable.value || ''}
301-
</Text>
302-
</div>
303-
))}
304-
</div>
305-
)}
313+
<GlobalVariablesPanel variables={globalVariables} className="cad-global-variables" />
306314
</div>
307315
{controlVisibility.isConsultInitiatedOrAccepted && !controlVisibility.wrapup.isVisible && (
308316
<div className={`call-control-consult-container ${callControlConsultClassName || ''}`}>

packages/contact-center/cc-components/src/components/task/CampaignErrorDialog/campaign-error-dialog.types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type CampaignErrorType = 'ACCEPT_FAILED' | 'SKIP_FAILED' | 'REMOVE_FAILED';
1+
export type CampaignErrorType = 'ACCEPT_FAILED' | 'SKIP_FAILED' | 'REMOVE_FAILED' | 'CANCEL_FAILED';
22

33
export interface CampaignErrorDialogProps {
44
errorType: CampaignErrorType;
@@ -10,6 +10,7 @@ export const ERROR_TITLES: Record<CampaignErrorType, string> = {
1010
ACCEPT_FAILED: "Can't accept contact",
1111
SKIP_FAILED: "Can't skip contact",
1212
REMOVE_FAILED: "Can't remove contact",
13+
CANCEL_FAILED: "Can't cancel contact",
1314
};
1415

1516
export const ERROR_MESSAGE =
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import React from 'react';
2+
import {Avatar, Button, ListItem, Text, Tooltip} from '@momentum-design/components/dist/react';
3+
import CampaignCountdown from '../../CampaignCountdown/campaign-countdown';
4+
import TaskTimer from '../../TaskTimer/index';
5+
import {CampaignTaskListItemProps} from '../../task.types';
6+
import {
7+
CAMPAIGN_ACCEPT,
8+
CAMPAIGN_CONNECTING,
9+
CAMPAIGN_SKIP,
10+
CAMPAIGN_SKIP_TOOLTIP,
11+
CAMPAIGN_SKIP_DISABLED_TOOLTIP,
12+
CAMPAIGN_REMOVE,
13+
CAMPAIGN_REMOVE_TOOLTIP,
14+
CAMPAIGN_REMOVE_DISABLED_TOOLTIP,
15+
CAMPAIGN_ACTIONS_LABEL,
16+
HANDLE_TIME,
17+
} from '../../constants';
18+
19+
/**
20+
* CampaignTaskListItem renders the ListItem row shared between the
21+
* CampaignTask inline card and the CampaignTaskPopover.
22+
*
23+
* Layout: Avatar | Title / Phone / Countdown | Accept + Skip/Remove buttons
24+
*/
25+
const CampaignTaskListItem: React.FC<CampaignTaskListItemProps> = ({
26+
title,
27+
phoneNumber,
28+
customerName,
29+
timeoutTimestamp,
30+
isAcceptClicked,
31+
isAccepted,
32+
isAcceptDisabled,
33+
isSkipDisabled,
34+
isRemoveDisabled,
35+
onAccept,
36+
onSkip,
37+
onRemove,
38+
onTimeout,
39+
handleTimestamp,
40+
logger,
41+
className,
42+
testIdPrefix = 'campaign-task',
43+
}) => {
44+
const skipTooltipText = isSkipDisabled ? CAMPAIGN_SKIP_DISABLED_TOOLTIP : CAMPAIGN_SKIP_TOOLTIP;
45+
const removeTooltipText = isRemoveDisabled ? CAMPAIGN_REMOVE_DISABLED_TOOLTIP : CAMPAIGN_REMOVE_TOOLTIP;
46+
const skipButtonId = `${testIdPrefix}-skip-btn`;
47+
const removeButtonId = `${testIdPrefix}-remove-btn`;
48+
49+
return (
50+
<ListItem className={className} data-testid={`${testIdPrefix}-list-item`}>
51+
<Avatar slot="leading-controls" icon-name="campaign-management-bold" className="campaign-avatar" />
52+
53+
<Text slot="leading-text-primary-label" type="body-large-medium" data-testid={`${testIdPrefix}-title`}>
54+
{title}
55+
</Text>
56+
{customerName && phoneNumber && phoneNumber !== customerName && (
57+
<Text slot="leading-text-secondary-label" type="body-midsize-regular" data-testid={`${testIdPrefix}-phone`}>
58+
{phoneNumber}
59+
</Text>
60+
)}
61+
{!isAccepted && timeoutTimestamp && (
62+
<div slot="leading-text-tertiary-label">
63+
<CampaignCountdown timeoutTimestamp={timeoutTimestamp} onTimeout={onTimeout} logger={logger} />
64+
</div>
65+
)}
66+
{isAccepted && handleTimestamp && (
67+
<Text
68+
slot="leading-text-tertiary-label"
69+
tagname="span"
70+
type="body-midsize-regular"
71+
className="campaign-task-handle-time"
72+
data-testid={`${testIdPrefix}-handle-time`}
73+
>
74+
{HANDLE_TIME} <TaskTimer startTimeStamp={handleTimestamp} />
75+
</Text>
76+
)}
77+
78+
{!isAccepted && (
79+
<div
80+
slot="trailing-controls"
81+
className="campaign-task-actions"
82+
aria-label={CAMPAIGN_ACTIONS_LABEL}
83+
data-testid={`${testIdPrefix}-actions`}
84+
>
85+
{!isAcceptClicked ? (
86+
<Button
87+
variant="primary"
88+
color="positive"
89+
size={28}
90+
onClick={onAccept}
91+
disabled={isAcceptDisabled}
92+
aria-label={CAMPAIGN_ACCEPT}
93+
data-testid={`${testIdPrefix}-accept-button`}
94+
>
95+
{CAMPAIGN_ACCEPT}
96+
</Button>
97+
) : (
98+
<Button
99+
variant="secondary"
100+
size={28}
101+
disabled
102+
aria-label={CAMPAIGN_CONNECTING}
103+
data-testid={`${testIdPrefix}-connecting-button`}
104+
>
105+
{CAMPAIGN_CONNECTING}
106+
</Button>
107+
)}
108+
109+
<div
110+
className="campaign-task-skip-remove"
111+
role="group"
112+
aria-label={`${CAMPAIGN_SKIP} and ${CAMPAIGN_REMOVE}`}
113+
data-testid={`${testIdPrefix}-skip-remove`}
114+
>
115+
<Button
116+
id={skipButtonId}
117+
variant="secondary"
118+
size={28}
119+
prefixIcon="skip-bold"
120+
onClick={onSkip}
121+
disabled={isSkipDisabled}
122+
aria-label={CAMPAIGN_SKIP}
123+
data-testid={`${testIdPrefix}-skip-button`}
124+
/>
125+
<Tooltip triggerID={skipButtonId} placement="bottom" tooltipType="label">
126+
{skipTooltipText}
127+
</Tooltip>
128+
129+
<Button
130+
id={removeButtonId}
131+
variant="secondary"
132+
size={28}
133+
prefixIcon="remove-bold"
134+
onClick={onRemove}
135+
disabled={isRemoveDisabled}
136+
aria-label={CAMPAIGN_REMOVE}
137+
data-testid={`${testIdPrefix}-remove-button`}
138+
/>
139+
<Tooltip triggerID={removeButtonId} placement="bottom" tooltipType="label">
140+
{removeTooltipText}
141+
</Tooltip>
142+
</div>
143+
</div>
144+
)}
145+
</ListItem>
146+
);
147+
};
148+
149+
export default CampaignTaskListItem;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
.campaign-task-popover {
2+
&__content {
3+
display: flex;
4+
flex-direction: column;
5+
padding: 0.5rem;
6+
gap: 0.5rem;
7+
min-height: 0;
8+
overflow: hidden;
9+
10+
// Cap the variables panel height inside the popover so it scrolls
11+
// instead of overflowing past the popover boundary.
12+
// Agent Desktop uses max-height: 120px (~7.5rem) on the details container.
13+
.global-variables-panel--two-column {
14+
max-height: 7.5rem;
15+
overflow-y: auto;
16+
}
17+
}
18+
19+
&__list-item {
20+
--mdc-listitem-padding-left-right: 0.75rem;
21+
--mdc-listitem-padding-top-bottom: 0.5rem;
22+
--mdc-listitem-background-color-hover: transparent;
23+
--mdc-listitem-background-color-active: transparent;
24+
--mdc-listitem-cursor: default;
25+
width: 100%;
26+
27+
.campaign-avatar {
28+
--mdc-avatar-default-background-color: var(--mds-color-theme-avatar-campaign);
29+
--mdc-avatar-default-foreground-color: var(--mds-color-theme-indicator-stable);
30+
}
31+
}
32+
33+
// Action buttons — vertical column on the right (Accept on top, Skip + Remove below)
34+
.campaign-task-actions {
35+
display: flex;
36+
flex-direction: column;
37+
justify-content: space-evenly;
38+
align-items: flex-end;
39+
gap: 0.25rem;
40+
height: 100%;
41+
}
42+
43+
.campaign-task-skip-remove {
44+
display: flex;
45+
align-items: center;
46+
gap: 0.25rem;
47+
margin-left: auto;
48+
}
49+
50+
// Icon buttons (skip / remove) — match call-control circular icon button style
51+
.campaign-task-icon-button {
52+
width: 1.75rem !important;
53+
height: 1.75rem !important;
54+
}
55+
56+
}

0 commit comments

Comments
 (0)