NTP-28762 | Bulk upload NDRA (#1339)

* NTP-28762 | Bulk upload NDRA

* NTP-28762 | Bulk upload NDRA

* NTP-28762 | Bulk upload NDRA

* NTP-28762 | Bulk upload NDRA

* NTP-28762 | Bulk NDRA Upload flow

* NTP-28762 | Text change

* NTP-28762 | Bulk NDRA Upload flow
This commit is contained in:
Mantri Ramkishor
2025-01-28 18:23:01 +05:30
committed by GitHub
parent a4dd702c7c
commit 1b6d218416
9 changed files with 151 additions and 20 deletions

View File

@@ -3,6 +3,7 @@ import { TabItem, Tabs } from '@navi/web-ui/lib/components';
import {
BULK_UPLOAD_DRAWER_TABS,
CREATE_NEW,
NDRA,
ROLE_OPTIONS,
UPDATE_EXISTING,
UPLOAD_NUMBERS,
@@ -19,6 +20,7 @@ import { SelectPickerOptionProps } from '@cp/src/components/interfaces';
import { RootState } from '@cp/src/store';
import { Roles } from '../../auth/constants/AuthConstants';
import { TabsChild } from '@navi/web-ui/lib/components/Tabs/types';
import CircularLoaderIcon from '../../Enach/Constants/CircularLoaderIconCopy';
type bulkUploaderDrawerProps = {
showUploadDrawer: boolean;
@@ -29,11 +31,14 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
const isCreateUserFeatureFlag = useSelector(
(store: RootState) => store.common.featureFlags?.createUserFeatureFlag
);
const isNdraUploadFeatureFlag = useSelector(
(store: RootState) => store.common.featureFlags?.ndraUploadAccess
);
const user = useSelector((state: RootState) => state.common.userData);
const isNumberManagementRole = user?.roles?.includes(Roles.ROLE_NUMBER_MANAGEMENT);
const [selectedTab, setSelectedTab] = useState(CREATE_NEW);
const [selectedTab, setSelectedTab] = useState<string>();
const [createSelectedRole, setCreateSelectedRole] = useState<
SelectPickerOptionProps | undefined
>();
@@ -46,6 +51,7 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
const [createUploadFile, setCreateUploadFile] = useState<File | null>(null);
const [editUploadFile, setEditUploadFile] = useState<File | null>(null);
const [numberUploadFile, setNumberUploadFile] = useState<File | null>(null);
const [ndraUploadFile, setNdraUploadFile] = useState<File | null>(null);
const handleCreateFileUpload = (file: File) => {
setCreateUploadFile(file);
@@ -62,18 +68,27 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
setUploadedFile(file);
};
const handleNdraFileUpload = (file: File) => {
setNdraUploadFile(file);
setUploadedFile(file);
};
useEffect(() => {
if (selectedTab) return;
if (isCreateUserFeatureFlag) {
setSelectedTab(CREATE_NEW);
setUploadedFile(createUploadFile);
} else if (isNumberManagementRole) {
setSelectedTab(UPLOAD_NUMBERS);
setUploadedFile(numberUploadFile);
} else if (isNdraUploadFeatureFlag) {
setSelectedTab(NDRA);
setUploadedFile(ndraUploadFile);
} else {
setSelectedTab(UPDATE_EXISTING);
setUploadedFile(editUploadFile);
}
}, [isCreateUserFeatureFlag]);
}, [isCreateUserFeatureFlag, isNdraUploadFeatureFlag]);
const handleTabChange = (newTab: string) => {
setSelectedTab(newTab);
@@ -81,6 +96,8 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
setUploadedFile(createUploadFile);
} else if (newTab === UPDATE_EXISTING) {
setUploadedFile(editUploadFile);
} else if (newTab === NDRA) {
setUploadedFile(ndraUploadFile);
} else {
setUploadedFile(numberUploadFile);
}
@@ -112,6 +129,37 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
setCreateUploadFile(null);
setIsUploadDisabled(false);
});
} else if (selectedTab === NDRA && ndraUploadFile) {
const url = getApiUrl(ApiKeys.UPLOAD_NDRA);
const formData = new FormData();
formData.append('file', ndraUploadFile);
axiosInstance
.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data',
donotHandleError: true
}
})
.then(response => {
toast.success('NDRA upload successful');
setShowUploadDrawer(false);
})
.catch(error => {
const errorMessage = error?.response?.data?.message;
toast.error(
errorMessage || (
<div>
Upload failed! Check upload history <br />
for more info
</div>
)
);
setShowUploadDrawer(false);
})
.finally(() => {
setNdraUploadFile(null);
setIsUploadDisabled(false);
});
} else {
if (editUploadFile) {
const url = getApiUrl(ApiKeys.UPLOAD_EDIT_AGENTS);
@@ -179,6 +227,10 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
return createActionType ? !validationOfFile(numberUploadFile) : true;
}
if (selectedTab === NDRA) {
return !validationOfFile(ndraUploadFile);
}
if (selectedTab === CREATE_NEW) {
return createSelectedRole ? !validationOfFile(createUploadFile) : true;
}
@@ -191,6 +243,8 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
return handleNumberFileUpload;
} else if (selectedTab === CREATE_NEW) {
return handleCreateFileUpload;
} else if (selectedTab === NDRA) {
return handleNdraFileUpload;
} else {
return handleEditFileUpload;
}
@@ -201,6 +255,8 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
return numberUploadFile;
} else if (selectedTab === CREATE_NEW) {
return createUploadFile;
} else if (selectedTab === NDRA) {
return ndraUploadFile;
} else {
return editUploadFile;
}
@@ -211,6 +267,8 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
return setNumberUploadFile;
} else if (selectedTab === CREATE_NEW) {
return setCreateUploadFile;
} else if (selectedTab === NDRA) {
return setNdraUploadFile;
} else {
return setEditUploadFile;
}
@@ -221,6 +279,8 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
return CREATE_NEW;
} else if (isNumberManagementRole) {
return UPLOAD_NUMBERS;
} else if (isNdraUploadFeatureFlag) {
return NDRA;
} else {
return UPDATE_EXISTING;
}
@@ -261,6 +321,9 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
if (item.key === UPLOAD_NUMBERS && !isNumberManagementRole) {
return acc;
}
if (item.key === NDRA && !isNdraUploadFeatureFlag) {
return acc;
}
{
acc.push(
<TabItem key={item.key} label={item.value}>
@@ -309,7 +372,10 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
</>
) : null}
<Typography variant="p5" className="text-grayscale-2 pb-1">
Upload a CSV to update user details
Upload a CSV to{' '}
{selectedTab === NDRA
? 'generate NDRA certificates'
: 'update user details'}
</Typography>
<FileUploader
key={item.key}
@@ -318,6 +384,14 @@ const BulkUploaderDrawer = ({ showUploadDrawer, setShowUploadDrawer }: bulkUploa
uploadedFile={getUploadedFileHandler()}
setUploadedFile={setUploadedFileHandler()}
/>
{isUploadDisabled ? (
<div className="flex items-center gap-1 mt-4">
<CircularLoaderIcon fill="var(--navi-color-gray-c3)" />
<Typography variant="h5" color="var(--navi-color-gray-c3)">
Validating...
</Typography>
</div>
) : null}
</div>
) : null}
</TabItem>

View File

@@ -9,6 +9,7 @@ import ErrorIcon from '@cp/src/assets/icons/ErrorIcon';
import {
BULK_UPLOAD_FLOW,
CREATE_NEW,
NDRA,
ROLE_OPTIONS,
UPLOAD_NUMBERS,
validationOfFile
@@ -31,13 +32,14 @@ const FileUploader = ({
const hiddenFileInput = useRef<HTMLInputElement>(null);
const [isDragging, setIsDragging] = useState(false);
const [hasError, setHasError] = useState(false);
const isSelectedTabCreateNew = selectedTab === CREATE_NEW;
const handleDownloadClick = () => {
if (selectedTab === CREATE_NEW) {
dispatch(downloadSampleFile(BULK_UPLOAD_FLOW.CREATE));
} else if (selectedTab === UPLOAD_NUMBERS) {
dispatch(downloadSampleFile(BULK_UPLOAD_FLOW.NUMBER));
} else if (selectedTab === NDRA) {
dispatch(downloadSampleFile(BULK_UPLOAD_FLOW.NDRA));
} else {
dispatch(downloadSampleFile(BULK_UPLOAD_FLOW.EDIT));
}

View File

@@ -6,10 +6,16 @@ import LoaderIcon from '@cp/src/assets/icons/LoaderIcon';
import ErrorIconOutline from '@cp/src/assets/icons/ErrorIconOutline';
import { useMemo } from 'react';
import { FILE_STATUS } from '../../AmeyoUtility/types';
import { fetchFailureReportFile, fetchFile } from './actions/uploadHistoryDrawerActions';
import {
fetchFailureReportFile,
fetchFile,
fetchNdraFailureFile
} from './actions/uploadHistoryDrawerActions';
import dayjs from 'dayjs';
import { DateTimeFormat } from '@cp/src/components/DateTimePicker/constants';
import { errorActionType, successActionType } from './types.ts/types';
import { BulkUploadFileType, errorActionType, successActionType } from './types.ts/types';
import { Tooltip, TooltipContent, TooltipTrigger } from '@cp/src/components/TooltipV2/TooltipV2';
import cx from 'classnames';
export const useColumnDefs = (): {
columnDefs: ColDef[];
@@ -20,6 +26,15 @@ export const useColumnDefs = (): {
fetchFile(fileReferenceId);
}
};
const handleFailureReportButtonClick = (fileReferenceId: string, type: string) => {
if (type === BulkUploadFileType.UPLOAD_NDRA_CERTIFICATE) {
fetchNdraFailureFile(fileReferenceId);
return;
}
fetchFailureReportFile(fileReferenceId);
};
const columnDefs: ColDef[] = useMemo(
() => [
{
@@ -150,11 +165,25 @@ export const useColumnDefs = (): {
onClick={() => handleFileButtonClick(data?.referenceId, data?.status)}
startAdornment={data?.status && icon}
>
<span className={styles.buttonText}>
{data?.status && (data?.status === 'COMPLETED' || data?.status === 'FAILED')
? 'Download'
: 'In Progress'}
</span>
<Tooltip placement="top" hideStrategy="referenceHidden">
<TooltipTrigger>
<div
className={cx(
styles.buttonText,
data?.originalFileName ? 'truncate block w-[80px]' : ''
)}
>
{data?.status && (data?.status === 'COMPLETED' || data?.status === 'FAILED')
? `${data?.originalFileName || 'Download'}`
: `${data?.originalFileName || 'In Progress'}`}
</div>
</TooltipTrigger>
{data?.originalFileName && (
<TooltipContent className={'tooltipWrapper'}>
{data?.originalFileName}
</TooltipContent>
)}
</Tooltip>
</Button>
) : (
<Typography variant="p4" color="var(--grayscale-2)">
@@ -176,7 +205,7 @@ export const useColumnDefs = (): {
<Button
variant="text"
className={styles.buttonWrapper}
onClick={() => fetchFailureReportFile(data?.referenceId)}
onClick={() => handleFailureReportButtonClick(data?.referenceId, data?.type)}
startAdornment={<Download />}
>
<span className={styles.buttonText}>Download</span>

View File

@@ -100,6 +100,7 @@
.ag-full-width-row .ag-cell-wrapper.ag-row-group {
padding-left: 19px;
padding-right: 4px;
border: 0px;
}
.ag-header-cell-text {
width: auto;

View File

@@ -53,3 +53,11 @@ export const fetchFile = (fileReferenceId: string) => {
getFileDownload(fileUrl);
});
};
export const fetchNdraFailureFile = (fileReferenceId: string) => {
const url = getApiUrl(ApiKeys.DOWNLOAD_NDRA_FAILURE_FILE, { referenceId: fileReferenceId });
return axiosInstance.get(url).then(response => {
const fileUrl = response?.data;
getFileDownload(fileUrl);
});
};

View File

@@ -2,16 +2,22 @@ export const successActionType: { [key: string]: string } = {
UPLOAD_USERS: 'Created new agents',
UPLOAD_EDIT_USER: 'Edited bulk agents',
UPLOAD_USER_NUMBER_MAPPING_ADD: 'Adding numbers',
UPLOAD_USER_NUMBER_MAPPING_DELETE: 'Deleting numbers'
UPLOAD_USER_NUMBER_MAPPING_DELETE: 'Deleting numbers',
UPLOAD_NDRA_CERTIFICATE: 'Uploaded NDRA'
};
export const errorActionType: { [key: string]: string } = {
UPLOAD_USERS: 'Failed to create',
UPLOAD_EDIT_USER: 'Failed to edit',
UPLOAD_USER_NUMBER_MAPPING_ADD: 'Failed to add numbers',
UPLOAD_USER_NUMBER_MAPPING_DELETE: 'Failed to delete numbers'
UPLOAD_USER_NUMBER_MAPPING_DELETE: 'Failed to delete numbers',
UPLOAD_NDRA_CERTIFICATE: 'Failed to upload NDRA'
};
export enum BulkUploadFileType {
UPLOAD_NDRA_CERTIFICATE = 'UPLOAD_NDRA_CERTIFICATE'
}
export const originalFileStatus: { [key: string]: string } = {
INITIATED: 'In progress',
IN_PROGRESS: 'In progress',

View File

@@ -336,7 +336,8 @@ const AllAgents = () => {
startTime: item.startTime,
owner: item.owner,
status: item.status,
referenceId: item.referenceId
referenceId: item.referenceId,
originalFileName: item.originalFileName
};
});
};

View File

@@ -85,15 +85,19 @@ export interface GridEvent {
export const BULK_UPLOAD_DRAWER_TABS = [
{
key: 'createNew',
value: 'Create New User'
value: 'New users'
},
{
key: 'updateExisting',
value: 'Update Existing Users'
value: 'Existing users'
},
{
key: 'uploadNumbers',
value: 'Update Phone Numbers'
value: 'Phone numbers'
},
{
key: 'uploadNdra',
value: 'NDRA'
}
];
@@ -126,12 +130,14 @@ export const UPLOAD_OPTIONS = [
export enum BULK_UPLOAD_FLOW {
CREATE = 'create',
EDIT = 'edit',
NUMBER = 'user_number_mapping'
NUMBER = 'user_number_mapping',
NDRA = 'ndra'
}
export const CREATE_NEW = 'createNew';
export const UPDATE_EXISTING = 'updateExisting';
export const UPLOAD_NUMBERS = 'uploadNumbers';
export const NDRA = 'uploadNdra';
export const uploadAgentsSampleCsvFile =
'phone_number,email_address,name,active,reporting_manager_email_address,state,employee_id,primary_language,secondary_language,tertiary_language,bucket_group,agent_center\n' +

View File

@@ -303,7 +303,9 @@ export enum ApiKeys {
UPDATE_ANOMALY_TICKET,
RESOLVE_ANOMALY_TICKET,
GET_ANOMALY_RCA_QUESTION,
GET_ANOMALY_ACTIVITY_LOG
GET_ANOMALY_ACTIVITY_LOG,
UPLOAD_NDRA,
DOWNLOAD_NDRA_FAILURE_FILE
}
// TODO: try to get rid of `as`
@@ -608,6 +610,8 @@ API_URLS[ApiKeys.GET_ANOMALY_RCA_QUESTION] =
'/longhorn/anomaly-tracker/question-tree/rca/{anomalyReferenceId}';
API_URLS[ApiKeys.GET_ANOMALY_ACTIVITY_LOG] =
'/longhorn/anomaly-tracker/activity-logs/{anomalyReferenceId}';
API_URLS[ApiKeys.UPLOAD_NDRA] = '/agent/bulk/ndra';
API_URLS[ApiKeys.DOWNLOAD_NDRA_FAILURE_FILE] = '/uploads/v2/{referenceId}/failure-report';
// TODO: try to get rid of `as`
const MOCK_API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;