Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat(cc-widgets): added new incomingtask and task ui based on figma design #402

Merged
merged 10 commits into from
Mar 3, 2025
2 changes: 1 addition & 1 deletion packages/contact-center/cc-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,4 @@
"react": ">=18.3.1",
"react-dom": ">=18.3.1"
}
}
}
2 changes: 1 addition & 1 deletion packages/contact-center/cc-widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,4 @@
"^.+\\.(css|less|scss)$": "babel-jest"
}
}
}
}
2 changes: 1 addition & 1 deletion packages/contact-center/station-login/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@
"^.+\\.(css|less|scss)$": "babel-jest"
}
}
}
}
2 changes: 1 addition & 1 deletion packages/contact-center/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@
"**/tests/**/*.tsx"
]
}
}
}
2 changes: 1 addition & 1 deletion packages/contact-center/task/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@
"^.+\\.(css|less|scss)$": "babel-jest"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,199 +1,30 @@
import React from 'react';
import {IncomingTaskPresentationalProps} from '../task.types';
import {ButtonPill} from '@momentum-ui/react-collaboration';

const styles: {[key: string]: React.CSSProperties} = {
box: {
backgroundColor: '#ffffff',
borderRadius: '8px',
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
padding: '20px',
maxWidth: '800px',
margin: '0 auto',
},
sectionBox: {
padding: '10px',
border: '1px solid #ddd',
borderRadius: '8px',
marginBottom: '20px',
},
fieldset: {
border: '1px solid #ccc',
borderRadius: '5px',
padding: '10px',
marginBottom: '20px',
position: 'relative',
},
legendBox: {
fontWeight: 'bold',
color: '#0052bf',
},
container: {
border: '1px solid #ccc',
borderRadius: '8px',
padding: '16px',
width: '350px',
boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.2)',
fontFamily: 'Arial, sans-serif',
backgroundColor: '#ffffff',
display: 'flex',
flexDirection: 'column',
},
topSection: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
iconWrapper: {
display: 'inline-block',
backgroundColor: '#d4f8e8',
borderRadius: '50%',
width: '40px',
height: '40px',
justifyContent: 'center',
alignItems: 'center',
marginRight: '10px',
},
iconSvg: {
width: '24px',
height: '24px',
color: '#146f5c',
},
callInfo: {
margin: 0,
fontSize: '1.2em',
color: '#333',
},
aniText: {
fontSize: '1.1em',
fontWeight: 'bold',
margin: '4px 0',
color: '#146f5c',
},
buttonsWrapper: {
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
marginLeft: '16px',
},
answerButton: {
padding: '8px 16px',
border: 'none',
borderRadius: '6px',
fontSize: '0.9em',
cursor: 'pointer',
fontWeight: 'bold',
backgroundColor: '#28a745',
color: '#fff',
marginBottom: '8px',
},
declineButton: {
padding: '8px 16px',
border: 'none',
borderRadius: '6px',
fontSize: '0.9em',
cursor: 'pointer',
fontWeight: 'bold',
backgroundColor: '#dc3545',
color: '#fff',
},
queueInfo: {
fontSize: '0.9em',
color: '#666',
marginTop: '8px',
},
timeElapsed: {
color: '#28a745',
fontWeight: 'bold',
},
callDetails: {
marginTop: '16px',
fontSize: '0.9em',
color: '#333',
},
detailItem: {
margin: '4px 0',
},
detailLabel: {
color: '#555',
fontWeight: 'bold',
},
};
import Task from '../Task';

const IncomingTaskPresentational: React.FunctionComponent<IncomingTaskPresentationalProps> = (props) => {
const {incomingTask, accept, decline, isBrowser} = props;
if (!incomingTask) {
return <></>; // hidden component
}

const callAssociationDetails = incomingTask.data.interaction.callAssociatedDetails;
const {ani, dn, virtualTeamName} = callAssociationDetails;
const timeElapsed = ''; // TODO: Calculate time elapsed

const callAssociationDetails = incomingTask?.data?.interaction?.callAssociatedDetails;
const ani = callAssociationDetails?.ani;
const virtualTeamName = callAssociationDetails?.virtualTeamName;
// rona timeout is not always available in the callAssociatedDetails object
const ronaTimeout = callAssociationDetails?.ronaTimeout ? Number(callAssociationDetails?.ronaTimeout) : null;
const startTimeStamp = incomingTask?.data?.interaction?.createdTimestamp;
return (
<div style={styles.box}>
<section style={styles.sectionBox}>
<fieldset style={styles.fieldset}>
<legend style={styles.legendBox}>Incoming Task</legend>
<div data-testid="incoming-task-presentational" style={styles.container}>
{/* Top Section - Call Info with Phone Icon */}
<div style={styles.topSection}>
<div style={{display: 'flex', alignItems: 'center'}}>
<span style={styles.iconWrapper}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={styles.iconSvg}
>
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.86 19.86 0 0 1-8.63-2.73 19.5 19.5 0 0 1-6-6A19.86 19.86 0 0 1 3.08 4.18 2 2 0 0 1 5 2h3a2 2 0 0 1 2 1.72c.2 1.52.71 2.94 1.41 4.24a2 2 0 0 1-.45 2.31L9.91 11a16 16 0 0 0 6 6l1.73-1.05a2 2 0 0 1 2.31-.45 16.11 16.11 0 0 0 4.24 1.41A2 2 0 0 1 22 16.92z"></path>
</svg>
</span>
<div>
<h2 style={styles.callInfo}>Incoming Call</h2>
<p data-testid="incoming-task-ani" style={styles.aniText}>
{ani}
</p>
</div>
</div>

{isBrowser && (
<div style={styles.buttonsWrapper}>
<ButtonPill style={styles.answerButton} onPress={accept}>
Answer
</ButtonPill>
<ButtonPill style={styles.declineButton} onPress={decline}>
Decline
</ButtonPill>
</div>
)}
</div>

{/* Queue and Timer Info */}
<p style={styles.queueInfo}>
{virtualTeamName} - <span style={styles.timeElapsed}>{timeElapsed}</span>
</p>

{/* Call Details Section */}
<div style={styles.callDetails}>
<p style={styles.detailItem}>
<strong style={styles.detailLabel}>Phone Number:</strong> {ani}
</p>
<p style={styles.detailItem}>
<strong style={styles.detailLabel}>DNIS:</strong> {dn}
</p>
<p style={styles.detailItem}>
<strong style={styles.detailLabel}>Queue Name:</strong> {virtualTeamName}
</p>
</div>
</div>
</fieldset>
</section>
</div>
<Task
title={ani}
queue={virtualTeamName}
isIncomingTask={true}
isBrowser={isBrowser}
acceptTask={accept}
declineTask={decline}
ronaTimeout={ronaTimeout}
startTimeStamp={startTimeStamp}
/>
);
};

Expand Down
101 changes: 101 additions & 0 deletions packages/contact-center/task/src/Task/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react';
import {ButtonPill, ListItemBase, ListItemBaseSection, Text} from '@momentum-ui/react-collaboration';
import {Avatar} from '@momentum-design/components/dist/react';
import {PressEvent} from '@react-types/shared';
import TaskTimer from '../TaskTimer';
import './styles.scss';

interface TaskProps {
title?: string;
state?: string;
startTimeStamp?: number;
ronaTimeout?: number;
selected?: boolean;
isIncomingTask?: boolean;
queue?: string;
acceptTask?: (e: PressEvent) => void;
declineTask?: (e: PressEvent) => void;
isBrowser?: boolean;
}

const Task: React.FC<TaskProps> = ({
title,
state,
startTimeStamp,
ronaTimeout,
selected = false,
isIncomingTask = false,
queue,
acceptTask,
declineTask,
isBrowser,
}) => {
const capitalizeFirstWord = (str: string) => {
return str.replace(/^\s*(\w)/, (match, firstLetter) => firstLetter.toUpperCase());
};

return (
<ListItemBase className={`task-list-item ${selected ? 'task-list-item--selected' : ''}`}>
<ListItemBaseSection position="start" className="task-list-item-start-section">
<Avatar
icon-name="handset-filled"
className={`task-list-item-avatar ${selected ? 'task-list-item-avatar--selected' : ''}`}
/>
</ListItemBaseSection>

<ListItemBaseSection position="fill">
<section className="task-details">
{title && (
<Text tagName="span" type={selected ? 'body-large-bold' : 'body-large-medium'} className="task-text">
{title}
</Text>
)}

{state && !isIncomingTask && (
<Text tagName="span" type="body-midsize-regular" className="task-text task-text--secondary">
{capitalizeFirstWord(state)}
</Text>
)}

{queue && isIncomingTask && (
<Text tagName="span" type="body-midsize-regular" className="task-text task-text--secondary">
{capitalizeFirstWord(queue)}
</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 task-text--secondary">
Handle Time: {' '}
<TaskTimer startTimeStamp={startTimeStamp} />
</Text>
)
: null}

{/* Time Left should render if it's an incoming call with ronaTimeout */}
{isIncomingTask && ronaTimeout && (
<Text tagName="span" type="body-midsize-regular" className="task-text task-text--secondary">
Time Left: {' '}
<TaskTimer countdown={true} ronaTimeout={ronaTimeout} />
</Text>
)}
</section>
</ListItemBaseSection>

<ListItemBaseSection position="end">
{isIncomingTask ? (
<ButtonPill onPress={acceptTask} color="join" disabled={!isBrowser}>
Ringing
</ButtonPill>
) : isBrowser ? (
<ButtonPill onPress={declineTask} color="join">
End
</ButtonPill>
) : null}
</ListItemBaseSection>
</ListItemBase>
);
};

export default Task;
31 changes: 31 additions & 0 deletions packages/contact-center/task/src/Task/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
:root {
--mds-color-theme-avatar-glass-normal: rgba(0, 0, 0, 0.07);
}

@media (prefers-color-scheme: dark) {
:root {
--mds-color-theme-avatar-glass-normal: rgba(255, 255, 255, 0.07);
}
}

.task-list-item {
display: flex;
padding: 0.5rem 0.75rem;
align-items: center;
align-self: stretch;
// have to override momentum default height for listitem to match the design
height: 5rem !important;

mdc-avatar {
--mdc-avatar-default-background-color: var(--mds-color-theme-avatar-glass-normal);
--mdc-avatar-default-foreground-color: var(--mds-color-theme-indicator-stable);
}
}

.task-list-item--selected {
background: var(--color-theme-background-primary-active, rgba(0, 0, 0, 0.11));

mdc-avatar {
--mdc-avatar-default-background-color: var(--mds-color-theme-avatar-glass-active, rgba(255, 255, 255, 0.8));
}
}
12 changes: 12 additions & 0 deletions packages/contact-center/task/src/TaskList/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.task-list {
align-self: stretch;
padding-inline-start: 0;
margin-block: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
width: 100%;
height: 100%;
list-style-type: none;
}
Loading