-
Notifications
You must be signed in to change notification settings - Fork 4
feat: sponsor purchases tab #757
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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()); | ||
| }); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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); | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
Comment on lines
+78
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Status change handler is missing row context.
Suggested fix- const handleStatusChange = (stat) => {
- console.log("STATUS : ", stat);
+ const handleStatusChange = (row, newStatus) => {
+ console.log("STATUS : ", row, newStatus);
+ // TODO: dispatch action to update purchase status
};And update the onChange handler: - onChange={(ev) => handleStatusChange(ev.target.value)}
+ onChange={(ev) => handleStatusChange(row, ev.target.value)}Also applies to: 102-121 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const tableColumns = [ | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| columnKey: "number", | ||||||||||||||||||||||||||
| header: T.translate("edit_sponsor.purchase_tab.order"), | ||||||||||||||||||||||||||
| sortable: true | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
|
Comment on lines
+82
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Check the reducer mapping for purchases data structure
rg -n -A 15 "const purchases" src/reducers/sponsors/sponsor-page-purchase-list-reducer.js | head -40Repository: fntechgit/summit-admin Length of output: 535 🏁 Script executed: # Also check if there's any mapping of "order" to "number" in the component or elsewhere
rg -n "columnKey.*number" src/pages/sponsors/sponsor-purchases-tab/index.jsRepository: fntechgit/summit-admin Length of output: 95 🏁 Script executed: # Check the purchase list reducer more broadly to understand the data shape
cat src/reducers/sponsors/sponsor-page-purchase-list-reducer.jsRepository: fntechgit/summit-admin Length of output: 2413 Fix column key mismatch for order number. The reducer maps Suggested fix const tableColumns = [
{
- columnKey: "number",
+ columnKey: "order",
header: T.translate("edit_sponsor.purchase_tab.order"),
sortable: true
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| 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 ( | ||||||||||||||||||||||||||
| <Select | ||||||||||||||||||||||||||
| fullWidth | ||||||||||||||||||||||||||
| variant="outlined" | ||||||||||||||||||||||||||
| value={row.status} | ||||||||||||||||||||||||||
| onChange={(ev) => handleStatusChange(ev.target.value)} | ||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||
| {Object.values(PURCHASE_STATUS).map((s) => ( | ||||||||||||||||||||||||||
| <MenuItem key={`purchase-status-${s}`} value={s}> | ||||||||||||||||||||||||||
| {s} | ||||||||||||||||||||||||||
| </MenuItem> | ||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||
| </Select> | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return row.status; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| columnKey: "amount", | ||||||||||||||||||||||||||
| header: T.translate("edit_sponsor.purchase_tab.amount"), | ||||||||||||||||||||||||||
| sortable: true | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| columnKey: "details", | ||||||||||||||||||||||||||
| header: "", | ||||||||||||||||||||||||||
| width: 100, | ||||||||||||||||||||||||||
| align: "center", | ||||||||||||||||||||||||||
| render: (row) => ( | ||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||
| variant="text" | ||||||||||||||||||||||||||
| color="inherit" | ||||||||||||||||||||||||||
| size="small" | ||||||||||||||||||||||||||
| onClick={() => handleDetails(row)} | ||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||
| {T.translate("edit_sponsor.purchase_tab.details")} | ||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| columnKey: "menu", | ||||||||||||||||||||||||||
| header: "", | ||||||||||||||||||||||||||
| width: 100, | ||||||||||||||||||||||||||
| align: "center", | ||||||||||||||||||||||||||
| render: (row) => ( | ||||||||||||||||||||||||||
| <IconButton size="large" onClick={() => handleMenu(row)}> | ||||||||||||||||||||||||||
| <MenuIcon fontSize="large" /> | ||||||||||||||||||||||||||
| </IconButton> | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||
| <Box sx={{ mt: 2 }}> | ||||||||||||||||||||||||||
| <Grid2 | ||||||||||||||||||||||||||
| container | ||||||||||||||||||||||||||
| spacing={2} | ||||||||||||||||||||||||||
| sx={{ | ||||||||||||||||||||||||||
| justifyContent: "center", | ||||||||||||||||||||||||||
| alignItems: "center", | ||||||||||||||||||||||||||
| mb: 2 | ||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||
| <Grid2 size={2}> | ||||||||||||||||||||||||||
| <Box component="span"> | ||||||||||||||||||||||||||
| {totalCount} {T.translate("edit_sponsor.purchase_tab.purchases")} | ||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||
| </Grid2> | ||||||||||||||||||||||||||
| <Grid2 size={2} offset={8}> | ||||||||||||||||||||||||||
| <SearchInput | ||||||||||||||||||||||||||
| term={term} | ||||||||||||||||||||||||||
| onSearch={handleSearch} | ||||||||||||||||||||||||||
| placeholder={T.translate("edit_sponsor.placeholders.search")} | ||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||
| </Grid2> | ||||||||||||||||||||||||||
| </Grid2> | ||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||
| <MuiTable | ||||||||||||||||||||||||||
| columns={tableColumns} | ||||||||||||||||||||||||||
| data={purchases} | ||||||||||||||||||||||||||
| options={{ sortCol: order, sortDir: orderDir }} | ||||||||||||||||||||||||||
| perPage={perPage} | ||||||||||||||||||||||||||
| totalRows={totalCount} | ||||||||||||||||||||||||||
| currentPage={currentPage} | ||||||||||||||||||||||||||
| onPageChange={handlePageChange} | ||||||||||||||||||||||||||
| onPerPageChange={handlePerPageChange} | ||||||||||||||||||||||||||
| onSort={handleSort} | ||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const mapStateToProps = ({ sponsorPagePurchaseListState }) => ({ | ||||||||||||||||||||||||||
| ...sponsorPagePurchaseListState | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| export default connect(mapStateToProps, { | ||||||||||||||||||||||||||
| getSponsorPurchases | ||||||||||||||||||||||||||
| })(SponsorPurchasesTab); | ||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for API failures.
The promise chain only has
.then()for success. If the request fails,stopLoading()won't be called, potentially leaving the UI in a loading state.Suggested fix
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()); - }); + )(params)(dispatch) + .then(() => { + dispatch(stopLoading()); + }) + .catch(() => { + dispatch(stopLoading()); + }); };Alternatively, use
.finally():🤖 Prompt for AI Agents