diff --git a/src/components/DisbursementDetails/index.tsx b/src/components/DisbursementDetails/index.tsx index bba2c0f..ef54ea7 100644 --- a/src/components/DisbursementDetails/index.tsx +++ b/src/components/DisbursementDetails/index.tsx @@ -5,11 +5,13 @@ import { Title, Notification, } from "@stellar/design-system"; +import BigNumber from "bignumber.js"; import { useWallets } from "apiQueries/useWallets"; import { useAssetsByWallet } from "apiQueries/useAssetsByWallet"; import { useCountries } from "apiQueries/useCountries"; import { useVerificationTypes } from "apiQueries/useVerificationTypes"; +import { AssetAmount } from "components/AssetAmount"; import { InfoTooltip } from "components/InfoTooltip"; import { formatUploadedFileDisplayName } from "helpers/formatUploadedFileDisplayName"; import { @@ -26,6 +28,7 @@ import "./styles.scss"; interface DisbursementDetailsProps { variant: DisbursementStep; details?: Disbursement; + futureBalance?: number; csvFile?: File; onChange?: (state: Disbursement) => void; onValidate?: (isValid: boolean) => void; @@ -50,12 +53,14 @@ const initDetails: Disbursement = { createdAt: "", status: "DRAFT", statusHistory: [], - smsRegistrationMessageTemplate: "" + smsRegistrationMessageTemplate: "", + stats: undefined, }; export const DisbursementDetails: React.FC = ({ variant, details = initDetails, + futureBalance = 0, csvFile, onChange, onValidate, @@ -244,6 +249,22 @@ export const DisbursementDetails: React.FC = ({ +
+ +
+ +
+
+ {variant === "confirmation" ? (
diff --git a/src/components/DisbursementDetails/styles.scss b/src/components/DisbursementDetails/styles.scss index f186abb..98e95b7 100644 --- a/src/components/DisbursementDetails/styles.scss +++ b/src/components/DisbursementDetails/styles.scss @@ -19,4 +19,8 @@ font-weight: var(--font-weight-medium); margin-top: pxToRem(4px); } + + &__negative { + color: var(--color-red-60); + } } diff --git a/src/pages/DisbursementDraftDetails.tsx b/src/pages/DisbursementDraftDetails.tsx index c919ccf..7d1075c 100644 --- a/src/pages/DisbursementDraftDetails.tsx +++ b/src/pages/DisbursementDraftDetails.tsx @@ -3,7 +3,9 @@ import { useNavigate, useParams } from "react-router-dom"; import { Badge, Heading, Link, Notification } from "@stellar/design-system"; import { useDispatch } from "react-redux"; import { useRedux } from "hooks/useRedux"; +import { useOrgAccountInfo } from "hooks/useOrgAccountInfo"; import { useDownloadCsvFile } from "hooks/useDownloadCsvFile"; +import BigNumber from "bignumber.js"; import { AppDispatch } from "store"; import { @@ -57,6 +59,8 @@ export const DisbursementDraftDetails = () => { const [isDraftInProgress, setIsDraftInProgress] = useState(false); const [isResponseSuccess, setIsResponseSuccess] = useState(false); + const allBalances = organization.data.assetBalances?.[0].balances; + const dispatch: AppDispatch = useDispatch(); const navigate = useNavigate(); const { isLoading: csvDownloadIsLoading } = useDownloadCsvFile( @@ -107,6 +111,8 @@ export const DisbursementDraftDetails = () => { disbursementDetails.status, ]); + useOrgAccountInfo(organization.data.distributionAccountPublicKey); + useEffect(() => { setDraftDetails(disbursementDetails); dispatch(setDraftIdAction(disbursementDetails.details.id)); @@ -176,6 +182,18 @@ export const DisbursementDraftDetails = () => { resetState(); }; + const handleCalculateFutureBalance = (): number => { + const assetBalance = BigNumber( + allBalances?.find((a) => a.assetCode === draftDetails?.details.asset.code) + ?.balance || 0, + ); + return assetBalance + .minus(BigNumber(draftDetails?.details.stats?.totalAmount || 0)) + .toNumber(); + }; + + const futureBalance = handleCalculateFutureBalance(); + const handleSubmitDisbursement = ( event: React.FormEvent, ) => { @@ -231,7 +249,8 @@ export const DisbursementDraftDetails = () => { }} isDraftDisabled={!isCsvFileUpdated} isSubmitDisabled={ - !(Boolean(draftDetails) && Boolean(csvFile) && canUserSubmit) + !(Boolean(draftDetails) && Boolean(csvFile) && canUserSubmit) || + futureBalance < 0 } isDraftPending={disbursementDrafts.status === "PENDING"} actionType={disbursementDrafts.actionType} @@ -286,6 +305,7 @@ export const DisbursementDraftDetails = () => { { { "organization", ); const { assetBalances, distributionAccountPublicKey } = organization.data; + const allBalances = assetBalances?.[0].balances; const [draftDetails, setDraftDetails] = useState(); const [customMessage, setCustomMessage] = useState(""); const [isDetailsValid, setIsDetailsValid] = useState(false); const [csvFile, setCsvFile] = useState(); + const [futureBalance, setFutureBalance] = useState(0); const [currentStep, setCurrentStep] = useState("edit"); const [isDraftInProgress, setIsDraftInProgress] = useState(false); @@ -155,9 +158,50 @@ export const DisbursementsNew = () => { if (apiError) { dispatch(clearDisbursementDraftsErrorAction()); } + calculateDisbursementTotalAmountFromFile(file); setCsvFile(file); }; + const calculateDisbursementTotalAmountFromFile = (file?: File) => { + if (file) { + const reader = new FileReader(); + reader.readAsText(file); + const handleLoadFile = () => { + const totalAmount = reader.result + ?.toString() + .split("\n") + .slice(1) + .reduce( + (accumulator, line) => + !line + ? accumulator + : BigNumber(accumulator) + .plus(BigNumber(line.split(",")[2])) + .toNumber(), + 0, + ); + + setDraftDetails({ + ...draftDetails, + stats: { + ...draftDetails?.stats, + totalAmount: totalAmount?.toString() ?? "0", + }, + } as Disbursement); + + // update future balance + const assetBalance = allBalances?.find( + (a) => a.assetCode === draftDetails?.asset.code, + )?.balance; + + if (totalAmount) { + setFutureBalance(Number(assetBalance) - totalAmount); + } + }; + reader.addEventListener("load", handleLoadFile, false); + } + }; + const handleViewDetails = () => { navigate(`${Routes.DISBURSEMENTS}/${disbursementDrafts.newDraftId}`); resetState(); @@ -179,7 +223,9 @@ export const DisbursementsNew = () => { Boolean(disbursementDrafts.newDraftId && currentStep === "preview") } isSubmitDisabled={ - organization.data.isApprovalRequired || !(draftDetails && csvFile) + organization.data.isApprovalRequired || + !(draftDetails && csvFile) || + BigNumber(futureBalance).lt(0) } isReviewDisabled={!isReviewEnabled} isDraftPending={disbursementDrafts.status === "PENDING"} @@ -198,7 +244,11 @@ export const DisbursementsNew = () => { if (currentStep === "preview") { return (
- + { { { if (apiError) { dispatch(clearDisbursementDraftsErrorAction());