diff --git a/.env.example b/.env.example index e3779db3f..70b5f46f5 100644 --- a/.env.example +++ b/.env.example @@ -12,7 +12,7 @@ PRINT_APP_URL=https://badge-print-app.dev.fnopen.com PUB_API_BASE_URL= OS_BASE_URL= SCOPES_BASE_REALM=${API_BASE_URL} -PURCHASES_API_SCOPES="purchases-show-medata/read purchases-show-medata/write show-form/read show-form/write customized-form/write customized-form/read carts/read carts/write" +PURCHASES_API_SCOPES="purchases-show-medata/read purchases-show-medata/write show-form/read show-form/write customized-form/write customized-form/read carts/read carts/write purchases/read" SPONSOR_USERS_API_SCOPES="show-medata/read show-medata/write access-requests/read access-requests/write sponsor-users/read sponsor-users/write groups/read groups/write" EMAIL_SCOPES="clients/read templates/read templates/write emails/read" FILE_UPLOAD_SCOPES="files/upload" diff --git a/src/actions/sponsor-purchases-actions.js b/src/actions/sponsor-purchases-actions.js new file mode 100644 index 000000000..41af07774 --- /dev/null +++ b/src/actions/sponsor-purchases-actions.js @@ -0,0 +1,81 @@ +/** + * Copyright 2018 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import { + authErrorHandler, + createAction, + getRequest, + startLoading, + stopLoading +} from "openstack-uicore-foundation/lib/utils/actions"; + +import { escapeFilterValue, getAccessTokenSafely } from "../utils/methods"; +import { + DEFAULT_CURRENT_PAGE, + DEFAULT_ORDER_DIR, + DEFAULT_PER_PAGE +} from "../utils/constants"; + +export const REQUEST_SPONSOR_PURCHASES = "REQUEST_SPONSOR_PURCHASES"; +export const RECEIVE_SPONSOR_PURCHASES = "RECEIVE_SPONSOR_PURCHASES"; + +export const getSponsorPurchases = + ( + term = "", + page = DEFAULT_CURRENT_PAGE, + perPage = DEFAULT_PER_PAGE, + order = "id", + orderDir = DEFAULT_ORDER_DIR + ) => + async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { entity: sponsor } = currentSponsorState; + 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 + }; + + if (filter.length > 0) { + params["filter[]"] = filter; + } + + // order + if (order != null && orderDir != null) { + const orderDirSign = orderDir === 1 ? "" : "-"; + params.order = `${orderDirSign}${order}`; + } + + return getRequest( + createAction(REQUEST_SPONSOR_PURCHASES), + createAction(RECEIVE_SPONSOR_PURCHASES), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/purchases`, + authErrorHandler, + { order, orderDir, page, perPage, term } + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + }); + }; diff --git a/src/i18n/en.json b/src/i18n/en.json index e6ca66683..6b08980ac 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -2470,6 +2470,15 @@ "pay_cc": "pay with credit card or ach", "pay_invoice": "pay with invoice" }, + "purchase_tab": { + "order": "Order", + "purchased": "Purchased", + "payment_method": "Payment Method", + "status": "Status", + "amount": "Amount", + "details": "Details", + "purchases": "purchases" + }, "placeholders": { "select_sponsorship": "Select a Sponsorship", "sponsorship_type": "Start typing to choose a Tier...", diff --git a/src/pages/sponsors/edit-sponsor-page.js b/src/pages/sponsors/edit-sponsor-page.js index 3a4e9a709..effffd9f5 100644 --- a/src/pages/sponsors/edit-sponsor-page.js +++ b/src/pages/sponsors/edit-sponsor-page.js @@ -45,6 +45,7 @@ import SponsorBadgeScans from "./sponsor-badge-scans"; import SponsorCartTab from "./sponsor-cart-tab"; import SponsorFormsManageItems from "./sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items"; import { SPONSOR_TABS } from "../../utils/constants"; +import SponsorPurchasesTab from "./sponsor-purchases-tab"; const CustomTabPanel = (props) => { const { children, value, index, ...other } = props; @@ -225,6 +226,9 @@ const EditSponsorPage = (props) => { + + + diff --git a/src/pages/sponsors/sponsor-purchases-tab/index.js b/src/pages/sponsors/sponsor-purchases-tab/index.js new file mode 100644 index 000000000..e3cbc28df --- /dev/null +++ b/src/pages/sponsors/sponsor-purchases-tab/index.js @@ -0,0 +1,204 @@ +/** + * Copyright 2024 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React, { useEffect } from "react"; +import { connect } from "react-redux"; +import T from "i18n-react/dist/i18n-react"; +import { + Box, + Button, + Grid2, + IconButton, + MenuItem, + Select +} from "@mui/material"; +import MenuIcon from "@mui/icons-material/Menu"; +import { getSponsorPurchases } from "../../../actions/sponsor-purchases-actions"; +import SearchInput from "../../../components/mui/search-input"; +import MuiTable from "../../../components/mui/table/mui-table"; +import { + DEFAULT_CURRENT_PAGE, + PURCHASE_STATUS +} from "../../../utils/constants"; + +const SponsorPurchasesTab = ({ + purchases, + term, + order, + orderDir, + currentPage, + perPage, + totalCount, + getSponsorPurchases +}) => { + useEffect(() => { + getSponsorPurchases(); + }, []); + + const handlePageChange = (page) => { + getSponsorPurchases(term, page, perPage, order, orderDir); + }; + + const handleSort = (key, dir) => { + getSponsorPurchases(term, currentPage, perPage, key, dir); + }; + + const handlePerPageChange = (newPerPage) => { + getSponsorPurchases( + term, + DEFAULT_CURRENT_PAGE, + newPerPage, + order, + orderDir + ); + }; + + const handleSearch = (searchTerm) => { + getSponsorPurchases(searchTerm); + }; + + const handleDetails = (item) => { + console.log("DETAILS : ", item); + }; + + const handleMenu = (item) => { + console.log("MENU : ", item); + }; + + const handleStatusChange = (stat) => { + console.log("STATUS : ", stat); + }; + + const tableColumns = [ + { + columnKey: "number", + header: T.translate("edit_sponsor.purchase_tab.order"), + sortable: true + }, + { + columnKey: "purchased", + header: T.translate("edit_sponsor.purchase_tab.purchased"), + sortable: true + }, + { + columnKey: "payment_method", + header: T.translate("edit_sponsor.purchase_tab.payment_method"), + sortable: true + }, + { + columnKey: "status", + header: T.translate("edit_sponsor.purchase_tab.status"), + sortable: true, + render: (row) => { + if (row.status === PURCHASE_STATUS.PENDING) { + return ( + + ); + } + + return row.status; + } + }, + { + columnKey: "amount", + header: T.translate("edit_sponsor.purchase_tab.amount"), + sortable: true + }, + { + columnKey: "details", + header: "", + width: 100, + align: "center", + render: (row) => ( + + ) + }, + { + columnKey: "menu", + header: "", + width: 100, + align: "center", + render: (row) => ( + handleMenu(row)}> + + + ) + } + ]; + + return ( + + + + + {totalCount} {T.translate("edit_sponsor.purchase_tab.purchases")} + + + + + + +
+ +
+
+ ); +}; + +const mapStateToProps = ({ sponsorPagePurchaseListState }) => ({ + ...sponsorPagePurchaseListState +}); + +export default connect(mapStateToProps, { + getSponsorPurchases +})(SponsorPurchasesTab); diff --git a/src/reducers/sponsors/sponsor-page-purchase-list-reducer.js b/src/reducers/sponsors/sponsor-page-purchase-list-reducer.js new file mode 100644 index 000000000..e962508b1 --- /dev/null +++ b/src/reducers/sponsors/sponsor-page-purchase-list-reducer.js @@ -0,0 +1,85 @@ +/** + * Copyright 2019 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import moment from "moment-timezone"; +import { amountFromCents } from "openstack-uicore-foundation/lib/utils/money"; +import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; +import { + REQUEST_SPONSOR_PURCHASES, + RECEIVE_SPONSOR_PURCHASES +} from "../../actions/sponsor-purchases-actions"; +import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; +import { MILLISECONDS_TO_SECONDS } from "../../utils/constants"; + +const DEFAULT_STATE = { + purchases: [], + order: "order", + orderDir: 1, + currentPage: 1, + lastPage: 1, + perPage: 10, + totalCount: 0, + term: "" +}; + +const sponsorPagePurchaseListReducer = (state = DEFAULT_STATE, action) => { + const { type, payload } = action; + + switch (type) { + case SET_CURRENT_SUMMIT: + case LOGOUT_USER: { + return DEFAULT_STATE; + } + case REQUEST_SPONSOR_PURCHASES: { + const { order, orderDir, page, perPage, term } = payload; + + return { + ...state, + order, + orderDir, + forms: [], + currentPage: page, + perPage, + term + }; + } + case RECEIVE_SPONSOR_PURCHASES: { + const { + current_page: currentPage, + total, + last_page: lastPage + } = payload.response; + + const purchases = payload.response.data.map((a) => ({ + ...a, + order: a.order_number, + amount: `$${amountFromCents(a.raw_amount)}`, + purchased: moment(a.created * MILLISECONDS_TO_SECONDS).format( + "YYYY/MM/DD HH:mm a" + ) + })); + + return { + ...state, + purchases, + currentPage, + totalCount: total, + lastPage + }; + } + default: + return state; + } +}; + +export default sponsorPagePurchaseListReducer; diff --git a/src/store.js b/src/store.js index b3289558e..d994d7617 100644 --- a/src/store.js +++ b/src/store.js @@ -167,6 +167,7 @@ import sponsorPageFormsListReducer from "./reducers/sponsors/sponsor-page-forms- import sponsorCustomizedFormReducer from "./reducers/sponsors/sponsor-customized-form-reducer.js"; import sponsorPageCartListReducer from "./reducers/sponsors/sponsor-page-cart-list-reducer"; import sponsorCustomizedFormItemsListReducer from "./reducers/sponsors/sponsor-customized-form-items-list-reducer.js"; +import sponsorPagePurchaseListReducer from "./reducers/sponsors/sponsor-page-purchase-list-reducer.js"; // default: localStorage if web, AsyncStorage if react-native @@ -255,6 +256,7 @@ const reducers = persistCombineReducers(config, { sponsorPageCartListState: sponsorPageCartListReducer, sponsorCustomizedFormState: sponsorCustomizedFormReducer, sponsorCustomizedFormItemsListState: sponsorCustomizedFormItemsListReducer, + sponsorPagePurchaseListState: sponsorPagePurchaseListReducer, currentSponsorPromocodeListState: sponsorPromocodeListReducer, currentSponsorExtraQuestionState: sponsorExtraQuestionReducer, currentSponsorAdvertisementState: sponsorAdvertisementReducer, @@ -305,7 +307,7 @@ const reducers = persistCombineReducers(config, { sponsoredProjectState: sponsoredProjectReducer, sponsoredProjectSponsorshipTypeState: sponsoredProjectSponsorshipTypeReducer, sponsoredProjectSponsorshipTypeSupportingCompanyState: - sponsoredProjectSponsorshipTypeSupportingCompanyReducer, + sponsoredProjectSponsorshipTypeSupportingCompanyReducer, scheduleSettingsState: scheduleSettingsReducer, scheduleSettingsListState: scheduleSettingsListReducer, currentSelectionPlanExtraQuestionState: selectionPlanExtraQuestionReducer, diff --git a/src/utils/constants.js b/src/utils/constants.js index 730ea805d..36d281fb0 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -240,3 +240,9 @@ export const fieldTypesWithOptions = [ "ComboBox", "RadioButtonList" ]; + +export const PURCHASE_STATUS = { + PENDING: "Pending", + PAID: "Paid", + CANCELLED: "Cancelled" +};