Skip to content

Commit

Permalink
Merge pull request #2501 from techmatters/CHI-2877-qa
Browse files Browse the repository at this point in the history
CHI2877 Teamsview feedback
  • Loading branch information
mythilytm authored Aug 23, 2024
2 parents e789151 + 81be828 commit 70fd21c
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright (C) 2021-2023 Technology Matters
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import { convertDurationToSeconds } from '../../../components/teamsView/teamsViewSorting';

describe('convertDurationToSeconds should convert', () => {
test('seconds', () => {
const input = '59s';
const expected = 59;
expect(convertDurationToSeconds(input)).toEqual(expected);
});

test('minutes and seconds with :', () => {
const input = '59:59';
const expected = 59 * 60 + 59;
expect(convertDurationToSeconds(input)).toEqual(expected);
});

test('hours', () => {
const input = '23h';
const expected = 23 * 60 * 60;
expect(convertDurationToSeconds(input)).toEqual(expected);
});

test('hours and minutes', () => {
const input = '23h 59min';
const expected = 23 * 60 * 60 + 59 * 60;
expect(convertDurationToSeconds(input)).toEqual(expected);
});

test('days', () => {
const input = '59d';
const expected = 59 * 24 * 60 * 60;
expect(convertDurationToSeconds(input)).toEqual(expected);
});

test('days and hours', () => {
const input = '5d 3h';
const expected = 5 * 24 * 60 * 60 + 3 * 60 * 60;
expect(convertDurationToSeconds(input)).toEqual(expected);
});

test('more than 30 days', () => {
const input = '30+d';
const expected = 30 * 24 * 60 * 60;
expect(convertDurationToSeconds(input)).toEqual(expected);
});
});
16 changes: 11 additions & 5 deletions plugin-hrm-form/src/components/teamsView/AgentColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ const MAX_NAME_LENGTH = 18;

export const setUpAgentColumn = () => {
if (!getAseloFeatureFlags().enable_teams_view_enhancements) return;
if (!getAseloFeatureFlags().enable_teams_view_enhancements2) return;

const agentSortingFn = (a: any, b: any): number => {
return a > b ? 1 : -1;
return a.worker.fullName.localeCompare(b.worker.fullName);
};

WorkersDataTable.Content.remove('worker');
Expand All @@ -37,7 +38,7 @@ export const setUpAgentColumn = () => {
key="agent"
header="Agent"
sortingFn={agentSortingFn}
style={{ width: '17%' }}
style={{ width: '18%' }}
content={item => <AgentCell item={item} />}
/>,
{ sortOrder: 0 },
Expand Down Expand Up @@ -72,13 +73,18 @@ const AgentCell = ({ item }) => {
const fullName = item?.worker?.fullName ?? '';

return (
<div>
<div style={{ marginRight: '4px', padding: '4px' }}>
{fullName.length > MAX_NAME_LENGTH ? (
<Tooltip title={fullName} enterDelay={500} enterTouchDelay={500}>
<AgentFullName>{`${fullName.substring(0, MAX_NAME_LENGTH)}…`}</AgentFullName>
<AgentFullName role="button" tabIndex={0} aria-label={fullName}>{`${fullName.substring(
0,
MAX_NAME_LENGTH,
)}…`}</AgentFullName>
</Tooltip>
) : (
<AgentFullName>{fullName}</AgentFullName>
<AgentFullName role="button" tabIndex={0} aria-label={fullName}>
{fullName}
</AgentFullName>
)}
<Labels />
</div>
Expand Down
30 changes: 11 additions & 19 deletions plugin-hrm-form/src/components/teamsView/SkillsColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,19 @@ const SkillsCell = ({ availableSkills, disabledSkills, workerName }) => {
...disabledSkills.map(skill => ({ skill, type: 'disabled' })),
];

if (combinedSkills.length === 0) {
return (
<OpaqueText style={{ fontSize: '12px' }}>
<Template code="TeamsView-NoSkills" aria-label={`No skills available for ${workerName}`} />
</OpaqueText>
);
}

return (
return combinedSkills.length === 0 ? (
<OpaqueText style={{ paddingLeft: '4px' }}>
<Template code="TeamsView-NoSkills" aria-label={`No skills available for ${workerName}`} />
</OpaqueText>
) : (
<SkillsList>
{combinedSkills.map(({ skill, type }) =>
skill.length > MAX_SKILL_LENGTH ? (
<Tooltip key={skill} title={skill}>
<StyledChip chipType={type}>{`${skill.substring(0, MAX_SKILL_LENGTH)}…`}</StyledChip>
</Tooltip>
) : (
<StyledChip key={skill} chipType={type}>
{skill}
{combinedSkills.map(({ skill, type }) => (
<Tooltip key={skill} title={skill}>
<StyledChip chipType={type}>
{skill.length > MAX_SKILL_LENGTH ? `${skill.substring(0, MAX_SKILL_LENGTH)}…` : skill}
</StyledChip>
),
)}
</Tooltip>
))}
</SkillsList>
);
};
1 change: 1 addition & 0 deletions plugin-hrm-form/src/components/teamsView/StatusColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const MAX_STATUS_LENGTH = 12;

export const setUpStatusColumn = () => {
if (!getAseloFeatureFlags().enable_teams_view_enhancements) return;
if (!getAseloFeatureFlags().enable_teams_view_enhancements2) return;

WorkersDataTable.Content.add(
<ColumnDefinition
Expand Down
9 changes: 6 additions & 3 deletions plugin-hrm-form/src/components/teamsView/styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ import { ChipBase, FontOpenSans } from '../../styles';
* Agent Column
*/

export const AgentFullName = styled(FontOpenSans)`
export const AgentFullName = styled('div')`
font-size: 14px;
font-weight: 700;
color: #000000;
font-family: Open Sans;
text-align: left;
`;
AgentFullName.displayName = 'AgentFullName';

Expand All @@ -37,8 +40,7 @@ export const StatusActivityName = styled(FontOpenSans)`
StatusActivityName.displayName = 'StatusActivityName';

export const SkillsList = styled('div')`
padding: 8px;
margin-bottom: 6px;
padding: 4px;
`;
SkillsList.displayName = 'SkillsList';

Expand Down Expand Up @@ -77,6 +79,7 @@ export const StyledChip = styled(ChipBase)<StyledChipProps>`
padding: 2px 10px;
border-width: 1px;
font-size: 12px;
font-family: Open Sans;
background-color: ${props => statusStyles[props.chipType].bgColor};
color: ${props => statusStyles[props.chipType].fontColor};
border-style: ${props => statusStyles[props.chipType].borderStyle};
Expand Down
76 changes: 60 additions & 16 deletions plugin-hrm-form/src/components/teamsView/teamsViewSorting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@

import { AgentsDataTable, TaskHelper, Manager } from '@twilio/flex-ui';
import { SupervisorWorkerState } from '@twilio/flex-ui/src/state/State.definition';
import { differenceInSeconds } from 'date-fns';

import { getAseloFeatureFlags } from '../../hrmConfig';

const ACTIVITIES = Array.from(Manager.getInstance().store.getState().flex.worker.activities.values()).reduce(
(accum, activity, currIndex) => {
accum[activity.name] = currIndex;
return accum;
},
{},
);
const activities = Manager.getInstance()?.store.getState()?.flex?.worker?.activities;
const WORKER_ACTIVITIES = activities
? Array.from(activities.values()).reduce((accum, activity, currIndex) => {
accum[activity.name] = currIndex;
return accum;
}, {})
: {};

/**
* Converts a duration string in the format "HH:MM:SS" or "MM:SS" to seconds.
Expand Down Expand Up @@ -101,8 +100,8 @@ const sortWorkersByChatDuration = (a: SupervisorWorkerState, b: SupervisorWorker
};

const sortWorkersByActivity = (a: SupervisorWorkerState, b: SupervisorWorkerState) => {
const aActivityValue = ACTIVITIES[a?.worker.activityName];
const bActivityValue = ACTIVITIES[b?.worker.activityName];
const aActivityValue = WORKER_ACTIVITIES[a?.worker.activityName];
const bActivityValue = WORKER_ACTIVITIES[b?.worker.activityName];

// Place available workers at the top
const aAvailability = a.worker.isAvailable ? 1 : 0;
Expand Down Expand Up @@ -145,6 +144,53 @@ export const sortSkills = (a: SupervisorWorkerState, b: SupervisorWorkerState) =
return aSkills - bSkills;
};

/**
* Converts a duration string in the format "59s", "59:59", "23h" or "23h 59min", "59d" or "5d 3h" to seconds, and '30+d' to seconds.
*/
export const convertDurationToSeconds = (duration: string): number => {
// Handle the "59:59" format (minutes and seconds)
if (duration.includes(':')) {
const [minutes, secs] = duration.split(':').map(Number);
return (minutes || 0) * 60 + (secs || 0);
}

// Handle the "30+d" format
if (duration.includes('+d')) {
const days = parseInt(duration.split('d')[0], 10);
return days * 60 * 60 * 24;
}

// Handle all other formats
const timeParts = duration.match(/\d+\s*[a-z]+/gi); // e.g. "23h 59min" or "59min"
let seconds = 0;

timeParts.forEach(timePart => {
const value = parseInt(timePart, 10);
const unit = timePart.match(/[a-z]+/i)[0];

switch (unit) {
case 's':
seconds += value;
break;
case 'min':
case 'm':
seconds += value * 60;
break;
case 'h':
seconds += value * 60 * 60;
break;
case 'd':
seconds += value * 60 * 60 * 24;
break;
default:
console.warn(`Unrecognized time unit: ${unit} for value: ${value}`);
break;
}
});

return seconds;
};

/**
* Sort by Status/Activity column
*
Expand All @@ -160,12 +206,11 @@ export const sortStatusColumn = (a: SupervisorWorkerState, b: SupervisorWorkerSt
return a.worker.isAvailable ? 1 : -1;
}

const aActivityValue = ACTIVITIES[a.worker.activityName] || 0;
const bActivityValue = ACTIVITIES[b.worker.activityName] || 0;
const aActivityValue = WORKER_ACTIVITIES[a.worker.activityName] || 0;
const bActivityValue = WORKER_ACTIVITIES[b.worker.activityName] || 0;

// dateUpdated is a string in the format "YYYY-MM-DDTHH:MM:SSZ"
const aUpdatedAt = differenceInSeconds(new Date(), new Date(a.worker.dateUpdated));
const bUpdatedAt = differenceInSeconds(new Date(), new Date(b.worker.dateUpdated));
const aUpdatedAt = convertDurationToSeconds(a?.worker?.activityDuration || '');
const bUpdatedAt = convertDurationToSeconds(b?.worker?.activityDuration || '');

// Place workers with "Offline" activity at the end
if (aActivityValue === 0 && bActivityValue === 0) {
Expand All @@ -181,7 +226,6 @@ export const sortStatusColumn = (a: SupervisorWorkerState, b: SupervisorWorkerSt
if (a.worker.activityName !== b.worker.activityName) {
return a.worker.activityName.localeCompare(b.worker.activityName);
}

// Sort by duration within the same activity
return aUpdatedAt - bUpdatedAt;
};
Expand Down
1 change: 1 addition & 0 deletions plugin-hrm-form/src/styles/typography.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,5 @@ SomethingWentWrongText.displayName = 'SomethingWentWrongText';

export const OpaqueText = styled('span')`
opacity: 0.7;
font-size: 12px;
`;
1 change: 1 addition & 0 deletions plugin-hrm-form/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ export type FeatureFlags = {
enable_separate_timeline_view: boolean; // Enables a limited inline case timelinbe with a link to the full timeline
enable_sort_cases: boolean; // Enables Sorting at Case List
enable_teams_view_enhancements: boolean; // Enables custom Teams View UI
enable_teams_view_enhancements2: boolean; // Enables custom Teams View UI with labels
enable_transfers: boolean; // Enables Transfering Contacts
enable_twilio_transcripts: boolean; // Enables Viewing Transcripts Stored at Twilio
enable_upload_documents: boolean; // Enables Case Documents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"enable_resources_elastic_search": true,
"enable_save_insights": true,
"enable_teams_view_enhancements": true,
"enable_teams_view_enhancements2": true,
"enable_voice_recordings": false
},
"contact_save_frequency": "onTabChange",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"attributes": {
"feature_flags": {},
"feature_flags": {
"enable_teams_view_enhancements2": true
},
"form_definitions_base_url": "https://assets-staging.tl.techmatters.org/form-definitions/",
"resources_base_url": "https://hrm-staging.tl.techmatters.org",
"logo_url" : "https://aselo-logo.s3.amazonaws.com/200+transparent+background+no+TM+staging.png"
Expand Down

0 comments on commit 70fd21c

Please sign in to comment.