From 24cc13906fd9580ca429f867a42099b3db5dcec7 Mon Sep 17 00:00:00 2001 From: Marwen Abid Date: Thu, 2 Jan 2025 10:35:12 -0800 Subject: [PATCH] [SDP-1451] Export Receivers and Payments (#203) --- ...getDisbursementsExport.ts => getExport.ts} | 11 +++--- src/pages/Disbursements.tsx | 9 ++++- src/pages/Payments.tsx | 19 ++++++++- src/pages/Receivers.tsx | 19 ++++++++- src/store/ducks/dataExport.ts | 39 +++++++++++++++++++ src/store/ducks/disbursements.ts | 26 ------------- src/types/index.ts | 2 + 7 files changed, 88 insertions(+), 37 deletions(-) rename src/api/{getDisbursementsExport.ts => getExport.ts} (77%) create mode 100644 src/store/ducks/dataExport.ts diff --git a/src/api/getDisbursementsExport.ts b/src/api/getExport.ts similarity index 77% rename from src/api/getDisbursementsExport.ts rename to src/api/getExport.ts index 9c9ee17..650e4da 100644 --- a/src/api/getDisbursementsExport.ts +++ b/src/api/getExport.ts @@ -1,15 +1,16 @@ import { API_URL } from "constants/envVariables"; import { getSdpTenantName } from "helpers/getSdpTenantName"; -import { DisbursementsSearchParams } from "types"; import { handleSearchParams } from "api/handleSearchParams"; +import { Export } from "types"; -export const getDisbursementsExport = async ( +export const getExport = async ( token: string, - searchParams?: DisbursementsSearchParams, + type: Export, + searchParams?: T, ): Promise => { const params = handleSearchParams(searchParams); - const response = await fetch(`${API_URL}/exports/disbursements${params}`, { + const response = await fetch(`${API_URL}/exports/${type}${params}`, { method: "GET", headers: { Authorization: `Bearer ${token}`, @@ -24,7 +25,7 @@ export const getDisbursementsExport = async ( const contentDisposition = response.headers.get("content-disposition"); const filename = contentDisposition ? contentDisposition.split("filename=")[1] - : `disbursements_${new Date().toISOString().split("T")[0]}.csv`; + : `${type}_${new Date().toISOString().split("T")[0]}.csv`; // Create a download from the response const blob = await response.blob(); diff --git a/src/pages/Disbursements.tsx b/src/pages/Disbursements.tsx index 81fb41c..5e7524a 100644 --- a/src/pages/Disbursements.tsx +++ b/src/pages/Disbursements.tsx @@ -22,8 +22,8 @@ import { AppDispatch } from "store"; import { getDisbursementsAction, getDisbursementsWithParamsAction, - exportDisbursementsAction, } from "store/ducks/disbursements"; +import { exportDataAction } from "store/ducks/dataExport"; import { resetDisbursementDetailsAction } from "store/ducks/disbursementDetails"; import { setDraftIdAction } from "store/ducks/disbursementDrafts"; import { CommonFilters, SortByDisbursements, SortDirection } from "types"; @@ -151,7 +151,12 @@ export const Disbursements = () => { return; } - dispatch(exportDisbursementsAction()); + dispatch( + exportDataAction({ + exportType: "disbursements", + searchParams: disbursements.searchParams, + }), + ); }; const handlePageLimitChange = ( diff --git a/src/pages/Payments.tsx b/src/pages/Payments.tsx index 6c7cf15..d734691 100644 --- a/src/pages/Payments.tsx +++ b/src/pages/Payments.tsx @@ -1,5 +1,9 @@ import { useState } from "react"; import { Button, Heading, Icon, Input, Select } from "@stellar/design-system"; +import { useDispatch } from "react-redux"; + +import { AppDispatch } from "store"; +import { exportDataAction } from "store/ducks/dataExport"; import { FilterMenu } from "components/FilterMenu"; import { Pagination } from "components/Pagination"; @@ -72,11 +76,22 @@ export const Payments = () => { setQueryFilters(initFilters); }; + const dispatch: AppDispatch = useDispatch(); + const handleExport = ( event: React.MouseEvent, ) => { event.preventDefault(); - alert("TODO: handle export"); + if (isLoading || isFetching) { + return; + } + + dispatch( + exportDataAction({ + exportType: "payments", + searchParams: filters, + }), + ); }; const handlePageLimitChange = ( @@ -168,7 +183,7 @@ export const Payments = () => { size="sm" icon={} onClick={handleExport} - disabled={true} + disabled={isLoading || isFetching} > Export diff --git a/src/pages/Receivers.tsx b/src/pages/Receivers.tsx index 7e027d1..38e1203 100644 --- a/src/pages/Receivers.tsx +++ b/src/pages/Receivers.tsx @@ -1,6 +1,10 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { Button, Heading, Icon, Input, Select } from "@stellar/design-system"; +import { useDispatch } from "react-redux"; + +import { AppDispatch } from "store"; +import { exportDataAction } from "store/ducks/dataExport"; import { FilterMenu } from "components/FilterMenu"; import { SearchInput } from "components/SearchInput"; @@ -88,11 +92,22 @@ export const Receivers = () => { setQueryFilters(isDefaultSort ? filters : { ...filters, sort, direction }); }; + const dispatch: AppDispatch = useDispatch(); + const handleExport = ( event: React.MouseEvent, ) => { event.preventDefault(); - alert("TODO: handle export"); + if (isLoading || isFetching) { + return; + } + + dispatch( + exportDataAction({ + exportType: "receivers", + searchParams: filters, + }), + ); }; const handlePageLimitChange = ( @@ -191,7 +206,7 @@ export const Receivers = () => { size="sm" icon={} onClick={handleExport} - disabled={true} + disabled={isLoading || isFetching} > Export diff --git a/src/store/ducks/dataExport.ts b/src/store/ducks/dataExport.ts new file mode 100644 index 0000000..9514bda --- /dev/null +++ b/src/store/ducks/dataExport.ts @@ -0,0 +1,39 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { getExport } from "api/getExport"; +import { normalizeApiError } from "helpers/normalizeApiError"; +import { endSessionIfTokenInvalid } from "helpers/endSessionIfTokenInvalid"; +import { refreshSessionToken } from "helpers/refreshSessionToken"; +import { RootState } from "store"; +import { ApiError, Export, RejectMessage } from "types"; +type ExportParams = { + exportType: Export; + searchParams?: T; +}; + +export const exportDataAction = createAsyncThunk< + undefined, + ExportParams, + { rejectValue: RejectMessage; state: RootState } +>( + "common/exportDataAction", + async ( + { exportType, searchParams }, + { rejectWithValue, getState, dispatch }, + ) => { + const { token } = getState().userAccount; + + try { + await getExport(token, exportType, searchParams); + refreshSessionToken(dispatch); + return; + } catch (error: unknown) { + const apiError = normalizeApiError(error as ApiError); + const errorString = apiError.message; + endSessionIfTokenInvalid(errorString, dispatch); + + return rejectWithValue({ + errorString: `Error exporting ${exportType}: ${errorString}`, + }); + } + }, +); diff --git a/src/store/ducks/disbursements.ts b/src/store/ducks/disbursements.ts index ff2a28b..4b395ba 100644 --- a/src/store/ducks/disbursements.ts +++ b/src/store/ducks/disbursements.ts @@ -1,7 +1,6 @@ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { RootState } from "store"; import { getDisbursements } from "api/getDisbursements"; -import { getDisbursementsExport } from "api/getDisbursementsExport"; import { formatDisbursements } from "helpers/formatDisbursements"; import { endSessionIfTokenInvalid } from "helpers/endSessionIfTokenInvalid"; import { refreshSessionToken } from "helpers/refreshSessionToken"; @@ -75,31 +74,6 @@ export const getDisbursementsWithParamsAction = createAsyncThunk< }, ); -export const exportDisbursementsAction = createAsyncThunk< - undefined, - undefined, - { rejectValue: RejectMessage; state: RootState } ->( - "disbursements/exportDisbursementsAction", - async (_, { rejectWithValue, getState, dispatch }) => { - const { token } = getState().userAccount; - const { searchParams } = getState().disbursements; - try { - await getDisbursementsExport(token, searchParams); - refreshSessionToken(dispatch); - return; - } catch (error: unknown) { - const apiError = normalizeApiError(error as ApiError); - const errorString = apiError.message; - endSessionIfTokenInvalid(errorString, dispatch); - - return rejectWithValue({ - errorString: `Error exporting disbursements: ${errorString}`, - }); - } - }, -); - const initialState: DisbursementsInitialState = { items: [], status: undefined, diff --git a/src/types/index.ts b/src/types/index.ts index 58f7984..b888e60 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -160,6 +160,8 @@ export type StellarAccountInfo = { balances: AccountBalanceItem[]; }; +export type Export = "disbursements" | "receivers" | "payments"; + // ============================================================================= // User // =============================================================================