Skip to content

Commit 07577f6

Browse files
brain-frogmolker
andauthored
feat(task): Update Outdial Dialpad UI to Match Figma (#538)
Co-authored-by: molker <molker@cisco.com>
1 parent 572450a commit 07577f6

18 files changed

Lines changed: 3457 additions & 1214 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Outbound Dial Labels and/or Strings
2+
export const OutdialStrings = {
3+
ANI_SELECT_LABEL: 'Outdial ANI',
4+
ANI_SELECT_PLACEHOLDER: 'Enter Outdial ANI',
5+
CALL_BUTTON_ARIA_LABEL: 'Start Outdial Call',
6+
DN_PLACEHOLDER: 'Enter number to dial',
7+
INCORRECT_DN_FORMAT: 'Incorrect format.',
8+
OUTDIAL_CALL: 'Outdial Call',
9+
};
10+
11+
// Utility Constants
12+
export const KEY_LIST = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'];
Lines changed: 14 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,34 @@
1-
.out-dial-call-box {
2-
position: relative;
3-
width: 100%;
4-
background-color: var(--mds-color-theme-background-primary-normal);
5-
border-radius: 0.5rem;
6-
box-shadow: var(--mds-shadow-2);
7-
padding: 1.25rem;
8-
max-width: 50rem;
9-
}
10-
11-
.out-dial-call-section-box {
12-
padding: 0.625rem;
13-
}
14-
15-
.out-dial-call-fieldset {
16-
border: 1px solid var(--mds-color-theme-outline-secondary-normal);
17-
border-radius: 0.325rem;
18-
padding: 0.625rem;
19-
margin-bottom: 1.25rem;
20-
}
21-
22-
.out-dial-call-legend-box {
23-
font-weight: bold;
24-
color: var(--mds-color-theme-text-primary-normal);
25-
}
26-
271
.keypad {
282
display: flex;
293
flex-direction: column;
304
align-items: center;
31-
background: var(--mds-color-theme-background-secondary-normal);
32-
padding: 1.25rem;
335
border-radius: 0.625rem;
346
width: 15.625rem;
357

36-
input {
37-
width: 100%;
38-
padding: 0.625rem;
39-
text-align: center;
40-
font-size: 1.125rem;
41-
margin-bottom: 0.625rem;
42-
background: var(--mds-color-theme-background-primary-normal);
43-
border: 1px solid var(--mds-color-theme-outline-secondary-normal);
44-
border-radius: 0.25rem;
45-
color: var(--mds-color-theme-text-primary-normal);
8+
9+
mdc-input {
10+
padding-bottom: 0; // default is 1 rem, 1.5 rem needed but provided by .keys
4611
}
4712

13+
.outdial-input {
14+
width: 100%;
15+
}
16+
4817
.keys {
4918
display: grid;
5019
grid-template-columns: repeat(3, 1fr);
51-
gap: 0.625rem;
20+
row-gap: 1rem;
21+
column-gap: 1.5rem;
22+
padding: 1.5rem 0;
23+
list-style-type: none;
24+
margin: 0;
5225
}
5326

5427
.key {
55-
width: 3.75rem;
56-
height: 3.75rem;
57-
display: flex;
58-
align-items: center;
5928
justify-content: center;
60-
background: var(--mds-color-theme-background-primary-normal);
29+
background: var(--mds-color-theme-background-alert-default-normal);
6130
color: var(--mds-color-theme-text-primary-normal);
62-
font-size: 1.25rem;
63-
border-radius: 50%;
64-
cursor: pointer;
31+
6532
transition: background-color 0.2s ease;
6633

6734
&:hover {
@@ -72,22 +39,4 @@
7239
background: var(--mds-color-theme-background-primary-active);
7340
}
7441
}
75-
}
76-
77-
.out-dial-call-btn {
78-
margin-top: 0.625rem;
79-
background: var(--mds-color-theme-background-alert-success-normal);
80-
color: var(--mds-color-theme-text-primary-normal);
81-
padding: 0.625rem 1.25rem;
82-
border-radius: 50%;
83-
cursor: pointer;
84-
transition: background-color 0.2s ease;
85-
86-
&:hover {
87-
background: var(--mds-color-theme-background-alert-success-hover);
88-
}
89-
90-
&:active {
91-
background: var(--mds-color-theme-background-alert-success-active);
92-
}
9342
}

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

Lines changed: 121 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,135 @@
1-
import React, {useState} from 'react';
2-
import {OutdialCallComponentProps} from '../task.types';
1+
import React, {useEffect, useMemo, useState} from 'react';
2+
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';
6+
import {OutdialStrings, KEY_LIST} from './constants';
57

8+
/**
9+
* OutdialCallComponent renders a dialpad UI for agents to initiate outbound calls.
10+
* It allows input of a destination number, selection of an ANI, and validates input.
11+
*
12+
* This component provides a keypad interface for entering a destination number, validates the input,
13+
* allows selection of an ANI (Automatic Number Identification), and triggers an outbound call action.
14+
*
15+
* @param props - Properties for the OutdialCallComponent.
16+
* @property startOutdial - Function to initiate the outdial call with the entered destination number.
17+
*/
618
const OutdialCallComponent: React.FunctionComponent<OutdialCallComponentProps> = (props) => {
7-
const {startOutdial} = props;
19+
const {logger, startOutdial, getOutdialANIEntries} = props;
20+
21+
// State Hooks
822
const [destination, setDestination] = useState('');
23+
const [isValidNumber, setIsValidNumber] = useState('');
24+
const [selectedANI, setSelectedANI] = useState(undefined);
25+
const [outdialANIList, setOutdialANIList] = useState<OutdialAniEntry[]>([]);
26+
27+
// Validate the input format using regex from agent desktop
28+
const regExForDnSpecialChars = useMemo(
29+
() => new RegExp('^[+1][0-9]{3,18}$|^[*#][+1][0-9*#:]{3,18}$|^[0-9*#]{3,18}$'),
30+
[]
31+
);
32+
33+
// useEffect and useState to allow for async fetching of outdial ANI entries
34+
useEffect(() => {
35+
// Give Select an empty list if outdial ANI entries are not provided
36+
const updateOutdialANIList = async () => {
37+
try {
38+
const result = await getOutdialANIEntries();
39+
setOutdialANIList(result);
40+
} catch (error) {
41+
logger?.error(`CC-Widgets: Task: Error fetching outdial ANI entries: ${error}`, {
42+
module: 'OutdialCallComponent',
43+
method: 'updateOutdialANIList',
44+
});
45+
setOutdialANIList([]);
46+
}
47+
};
48+
updateOutdialANIList();
49+
}, []);
950

10-
const updateOutboundNumber = (e: React.ChangeEvent<HTMLInputElement>) => {
11-
// Allow only valid input that is digits, #, *, and +
12-
const VALID_KEYPAD_CHARS = /[\d#*+]/g;
13-
const filteredValue = e.target.value.match(VALID_KEYPAD_CHARS)?.join('') || '';
14-
setDestination(filteredValue);
51+
/**
52+
* validateOutboundNumber
53+
* @param value the dial number to validate
54+
* If the input is invalid, sets an error message on dial number input
55+
*/
56+
const validateOutboundNumber = (value: string) => {
57+
if (value && !regExForDnSpecialChars.test(value)) {
58+
setIsValidNumber(OutdialStrings.INCORRECT_DN_FORMAT);
59+
} else {
60+
setIsValidNumber('');
61+
}
1562
};
1663

17-
// Function to press a key on the outdial keypad.
18-
const handelKeyPress = (value: string) => {
19-
setDestination((prev) => prev + value);
64+
/**
65+
* handleOnClick
66+
* @param value The key value pressed
67+
* Appends the pressed key to the destination input field
68+
*/
69+
const handleOnClick = (value: string) => {
70+
setDestination(destination + value);
71+
validateOutboundNumber(destination + value);
2072
};
2173

2274
return (
23-
<div className="out-dial-call-box">
24-
<section className="out-dial-call-section-box">
25-
<fieldset className="out-dial-call-fieldset">
26-
<legend className="out-dial-call-legend-box">Outdial Call</legend>
27-
<div className="keypad">
28-
<input
29-
onChange={updateOutboundNumber}
30-
id="outBoundDialNumber"
31-
placeholder="Enter number to dial"
32-
value={destination}
33-
/>
34-
<div className="keys">
35-
{['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'].map((key) => (
36-
<div key={key} className="key" onClick={() => handelKeyPress(key)}>
37-
{key}
38-
</div>
39-
))}
40-
</div>
41-
<button className="out-dial-call-btn" onClick={() => startOutdial(destination)}>
42-
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24">
43-
<path d="M6.62 10.79a15.053 15.053 0 006.59 6.59l2.2-2.2a1 1 0 011.11-.27c1.12.45 2.33.69 3.58.69a1 1 0 011 1v3.5a1 1 0 01-1 1C10.29 21 3 13.71 3 4.5a1 1 0 011-1h3.5a1 1 0 011 1c0 1.25.24 2.46.69 3.58a1 1 0 01-.27 1.11l-2.2 2.2z" />
44-
</svg>
45-
</button>
46-
</div>
47-
</fieldset>
48-
</section>
49-
</div>
75+
<article className="keypad" data-testid="outdial-call-container">
76+
<Input
77+
className="outdial-input"
78+
id="outdial-number-input"
79+
name="outdial-number-input"
80+
data-testid="outdial-number-input"
81+
helpText={isValidNumber}
82+
helpTextType={isValidNumber ? 'error' : 'default'}
83+
placeholder={OutdialStrings.DN_PLACEHOLDER}
84+
value={destination}
85+
onChange={(e: unknown) => {
86+
const inputValue = (e as React.ChangeEvent<HTMLInputElement>).target.value;
87+
setDestination(inputValue);
88+
validateOutboundNumber(inputValue);
89+
}}
90+
/>
91+
<ul className="keys" data-testid="outdial-keypad-keys">
92+
{KEY_LIST.map((key) => (
93+
<li key={key}>
94+
<Button className="key button" onClick={() => handleOnClick(key)}>
95+
{key}
96+
</Button>
97+
</li>
98+
))}
99+
</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>
125+
<Button
126+
data-testid="outdial-call-button"
127+
prefixIcon={'handset-regular'}
128+
onClick={() => startOutdial(destination, selectedANI)}
129+
disabled={!!isValidNumber || !destination}
130+
size={40}
131+
/>
132+
</article>
50133
);
51134
};
52135

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,14 +477,34 @@ export type CallControlComponentProps = Pick<
477477
| 'consultTransferOptions'
478478
>;
479479

480+
export type OutdialAniEntry = {
481+
/** Unique identifier for the ANI entry */
482+
id: string;
483+
/** Display name for the ANI entry */
484+
name: string;
485+
/** Phone number associated with this ANI entry */
486+
number: string;
487+
/** Related links for this ANI entry */
488+
links: string[];
489+
/** Timestamp when this entry was created (Unix timestamp in milliseconds) */
490+
createdTime: number;
491+
/** Timestamp when this entry was last updated (Unix timestamp in milliseconds) */
492+
lastUpdatedTime: number;
493+
};
494+
480495
/**
481496
* Interface representing the properties for OutdialCall component.
482497
*/
483498
export interface OutdialCallProps {
484499
/**
485500
* Function to start outdial call.
486501
*/
487-
startOutdial: (destination: string) => void;
502+
startOutdial: (destination: string, origin?: string) => void;
503+
504+
/**
505+
* Function to get a list of Outdial ANI entries.
506+
*/
507+
getOutdialANIEntries: () => Promise<OutdialAniEntry[]>;
488508

489509
/**
490510
* CC SDK Instance.
@@ -497,7 +517,7 @@ export interface OutdialCallProps {
497517
logger: ILogger;
498518
}
499519

500-
export type OutdialCallComponentProps = Pick<OutdialCallProps, 'startOutdial'>;
520+
export type OutdialCallComponentProps = Pick<OutdialCallProps, 'logger' | 'startOutdial' | 'getOutdialANIEntries'>;
501521

502522
/**
503523
* Interface representing the properties for CallControlListItem component.

0 commit comments

Comments
 (0)