diff --git a/src/actions/sponsorship-actions.js b/src/actions/sponsorship-actions.js index 6c97c4da5..e0f8e2f62 100644 --- a/src/actions/sponsorship-actions.js +++ b/src/actions/sponsorship-actions.js @@ -12,7 +12,7 @@ * */ import T from "i18n-react/dist/i18n-react"; -import * as _ from "lodash"; +import debounce from "lodash/debounce"; import { getRequest, putRequest, @@ -21,15 +21,12 @@ import { createAction, stopLoading, startLoading, - showMessage, - showSuccessMessage, - authErrorHandler, escapeFilterValue, fetchResponseHandler, fetchErrorHandler } from "openstack-uicore-foundation/lib/utils/actions"; import URI from "urijs"; -import history from "../history"; +import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; import { getAccessTokenSafely } from "../utils/methods"; import { DEBOUNCE_WAIT, @@ -48,12 +45,19 @@ export const SPONSORSHIP_UPDATED = "SPONSORSHIP_UPDATED"; export const SPONSORSHIP_ADDED = "SPONSORSHIP_ADDED"; export const SPONSORSHIP_DELETED = "SPONSORSHIP_DELETED"; -/** **************** SPONSORS *************************************** */ +/* ***************** SPONSORS *************************************** */ export const getSponsorships = - (page = 1, perPage = DEFAULT_PER_PAGE, order = "name", orderDir = 1) => + ( + term = "", + page = 1, + perPage = DEFAULT_PER_PAGE, + order = "name", + orderDir = 1 + ) => async (dispatch) => { const accessToken = await getAccessTokenSafely(); + const filter = []; dispatch(startLoading()); @@ -63,6 +67,17 @@ export const getSponsorships = access_token: accessToken }; + if (term) { + const escapedTerm = escapeFilterValue(term); + filter.push( + `name=@${escapedTerm},label=@${escapedTerm},size=@${escapedTerm}` + ); + } + + if (filter.length > 0) { + params["filter[]"] = filter; + } + // order if (order != null && orderDir != null) { const orderDirSign = orderDir === 1 ? "+" : "-"; @@ -73,9 +88,9 @@ export const getSponsorships = createAction(REQUEST_SPONSORSHIPS), createAction(RECEIVE_SPONSORSHIPS), `${window.API_BASE_URL}/api/v1/sponsorship-types`, - authErrorHandler, - { order, orderDir, page } - )(params)(dispatch).then(() => { + snackbarErrorHandler, + { order, orderDir, page, perPage } + )(params)(dispatch).finally(() => { dispatch(stopLoading()); }); }; @@ -93,8 +108,8 @@ export const getSponsorship = (sponsorshipId) => async (dispatch) => { null, createAction(RECEIVE_SPONSORSHIP), `${window.API_BASE_URL}/api/v1/sponsorship-types/${sponsorshipId}`, - authErrorHandler - )(params)(dispatch).then(() => { + snackbarErrorHandler + )(params)(dispatch).finally(() => { dispatch(stopLoading()); }); }; @@ -115,40 +130,42 @@ export const saveSponsorship = (entity) => async (dispatch) => { const normalizedEntity = normalizeSponsorship(entity); if (entity.id) { - putRequest( + return putRequest( createAction(UPDATE_SPONSORSHIP), createAction(SPONSORSHIP_UPDATED), `${window.API_BASE_URL}/api/v1/sponsorship-types/${entity.id}`, normalizedEntity, - authErrorHandler, + snackbarErrorHandler, entity - )(params)(dispatch).then(() => { - dispatch( - showSuccessMessage(T.translate("edit_sponsorship.sponsorship_saved")) - ); - }); - } else { - const success_message = { - title: T.translate("general.done"), - html: T.translate("edit_sponsorship.sponsorship_created"), - type: "success" - }; + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_sponsorship.sponsorship_saved") + }) + ); + }) + .finally(() => dispatch(stopLoading())); + } - postRequest( - createAction(UPDATE_SPONSORSHIP), - createAction(SPONSORSHIP_ADDED), - `${window.API_BASE_URL}/api/v1/sponsorship-types`, - normalizedEntity, - authErrorHandler, - entity - )(params)(dispatch).then((payload) => { + return postRequest( + createAction(UPDATE_SPONSORSHIP), + createAction(SPONSORSHIP_ADDED), + `${window.API_BASE_URL}/api/v1/sponsorship-types`, + normalizedEntity, + snackbarErrorHandler, + entity + )(params)(dispatch) + .then(() => { dispatch( - showMessage(success_message, () => { - history.push(`/app/sponsorship-types/${payload.response.id}`); + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_sponsorship.sponsorship_created") }) ); - }); - } + }) + .finally(() => dispatch(stopLoading())); }; export const deleteSponsorship = (sponsorshipId) => async (dispatch) => { @@ -163,8 +180,8 @@ export const deleteSponsorship = (sponsorshipId) => async (dispatch) => { createAction(SPONSORSHIP_DELETED)({ sponsorshipId }), `${window.API_BASE_URL}/api/v1/sponsorship-types/${sponsorshipId}`, null, - authErrorHandler - )(params)(dispatch).then(() => { + snackbarErrorHandler + )(params)(dispatch).finally(() => { dispatch(stopLoading()); }); }; @@ -175,7 +192,7 @@ const normalizeSponsorship = (entity) => { return normalizedEntity; }; -export const querySponsorships = _.debounce(async (input, callback) => { +export const querySponsorships = debounce(async (input, callback) => { const accessToken = await getAccessTokenSafely(); const endpoint = URI(`${window.API_BASE_URL}/api/v1/sponsorship-types`); input = escapeFilterValue(input); @@ -192,7 +209,7 @@ export const querySponsorships = _.debounce(async (input, callback) => { .catch(fetchErrorHandler); }, DEBOUNCE_WAIT); -export const querySponsorshipsBySummit = _.debounce( +export const querySponsorshipsBySummit = debounce( async (input, summitId, callback) => { const accessToken = await getAccessTokenSafely(); const endpoint = URI( diff --git a/src/i18n/en.json b/src/i18n/en.json index 4599c5b52..01777ca8b 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -2886,22 +2886,25 @@ "sponsorship_types_list": "Tier List", "sponsorship_type": "Tier", "widget_title": "Widget Title", - "no_sponsorships": "No sponsorships found for current search criteria.", - "add_sponsorship": "Add Sponsorship", + "no_sponsorships": "No tiers found for current search criteria.", + "add_sponsorship": "Add Tier", "name": "Name", "label": "Label", "size": "Size", "order": "Order", - "remove_warning": "Are you sure you want to delete sponsorship " + "remove_warning": "Are you sure you want to delete tier {name}", + "placeholders": { + "search": "Search tier" + } }, "edit_sponsorship": { - "sponsorship": "Sponsorship", + "sponsorship": "Tier", "name": "Name", "order": "Order", "label": "Label", "size": "Size", - "sponsorship_saved": "Sponsorship saved successfully", - "sponsorship_created": "Sponsorship created successfully", + "sponsorship_saved": "Tier saved successfully", + "sponsorship_created": "Tier created successfully", "placeholders": { "select_size": "Select a Size" } diff --git a/src/pages/sponsorship-types/__tests__/sponsorship-list-page.test.js b/src/pages/sponsorship-types/__tests__/sponsorship-list-page.test.js new file mode 100644 index 000000000..f13547ca9 --- /dev/null +++ b/src/pages/sponsorship-types/__tests__/sponsorship-list-page.test.js @@ -0,0 +1,203 @@ +import React from "react"; +import { act, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { renderWithRedux } from "../../../utils/test-utils"; +import SponsorshipListPage from "../sponsorship-list-page"; +import * as sponsorshipActions from "../../../actions/sponsorship-actions"; + +jest.mock("i18n-react/dist/i18n-react", () => ({ + translate: jest.fn((key) => key) +})); + +jest.mock("sweetalert2", () => ({ + fire: jest.fn(() => Promise.resolve({ value: true })) +})); + +jest.mock( + "openstack-uicore-foundation/lib/components/mui/table", + () => + function MockMuiTable({ data, onEdit, onDelete }) { + return ( +
+ {data.map((row) => ( +
+ + +
+ ))} +
+ ); + } +); + +jest.mock( + "../components/sponsorship-dialog", + () => + function MockSponsorshipDialog({ onSave, onClose }) { + return ( +
+ + +
+ ); + } +); + +jest.mock("../../../actions/sponsorship-actions", () => { + const original = jest.requireActual("../../../actions/sponsorship-actions"); + return { + __esModule: true, + ...original, + getSponsorships: jest.fn(() => () => Promise.resolve()), + getSponsorship: jest.fn(() => () => Promise.resolve()), + saveSponsorship: jest.fn(() => () => Promise.resolve()), + deleteSponsorship: jest.fn(() => () => Promise.resolve()), + resetSponsorshipForm: jest.fn(() => ({ type: "RESET_SPONSORSHIP_FORM" })) + }; +}); + +const SPONSORSHIPS = [ + { id: 1, name: "Gold", label: "Gold Tier", size: "Large" }, + { id: 2, name: "Silver", label: "Silver Tier", size: "Medium" } +]; + +const buildState = (listOverrides = {}) => ({ + currentSponsorshipListState: { + sponsorships: SPONSORSHIPS, + currentPage: 1, + lastPage: 1, + perPage: 10, + order: "name", + orderDir: 1, + totalSponsorships: 2, + ...listOverrides + }, + currentSponsorshipState: { + entity: { id: 0, name: "", label: "", size: "", order: 0 }, + errors: {} + } +}); + +describe("SponsorshipListPage", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should call getSponsorships on mount", () => { + renderWithRedux(, { initialState: buildState() }); + expect(sponsorshipActions.getSponsorships).toHaveBeenCalledTimes(1); + }); + + it("should render the table when sponsorships exist", () => { + renderWithRedux(, { initialState: buildState() }); + expect(screen.getByTestId("mui-table")).toBeInTheDocument(); + expect(screen.getByTestId("row-1")).toBeInTheDocument(); + expect(screen.getByTestId("row-2")).toBeInTheDocument(); + }); + + it("should not render the table when the list is empty", () => { + renderWithRedux(, { + initialState: buildState({ sponsorships: [], totalSponsorships: 0 }) + }); + expect(screen.queryByTestId("mui-table")).not.toBeInTheDocument(); + }); + + it("should open the dialog when the Add button is clicked", async () => { + const user = userEvent.setup(); + renderWithRedux(, { initialState: buildState() }); + + expect(screen.queryByTestId("sponsorship-dialog")).not.toBeInTheDocument(); + + await act(async () => { + await user.click(screen.getByText("sponsorship_list.add_sponsorship")); + }); + + expect(screen.getByTestId("sponsorship-dialog")).toBeInTheDocument(); + }); + + it("should open the dialog and fetch the entity when a row edit button is clicked", async () => { + const user = userEvent.setup(); + renderWithRedux(, { initialState: buildState() }); + + await act(async () => { + await user.click(screen.getByTestId("edit-1")); + }); + + expect(sponsorshipActions.getSponsorship).toHaveBeenCalledWith(1); + expect(screen.getByTestId("sponsorship-dialog")).toBeInTheDocument(); + }); + + it("should close the dialog and reset form when dialog close is triggered", async () => { + const user = userEvent.setup(); + renderWithRedux(, { initialState: buildState() }); + + await act(async () => { + await user.click(screen.getByText("sponsorship_list.add_sponsorship")); + }); + + await act(async () => { + await user.click(screen.getByTestId("dialog-close")); + }); + + expect(sponsorshipActions.resetSponsorshipForm).toHaveBeenCalled(); + expect(screen.queryByTestId("sponsorship-dialog")).not.toBeInTheDocument(); + }); + + it("should call saveSponsorship, refresh list, and close dialog when save is triggered", async () => { + const user = userEvent.setup(); + renderWithRedux(, { initialState: buildState() }); + + await act(async () => { + await user.click(screen.getByText("sponsorship_list.add_sponsorship")); + }); + + await act(async () => { + await user.click(screen.getByTestId("dialog-save")); + }); + + expect(sponsorshipActions.saveSponsorship).toHaveBeenCalledWith({ + id: 0, + name: "New Tier" + }); + expect(sponsorshipActions.getSponsorships).toHaveBeenCalledTimes(2); + expect(screen.queryByTestId("sponsorship-dialog")).not.toBeInTheDocument(); + }); + + it("should call getSponsorships with the search term when Enter is pressed", async () => { + const user = userEvent.setup(); + renderWithRedux(, { initialState: buildState() }); + + const searchInput = screen.getByPlaceholderText( + "sponsorship_list.placeholders.search" + ); + + await act(async () => { + await user.type(searchInput, "Gold{Enter}"); + }); + + expect(sponsorshipActions.getSponsorships).toHaveBeenCalledWith( + "Gold", + 1, + 10, + "name", + 1 + ); + }); +}); diff --git a/src/pages/sponsorship-types/components/__tests__/sponsorship-dialog.test.js b/src/pages/sponsorship-types/components/__tests__/sponsorship-dialog.test.js new file mode 100644 index 000000000..0289c4784 --- /dev/null +++ b/src/pages/sponsorship-types/components/__tests__/sponsorship-dialog.test.js @@ -0,0 +1,121 @@ +import React from "react"; +import { act, render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import SponsorshipDialog from "../sponsorship-dialog"; + +jest.mock("i18n-react/dist/i18n-react", () => ({ + translate: jest.fn((key) => key) +})); + +jest.mock("../../../../hooks/useScrollToError", () => jest.fn()); + +const DEFAULT_ENTITY = { id: 0, name: "", label: "", size: "", order: 0 }; + +const EXISTING_ENTITY = { + id: 5, + name: "Platinum", + label: "Plat", + size: "Big", + order: 1 +}; + +describe("SponsorshipDialog", () => { + const onSave = jest.fn(); + const onClose = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should show the Add title for a new entity", () => { + render( + + ); + expect(screen.getByText(/general\.add/i)).toBeInTheDocument(); + }); + + it("should show the Edit title for an existing entity", () => { + render( + + ); + expect(screen.getByText(/general\.edit/i)).toBeInTheDocument(); + }); + + it("should pre-fill fields with the existing entity values", () => { + render( + + ); + expect(screen.getByDisplayValue("Platinum")).toBeInTheDocument(); + expect(screen.getByDisplayValue("Plat")).toBeInTheDocument(); + }); + + it("should call onClose when the close button is clicked", async () => { + const user = userEvent.setup(); + render( + + ); + + await act(async () => { + await user.click(screen.getByRole("button", { name: "close" })); + }); + + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it("should show a required validation error when submitted without a name", async () => { + const user = userEvent.setup(); + render( + + ); + + await act(async () => { + await user.click(screen.getByText("general.save")); + }); + + expect(screen.getByText("validation.required")).toBeInTheDocument(); + expect(onSave).not.toHaveBeenCalled(); + }); + + it("should call onSave with form values when submitted with a valid name", async () => { + const user = userEvent.setup(); + render( + + ); + + await act(async () => { + await user.type(screen.getByRole("textbox", { name: /name/i }), "Bronze"); + }); + + await act(async () => { + await user.click(screen.getByText("general.save")); + }); + + expect(onSave).toHaveBeenCalledWith( + expect.objectContaining({ name: "Bronze" }) + ); + }); +}); diff --git a/src/pages/sponsorship-types/components/sponsorship-dialog.js b/src/pages/sponsorship-types/components/sponsorship-dialog.js new file mode 100644 index 000000000..172c6e233 --- /dev/null +++ b/src/pages/sponsorship-types/components/sponsorship-dialog.js @@ -0,0 +1,147 @@ +import React from "react"; +import T from "i18n-react/dist/i18n-react"; +import PropTypes from "prop-types"; +import { FormikProvider, useFormik } from "formik"; +import * as yup from "yup"; +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Button, + InputLabel, + Box, + IconButton, + Divider, + Grid2, + MenuItem +} from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import MuiFormikTextField from "../../../components/mui/formik-inputs/mui-formik-textfield"; +import MuiFormikSelect from "../../../components/mui/formik-inputs/mui-formik-select"; +import useScrollToError from "../../../hooks/useScrollToError"; +import { requiredStringValidation } from "../../../utils/yup"; + +const SIZE_OPTIONS = ["Small", "Medium", "Large", "Big"]; + +const SponsorshipDialog = ({ entity: initialEntity, onClose, onSave }) => { + const formik = useFormik({ + initialValues: { + id: initialEntity?.id ?? 0, + name: initialEntity?.name ?? "", + label: initialEntity?.label ?? "", + size: initialEntity?.size ?? "", + order: initialEntity?.order ?? 0 + }, + enableReinitialize: true, + validationSchema: yup.object().shape({ + name: requiredStringValidation() + }), + onSubmit: (values) => onSave(values) + }); + + useScrollToError(formik); + + const handleClose = () => { + formik.resetForm(); + onClose(); + }; + + const title = initialEntity?.id + ? `${T.translate("general.edit")} ${T.translate( + "edit_sponsorship.sponsorship" + )}` + : `${T.translate("general.add")} ${T.translate( + "edit_sponsorship.sponsorship" + )}`; + + return ( + + + {title} + + + + + + + + + + + + {T.translate("edit_sponsorship.name")} * + + + + + + {T.translate("edit_sponsorship.label")} + + + + + + {T.translate("edit_sponsorship.size")} + + + + {SIZE_OPTIONS.map((s) => ( + + {s} + + ))} + + + + + + + + + + + + + ); +}; + +SponsorshipDialog.propTypes = { + entity: PropTypes.object, + onClose: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired +}; + +export default SponsorshipDialog; diff --git a/src/pages/sponsorship-types/sponsorship-list-page.js b/src/pages/sponsorship-types/sponsorship-list-page.js index a0d89bd0b..21ee100fb 100644 --- a/src/pages/sponsorship-types/sponsorship-list-page.js +++ b/src/pages/sponsorship-types/sponsorship-list-page.js @@ -9,168 +9,239 @@ * 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 from "react"; +import React, { useEffect, useState } from "react"; import { connect } from "react-redux"; import T from "i18n-react/dist/i18n-react"; -import Swal from "sweetalert2"; -import { Pagination } from "react-bootstrap"; -import { Table } from "openstack-uicore-foundation/lib/components"; +import { Box, Button, Grid2, TextField } from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import SearchIcon from "@mui/icons-material/Search"; +import MuiTable from "openstack-uicore-foundation/lib/components/mui/table"; import { getSponsorships, - deleteSponsorship + getSponsorship, + saveSponsorship, + deleteSponsorship, + resetSponsorshipForm } from "../../actions/sponsorship-actions"; - -class SponsorshipListPage extends React.Component { - constructor(props) { - super(props); - - this.handleEdit = this.handleEdit.bind(this); - this.handleDelete = this.handleDelete.bind(this); - this.handleSort = this.handleSort.bind(this); - this.handlePageChange = this.handlePageChange.bind(this); - this.handleNewSponsorship = this.handleNewSponsorship.bind(this); - - this.state = {}; - } - - componentDidMount() { - const { currentPage, perPage, order, orderDir } = this.props; - this.props.getSponsorships(currentPage, perPage, order, orderDir); - } - - handleEdit(sponsorship_id) { - const { history } = this.props; - history.push(`/app/sponsorship-types/${sponsorship_id}`); - } - - handlePageChange(page) { - const { order, orderDir, perPage } = this.props; - this.props.getSponsorships(page, perPage, order, orderDir); - } - - handleDelete(sponsorshipId) { - const { deleteSponsorship, sponsorships } = this.props; - let sponsorship = sponsorships.find((t) => t.id === sponsorshipId); - - Swal.fire({ - title: T.translate("general.are_you_sure"), - text: - T.translate("sponsorship_list.remove_warning") + " " + sponsorship.name, - type: "warning", - showCancelButton: true, - confirmButtonColor: "#DD6B55", - confirmButtonText: T.translate("general.yes_delete") - }).then(function (result) { - if (result.value) { - deleteSponsorship(sponsorshipId); - } - }); - } - - handleSort(index, key, dir, func) { - const { perPage, page } = this.props; - this.props.getSponsorships(page, perPage, key, dir); - } - - handleNewSponsorship(ev) { - const { history } = this.props; - history.push(`/app/sponsorship-types/new`); - } - - render() { - const { - sponsorships, - lastPage, - currentPage, +import { DEFAULT_CURRENT_PAGE } from "../../utils/constants"; +import SponsorshipDialog from "./components/sponsorship-dialog"; + +const SponsorshipListPage = ({ + sponsorships, + currentSponsorship, + currentPage, + perPage, + order, + orderDir, + totalSponsorships, + getSponsorships, + getSponsorship, + saveSponsorship, + deleteSponsorship, + resetSponsorshipForm +}) => { + const [open, setOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + + useEffect(() => { + getSponsorships(); + }, [getSponsorships]); + + const handlePageChange = (page) => { + getSponsorships(searchTerm, page, perPage, order, orderDir); + }; + + const handlePerPageChange = (newPerPage) => { + getSponsorships( + searchTerm, + DEFAULT_CURRENT_PAGE, + newPerPage, order, - orderDir, - totalSponsorships - } = this.props; - - const columns = [ - { - columnKey: "name", - value: T.translate("sponsorship_list.name"), - sortable: true - }, - { - columnKey: "label", - value: T.translate("sponsorship_list.label"), - sortable: true - }, - { - columnKey: "size", - value: T.translate("sponsorship_list.size"), - sortable: true - } - ]; - - const table_options = { - sortCol: order, - sortDir: orderDir, - actions: { - edit: { onClick: this.handleEdit }, - delete: { onClick: this.handleDelete } - } - }; - - return ( -
-

- {" "} - {T.translate("sponsorship_list.sponsorship_types_list")} ( - {totalSponsorships}) -

-
-
- -
-
- - {sponsorships.length === 0 && ( -
{T.translate("sponsorship_list.no_sponsorships")}
- )} - - {sponsorships.length > 0 && ( - <> - - - - )} - + orderDir ); - } -} - -const mapStateToProps = ({ currentSponsorshipListState }) => ({ - ...currentSponsorshipListState + }; + + const handleSort = (key, dir) => { + getSponsorships(searchTerm, currentPage, perPage, key, dir); + }; + + const handleSearch = (ev) => { + setSearchTerm(ev.target.value); + if (ev.key === "Enter") { + getSponsorships( + searchTerm, + DEFAULT_CURRENT_PAGE, + perPage, + order, + orderDir + ); + } + }; + + const handleRowEdit = (row) => { + getSponsorship(row.id).then(() => setOpen(true)); + }; + + const handleNew = () => { + resetSponsorshipForm(); + setOpen(true); + }; + + const handleClose = () => { + resetSponsorshipForm(); + setOpen(false); + }; + + const handleSave = (entity) => { + saveSponsorship(entity) + .then(() => + getSponsorships( + searchTerm, + DEFAULT_CURRENT_PAGE, + perPage, + order, + orderDir + ) + ) + .then(() => setOpen(false)); + }; + + const handleDelete = (sponsorshipId) => { + deleteSponsorship(sponsorshipId).then(() => + getSponsorships( + searchTerm, + DEFAULT_CURRENT_PAGE, + perPage, + order, + orderDir + ) + ); + }; + + const columns = [ + { + columnKey: "name", + header: T.translate("sponsorship_list.name"), + sortable: true + }, + { + columnKey: "label", + header: T.translate("sponsorship_list.label"), + sortable: true + }, + { + columnKey: "size", + header: T.translate("sponsorship_list.size"), + sortable: true + } + ]; + + const tableOptions = { sortCol: order, sortDir: orderDir }; + + return ( +
+

{T.translate("sponsorship_list.sponsorship_types_list")}

+ + + + {totalSponsorships}{" "} + {T.translate("sponsorship_list.sponsorship_types")} + + + + } + }} + onChange={(e) => setSearchTerm(e.target.value)} + onKeyDown={handleSearch} + sx={{ "& .MuiOutlinedInput-root": { height: "36px" } }} + /> + + + + + {sponsorships.length > 0 && ( + + T.translate("sponsorship_list.remove_warning", { name }) + } + /> + )} + + {sponsorships.length === 0 && ( +
{T.translate("sponsorship_list.no_sponsorships")}
+ )} + + {open && ( + + )} +
+ ); +}; + +const mapStateToProps = ({ + currentSponsorshipListState, + currentSponsorshipState +}) => ({ + ...currentSponsorshipListState, + currentSponsorship: currentSponsorshipState.entity }); export default connect(mapStateToProps, { getSponsorships, - deleteSponsorship + getSponsorship, + saveSponsorship, + deleteSponsorship, + resetSponsorshipForm })(SponsorshipListPage); diff --git a/src/reducers/sponsorships/sponsorship-list-reducer.js b/src/reducers/sponsorships/sponsorship-list-reducer.js index baa6e610a..4478b86aa 100644 --- a/src/reducers/sponsorships/sponsorship-list-reducer.js +++ b/src/reducers/sponsorships/sponsorship-list-reducer.js @@ -9,7 +9,9 @@ * 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 { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; import { RECEIVE_SPONSORSHIPS, @@ -18,7 +20,6 @@ import { } from "../../actions/sponsorship-actions"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; -import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; const DEFAULT_STATE = { sponsorships: [], @@ -38,24 +39,24 @@ const sponsorshipListReducer = (state = DEFAULT_STATE, action) => { return DEFAULT_STATE; } case REQUEST_SPONSORSHIPS: { - let { order, orderDir } = payload; + const { order, orderDir, perPage } = payload; - return { ...state, order, orderDir }; + return { ...state, order, orderDir, perPage }; } case RECEIVE_SPONSORSHIPS: { - let { current_page, total, last_page } = payload.response; - let sponsorships = payload.response.data; + const { current_page, total, last_page } = payload.response; + const sponsorships = payload.response.data; return { ...state, - sponsorships: sponsorships, + sponsorships, totalSponsorships: total, currentPage: current_page, lastPage: last_page }; } case SPONSORSHIP_DELETED: { - let { sponsorshipId } = payload; + const { sponsorshipId } = payload; return { ...state, sponsorships: state.sponsorships.filter((t) => t.id !== sponsorshipId)