From cb2f3f7e7a0a1e32e73a03d66dd7df02c38f1b81 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Tue, 19 May 2026 18:24:13 -0300 Subject: [PATCH 1/4] chore: add show level purchases view --- src/actions/sponsor-purchases-actions.js | 131 ++++++++- src/components/menu/menu-definition.js | 5 + src/i18n/en.json | 11 + src/layouts/sponsor-layout.js | 19 +- .../sponsors/show-pages-list-page/index.js | 18 +- .../sponsors/show-purchase-list-page/index.js | 254 ++++++++++++++++++ .../tabs/sponsor-purchases-tab/index.js | 13 +- .../sponsors/show-purchase-list-reducer.js | 99 +++++++ src/store.js | 2 + 9 files changed, 525 insertions(+), 27 deletions(-) create mode 100644 src/pages/sponsors/show-purchase-list-page/index.js create mode 100644 src/reducers/sponsors/show-purchase-list-reducer.js diff --git a/src/actions/sponsor-purchases-actions.js b/src/actions/sponsor-purchases-actions.js index 2b38f833d..224ebeb99 100644 --- a/src/actions/sponsor-purchases-actions.js +++ b/src/actions/sponsor-purchases-actions.js @@ -19,7 +19,8 @@ import { postRequest, putRequest, startLoading, - stopLoading + stopLoading, + getCSV } from "openstack-uicore-foundation/lib/utils/actions"; import T from "i18n-react/dist/i18n-react"; import { escapeFilterValue, getAccessTokenSafely } from "../utils/methods"; @@ -31,6 +32,8 @@ import { } from "../utils/constants"; import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; +export const REQUEST_ALL_SPONSOR_PURCHASES = "REQUEST_ALL_SPONSOR_PURCHASES"; +export const RECEIVE_ALL_SPONSOR_PURCHASES = "RECEIVE_ALL_SPONSOR_PURCHASES"; export const REQUEST_SPONSOR_PURCHASES = "REQUEST_SPONSOR_PURCHASES"; export const RECEIVE_SPONSOR_PURCHASES = "RECEIVE_SPONSOR_PURCHASES"; export const SPONSOR_PURCHASE_STATUS_UPDATED = @@ -40,6 +43,118 @@ export const CLEAR_SPONSOR_ORDER = "CLEAR_SPONSOR_ORDER"; export const SPONSOR_CLIENT_ADDRESS_UPDATED = "SPONSOR_CLIENT_ADDRESS_UPDATED"; export const SPONSOR_CLIENT_UPDATED = "SPONSOR_CLIENT_UPDATED"; +export const getAllSponsorPurchases = + ( + term = "", + page = DEFAULT_CURRENT_PAGE, + perPage = DEFAULT_PER_PAGE, + order = "created", + orderDir = -1 + ) => + async (dispatch, getState) => { + const { currentSummitState } = getState(); + const { currentSummit } = currentSummitState; + const accessToken = await getAccessTokenSafely(); + const filter = []; + + dispatch(startLoading()); + + if (term) { + const escapedTerm = escapeFilterValue(term); + filter.push( + `number==${escapedTerm},purchased_by_email=@${escapedTerm},purchased_by_full_name=@${escapedTerm}` + ); + } + + const params = { + page, + per_page: perPage, + access_token: accessToken, + expand: "sponsor" + }; + + if (filter.length > 0) { + params["filter[]"] = filter; + } + + // order + if (order != null && orderDir != null) { + const orderDirSign = orderDir === 1 ? "" : "-"; + switch (order) { + case "purchased": + params.order = `${orderDirSign}created`; + break; + case "amount": + params.order = `${orderDirSign}raw_amount`; + break; + default: + params.order = `${orderDirSign}${order}`; + } + } + + return getRequest( + createAction(REQUEST_ALL_SPONSOR_PURCHASES), + createAction(RECEIVE_ALL_SPONSOR_PURCHASES), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/purchases`, + authErrorHandler, + { order, orderDir, page, perPage, term } + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + }); + }; + +export const exportAllSponsorPurchases = + (term = null, order, orderDir) => + async (dispatch, getState) => { + const { currentSummitState } = getState(); + const accessToken = await getAccessTokenSafely(); + const { currentSummit } = currentSummitState; + const filter = []; + const filename = `${currentSummit.id}-purchases.csv`; + + if (term) { + const escapedTerm = escapeFilterValue(term); + filter.push( + `number==${escapedTerm},purchased_by_email=@${escapedTerm},purchased_by_full_name=@${escapedTerm}` + ); + } + + const params = { + access_token: accessToken, + expand: "sponsor", + relations: "sponsor", + fields: + "order_number,purchased_date,sponsor.name,payment_method,status,amount" + }; + + if (filter.length > 0) { + params["filter[]"] = filter; + } + + // order + if (order != null && orderDir != null) { + const orderDirSign = orderDir === 1 ? "" : "-"; + switch (order) { + case "purchased": + params.order = `${orderDirSign}created`; + break; + case "amount": + params.order = `${orderDirSign}raw_amount`; + break; + default: + params.order = `${orderDirSign}${order}`; + } + } + + dispatch( + getCSV( + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/purchases/csv`, + params, + filename + ) + ); + }; + export const getSponsorPurchases = ( term = "", @@ -101,10 +216,9 @@ export const getSponsorPurchases = }; export const approveSponsorPurchase = - (paymentId) => async (dispatch, getState) => { - const { currentSummitState, currentSponsorState } = getState(); + (sponsorId, paymentId) => async (dispatch, getState) => { + const { currentSummitState } = getState(); const { currentSummit } = currentSummitState; - const { entity: sponsor } = currentSponsorState; const accessToken = await getAccessTokenSafely(); const params = { @@ -119,7 +233,7 @@ export const approveSponsorPurchase = paymentId, status: PURCHASE_STATUS.PAID }), - `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/payments/${paymentId}/approve`, + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/payments/${paymentId}/approve`, {}, snackbarErrorHandler )(params)(dispatch) @@ -138,10 +252,9 @@ export const approveSponsorPurchase = }; export const rejectSponsorPurchase = - (paymentId) => async (dispatch, getState) => { - const { currentSummitState, currentSponsorState } = getState(); + (sponsorId, paymentId) => async (dispatch, getState) => { + const { currentSummitState } = getState(); const { currentSummit } = currentSummitState; - const { entity: sponsor } = currentSponsorState; const accessToken = await getAccessTokenSafely(); const params = { @@ -156,7 +269,7 @@ export const rejectSponsorPurchase = paymentId, status: PURCHASE_STATUS.CANCELLED }), - `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/payments/${paymentId}/cancel`, + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/payments/${paymentId}/cancel`, null, snackbarErrorHandler )(params)(dispatch) diff --git a/src/components/menu/menu-definition.js b/src/components/menu/menu-definition.js index d853d6d58..0baaa4985 100644 --- a/src/components/menu/menu-definition.js +++ b/src/components/menu/menu-definition.js @@ -218,6 +218,11 @@ export const getSummitItems = (summitId) => [ linkUrl: `summits/${summitId}/sponsors/pages`, accessRoute: "admin-sponsors" }, + { + name: "sponsor_purchases", + linkUrl: `summits/${summitId}/sponsors/purchases`, + accessRoute: "admin-sponsors" + }, { name: "sponsorship_list", linkUrl: `summits/${summitId}/sponsorships`, diff --git a/src/i18n/en.json b/src/i18n/en.json index f5a408eb3..1222d713c 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -90,6 +90,7 @@ "to": "To", "from": "From", "placeholders": { + "search": "Search...", "search_speakers": "Search Speakers by Name, Email, Speaker Id or Member Id", "select_acceptance_criteria": "Select acceptance criteria", "select_invitation_status": "Select status" @@ -186,6 +187,7 @@ "sponsor_list": "Sponsor List", "sponsor_forms": "Forms", "sponsor_pages": "Pages", + "sponsor_purchases": "Purchases", "sponsorship_list": "Tiers", "sponsor_users": "Users", "sponsors_promocodes": "Promo Codes", @@ -2866,6 +2868,15 @@ } } }, + "sponsor_show_purchases": { + "order": "Order", + "purchased": "Purchased", + "payment_method": "Payment Method", + "status": "Status", + "amount": "Amount", + "details": "Details", + "purchases": "Purchases" + }, "sponsor_users": { "users": "Users", "access_request": "access request", diff --git a/src/layouts/sponsor-layout.js b/src/layouts/sponsor-layout.js index cba7da2e6..68dd29cd6 100644 --- a/src/layouts/sponsor-layout.js +++ b/src/layouts/sponsor-layout.js @@ -44,6 +44,9 @@ const SponsorUsersListPage = React.lazy(() => const ShowPagesListPage = React.lazy(() => import("../pages/sponsors/show-pages-list-page") ); +const SponsorOrdersListPage = React.lazy(() => + import("../pages/sponsors/show-purchase-list-page") +); const SponsorLayout = ({ match }) => (
@@ -85,20 +88,16 @@ const SponsorLayout = ({ match }) => ( /> ( -
- -
- )} strict exact component={ShowPagesListPage} /> + +
+ +

{T.translate("show_pages.pages")}

{ + useEffect(() => { + getAllSponsorPurchases(); + }, []); + + const handlePageChange = (page) => { + getAllSponsorPurchases(term, page, perPage, order, orderDir); + }; + + const handleSort = (key, dir) => { + getAllSponsorPurchases(term, currentPage, perPage, key, dir); + }; + + const handlePerPageChange = (newPerPage) => { + getAllSponsorPurchases( + term, + DEFAULT_CURRENT_PAGE, + newPerPage, + order, + orderDir + ); + }; + + const handleExport = () => { + exportAllSponsorPurchases(); + }; + + const handleSearch = (searchTerm) => { + getAllSponsorPurchases(searchTerm); + }; + + const handleDetails = (item) => { + history.push(`${item.sponsor_id}/purchases/${item.id}`); + }; + + const handleMenu = (item) => { + console.log("MENU : ", item); + }; + + const handleStatusChange = (sponsorId, purchaseId, newStatus) => { + if (newStatus === PURCHASE_STATUS.PAID) + approveSponsorPurchase(sponsorId, purchaseId); + if (newStatus === PURCHASE_STATUS.CANCELLED) + rejectSponsorPurchase(sponsorId, purchaseId); + }; + + const tableColumns = [ + { + columnKey: "number", + header: T.translate("sponsor_show_purchases.order"), + sortable: true + }, + { + columnKey: "purchased", + header: T.translate("sponsor_show_purchases.purchased"), + sortable: true + }, + { + columnKey: "payment_method", + header: T.translate("sponsor_show_purchases.payment_method"), + sortable: true + }, + { + columnKey: "status", + header: T.translate("sponsor_show_purchases.status"), + sortable: true, + render: (row) => { + if ( + row.payment_method === PURCHASE_METHODS.INVOICE && + row.status === PURCHASE_STATUS.PENDING + ) { + return ( + + ); + } + + return row.status; + } + }, + { + columnKey: "amount", + header: T.translate("sponsor_show_purchases.amount"), + sortable: true + }, + { + columnKey: "details", + header: "", + width: 100, + align: "center", + render: (row) => ( + + ) + }, + { + columnKey: "menu", + header: "", + width: 100, + align: "center", + render: (row) => ( + handleMenu(row)} + > + + + ) + } + ]; + + return ( +
+
+ +
+

{T.translate("sponsor_show_purchases.purchases")}

+ + + + {totalCount}{" "} + {T.translate("sponsor_show_purchases.purchases").toLowerCase()} + + + + + + + + + +
+ +
+
+ ); +}; + +const mapStateToProps = ({ showPurchaseListState }) => ({ + ...showPurchaseListState +}); + +export default connect(mapStateToProps, { + getAllSponsorPurchases, + exportAllSponsorPurchases, + approveSponsorPurchase, + rejectSponsorPurchase +})(ShowPurchaseListPage); diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js index 9e7de9dfa..c7d0770a2 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js @@ -85,9 +85,10 @@ const SponsorPurchasesTab = ({ }; const handleStatusChange = (purchaseId, newStatus) => { - if (newStatus === PURCHASE_STATUS.PAID) approveSponsorPurchase(purchaseId); + if (newStatus === PURCHASE_STATUS.PAID) + approveSponsorPurchase(sponsor.id, purchaseId); if (newStatus === PURCHASE_STATUS.CANCELLED) - rejectSponsorPurchase(purchaseId); + rejectSponsorPurchase(sponsor.id, purchaseId); }; const tableColumns = [ @@ -149,7 +150,7 @@ const SponsorPurchasesTab = ({ render: (row) => (
diff --git a/src/pages/sponsors/show-purchase-list-page/index.js b/src/pages/sponsors/show-purchase-list-page/index.js index d5e0e72cc..43f42d840 100644 --- a/src/pages/sponsors/show-purchase-list-page/index.js +++ b/src/pages/sponsors/show-purchase-list-page/index.js @@ -76,7 +76,7 @@ const ShowPurchaseListPage = ({ }; const handleExport = () => { - exportAllSponsorPurchases(); + exportAllSponsorPurchases(term, order, orderDir); }; const handleSearch = (searchTerm) => {