From 620bbd47ab4a88984f847cb95cdf3650cca09607 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Mon, 28 Oct 2024 16:18:01 +0200 Subject: [PATCH 01/42] feat: Enhance GraphQL queries with additional fields for transfer details --- src/apollo/query.ts | 87 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/apollo/query.ts b/src/apollo/query.ts index 28d867a..3784670 100644 --- a/src/apollo/query.ts +++ b/src/apollo/query.ts @@ -6,25 +6,38 @@ export const GET_TRANSFER = gql` transfer(transferId: $transferId) { transferId transferState + baseUseCase transactionType currency amount + sourceAmount + sourceCurrency + conversionType + conversionState settlementId + conversionSettlementBatchID + submittedDate + conversionSubmittedDate createdAt quoteId partyLookupEvents quoteEvents transferEvents settlementEvents + transferSettlementBatchID + fxp + fxpProxy payeeDFSP { id name description + proxy } payerDFSP { id name description + proxy } payerParty { id @@ -44,6 +57,43 @@ export const GET_TRANSFER = gql` idType idValue } + transferTerms { + quoteAmount + quoteAmountType + transferAmount + payeeReceiveAmount + payeeFspFee + payeeFspCommission + expirationDate + geoCode + ilpPacket + transactionId + } + conversions { + conversionRequestId + conversionId + conversionCommitRequestId + conversionState + conversionStateChanges + counterPartyFSP + conversionSettlementWindowId + } + conversionTerms { + conversionId + determiningTransferId + initiatingFsp + counterPartyFsp + amountType + transferAmount + expiration + charges + } + fxQuotes { + Amount + } + fxTransfers { + Amount + } } } `; @@ -112,6 +162,43 @@ export const GET_TRANSFERS_WITH_EVENTS = gql` idType idValue } + transferTerms { + quoteAmount + quoteAmountType + transferAmount + payeeReceiveAmount + payeeFspFee + payeeFspCommission + expirationDate + geoCode + ilpPacket + transactionId + } + conversions { + conversionRequestId + conversionId + conversionCommitRequestId + conversionState + conversionStateChanges + counterPartyFSP + conversionSettlementWindowId + } + conversionTerms { + conversionId + determiningTransferId + initiatingFsp + counterPartyFsp + amountType + transferAmount + expiration + charges + } + fxQuotes { + Amount + } + fxTransfers { + Amount + } } } `; From aefe82ba33ce632646010471322210698995b24b Mon Sep 17 00:00:00 2001 From: Mayhem Date: Tue, 29 Oct 2024 14:50:15 +0200 Subject: [PATCH 02/42] Added optional chaining to safely access the uid=1000(harlem) gid=1000(harlem) groups=1000(harlem),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),116(netdev),1001(docker) property of in the TransferDetails component. This prevents runtime errors when is null or undefined. --- src/App/Transfers/TransferDetails/index.tsx | 103 ++++++++++++++++++++ src/App/Transfers/Transfers.scss | 5 + 2 files changed, 108 insertions(+) diff --git a/src/App/Transfers/TransferDetails/index.tsx b/src/App/Transfers/TransferDetails/index.tsx index a2f5f9e..b46e135 100644 --- a/src/App/Transfers/TransferDetails/index.tsx +++ b/src/App/Transfers/TransferDetails/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ import React, { FC } from 'react'; import { Modal, Tabs, Tab, TabPanel, FormField, Button } from 'components'; import { connect } from 'react-redux'; @@ -235,14 +236,116 @@ const TransferDetails: FC = ({ ); + const TransferPartiesTab = ( + + + + + + + + + + + + + + + +
+
+ +
Payer Details
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ +
Payee Details
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ ); + return (
Basic Information Technical Details + Transfer Parties {BasicInformationTab} {TechnicalDetailsTab} + {TransferPartiesTab}
diff --git a/src/App/Transfers/Transfers.scss b/src/App/Transfers/Transfers.scss index 4b888d8..96dfeb5 100644 --- a/src/App/Transfers/Transfers.scss +++ b/src/App/Transfers/Transfers.scss @@ -8,6 +8,11 @@ margin: 4px; } +.transferPartiesTab .rc-formfield { + width: 300px; + margin: 4px; +} + .partyDetailsModal .rc-formfield { width: 300px; margin: 4px; From 3d7f6007b5910602305989bcfad7fc62bfdb1328 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Thu, 31 Oct 2024 09:51:08 +0200 Subject: [PATCH 03/42] Added optional chaining to safely access the uid=1000(harlem) gid=1000(harlem) groups=1000(harlem),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),116(netdev),1001(docker) property of in the TransferDetails component. This prevents runtime errors when is null or undefined. --- src/App/Transfers/TransferDetails/index.tsx | 12 ++++++++++-- src/apollo/query.ts | 2 ++ src/apollo/types.ts | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/App/Transfers/TransferDetails/index.tsx b/src/App/Transfers/TransferDetails/index.tsx index b46e135..22f5bfd 100644 --- a/src/App/Transfers/TransferDetails/index.tsx +++ b/src/App/Transfers/TransferDetails/index.tsx @@ -294,7 +294,11 @@ const TransferDetails: FC = ({
- +
@@ -328,7 +332,11 @@ const TransferDetails: FC = ({
- +
diff --git a/src/apollo/query.ts b/src/apollo/query.ts index 3784670..12af02e 100644 --- a/src/apollo/query.ts +++ b/src/apollo/query.ts @@ -47,6 +47,7 @@ export const GET_TRANSFER = gql` dateOfBirth idType idValue + supportedCurrency } payeeParty { id @@ -56,6 +57,7 @@ export const GET_TRANSFER = gql` dateOfBirth idType idValue + supportedCurrency } transferTerms { quoteAmount diff --git a/src/apollo/types.ts b/src/apollo/types.ts index a5fe7c6..6532090 100644 --- a/src/apollo/types.ts +++ b/src/apollo/types.ts @@ -119,6 +119,7 @@ export type Party = { dateOfBirth?: Maybe; idType?: Maybe; idValue?: Maybe; + supportedCurrency?: Maybe; }; export enum PartyIdType { From 20233bba668f8cb3aa653ff760f7adbd8ef88e64 Mon Sep 17 00:00:00 2001 From: Mayhem Date: Mon, 4 Nov 2024 08:49:34 +0200 Subject: [PATCH 04/42] merged the code in preparation for enhancements --- .../Dashboard/ErrorsByPayeeChart.tsx | 2 +- .../Dashboard/ErrorsByPayerChart.tsx | 2 +- .../Dashboard/ErrorsBySourceCurrencyChart.tsx | 95 ++++ .../Dashboard/ErrorsByTargetCurrencyChart.tsx | 96 ++++ .../Dashboard/TransfersByPayeeChart.tsx | 2 +- .../Dashboard/TransfersByPayerChart.tsx | 2 +- .../TransfersBySourceCurrencyChart.tsx | 106 ++++ .../TransfersByTargetCurrencyChart.tsx | 106 ++++ src/App/Transfers/Dashboard/index.tsx | 16 +- src/App/Transfers/TransferDetails/index.tsx | 503 +++++++++++++++++- src/App/Transfers/Transfers.scss | 5 + src/App/Transfers/slice.ts | 3 + src/App/Transfers/types.ts | 3 + src/App/Transfers/views.tsx | 60 ++- src/apollo/mocks.ts | 8 + src/apollo/types.ts | 135 +++++ 16 files changed, 1100 insertions(+), 44 deletions(-) create mode 100644 src/App/Transfers/Dashboard/ErrorsBySourceCurrencyChart.tsx create mode 100644 src/App/Transfers/Dashboard/ErrorsByTargetCurrencyChart.tsx create mode 100644 src/App/Transfers/Dashboard/TransfersBySourceCurrencyChart.tsx create mode 100644 src/App/Transfers/Dashboard/TransfersByTargetCurrencyChart.tsx diff --git a/src/App/Transfers/Dashboard/ErrorsByPayeeChart.tsx b/src/App/Transfers/Dashboard/ErrorsByPayeeChart.tsx index 9ef57ea..767e0f3 100644 --- a/src/App/Transfers/Dashboard/ErrorsByPayeeChart.tsx +++ b/src/App/Transfers/Dashboard/ErrorsByPayeeChart.tsx @@ -77,7 +77,7 @@ const ByPayeeChart: FC = ({ filtersModel, onFilterChange }) => { = ({ filtersModel, onFilterChange }) => { ({ + filtersModel: selectors.getTransfersFilter(state), +}); +const dispatchProps = () => ({}); +interface ConnectorProps { + filtersModel: TransfersFilter; +} +const BySourceCurrencyChart: FC = ({ filtersModel }) => { + const { loading, error, data } = useQuery(GET_TRANSFER_SUMMARY, { + fetchPolicy: 'no-cache', + variables: { + startDate: filtersModel.from, + endDate: filtersModel.to, + }, + }); + const [activeIndex, setActiveIndex] = useState(); + const onPieEnter = (_: any, index: number) => { + setActiveIndex(index); + }; + const onPieLeave = () => { + setActiveIndex(undefined); + }; + let content = null; + if (error) { + content = Error fetching transfers: {error.message}; + } else if (loading) { + content = ; + } else { + const summary = data.transferSummary + .filter((obj: TransferSummary) => { + return obj.errorCode !== null; + }) + .slice() + .sort((a: TransferSummary, b: TransferSummary) => b.count - a.count); + const firstThree = summary.slice(0, 3); + const remainingSummary = { + errorCode: 'Other', + count: summary.slice(3).reduce((n: number, { count }: TransferSummary) => n + count, 0), + }; + if (remainingSummary.count > 0) { + firstThree.push(remainingSummary); + } + content = ( + + + + {firstThree.map((_entry: any, index: number) => ( + + ))} + + + + ); + } + return content; +}; +export default connect(stateProps, dispatchProps, null, { context: ReduxContext })( + BySourceCurrencyChart, +); diff --git a/src/App/Transfers/Dashboard/ErrorsByTargetCurrencyChart.tsx b/src/App/Transfers/Dashboard/ErrorsByTargetCurrencyChart.tsx new file mode 100644 index 0000000..99df48c --- /dev/null +++ b/src/App/Transfers/Dashboard/ErrorsByTargetCurrencyChart.tsx @@ -0,0 +1,96 @@ +import React, { FC, useState } from 'react'; +import { connect } from 'react-redux'; +import { Cell, Legend, Pie, PieChart, Tooltip } from 'recharts'; +import { ReduxContext, State } from 'store'; +import { MessageBox, Spinner } from 'components'; +import { useQuery } from '@apollo/client'; +import { TransferSummary } from 'apollo/types'; +import { GET_TRANSFER_SUMMARY } from 'apollo/query'; +import * as selectors from '../selectors'; +import { TransfersFilter } from '../types'; +import { RED_CHART_GRADIENT_COLORS, renderActiveShape, renderRedLegend } from './utils'; + +const stateProps = (state: State) => ({ + filtersModel: selectors.getTransfersFilter(state), +}); +const dispatchProps = () => ({}); +interface ConnectorProps { + filtersModel: TransfersFilter; +} +const BySourceCurrencyChart: FC = ({ filtersModel }) => { + const { loading, error, data } = useQuery(GET_TRANSFER_SUMMARY, { + fetchPolicy: 'no-cache', + variables: { + startDate: filtersModel.from, + endDate: filtersModel.to, + }, + }); + const [activeIndex, setActiveIndex] = useState(); + const onPieEnter = (_: any, index: number) => { + setActiveIndex(index); + }; + const onPieLeave = () => { + setActiveIndex(undefined); + }; + let content = null; + if (error) { + content = Error fetching transfers: {error.message}; + } else if (loading) { + content = ; + } else { + const summary = data.transferSummary + .filter((obj: TransferSummary) => { + return obj.errorCode !== null; + }) + .slice() + .sort((a: TransferSummary, b: TransferSummary) => b.count - a.count); + const firstThree = summary.slice(0, 3); + const remainingSummary = { + errorCode: 'Other', + count: summary.slice(3).reduce((n: number, { count }: TransferSummary) => n + count, 0), + }; + if (remainingSummary.count > 0) { + firstThree.push(remainingSummary); + } + content = ( + + + + {firstThree.map((_entry: any, index: number) => ( + + ))} + + + + ); + } + return content; +}; + +export default connect(stateProps, dispatchProps, null, { context: ReduxContext })( + BySourceCurrencyChart, +); diff --git a/src/App/Transfers/Dashboard/TransfersByPayeeChart.tsx b/src/App/Transfers/Dashboard/TransfersByPayeeChart.tsx index b603bbd..01c718a 100644 --- a/src/App/Transfers/Dashboard/TransfersByPayeeChart.tsx +++ b/src/App/Transfers/Dashboard/TransfersByPayeeChart.tsx @@ -69,7 +69,7 @@ const ByPayeeChart: FC = ({ filtersModel, onFilterChange }) => { = ({ filtersModel, onFilterChange }) => { ({ + filtersModel: selectors.getTransfersFilter(state), +}); +const dispatchProps = (dispatch: Dispatch) => ({ + onFilterChange: (field: string, value: FilterChangeValue | string) => + dispatch(actions.setTransferFinderFilter({ field, value })), +}); +interface ConnectorProps { + filtersModel: TransfersFilter; + onFilterChange: (field: string, value: FilterChangeValue | string) => void; +} +const BySourceCurrencyChart: FC = ({ filtersModel, onFilterChange }) => { + const { loading, error, data } = useQuery(GET_TRANSFER_SUMMARY_BY_CURRENCY, { + fetchPolicy: 'no-cache', + variables: { + startDate: filtersModel.from, + endDate: filtersModel.to, + }, + }); + const [activeIndex, setActiveIndex] = useState(); + const onPieEnter = (_: any, index: number) => { + setActiveIndex(index); + }; + const onPieLeave = () => { + setActiveIndex(undefined); + }; + let content = null; + if (error) { + content = Error fetching transfers: {error.message}; + } else if (loading) { + content = ; + } else { + const summary = data.transferSummary + .filter((obj: TransferSummary) => { + return obj.errorCode === null; + }) + .slice() + .sort((a: TransferSummary, b: TransferSummary) => b.count - a.count); + const firstThree = summary.slice(0, 3); + const remainingSummary = { + currency: 'Other', + count: summary.slice(3).reduce((n: number, { count }: TransferSummary) => n + count, 0), + }; + if (remainingSummary.count > 0) { + firstThree.push(remainingSummary); + } + content = ( + + + { + if (value.name !== 'Other') { + onFilterChange('currency', value.name); + } + }} + activeIndex={activeIndex} + activeShape={renderActiveShape} + onMouseEnter={onPieEnter} + onMouseLeave={onPieLeave} + > + {firstThree.map((_entry: any, index: number) => ( + + ))} + + + + ); + } + return content; +}; +export default connect(stateProps, dispatchProps, null, { context: ReduxContext })( + BySourceCurrencyChart, +); diff --git a/src/App/Transfers/Dashboard/TransfersByTargetCurrencyChart.tsx b/src/App/Transfers/Dashboard/TransfersByTargetCurrencyChart.tsx new file mode 100644 index 0000000..111499f --- /dev/null +++ b/src/App/Transfers/Dashboard/TransfersByTargetCurrencyChart.tsx @@ -0,0 +1,106 @@ +import { GET_TRANSFER_SUMMARY_BY_CURRENCY } from 'apollo/query'; +import React, { FC, useState } from 'react'; +import { connect } from 'react-redux'; +import { Cell, Legend, Pie, PieChart, Tooltip } from 'recharts'; +import { ReduxContext } from 'store'; +import { State, Dispatch } from 'store/types'; +import { MessageBox, Spinner } from 'components'; +import { useQuery } from '@apollo/client'; +import { TransferSummary } from 'apollo/types'; +import { FilterChangeValue, TransfersFilter } from '../types'; +import { actions } from '../slice'; +import * as selectors from '../selectors'; +import { GREEN_CHART_GRADIENT_COLORS, renderActiveShape, renderGreenLegend } from './utils'; + +const stateProps = (state: State) => ({ + filtersModel: selectors.getTransfersFilter(state), +}); +const dispatchProps = (dispatch: Dispatch) => ({ + onFilterChange: (field: string, value: FilterChangeValue | string) => + dispatch(actions.setTransferFinderFilter({ field, value })), +}); +interface ConnectorProps { + filtersModel: TransfersFilter; + onFilterChange: (field: string, value: FilterChangeValue | string) => void; +} +const ByTargetCurrencyChart: FC = ({ filtersModel, onFilterChange }) => { + const { loading, error, data } = useQuery(GET_TRANSFER_SUMMARY_BY_CURRENCY, { + fetchPolicy: 'no-cache', + variables: { + startDate: filtersModel.from, + endDate: filtersModel.to, + }, + }); + const [activeIndex, setActiveIndex] = useState(); + const onPieEnter = (_: any, index: number) => { + setActiveIndex(index); + }; + const onPieLeave = () => { + setActiveIndex(undefined); + }; + let content = null; + if (error) { + content = Error fetching transfers: {error.message}; + } else if (loading) { + content = ; + } else { + const summary = data.transferSummary + .filter((obj: TransferSummary) => { + return obj.errorCode === null; + }) + .slice() + .sort((a: TransferSummary, b: TransferSummary) => b.count - a.count); + const firstThree = summary.slice(0, 3); + const remainingSummary = { + currency: 'Other', + count: summary.slice(3).reduce((n: number, { count }: TransferSummary) => n + count, 0), + }; + if (remainingSummary.count > 0) { + firstThree.push(remainingSummary); + } + content = ( + + + { + if (value.name !== 'Other') { + onFilterChange('currency', value.name); + } + }} + activeIndex={activeIndex} + activeShape={renderActiveShape} + onMouseEnter={onPieEnter} + onMouseLeave={onPieLeave} + > + {firstThree.map((_entry: any, index: number) => ( + + ))} + + + + ); + } + return content; +}; +export default connect(stateProps, dispatchProps, null, { context: ReduxContext })( + ByTargetCurrencyChart, +); diff --git a/src/App/Transfers/Dashboard/index.tsx b/src/App/Transfers/Dashboard/index.tsx index cfe0296..0e4d904 100644 --- a/src/App/Transfers/Dashboard/index.tsx +++ b/src/App/Transfers/Dashboard/index.tsx @@ -2,13 +2,15 @@ import React, { FC } from 'react'; import { connect } from 'react-redux'; import { ReduxContext } from 'store'; import { Row } from 'antd'; -import TransfersByCurrencyChart from './TransfersByCurrencyChart'; import ErrorsByPayeeChart from './ErrorsByPayeeChart'; import ErrorsByPayerChart from './ErrorsByPayerChart'; -import ErrorsByErrorCodeChart from './ErrorsByErrorCodeChart'; import TransfersByPayeeChart from './TransfersByPayeeChart'; import TransfersByPayerChart from './TransfersByPayerChart'; import TransferTotalSummary from './TransferTotalSummary'; +import BySourceCurrencyChart from './TransfersBySourceCurrencyChart'; +import ByTargetCurrencyChart from './TransfersByTargetCurrencyChart'; +import ErrorsBySourceCurrencyChart from './ErrorsBySourceCurrencyChart'; +import ErrorsByTargetCurrencyChart from './ErrorsByTargetCurrencyChart'; import ErrorSummary from './ErrorSummary'; const stateProps = () => ({}); @@ -20,15 +22,17 @@ interface ConnectorProps {} const Dashboard: FC = () => { return (
- + - + + - + - + + diff --git a/src/App/Transfers/TransferDetails/index.tsx b/src/App/Transfers/TransferDetails/index.tsx index 22f5bfd..19e1b28 100644 --- a/src/App/Transfers/TransferDetails/index.tsx +++ b/src/App/Transfers/TransferDetails/index.tsx @@ -62,6 +62,24 @@ const TransferDetails: FC = ({ label="Transfer State" value={transferDetails.transferState || ''} /> + + + {errorCodeField ||
} @@ -105,6 +123,39 @@ const TransferDetails: FC = ({ }); }} /> +