TP-78258 | generic upload component (#1076)
* TP-78258 | generic upload component * TP-78258 | dra and generic upload component * TP-78258 | dra temp * TP-78258 | dra temp * TP-69871 | fix issues * TP-69871 | fix issues * TP-69871 | fix issues * TP-78258 | dra certificate fixes * TP-69871 | fixed uat feedbacks * TP-69871 | fixed uat feedbacks * TP-69871 | fixed uat feedbacks * TP-69871 | fixed uat feedbacks * TP-69871 | fixed uat feedbacks
This commit is contained in:
12
src/App.tsx
12
src/App.tsx
@@ -211,12 +211,12 @@ const App = () => {
|
||||
registerNavigateAndDispatch(navigate, dispatch);
|
||||
const { config } = window;
|
||||
|
||||
initApm({
|
||||
serviceName: config.APM_APP_NAME,
|
||||
serverUrl: config.APM_BASE_URL,
|
||||
serviceVersion: '1.0.0',
|
||||
environment: config.ENV
|
||||
});
|
||||
// initApm({
|
||||
// serviceName: config.APM_APP_NAME,
|
||||
// serverUrl: config.APM_BASE_URL,
|
||||
// serviceVersion: '1.0.0',
|
||||
// environment: config.ENV
|
||||
// });
|
||||
const setFCMTokenRedux = (fcmToken: string) => {
|
||||
dispatch(setFcmToken(fcmToken));
|
||||
};
|
||||
|
||||
@@ -5,19 +5,17 @@ const ErrorIconOutline: React.FC<IconProps> = props => {
|
||||
const { className, size = 16, fillColor = '#E92C2C', onClick } = props;
|
||||
return (
|
||||
<svg
|
||||
onClick={onClick}
|
||||
className={className}
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={`"0 0 ${size} ${size}"`}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.99967 4.66536C8.36634 4.66536 8.66634 4.96536 8.66634 5.33203V7.9987C8.66634 8.36536 8.36634 8.66536 7.99967 8.66536C7.63301 8.66536 7.33301 8.36536 7.33301 7.9987V5.33203C7.33301 4.96536 7.63301 4.66536 7.99967 4.66536ZM7.99301 1.33203C4.31301 1.33203 1.33301 4.3187 1.33301 7.9987C1.33301 11.6787 4.31301 14.6654 7.99301 14.6654C11.6797 14.6654 14.6663 11.6787 14.6663 7.9987C14.6663 4.3187 11.6797 1.33203 7.99301 1.33203ZM7.99967 13.332C5.05301 13.332 2.66634 10.9454 2.66634 7.9987C2.66634 5.05203 5.05301 2.66536 7.99967 2.66536C10.9463 2.66536 13.333 5.05203 13.333 7.9987C13.333 10.9454 10.9463 13.332 7.99967 13.332ZM8.66634 11.332H7.33301V9.9987H8.66634V11.332Z"
|
||||
fill={fillColor}
|
||||
d="M10.0013 5.83366C10.4596 5.83366 10.8346 6.20866 10.8346 6.66699V10.0003C10.8346 10.4587 10.4596 10.8337 10.0013 10.8337C9.54297 10.8337 9.16797 10.4587 9.16797 10.0003V6.66699C9.16797 6.20866 9.54297 5.83366 10.0013 5.83366ZM9.99297 1.66699C5.39297 1.66699 1.66797 5.40033 1.66797 10.0003C1.66797 14.6003 5.39297 18.3337 9.99297 18.3337C14.6013 18.3337 18.3346 14.6003 18.3346 10.0003C18.3346 5.40033 14.6013 1.66699 9.99297 1.66699ZM10.0013 16.667C6.31797 16.667 3.33464 13.6837 3.33464 10.0003C3.33464 6.31699 6.31797 3.33366 10.0013 3.33366C13.6846 3.33366 16.668 6.31699 16.668 10.0003C16.668 13.6837 13.6846 16.667 10.0013 16.667ZM10.8346 14.167H9.16797V12.5003H10.8346V14.167Z"
|
||||
fill="#E92C2C"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
29
src/assets/images/icons/DownloadIcon.tsx
Normal file
29
src/assets/images/icons/DownloadIcon.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
|
||||
type DownloadIconProps = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
function DownloadIcon({ width = 20, height = 20 }: DownloadIconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<mask id="mask0_19520_36027" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
|
||||
<rect width="20" height="20" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_19520_36027)">
|
||||
<path
|
||||
d="M4.9987 16.6663C4.54036 16.6663 4.14814 16.5033 3.82203 16.1772C3.49536 15.8505 3.33203 15.458 3.33203 14.9997V12.4997H4.9987V14.9997H14.9987V12.4997H16.6654V14.9997C16.6654 15.458 16.5023 15.8505 16.1762 16.1772C15.8495 16.5033 15.457 16.6663 14.9987 16.6663H4.9987ZM9.9987 13.333L5.83203 9.16634L6.9987 7.95801L9.16536 10.1247V3.33301H10.832V10.1247L12.9987 7.95801L14.1654 9.16634L9.9987 13.333Z"
|
||||
fill="#0276FE"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default DownloadIcon;
|
||||
28
src/assets/images/icons/FileIcon.tsx
Normal file
28
src/assets/images/icons/FileIcon.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
type FileIconProps = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
function FileIcon({ width = 20, height = 20 }: FileIconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<mask id="mask0_19536_36083" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
|
||||
<rect width="20" height="20" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_19536_36083)">
|
||||
<path
|
||||
d="M7 15H13V13.5H7V15ZM7 12H13V10.5H7V12ZM5.49417 18C5.08139 18 4.72917 17.8531 4.4375 17.5594C4.14583 17.2656 4 16.9125 4 16.5V3.5C4 3.0875 4.14687 2.73438 4.44062 2.44063C4.73437 2.14688 5.0875 2 5.5 2H12L16 6V16.5C16 16.9125 15.8531 17.2656 15.5592 17.5594C15.2653 17.8531 14.9119 18 14.4992 18H5.49417ZM11 7V3.5H5.5V16.5H14.5V7H11Z"
|
||||
fill="#0276FE"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export default FileIcon;
|
||||
185
src/components/UploadComponent/UploadComponent.tsx
Normal file
185
src/components/UploadComponent/UploadComponent.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import cx from 'classnames';
|
||||
import UploadFileIcon from '@cp/assets/icons/UploadFileIcon';
|
||||
import Button from '@primitives/Button';
|
||||
import Typography from '@primitives/Typography';
|
||||
import { noop } from '@utils/common';
|
||||
import UploadSuccessComponent from '@cp/components/UploadComponent/UploadSuccessComponent';
|
||||
import { RegisterOptions } from 'react-hook-form';
|
||||
import { useMergeRefs } from '@floating-ui/react';
|
||||
|
||||
export type ErrorMessage = {
|
||||
fileName: string | null;
|
||||
message: string | boolean;
|
||||
};
|
||||
|
||||
type UploadComponentProps = {
|
||||
isSampleFileAvailable?: boolean;
|
||||
supportedFile?: string;
|
||||
MAX_FILE_SIZE_IN_MB?: number;
|
||||
validationOfFile?: (file: File | undefined) => boolean;
|
||||
file?: File | null;
|
||||
setFile: (file: File | null) => void;
|
||||
setHasError: (errorMessage: ErrorMessage) => void;
|
||||
isValidationInfoVisible?: boolean;
|
||||
uploadContentContainer?: (
|
||||
handleUploadClick: (e: React.ChangeEvent) => void
|
||||
) => React.ReactElement;
|
||||
containerClassName?: string;
|
||||
register?: RegisterOptions;
|
||||
refProp?: React.Ref<HTMLInputElement>;
|
||||
};
|
||||
|
||||
function UploadComponent({
|
||||
isSampleFileAvailable = false,
|
||||
MAX_FILE_SIZE_IN_MB = 5,
|
||||
supportedFile = '.csv',
|
||||
validationOfFile,
|
||||
file: uploadedFile = null,
|
||||
setFile: setUploadedFile,
|
||||
setHasError = (errorMessage: ErrorMessage) => noop,
|
||||
isValidationInfoVisible = false,
|
||||
uploadContentContainer,
|
||||
containerClassName = '',
|
||||
refProp,
|
||||
...rest
|
||||
}: UploadComponentProps) {
|
||||
const hiddenFileInput = useRef<HTMLInputElement>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const mergedRef = useMergeRefs([hiddenFileInput, refProp]);
|
||||
|
||||
const defaultValidationOfFile = (file: File | undefined) => {
|
||||
if (file) {
|
||||
if (file?.size > MAX_FILE_SIZE) {
|
||||
return `File size is greater than ${MAX_FILE_SIZE_IN_MB} MB`;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleFile = (file: File | undefined) => {
|
||||
if (file) {
|
||||
setUploadedFile(file);
|
||||
const validationFunc = validationOfFile || defaultValidationOfFile;
|
||||
const fileValidationMessage = validationFunc(file);
|
||||
if (!fileValidationMessage) {
|
||||
setUploadedFile(file);
|
||||
} else {
|
||||
setHasError({
|
||||
fileName: file.name,
|
||||
message: fileValidationMessage
|
||||
});
|
||||
setUploadedFile(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const selectedFile = event.target.files?.[0];
|
||||
handleFile(selectedFile);
|
||||
};
|
||||
|
||||
const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setIsDragging(true);
|
||||
};
|
||||
const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(true);
|
||||
};
|
||||
|
||||
const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setIsDragging(false);
|
||||
const selectedFile = event.dataTransfer.files?.[0];
|
||||
handleFile(selectedFile);
|
||||
};
|
||||
const MAX_FILE_SIZE = 1e6 * MAX_FILE_SIZE_IN_MB;
|
||||
|
||||
const handleUploadClick = (e: React.ChangeEvent<any>) => {
|
||||
e.preventDefault();
|
||||
hiddenFileInput.current?.focus();
|
||||
hiddenFileInput.current?.click();
|
||||
};
|
||||
|
||||
const handleDownloadClick = (e: React.ChangeEvent<any>) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
return (
|
||||
<div className="">
|
||||
<div className="">
|
||||
<div
|
||||
className={cx(
|
||||
{
|
||||
'border-dashed border-[2px] border-[--blue-base] bg-[--blue-light-bg] p-[24px] m-[12px] flex flex-col items-center justify-space-evenly rounded-lg':
|
||||
true,
|
||||
['!bg-[--blue-bg] border-1 border-solid border-[--blue-bg]']:
|
||||
isDragging && !uploadedFile
|
||||
},
|
||||
containerClassName
|
||||
)}
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={handleDrop}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
<input
|
||||
id="fileUpload"
|
||||
type="file"
|
||||
accept={supportedFile}
|
||||
onChange={handleChange}
|
||||
onClick={(e: React.ChangeEvent<any>) => {
|
||||
e.target.value = '';
|
||||
}}
|
||||
ref={hiddenFileInput}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
{uploadContentContainer ? (
|
||||
uploadContentContainer(handleUploadClick)
|
||||
) : (
|
||||
<div className="upload-container-content">
|
||||
<UploadFileIcon />
|
||||
<div className={'text-[--grayscale-content-2]'}>
|
||||
<Button variant="text" onClick={handleUploadClick}>
|
||||
Click to Select
|
||||
</Button>{' '}
|
||||
or drag and drop here
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isValidationInfoVisible ? (
|
||||
<div className={'flex items-center gap-2 '}>
|
||||
<Typography variant={'p5'} className="text-[--grayscale-3] text-center pt-2 pb-2">
|
||||
Upload only .csv
|
||||
</Typography>
|
||||
<div className="w-[1px] h-[20px] bg-[#caced5] mt-[4px]" />
|
||||
<Typography variant={'p5'} className="text-[--grayscale-3] text-center pt-2 pb-2">
|
||||
Max 5 MB size allowed
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
{isSampleFileAvailable ? (
|
||||
<Button
|
||||
onClick={handleDownloadClick}
|
||||
variant="text"
|
||||
className="text-center text-[--blue-base]"
|
||||
>
|
||||
{' '}
|
||||
Download Sample file
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UploadComponent;
|
||||
53
src/components/UploadComponent/UploadErrorComponent.tsx
Normal file
53
src/components/UploadComponent/UploadErrorComponent.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import styles from '@cp/pages/AmeyoUtility/components/UploadNumbers/index.module.scss';
|
||||
import { CheckIcon, CloseIcon } from '@icons';
|
||||
import Button from '@primitives/Button';
|
||||
import uploadComponent from '@cp/components/UploadComponent/UploadComponent';
|
||||
import ErrorIconOutline from '@cp/assets/icons/ErrorIconOutline';
|
||||
import cx from 'classnames';
|
||||
|
||||
type UploadErrorComponent = {
|
||||
message: string | boolean;
|
||||
onCancel: () => void;
|
||||
containerClassName?: string;
|
||||
fileName: string;
|
||||
};
|
||||
|
||||
function UploadErrorComponent({
|
||||
message,
|
||||
onCancel,
|
||||
containerClassName,
|
||||
fileName
|
||||
}: UploadErrorComponent) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cx(
|
||||
'flex justify-between items-center text-gray-600 p-1.5 mt-3.5 rounded-lg border border-solid border-[--red-base] bg-[#FFF5F5] h-[40px] ml-3 gap-2',
|
||||
containerClassName
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<ErrorIconOutline size={20} fillColor1="var(--red-base)" />
|
||||
<div className="max-w-[250px] text-ellipsis whitespace-nowrap overflow-hidden">
|
||||
{fileName}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onCancel();
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-red-400 ml-[20px] mt-[10px]"> {message}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default UploadErrorComponent;
|
||||
50
src/components/UploadComponent/UploadSuccessComponent.tsx
Normal file
50
src/components/UploadComponent/UploadSuccessComponent.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import styles from '@cp/pages/AmeyoUtility/components/UploadNumbers/index.module.scss';
|
||||
import { CheckIcon, CloseIcon } from '@icons';
|
||||
import Button from '@primitives/Button';
|
||||
import cx from 'classnames';
|
||||
import CircularLoader from '@navi/web-ui/lib/icons/CircularLoaderIcon';
|
||||
|
||||
type UploadSuccessComponent = {
|
||||
name: string;
|
||||
onCancel: () => void;
|
||||
containerClassName?: string;
|
||||
isInProgress?: boolean;
|
||||
};
|
||||
|
||||
function UploadSuccessComponent({
|
||||
name,
|
||||
onCancel,
|
||||
containerClassName,
|
||||
isInProgress
|
||||
}: UploadSuccessComponent) {
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'flex items-center justify-between text-gray-600 p-1.5 mt-3.5 rounded-lg border border-solid border-green-500 bg-[#f5fff8] h-[40px] ml-3 gap-2',
|
||||
containerClassName
|
||||
)}
|
||||
>
|
||||
<div className=" flex gap-2 items-center">
|
||||
<div>
|
||||
{isInProgress ? (
|
||||
<CircularLoader
|
||||
width={20}
|
||||
height={20}
|
||||
color="var(--green-base)"
|
||||
fill={'var(--green-base)'}
|
||||
/>
|
||||
) : (
|
||||
<CheckIcon width={20} height={20} color="var(--green-base)" fill="var(--green-base)" />
|
||||
)}
|
||||
</div>
|
||||
<div className="max-w-[250px] text-ellipsis overflow-hidden">{name}</div>
|
||||
</div>
|
||||
<Button variant="text" onClick={onCancel}>
|
||||
<CloseIcon />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UploadSuccessComponent;
|
||||
4
src/components/UploadComponent/index.tsx
Normal file
4
src/components/UploadComponent/index.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
import uploadComponent from '@cp/components/UploadComponent/UploadComponent';
|
||||
import UseUpload from '@cp/components/UploadComponent/useUpload';
|
||||
|
||||
export { uploadComponent, UseUpload };
|
||||
47
src/components/UploadComponent/useUpload.ts
Normal file
47
src/components/UploadComponent/useUpload.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import axiosInstance from '@cp/utils/ApiHelper';
|
||||
import { ErrorMessage } from '@cp/components/UploadComponent/UploadComponent';
|
||||
|
||||
export enum UPLOAD_STATUS {
|
||||
IDLE = 'IDLE',
|
||||
IN_PROGRESS = 'IN_PROGRESS',
|
||||
SUCCESS = 'SUCCESS',
|
||||
ERROR = 'ERROR'
|
||||
}
|
||||
|
||||
interface UseUploadParams {
|
||||
onFileUploadedSuccess?: () => void;
|
||||
onFileUploadedError?: () => void;
|
||||
}
|
||||
|
||||
function UseUpload({ onFileUploadedSuccess, onFileUploadedError }: UseUploadParams) {
|
||||
const [fileStatus, setFileStatus] = useState(UPLOAD_STATUS.IDLE);
|
||||
const [file, setFile] = useState(null as File | null);
|
||||
const [error, setError] = useState<ErrorMessage>({ message: '', fileName: null });
|
||||
const upload = useCallback((file: File, uploadUrl: string) => {
|
||||
setFileStatus(UPLOAD_STATUS.IN_PROGRESS);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return axiosInstance
|
||||
.put(uploadUrl, formData)
|
||||
.then(_ => {
|
||||
setFileStatus(UPLOAD_STATUS.SUCCESS);
|
||||
onFileUploadedSuccess && onFileUploadedSuccess();
|
||||
})
|
||||
.catch(_ => {
|
||||
setFileStatus(UPLOAD_STATUS.ERROR);
|
||||
onFileUploadedError && onFileUploadedError();
|
||||
});
|
||||
}, []);
|
||||
|
||||
const resetFileStatus = useCallback(() => {
|
||||
setFileStatus(UPLOAD_STATUS.IDLE);
|
||||
}, []);
|
||||
|
||||
return { fileStatus, upload, resetFileStatus, setFile, file, error, setError };
|
||||
}
|
||||
|
||||
export default UseUpload;
|
||||
@@ -46,6 +46,9 @@ import InactiveTextContainer from './components/form/InactiveTextContainer';
|
||||
import styles from './AgentForm.module.scss';
|
||||
import cx from 'classnames';
|
||||
import { Typography } from '@navi/web-ui/lib/primitives';
|
||||
import UploadComponent from '@cp/components/UploadComponent/UploadComponent';
|
||||
import AgentFormUploadControl from '@cp/pages/AllAgents/AgentForm/components/form/AgentFormUploadControl';
|
||||
import AgentFormDownloadDraCertificate from '@cp/pages/AllAgents/AgentForm/components/form/AgentFormDownloadDraCertificate';
|
||||
|
||||
export const AgentForm = forwardRef((props: AgentFormProps, ref) => {
|
||||
const {
|
||||
@@ -86,7 +89,8 @@ export const AgentForm = forwardRef((props: AgentFormProps, ref) => {
|
||||
getValues,
|
||||
setValue,
|
||||
watch,
|
||||
setFocus
|
||||
setFocus,
|
||||
register
|
||||
} = useForm<AgentFormValues>({
|
||||
defaultValues
|
||||
});
|
||||
@@ -132,6 +136,10 @@ export const AgentForm = forwardRef((props: AgentFormProps, ref) => {
|
||||
const enableStateEdit = rolesContainAnyOfV1(agentStateAndLanguageEditRoles);
|
||||
const disableStateEdit = !!(!enableStateEdit && agentData?.referenceId);
|
||||
const isUserManagementRole = user?.roles?.includes(Roles.ROLE_USER_MANAGEMENT_ADMIN);
|
||||
const isDraUploadAccessFeatureFlagEnabled = useSelector(
|
||||
(store: RootState) => store.common.featureFlags?.draUploadAccess
|
||||
);
|
||||
|
||||
const getAgencyCode = () => {
|
||||
if (isEditable || isUserManagementRole) {
|
||||
return watch('agencyCodeRequested');
|
||||
@@ -179,7 +187,9 @@ export const AgentForm = forwardRef((props: AgentFormProps, ref) => {
|
||||
tertiaryRegion: agentData?.tertiaryRegion || '',
|
||||
bucketGroup: agentData?.bucketGroup,
|
||||
agencyCodeRequested: agentData?.agencyCodeRequested,
|
||||
agentCenter: agentData?.agentCenter
|
||||
agentCenter: agentData?.agentCenter,
|
||||
draCertificateView: agentData?.draCertificateView,
|
||||
draCertificate: {}
|
||||
},
|
||||
{
|
||||
keepDirty: !!isDrawerOpen
|
||||
@@ -262,7 +272,8 @@ export const AgentForm = forwardRef((props: AgentFormProps, ref) => {
|
||||
tertiaryLanguage,
|
||||
bucketGroup,
|
||||
agencyCodeRequested,
|
||||
agentCenter
|
||||
agentCenter,
|
||||
draCertificate
|
||||
} = data;
|
||||
|
||||
const createAgentRequest = {
|
||||
@@ -287,7 +298,13 @@ export const AgentForm = forwardRef((props: AgentFormProps, ref) => {
|
||||
reportingManagerReferenceId:
|
||||
managerList.find(
|
||||
manager => manager?.additionalData?.emailAddress === reportingManagerEmailAddress
|
||||
)?.additionalData?.referenceId || ''
|
||||
)?.additionalData?.referenceId || '',
|
||||
draCertificateView: {
|
||||
draCertificateUpdateRequest: {
|
||||
mode: 'ADD',
|
||||
uploadReferenceId: draCertificate?.referenceId || null
|
||||
}
|
||||
}
|
||||
};
|
||||
createAgent(createAgentRequest, setIsSubmitButtonDisabled, setShowLoader, onClose);
|
||||
};
|
||||
@@ -309,7 +326,8 @@ export const AgentForm = forwardRef((props: AgentFormProps, ref) => {
|
||||
tertiaryLanguage,
|
||||
bucketGroup,
|
||||
agencyCodeRequested,
|
||||
agentCenter
|
||||
agentCenter,
|
||||
draCertificate
|
||||
} = data;
|
||||
|
||||
const updateAgentRequest = {
|
||||
@@ -334,7 +352,17 @@ export const AgentForm = forwardRef((props: AgentFormProps, ref) => {
|
||||
reportingManagerReferenceId:
|
||||
managerList.find(
|
||||
manager => manager?.additionalData?.emailAddress === reportingManagerEmailAddress
|
||||
)?.additionalData?.referenceId || ''
|
||||
)?.additionalData?.referenceId || '',
|
||||
draCertificateView: {
|
||||
draCertificateUpdateRequest: draCertificate
|
||||
? {
|
||||
mode: 'EDIT',
|
||||
uploadReferenceId: draCertificate?.referenceId
|
||||
}
|
||||
: {
|
||||
mode: 'DELETE'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateAgent(
|
||||
@@ -706,6 +734,30 @@ export const AgentForm = forwardRef((props: AgentFormProps, ref) => {
|
||||
showRequiredIcon={false}
|
||||
/>
|
||||
</div>
|
||||
{watch('draCertificateView') ? (
|
||||
<div className="w-[364px] ml-2 mt-4">
|
||||
<AgentFormDownloadDraCertificate
|
||||
control={control}
|
||||
name={'draCertificateView'}
|
||||
rules={{ required: false }}
|
||||
isEditDisable={isEditDisabled || !isDraUploadAccessFeatureFlagEnabled}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{isDraUploadAccessFeatureFlagEnabled ? (
|
||||
<AgentFormUploadControl
|
||||
control={control}
|
||||
name={'draCertificate'}
|
||||
rules={{ required: false }}
|
||||
errors={errors}
|
||||
isEditDisabled={false}
|
||||
styles={{}}
|
||||
setSubmitButtonDisabled={setIsSubmitButtonDisabled}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import Button from '@primitives/Button';
|
||||
import cx from 'classnames';
|
||||
import FileIcon from '@cp/assets/images/icons/FileIcon';
|
||||
import DownloadIcon from '@cp/assets/images/icons/DownloadIcon';
|
||||
import { Control, useController } from 'react-hook-form';
|
||||
import { AgentFormValues } from '@cp/pages/AllAgents/AgentForm/interfaces/types';
|
||||
import { GenericObject } from '@cp/src/types/CommonConstans';
|
||||
import { getFileDownload } from '@cp/utils/commonUtils';
|
||||
import { EditIcon } from '@icons';
|
||||
import CrossIcon from '@cp/assets/icons/CrossIcon';
|
||||
|
||||
type AgentFormDownloadDraCertificateProps = {
|
||||
name: keyof AgentFormValues;
|
||||
containerClassName?: string;
|
||||
control: Control<AgentFormValues, any>;
|
||||
rules?: GenericObject;
|
||||
isEditDisable: boolean;
|
||||
};
|
||||
|
||||
function AgentFormDownloadDraCertificate({
|
||||
name,
|
||||
containerClassName,
|
||||
control,
|
||||
rules,
|
||||
isEditDisable
|
||||
}: AgentFormDownloadDraCertificateProps) {
|
||||
const { field } = useController({
|
||||
name,
|
||||
control,
|
||||
rules
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<div className="text-[--grayscale-2] text-[14px] ml-[14px] mt-[16px]">DRA Certificate</div>
|
||||
<div
|
||||
className={cx(
|
||||
'flex items-center justify-between text-gray-600 mt-3.5 rounded-lg border border-solid border-[--blue-border] bg-[#F0F6FF] h-[40px] ml-3 gap-2 p-2',
|
||||
containerClassName
|
||||
)}
|
||||
>
|
||||
<div className=" flex gap-2 items-center">
|
||||
<div>
|
||||
<FileIcon width={20} height={20} />
|
||||
</div>
|
||||
<div className="max-w-[200px] text-ellipsis overflow-hidden">
|
||||
{'Agent DRA Certificate.pdf'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="controls flex items-center gap-1 mr-2">
|
||||
{!isEditDisable ? (
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
field.onChange(null);
|
||||
}}
|
||||
>
|
||||
<CrossIcon height={16} width={16} fillColor={'var(--blue-base)'} />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
// @ts-expect-error ts-migrate(2532) FIXME: Object is possibly 'undefined'.
|
||||
getFileDownload(field?.value?.draCertificate?.uri, true);
|
||||
}}
|
||||
>
|
||||
<DownloadIcon height={20} width={20} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AgentFormDownloadDraCertificate;
|
||||
@@ -0,0 +1,137 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Control, FieldErrorsImpl, RegisterOptions, useController } from 'react-hook-form';
|
||||
import { AgentFormValues } from '../../interfaces/types';
|
||||
import useUpload, { UPLOAD_STATUS } from '@cp/components/UploadComponent/useUpload';
|
||||
import UploadComponent from '@cp/components/UploadComponent/UploadComponent';
|
||||
import UploadErrorComponent from '@cp/components/UploadComponent/UploadErrorComponent';
|
||||
import UploadIcon from '@navi/web-ui/lib/icons/UploadIcon';
|
||||
import UploadSuccessComponent from '@cp/components/UploadComponent/UploadSuccessComponent';
|
||||
import axiosInstance, { ApiKeys, getApiUrl } from '@cp/utils/ApiHelper';
|
||||
|
||||
type AgentFormUploadControlProps = {
|
||||
control: Control<AgentFormValues, any>;
|
||||
name: keyof AgentFormValues;
|
||||
rules: RegisterOptions;
|
||||
errors: Partial<FieldErrorsImpl<AgentFormValues>>;
|
||||
isEditDisabled: boolean | undefined;
|
||||
styles: CSSModuleClasses;
|
||||
inputLabel?: string;
|
||||
isRequired?: boolean;
|
||||
setSubmitButtonDisabled: (isDisabled: boolean) => void;
|
||||
};
|
||||
|
||||
const customAgentFormUploadComponent = (handleUploadClick: (e: React.ChangeEvent<any>) => void) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<UploadIcon color="var(--blue-base)" width={20} height={20} />
|
||||
<div
|
||||
className="text-[--blue-base] font-medium cursor-pointer"
|
||||
onClick={e => {
|
||||
handleUploadClick(e);
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
Click to Upload{' '}
|
||||
<span className="text-[--grayscale-content-2]">or drag and drop here</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const AgentFormUploadControl: React.FC<AgentFormUploadControlProps> = props => {
|
||||
const { control, name, rules, errors, styles, inputLabel = '', setSubmitButtonDisabled } = props;
|
||||
|
||||
const { field } = useController({
|
||||
name,
|
||||
control,
|
||||
rules
|
||||
});
|
||||
|
||||
const { file, setFile, upload, fileStatus, error, setError } = useUpload({});
|
||||
useEffect(() => {
|
||||
if (file) {
|
||||
const getUploadUrl = getApiUrl(
|
||||
ApiKeys.GET_DOCUMENT_UPLOAD_URL,
|
||||
{},
|
||||
{
|
||||
documentType: 'DRA_CERTIFICATE',
|
||||
documentContentType: 'PDF'
|
||||
}
|
||||
);
|
||||
axiosInstance.get(getUploadUrl).then(async res => {
|
||||
//presigned url fetch and pass it to upload function
|
||||
setSubmitButtonDisabled(true);
|
||||
upload(file, res.data?.preSignedPutUrl)
|
||||
?.then(() => {
|
||||
//on success
|
||||
const ackUrl = getApiUrl(ApiKeys.DOCUMENT_UPLOAD_ACK_URL);
|
||||
|
||||
axiosInstance
|
||||
.post(ackUrl, {
|
||||
referenceId: res?.data?.referenceId,
|
||||
status: 'UPLOAD_SUCCESS',
|
||||
message: ''
|
||||
})
|
||||
.then(res => {
|
||||
field.onChange(res?.data);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
setError({ message: err?.message || '', fileName: file?.name || '' });
|
||||
})
|
||||
.finally(() => {
|
||||
setSubmitButtonDisabled(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [file]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-[--grayscale-2] text-[14px] ml-[20px] mt-[16px]">
|
||||
Upload DRA Certificate
|
||||
</div>
|
||||
{error?.message ? (
|
||||
<div className={styles.errorContainer}>
|
||||
<UploadErrorComponent
|
||||
containerClassName="w-[352px] box-border ml-[18px] !px-[16px] !py-[24px]"
|
||||
message={error?.message || ''}
|
||||
fileName={error?.fileName || ''}
|
||||
onCancel={() => {
|
||||
setError({ message: '', fileName: '' });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{error?.message ? null : (
|
||||
<div>
|
||||
{file ? (
|
||||
<UploadSuccessComponent
|
||||
containerClassName="w-[348px] box-border ml-[18px] !px-[16px] !py-[24px]"
|
||||
name={file?.name || ''}
|
||||
isInProgress={fileStatus === UPLOAD_STATUS.IN_PROGRESS}
|
||||
onCancel={() => {
|
||||
setFile(null);
|
||||
field.onChange(null);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-[360px] ml-5 mt-3">
|
||||
<UploadComponent
|
||||
setFile={setFile}
|
||||
file={file}
|
||||
uploadContentContainer={customAgentFormUploadComponent}
|
||||
containerClassName="w-[348px] !py-[16px] !px-[0] box-border !m-0"
|
||||
supportedFile=".pdf"
|
||||
setHasError={setError}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentFormUploadControl;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { PageRequest } from '@cp/src/components/interfaces';
|
||||
import { Roles } from '@cp/src/pages/auth/constants/AuthConstants';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { GenericObject } from '@cp/src/types/CommonConstans';
|
||||
|
||||
export interface AgentFormProps {
|
||||
ref?: any;
|
||||
@@ -33,6 +34,12 @@ export interface AgentFormValues {
|
||||
bucketGroup: string;
|
||||
reportingManagerReferenceId?: string;
|
||||
agentCenter?: string;
|
||||
draCertificate?: GenericObject;
|
||||
draCertificateView?: {
|
||||
draCertificate: {
|
||||
uri: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface Agent {
|
||||
@@ -62,6 +69,11 @@ export interface Agent {
|
||||
agencyCodeRequested?: string;
|
||||
reportingManagerReferenceId?: string;
|
||||
agentCenter?: string;
|
||||
draCertificateView?: {
|
||||
draCertificate: {
|
||||
uri: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface AgentPageRequest extends PageRequest {
|
||||
|
||||
@@ -149,6 +149,7 @@ export const FeeWaiverHistoryColumnDefs: ColDefsType[] = [
|
||||
{
|
||||
headerName: 'Amount(incl. GST)',
|
||||
field: 'feeAmount',
|
||||
|
||||
width: 150,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const value = formatAmount(params.data?.deferredAmount, false) || '--';
|
||||
|
||||
@@ -253,7 +253,9 @@ export enum ApiKeys {
|
||||
HUMAN_REMNINDER_DISPOSE_CALL_V2,
|
||||
PHONENUMBER_SECONDARY_SOURCES,
|
||||
VALIDATE_PHONE_NUMBER,
|
||||
REMOVE_CONTACT
|
||||
REMOVE_CONTACT,
|
||||
GET_DOCUMENT_UPLOAD_URL,
|
||||
DOCUMENT_UPLOAD_ACK_URL
|
||||
}
|
||||
|
||||
// TODO: try to get rid of `as`
|
||||
@@ -510,6 +512,8 @@ API_URLS[ApiKeys.HUMAN_REMINDER_AVAILABLITY_V2] =
|
||||
API_URLS[ApiKeys.PHONENUMBER_SECONDARY_SOURCES] = '/list-longhorn-secondary-sources';
|
||||
API_URLS[ApiKeys.VALIDATE_PHONE_NUMBER] = '/validate-existing-telephone/{number}';
|
||||
API_URLS[ApiKeys.REMOVE_CONTACT] = '/global-access/telephone/remove';
|
||||
API_URLS[ApiKeys.GET_DOCUMENT_UPLOAD_URL] = 'v2/file-uploads/upload-url';
|
||||
API_URLS[ApiKeys.DOCUMENT_UPLOAD_ACK_URL] = '/v2/file-uploads/upload-ack';
|
||||
|
||||
// TODO: try to get rid of `as`
|
||||
const MOCK_API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
|
||||
|
||||
Reference in New Issue
Block a user