From 0f07ac7b68003fffbe39d3045fd4a1788d9f91b8 Mon Sep 17 00:00:00 2001 From: Arpit Agarwal Date: Mon, 19 Dec 2022 20:11:06 +0530 Subject: [PATCH] TP-13228 | Integration to collection portal | Arpit (#110) --- config.template.js | 3 +- configuration.js | 2 +- entrypoint.sh | 1 + package.json | 3 +- .../actions/interactionsActions.ts | 51 ++++++ .../components/Interactions/index.module.scss | 25 +++ .../components/Interactions/index.tsx | 156 ++++++++++++++++++ .../constants/Interactions.constant.ts | 1 + src/pages/CaseDetails/constants/index.tsx | 13 +- .../CaseDetails/interfaces/CaseDetail.type.ts | 6 + .../interfaces/Interactions.type.ts | 41 +++++ src/pages/auth/AuthActions.ts | 18 +- src/types/AppConfig.ts | 1 + src/utils/ApiHelper.ts | 4 +- vite.config.ts | 2 +- yarn.lock | 25 +++ 16 files changed, 343 insertions(+), 9 deletions(-) create mode 100644 src/pages/CaseDetails/actions/interactionsActions.ts create mode 100644 src/pages/CaseDetails/components/Interactions/index.module.scss create mode 100644 src/pages/CaseDetails/components/Interactions/index.tsx create mode 100644 src/pages/CaseDetails/constants/Interactions.constant.ts create mode 100644 src/pages/CaseDetails/interfaces/Interactions.type.ts diff --git a/config.template.js b/config.template.js index acc0dbc7..b5d782e4 100644 --- a/config.template.js +++ b/config.template.js @@ -3,5 +3,6 @@ window.config = { APM_BASE_URL: '', APM_APP_NAME: '', AUTH_BASE_URL: '', - AUTH_CLIENT_ID: '' + AUTH_CLIENT_ID: '', + WA_EXTENSION_ID: '' }; diff --git a/configuration.js b/configuration.js index 57da964a..73b66a93 100644 --- a/configuration.js +++ b/configuration.js @@ -3,7 +3,7 @@ window.config = { APM_BASE_URL: 'https://apm-server.np.navi-sa.in/', APM_APP_NAME: 'collections-portal', - + WA_EXTENSION_ID: 'knolhkbdaenghmbiodaciihedjkchbdh', // APM_APP_NAME: 'longhorn-portal', AUTH_BASE_URL: 'https://dev-dark-knight.np.navi-sa.in', AUTH_CLIENT_ID: 'KBDpUxvr5S', diff --git a/entrypoint.sh b/entrypoint.sh index 5828538c..5909f4c1 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -7,6 +7,7 @@ sed -i "s~~${APM_APP_NAME}~g" /usr/share/nginx/html/configuration. sed -i "s~~${AUTH_BASE_URL}~g" /usr/share/nginx/html/configuration.js sed -i "s~~${AUTH_CLIENT_ID}~g" /usr/share/nginx/html/configuration.js sed -i "s~~${SENTRY_DSN}~g" /usr/share/nginx/html/configuration.js +sed -i "s~${WA_EXTENSION_ID}~g" /usr/share/nginx/html/configuration.js sed -i 's~~/configuration.js~g' /usr/share/nginx/html/index.html exec "$@" diff --git a/package.json b/package.json index e81c3d5a..cab9b4bc 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,8 @@ "ts-jest": "^27.1.3", "typescript": "^4.5.4", "vite": "^2.8.0", - "vite-tsconfig-paths": "^3.5.1" + "vite-tsconfig-paths": "^3.5.1", + "@types/chrome": "0.0.203" }, "engines": { "npm": ">=6.14.15", diff --git a/src/pages/CaseDetails/actions/interactionsActions.ts b/src/pages/CaseDetails/actions/interactionsActions.ts new file mode 100644 index 00000000..132455c5 --- /dev/null +++ b/src/pages/CaseDetails/actions/interactionsActions.ts @@ -0,0 +1,51 @@ +import { Dispatch } from '@reduxjs/toolkit'; +import axiosInstance, { ApiKeys, getApiUrl } from '../../../utils/ApiHelper'; +import { STORE_KEYS } from '../constants'; +import { IWAInteractionPayload } from '../interfaces/Interactions.type'; +import { + setCaseDetail, + setCaseDetailError, + setCaseDetailLoading +} from '../reducers/caseDetailSlice'; + +export const getWAInteractions = + ( + lan: string, + customerRefrenceId: string, + waInteractionsPayload: IWAInteractionPayload = { + customerReferenceId: customerRefrenceId, + size: 10, + page: 0 + } + ) => + (dispatch: Dispatch) => { + dispatch( + setCaseDetailLoading({ + lan, + customerRefrenceId, + key: STORE_KEYS.WA_INTERACTIONS, + loading: true + }) + ); + axiosInstance + .get(getApiUrl(ApiKeys.WA_INTERACTIONS), { params: waInteractionsPayload }) + .then(res => { + dispatch( + setCaseDetail({ + lan, + customerRefrenceId, + key: STORE_KEYS.WA_INTERACTIONS, + data: res.data + }) + ); + }) + .catch(() => { + dispatch( + setCaseDetailError({ + lan, + customerRefrenceId, + key: STORE_KEYS.WA_INTERACTIONS + }) + ); + }); + }; diff --git a/src/pages/CaseDetails/components/Interactions/index.module.scss b/src/pages/CaseDetails/components/Interactions/index.module.scss new file mode 100644 index 00000000..67df7868 --- /dev/null +++ b/src/pages/CaseDetails/components/Interactions/index.module.scss @@ -0,0 +1,25 @@ +.waInteractionsWrapper { + padding: 24px; + .header { + display: flex; + justify-content: space-between; + &__text { + color: var(--navi-color-gray-c2); + } + &__switch { + display: flex; + gap: 8px; + & > span { + color: var(--navi-color-gray-c2); + cursor: pointer; + } + } + } + .table { + height: max-content; + margin-top: 12px; + } +} +.emptyListText { + color: var(--navi-color-gray-c3); +} diff --git a/src/pages/CaseDetails/components/Interactions/index.tsx b/src/pages/CaseDetails/components/Interactions/index.tsx new file mode 100644 index 00000000..de8eaf2f --- /dev/null +++ b/src/pages/CaseDetails/components/Interactions/index.tsx @@ -0,0 +1,156 @@ +import React, { useEffect } from 'react'; +import styles from './index.module.scss'; +import AgTable from '@navi/web-ui/lib/components/AgTable/AgTable'; +import Pagination from '@navi/web-ui/lib/components/Pagination/Pagination'; +import Typography from '@navi/web-ui/lib/primitives/Typography/Typography'; +import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from 'src/store'; +import { createKey } from 'src/utils/CaseDetail.utils'; +import { getWAInteractions as fetchWAInteractions } from '../../actions/interactionsActions'; +import { ICellRendererParams } from 'ag-grid-community/dist/lib/rendering/cellRenderers/iCellRenderer'; +import { NO_FEEDBACK_HISTORY_TEXT } from '../../constants/Overview.constant'; +import { formatDateTime, isObjectEmpty } from 'src/utils/commonUtils'; +import { createQueryParams, readQueryParams } from 'src/utils/QueryParamsHelper'; +import { QueryParams } from 'src/components/interfaces'; +import { WA_INTERACTIONS_TABLE } from '../../constants/Interactions.constant'; +import SuspenseLoader from 'src/components/SkeletonLoader/SuspenseLoader'; +import TableLoader from 'src/components/TableLoader/TableLoader'; +import { IWAInteractionPayload } from '../../interfaces/Interactions.type'; +import { wrap } from '@sentry/browser/types/helpers'; + +const WAINTERACTIONS_COL_DEFS = [ + { + field: 'agentName', + headerName: 'Agent Name' + }, + { field: 'agentNumber', headerName: 'Agent Number' }, + { field: 'messageText', headerName: 'Message', width: '400px' }, + { + field: 'messageTimestamp', + headerName: 'Time', + cellRenderer: (params: ICellRendererParams) => + new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: 'short', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }).format(new Date(params.value)), + width: '300px' + }, + { + field: 'messageType', + headerName: 'Message Type' + }, + { + field: 'source', + headerName: 'Source' + } +]; + +const Interactions = () => { + const { loanId = '', customerId = '' } = useParams(); + const pageData = useSelector( + (state: RootState) => state.caseDetail.pageData?.[createKey(loanId, customerId)] + ); + const { data: waInteractionsData, loading: waInteractionsLoading } = + pageData?.waInteractions || {}; + const [searchParams] = useSearchParams(); + const { [WA_INTERACTIONS_TABLE]: queryParams } = readQueryParams(); + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const createPayloadForWAInteractions = () => { + const { [WA_INTERACTIONS_TABLE]: queryParams } = readQueryParams(); + return { + page: queryParams?.page || 0, + size: queryParams?.size || 10, + customerReferenceId: customerId + } as IWAInteractionPayload; + }; + + const getWAInteractionsData = () => { + const payload = createPayloadForWAInteractions(); + dispatch(fetchWAInteractions(loanId, customerId, payload)); + }; + + useEffect(() => { + if (!pageData?.waInteractions) { + getWAInteractionsData(); + } + }, [pageData?.waInteractions]); + + useEffect(() => { + if (!isObjectEmpty(queryParams)) { + getWAInteractionsData(); + } + }, [searchParams]); + + const updateQueryParamsAndNavigate = (params?: QueryParams, lastUsed?: string) => { + const url = createQueryParams({ + [WA_INTERACTIONS_TABLE]: { ...queryParams, ...params, lastUsed } + }); + navigate(url, { + replace: true + }); + }; + + const getCurrentPageNumber = (): number => { + return Number(queryParams?.page) + 1 || 1; + }; + + const getCurrentPageSize = (): number => { + return Number(queryParams?.size) || 10; + }; + + const handlePageSizeChange = (size: number) => + updateQueryParamsAndNavigate({ size: size.toString(), page: 0 }); + + const handlePageChange = (page: number) => + updateQueryParamsAndNavigate({ page: (page - 1).toString() }); + + return ( +
+
+ + Whatsapp Interactions + +
+
+ } + > + ( + + {NO_FEEDBACK_HISTORY_TEXT} + + )} + PaginationComponent={ + + } + /> + +
+
+ ); +}; +export default Interactions; diff --git a/src/pages/CaseDetails/constants/Interactions.constant.ts b/src/pages/CaseDetails/constants/Interactions.constant.ts new file mode 100644 index 00000000..9477d7d8 --- /dev/null +++ b/src/pages/CaseDetails/constants/Interactions.constant.ts @@ -0,0 +1 @@ +export const WA_INTERACTIONS_TABLE = 'waInteractionsTable'; diff --git a/src/pages/CaseDetails/constants/index.tsx b/src/pages/CaseDetails/constants/index.tsx index 72cb5fde..1a799a96 100644 --- a/src/pages/CaseDetails/constants/index.tsx +++ b/src/pages/CaseDetails/constants/index.tsx @@ -1,5 +1,7 @@ +import { Integrations } from '@sentry/core'; import CommunicationHistory from '../components/CommunicationHistory'; import EmiSchedule from '../components/EmiSchedule'; +import Interactions from '../components/Interactions'; import MoreDetails from '../components/MoreDetails'; import Overview from '../components/Overview'; @@ -7,7 +9,8 @@ export const TAB_KEYS = { OVERVIEW: 'overview', EMI_SCHEDULE: 'emi_schedule', MORE_DETAILS: 'more_details', - COMMUNICATION_HISTORY: 'communication_history' + COMMUNICATION_HISTORY: 'communication_history', + INTERACTIONS: 'interactions' }; export const ToastMessage = { @@ -29,6 +32,11 @@ export const TABS = [ key: TAB_KEYS.MORE_DETAILS, value: 'More details', component: + }, + { + key: TAB_KEYS.INTERACTIONS, + value: 'Interactions', + component: } // { // key: TAB_KEYS.COMMUNICATION_HISTORY, @@ -63,5 +71,6 @@ export enum STORE_KEYS { PENDING_FEE_COMPONENTS = 'pendingFeeComponents', EMI_BREAKUP = 'emiBreakup', REPAYMENT_DUE = 'repaymentDue', - MONTHLY_ENACH = 'monthlyEnach' + MONTHLY_ENACH = 'monthlyEnach', + WA_INTERACTIONS = 'waInteractions' } diff --git a/src/pages/CaseDetails/interfaces/CaseDetail.type.ts b/src/pages/CaseDetails/interfaces/CaseDetail.type.ts index bfdeb16b..34a80996 100644 --- a/src/pages/CaseDetails/interfaces/CaseDetail.type.ts +++ b/src/pages/CaseDetails/interfaces/CaseDetail.type.ts @@ -4,6 +4,7 @@ import { EMIBreakUp } from './EmiSchedule.type'; import { FeedbackHistory, Repayment } from './Overview.type'; import { PendingFeeComponent } from './EmiSchedule.type'; import Amount from '../feedbackForm/component/Amount'; +import { IWAInteraction } from './Interactions.type'; interface Email { emailAddress: string; @@ -166,6 +167,10 @@ export interface IFeedbackHistory extends IApiData { data: FeedbackHistory; } +export interface IWAInteractions extends IApiData { + data: IWAInteraction; +} + export interface ITelephones extends IApiData { data: Array; } @@ -227,6 +232,7 @@ export interface CaseDetailsPageData { addresses?: IAddresses; emiBreakup?: IEMIBreakUp; repaymentDue?: IRepayment; + waInteractions?: IWAInteractions; emiSummary?: IEmiSummary; monthlyEnach?: IMonthlyEnach; __internal__lastUpdatedAt: number; diff --git a/src/pages/CaseDetails/interfaces/Interactions.type.ts b/src/pages/CaseDetails/interfaces/Interactions.type.ts new file mode 100644 index 00000000..d64d21c1 --- /dev/null +++ b/src/pages/CaseDetails/interfaces/Interactions.type.ts @@ -0,0 +1,41 @@ +export interface IWAInteractionPayload { + page: number; + size: number; + customerReferenceId: string; +} + +export interface IWAInteraction { + content?: IWAInteractionContent[]; + pageable: Pageable; + last: boolean; + totalPages: number; + totalElements: number; + sort: Sort; + first: boolean; + number: number; + size: number; + numberOfElements: number; + empty: boolean; +} + +export interface IWAInteractionContent { + feedbackTime: string; + agentName?: string; + feedback: string; + comments?: string; +} + +export interface Pageable { + sort: Sort; + pageNumber: number; + pageSize: number; + offset: number; + paged: boolean; + unpaged: boolean; +} + +export interface Sort { + sorted: boolean; + empty: boolean; + unsorted: boolean; +} diff --git a/src/pages/auth/AuthActions.ts b/src/pages/auth/AuthActions.ts index 7248ef5e..ce9c8d87 100644 --- a/src/pages/auth/AuthActions.ts +++ b/src/pages/auth/AuthActions.ts @@ -7,15 +7,23 @@ import { Dispatch } from '@reduxjs/toolkit'; const LONGHORN_SESSION_TOKEN = 'LONGHORN_SESSION_TOKEN'; const LONGHORN_DEVICE_ID = 'LONGHORN_DEVICE_ID'; - +export const POST_MESSAGE_LOG_IN = 'Logged In'; +const config = window.config ?? {}; interface dispatchProps { payload: any; type: string; } - +export const sendMessageToPlugin = (params: any) => { + if (typeof chrome?.runtime !== 'undefined') { + chrome.runtime.sendMessage(config.WA_EXTENSION_ID, { + authParams: params + }); + } +}; export const getDeviceId = () => localStorage.getItem(LONGHORN_DEVICE_ID); export const clearToken = () => { localStorage.removeItem(LONGHORN_SESSION_TOKEN); + sendMessageToPlugin(null); // localStorage.removeItem(ALLOCATED_CASES_PAGE_REQUEST); // pushToLocalStorage(SLASH_TAB_OPEN, false); }; @@ -90,6 +98,12 @@ export const verifyOTP = ( dispatch(setAuthData({ token: response.data.accessToken, isLoggedIn: true })); dispatch(setFormLoading(false)); navigateToCases(); + const authParams = { + sessionId: response.data.accessToken, + deviceId: getDeviceId() + }; + sendMessageToPlugin(authParams); + window.top?.postMessage(POST_MESSAGE_LOG_IN, '*'); } }) .catch(err => { diff --git a/src/types/AppConfig.ts b/src/types/AppConfig.ts index 8bd5febe..a12b9c32 100644 --- a/src/types/AppConfig.ts +++ b/src/types/AppConfig.ts @@ -3,6 +3,7 @@ export interface AppConfig { APM_BASE_URL: string; APM_APP_NAME: string; AUTH_BASE_URL: string; + WA_EXTENSION_ID: string; ENVIRONMENT: string; AUTH_CLIENT_ID: string; SENTRY_DSN: string; diff --git a/src/utils/ApiHelper.ts b/src/utils/ApiHelper.ts index 546f6a8f..ce4d2e46 100644 --- a/src/utils/ApiHelper.ts +++ b/src/utils/ApiHelper.ts @@ -42,7 +42,8 @@ export enum ApiKeys { FETCH_SLASH_CRM_ID, CREATE_CALL_BRIDGE, FETCH_INTERACTION, - MONTHLTY_ENACH_REQUEST + MONTHLTY_ENACH_REQUEST, + WA_INTERACTIONS } // TODO: try to get rid of `as` @@ -80,6 +81,7 @@ API_URLS[ApiKeys.SEND_PAYMENT_LINK] = '/send-payment-link'; API_URLS[ApiKeys.GENERATE_PAYMENT_LINK] = '/generate-payment-link'; API_URLS[ApiKeys.FETCH_SLASH_CRM_ID] = '/users/encryptedSlashCrmId'; API_URLS[ApiKeys.MONTHLTY_ENACH_REQUEST] = '/monthly-enach-request'; +API_URLS[ApiKeys.WA_INTERACTIONS] = '/agent-interactions/fetch/whatsapp'; // TODO: try to get rid of `as` const MOCK_API_URLS: Record = {} as Record; MOCK_API_URLS[ApiKeys.PEOPLE] = 'people.json'; diff --git a/vite.config.ts b/vite.config.ts index c3b9ed30..5d5636fe 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -25,7 +25,7 @@ export default ({ mode }) => { https: true, proxy: { '/api': { - target: 'https://qa-longhorn-server.np.navi-tech.in', + target: 'https://dev-longhorn-server.np.navi-tech.in', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } diff --git a/yarn.lock b/yarn.lock index 15132b9e..10d8625d 100755 --- a/yarn.lock +++ b/yarn.lock @@ -809,6 +809,14 @@ dependencies: "@types/node" "*" +"@types/chrome@0.0.203": + version "0.0.203" + resolved "https://nexus.cmd.navi-tech.in/repository/navi-commons/@types/chrome/-/chrome-0.0.203.tgz#cf883669ba489ec965e48d32cdcf14f45437f7cf" + integrity sha512-JlQNebwpBETVc8U1Rr2inDFuOTtn0lahRAhnddx1dd0S5RrLAFJEEsyIu7AXI14mkCgSunksnuLGioH8kvBqRA== + dependencies: + "@types/filesystem" "*" + "@types/har-format" "*" + "@types/enzyme@^3.10.11": version "3.10.12" resolved "https://nexus.cmd.navi-tech.in/repository/navi-commons/@types/enzyme/-/enzyme-3.10.12.tgz#ac4494801b38188935580642f772ad18f72c132f" @@ -817,6 +825,18 @@ "@types/cheerio" "*" "@types/react" "*" +"@types/filesystem@*": + version "0.0.32" + resolved "https://nexus.cmd.navi-tech.in/repository/navi-commons/@types/filesystem/-/filesystem-0.0.32.tgz#307df7cc084a2293c3c1a31151b178063e0a8edf" + integrity sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ== + dependencies: + "@types/filewriter" "*" + +"@types/filewriter@*": + version "0.0.29" + resolved "https://nexus.cmd.navi-tech.in/repository/navi-commons/@types/filewriter/-/filewriter-0.0.29.tgz#a48795ecadf957f6c0d10e0c34af86c098fa5bee" + integrity sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ== + "@types/graceful-fs@^4.1.2": version "4.1.5" resolved "https://nexus.cmd.navi-tech.in/repository/navi-commons/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -824,6 +844,11 @@ dependencies: "@types/node" "*" +"@types/har-format@*": + version "1.2.10" + resolved "https://nexus.cmd.navi-tech.in/repository/navi-commons/@types/har-format/-/har-format-1.2.10.tgz#7b4e1e0ada4d17684ac3b05d601a4871cfab11fc" + integrity sha512-o0J30wqycjF5miWDKYKKzzOU1ZTLuA42HZ4HE7/zqTOc/jTLdQ5NhYWvsRQo45Nfi1KHoRdNhteSI4BAxTF1Pg== + "@types/hoist-non-react-statics@^3.3.0": version "3.3.1" resolved "https://nexus.cmd.navi-tech.in/repository/navi-commons/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"