diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js index 78091a090..13667ace3 100644 --- a/src/actions/sponsor-pages-actions.js +++ b/src/actions/sponsor-pages-actions.js @@ -23,12 +23,17 @@ import { import T from "i18n-react/dist/i18n-react"; import { getAccessTokenSafely } from "../utils/methods"; import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; -import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, DEFAULT_PER_PAGE } from "../utils/constants"; +import { + DEFAULT_CURRENT_PAGE, + DEFAULT_ORDER_DIR, + DEFAULT_PER_PAGE +} from "../utils/constants"; export const GLOBAL_PAGE_CLONED = "GLOBAL_PAGE_CLONED"; export const REQUEST_SPONSOR_MANAGED_PAGES = "REQUEST_SPONSOR_MANAGED_PAGES"; export const RECEIVE_SPONSOR_MANAGED_PAGES = "RECEIVE_SPONSOR_MANAGED_PAGES"; +export const SPONSOR_MANAGED_PAGE_ADDED = "SPONSOR_MANAGED_PAGE_ADDED"; export const REQUEST_SPONSOR_CUSTOMIZED_PAGES = "REQUEST_SPONSOR_CUSTOMIZED_PAGES"; @@ -135,6 +140,49 @@ export const getSponsorManagedPages = }); }; +export const saveSponsorManagedPage = + (entity) => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { + entity: { id: sponsorId } + } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const normalizedEntity = normalizeSponsorManagedPage(entity); + + const params = { + access_token: accessToken, + fields: "id,code,name,kind,modules_count,allowed_add_ons" + }; + + return postRequest( + null, + createAction(SPONSOR_MANAGED_PAGE_ADDED), + `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/managed-pages`, + normalizedEntity, + snackbarErrorHandler + )(params)(dispatch).finally(() => { + dispatch(stopLoading()); + }); + }; + +const normalizeSponsorManagedPage = (entity) => { + const normalizedEntity = { + show_page_ids: entity.pages, + allowed_add_ons: entity.add_ons.map((a) => a.id), + apply_to_all_add_ons: false + }; + + if (entity.add_ons.includes("all")) { + normalizedEntity.apply_to_all_add_ons = true; + normalizedEntity.allowed_add_ons = []; + } + + return normalizedEntity; +}; /* ************************************************************************ */ /* CUSTOMIZED PAGES */ /* ************************************************************************ */ diff --git a/src/components/mui/formik-inputs/mui-formik-select-group.js b/src/components/mui/formik-inputs/mui-formik-select-group.js index 9a1261d0a..60be7c1df 100644 --- a/src/components/mui/formik-inputs/mui-formik-select-group.js +++ b/src/components/mui/formik-inputs/mui-formik-select-group.js @@ -44,6 +44,7 @@ const MuiFormikSelectGroup = ({ selectAllLabel = "Select All", getOptionLabel = (item) => item.name, getOptionValue = (item) => item.id, + noOptionsLabel = "No items", getGroupId = null, getGroupLabel = null, disabled = false @@ -213,6 +214,79 @@ const MuiFormikSelectGroup = ({ .flat() .filter(Boolean); + const renderMenuContent = () => { + if (loading) { + return ( + + + + ); + } + + if (options.length === 0) { + return ( + + + + ); + } + + return ( + <> + {showSelectAll && ( + <> + { + // custom event value to select all + handleChange({ target: { value: ["selectAll"] } }); + }} + > + 0 && !isAllSelected} + sx={{ + p: 1, + "& svg": { + fontSize: 24 + } + }} + /> + + + + + )} + {renderGroupedOptions()} + + ); + }; + const IconWithLoading = useMemo(() => getCustomIcon(loading), [loading]); return ( @@ -243,54 +317,7 @@ const MuiFormikSelectGroup = ({ error={Boolean(error)} IconComponent={IconWithLoading} > - {loading ? ( - - - - ) : ( - <> - {showSelectAll && options.length > 0 && ( - <> - { - // custom event value to select all - handleChange({ target: { value: ["selectAll"] } }); - }} - > - 0 && !isAllSelected} - sx={{ - p: 1, - "& svg": { - fontSize: 24 - } - }} - /> - - - - - )} - {renderGroupedOptions()} - - )} + {renderMenuContent()} {error && (
handleSort("date", "+") // }, // { label: "Newest", onClick: () => handleSort("date", "+") }, - { label: "A-Z", onClick: () => handleSort("name", 1) }, - { label: "Z-A", onClick: () => handleSort("name", 0) } + { + label: T.translate("general.sort_asc_label"), + onClick: () => handleSort("name", 1) + }, + { + label: T.translate("general.sort_desc_label"), + onClick: () => handleSort("name", 0) + } ]} > - sort by + {" "} + {T.translate("general.sort_by")} diff --git a/src/pages/sponsors-global/form-templates/form-template-from-duplicate-popup.js b/src/pages/sponsors-global/form-templates/form-template-from-duplicate-popup.js index f9a2336d4..67dcf3f10 100644 --- a/src/pages/sponsors-global/form-templates/form-template-from-duplicate-popup.js +++ b/src/pages/sponsors-global/form-templates/form-template-from-duplicate-popup.js @@ -119,11 +119,18 @@ const FormTemplateFromDuplicateDialog = ({ buttonSx={{ color: "#000" }} menuItems={[ // { label: "Newest", onClick: () => handleSort("+date") }, - { label: "A-Z", onClick: () => handleSort("name", 1) }, - { label: "Z-A", onClick: () => handleSort("name", 0) } + { + label: T.translate("general.sort_asc_label"), + onClick: () => handleSort("name", 1) + }, + { + label: T.translate("general.sort_desc_label"), + onClick: () => handleSort("name", 0) + } ]} > - sort by + {" "} + {T.translate("general.sort_by")} diff --git a/src/pages/sponsors/sponsor-forms-tab/components/add-sponsor-form-template-popup/index.js b/src/pages/sponsors/sponsor-forms-tab/components/add-sponsor-form-template-popup/index.js index 3a6ff11f7..77d604d3e 100644 --- a/src/pages/sponsors/sponsor-forms-tab/components/add-sponsor-form-template-popup/index.js +++ b/src/pages/sponsors/sponsor-forms-tab/components/add-sponsor-form-template-popup/index.js @@ -227,11 +227,18 @@ const AddSponsorFormTemplatePopup = ({ buttonId="sort-button" menuId="sort-menu" menuItems={[ - { label: "A-Z", onClick: () => handleSort("name", 1) }, - { label: "Z-A", onClick: () => handleSort("name", 0) } + { + label: T.translate("general.sort_asc_label"), + onClick: () => handleSort("name", 1) + }, + { + label: T.translate("general.sort_desc_label"), + onClick: () => handleSort("name", 0) + } ]} > - sort by + {" "} + {T.translate("general.sort_by")} diff --git a/src/pages/sponsors/sponsor-forms-tab/components/manage-items/sponsor-form-item-from-inventory.js b/src/pages/sponsors/sponsor-forms-tab/components/manage-items/sponsor-form-item-from-inventory.js index 8bb4643eb..f8b8fd61c 100644 --- a/src/pages/sponsors/sponsor-forms-tab/components/manage-items/sponsor-form-item-from-inventory.js +++ b/src/pages/sponsors/sponsor-forms-tab/components/manage-items/sponsor-form-item-from-inventory.js @@ -220,7 +220,8 @@ const SponsorFormItemFromInventoryPopup = ({ } ]} > - sort by + {" "} + {T.translate("general.sort_by")} diff --git a/src/pages/sponsors/sponsor-pages-tab/components/add-sponsor-page-template-popup/index.js b/src/pages/sponsors/sponsor-pages-tab/components/add-sponsor-page-template-popup/index.js new file mode 100644 index 000000000..80ae664f6 --- /dev/null +++ b/src/pages/sponsors/sponsor-pages-tab/components/add-sponsor-page-template-popup/index.js @@ -0,0 +1,345 @@ +/** + * Copyright 2026 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, useState } from "react"; +import T from "i18n-react/dist/i18n-react"; +import { connect } from "react-redux"; +import PropTypes from "prop-types"; +import { + Box, + Button, + Checkbox, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + FormControlLabel, + Grid2, + IconButton, + TextField, + Typography +} from "@mui/material"; +import SwapVertIcon from "@mui/icons-material/SwapVert"; +import SearchIcon from "@mui/icons-material/Search"; +import CloseIcon from "@mui/icons-material/Close"; +import * as yup from "yup"; +import { FormikProvider, useFormik } from "formik"; +import MuiTable from "../../../../../components/mui/table/mui-table"; +import MenuButton from "../../../../../components/mui/menu-button"; +import { querySponsorAddons } from "../../../../../actions/sponsor-actions"; +import { getShowPages } from "../../../../../actions/show-pages-actions"; +import { + DEFAULT_CURRENT_PAGE, + FIVE_PER_PAGE +} from "../../../../../utils/constants"; +import MuiFormikSelectGroup from "../../../../../components/mui/formik-inputs/mui-formik-select-group"; + +const AddSponsorPageTemplatePopup = ({ + onClose, + onSubmit, + showPages, + currentPage, + perPage, + order, + orderDir, + totalCount, + term = "", + getShowPages, + sponsor, + summitId +}) => { + const [searchTerm, setSearchTerm] = useState(""); + const [selectedPages, setSelectedPages] = useState([]); + + const sponsorshipIds = sponsor.sponsorships.map((e) => e.id); + + const sponsorshipTypeIds = sponsor.sponsorships.map((e) => e.type.id); + + const formik = useFormik({ + initialValues: { + add_ons: [] + }, + validationSchema: yup.object({ + add_ons: yup + .array() + .test( + "add_ons-required", + T.translate("validation.add_on_required"), + (value) => value?.includes("all") || value?.length > 0 + ) + }), + onSubmit: (values) => { + const { add_ons } = values; + const entity = { + pages: selectedPages, + add_ons + }; + onSubmit(entity); + }, + enableReinitialize: true + }); + + useEffect(() => { + getShowPages( + term, + DEFAULT_CURRENT_PAGE, + FIVE_PER_PAGE, + order, + orderDir, + false, + sponsorshipTypeIds + ); + }, []); + + const handlePageChange = (page) => { + getShowPages( + term, + page, + FIVE_PER_PAGE, + order, + orderDir, + false, + sponsorshipTypeIds + ); + }; + + const handleSort = (key, dir) => { + getShowPages( + term, + DEFAULT_CURRENT_PAGE, + FIVE_PER_PAGE, + key, + dir, + false, + sponsorshipTypeIds + ); + }; + + const handleOnSearch = (ev) => { + if (ev.key === "Enter") { + ev.preventDefault(); + ev.stopPropagation(); + getShowPages( + searchTerm, + DEFAULT_CURRENT_PAGE, + perPage, + order, + orderDir, + false, + sponsorshipTypeIds + ); + } + }; + + const handleSelected = (id, isSelected) => { + if (isSelected) { + setSelectedPages([...selectedPages, id]); + return; + } + const updatedSelected = selectedPages.filter((e) => e !== id); + setSelectedPages(updatedSelected); + }; + + const handleClose = () => { + onClose(); + }; + + const tableOptions = { + sortCol: order, + sortDir: orderDir + }; + + const columns = [ + { + columnKey: "select", + header: "", + width: 30, + align: "center", + render: (row) => ( + handleSelected(row.id, ev.target.checked)} + /> + } + /> + ) + }, + { + columnKey: "code", + header: T.translate("edit_sponsor.pages_tab.code"), + sortable: false + }, + { + columnKey: "name", + header: T.translate("edit_sponsor.pages_tab.name"), + sortable: false + }, + { + columnKey: "info_mod", + header: T.translate("edit_sponsor.pages_tab.info_mod") + }, + { + columnKey: "upload_mod", + header: T.translate("edit_sponsor.pages_tab.upload_mod") + }, + { + columnKey: "download_mod", + header: T.translate("edit_sponsor.pages_tab.download_mod") + } + ]; + + return ( + + + + {T.translate("edit_sponsor.pages_tab.add_page_using_template")} + + handleClose()} sx={{ mr: 1 }}> + + + + + + + + + addon.sponsorship.type.id} + getGroupLabel={(addon) => addon.sponsorship.type.type.name} + noOptionsLabel={T.translate( + "edit_sponsor.pages_tab.no_add_ons" + )} + placeholder={T.translate( + "edit_sponsor.placeholders.select_add_ons" + )} + /> + + + + + {selectedPages.length}{" "} + {T.translate("edit_sponsor.pages_tab.items_selected")} + + + + + handleSort("name", 1) + }, + { + label: T.translate("general.sort_desc_label"), + onClick: () => handleSort("name", 0) + } + ]} + > + {" "} + {T.translate("general.sort_by")} + + + + + } + }} + onChange={(event) => setSearchTerm(event.target.value)} + onKeyDown={handleOnSearch} + fullWidth + sx={{ + "& .MuiOutlinedInput-root": { + height: "36px" + } + }} + /> + + + + + {showPages.length > 0 && ( + + + + )} + {showPages.length === 0 && ( + + {T.translate("edit_sponsor.pages_tab.no_pages")} + + )} + + + + + + + + + ); +}; + +AddSponsorPageTemplatePopup.propTypes = { + onClose: PropTypes.func.isRequired +}; + +const mapStateToProps = ({ showPagesListState }) => ({ + ...showPagesListState +}); + +export default connect(mapStateToProps, { + getShowPages +})(AddSponsorPageTemplatePopup); diff --git a/src/pages/sponsors/sponsor-pages-tab/index.js b/src/pages/sponsors/sponsor-pages-tab/index.js index f14224c8f..5ca762e97 100644 --- a/src/pages/sponsors/sponsor-pages-tab/index.js +++ b/src/pages/sponsors/sponsor-pages-tab/index.js @@ -11,7 +11,7 @@ * limitations under the License. * */ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { connect } from "react-redux"; import T from "i18n-react/dist/i18n-react"; import { @@ -25,21 +25,28 @@ import { import AddIcon from "@mui/icons-material/Add"; import { getSponsorManagedPages, - getSponsorCustomizedPages + getSponsorCustomizedPages, + saveSponsorManagedPage } from "../../../actions/sponsor-pages-actions"; import CustomAlert from "../../../components/mui/custom-alert"; import SearchInput from "../../../components/mui/search-input"; import MuiTable from "../../../components/mui/table/mui-table"; import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants"; +import AddSponsorPageTemplatePopup from "./components/add-sponsor-page-template-popup"; const SponsorPagesTab = ({ + sponsor, + summitId, term, hideArchived, managedPages, customizedPages, getSponsorManagedPages, - getSponsorCustomizedPages + getSponsorCustomizedPages, + saveSponsorManagedPage }) => { + const [openPopup, setOpenPopup] = useState(null); + useEffect(() => { getSponsorManagedPages(); getSponsorCustomizedPages(); @@ -140,6 +147,10 @@ const SponsorPagesTab = ({ ); }; + const handleUsingTemplate = () => { + setOpenPopup("template"); + }; + const handleArchiveCustomizedPage = (item) => console.log("ARCHIVE CUSTOMIZED ", item); @@ -181,6 +192,12 @@ const SponsorPagesTab = ({ ); }; + const handleSaveManagedPageFromTemplate = (entity) => { + saveSponsorManagedPage(entity) + .then(() => getSponsorManagedPages()) + .finally(() => setOpenPopup(null)); + }; + const baseColumns = (name) => [ { columnKey: "name", @@ -276,7 +293,7 @@ const SponsorPagesTab = ({ variant="contained" size="medium" fullWidth - onClick={() => console.log("open popup template")} + onClick={handleUsingTemplate} startIcon={} sx={{ height: "36px" }} > @@ -336,6 +353,15 @@ const SponsorPagesTab = ({ onArchive={handleArchiveManagedPage} />
+ + {openPopup === "template" && ( + setOpenPopup(null)} + /> + )} ); }; @@ -346,5 +372,6 @@ const mapStateToProps = ({ sponsorPagePagesListState }) => ({ export default connect(mapStateToProps, { getSponsorManagedPages, + saveSponsorManagedPage, getSponsorCustomizedPages })(SponsorPagesTab);