From 221a9a797a30f285c938321a055ff421735d3402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 11 Dec 2025 17:44:54 -0300 Subject: [PATCH 01/22] fix: not render saved options if they're not available (#718) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../forms/sponsor-general-form/badge-scan-settings.js | 7 +++++-- src/components/mui/chip-select-input.js | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/forms/sponsor-general-form/badge-scan-settings.js b/src/components/forms/sponsor-general-form/badge-scan-settings.js index f9c6e41ad..ef00c54f9 100644 --- a/src/components/forms/sponsor-general-form/badge-scan-settings.js +++ b/src/components/forms/sponsor-general-form/badge-scan-settings.js @@ -40,8 +40,11 @@ const BadgeScanSettings = ({ const selectedCount = currentSettings && currentSettings.columns - ? renderOptions(denormalizeLeadReportSettings(currentSettings.columns)) - .length + ? renderOptions( + denormalizeLeadReportSettings(currentSettings.columns) + ).filter((option) => + availableLeadReportColumns.some((col) => col.value === option.value) + ).length : 0; const handleUpsertSettings = (newValues) => { diff --git a/src/components/mui/chip-select-input.js b/src/components/mui/chip-select-input.js index 691d34d6f..75c0dfc52 100644 --- a/src/components/mui/chip-select-input.js +++ b/src/components/mui/chip-select-input.js @@ -86,7 +86,8 @@ const ChipSelectInput = ({ renderValue={(selected) => ( {selected.map((value) => { - const op = availableOptions.find((op) => op.value === value); + const op = availableOptions.find((opt) => opt.value === value); + if (!op) return null; return ( Date: Mon, 15 Dec 2025 11:12:09 -0300 Subject: [PATCH 02/22] fix: prevent sponsor edit page to fail if no company is assigned (#722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/layouts/sponsor-id-layout.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/layouts/sponsor-id-layout.js b/src/layouts/sponsor-id-layout.js index a488d5585..ae3eee227 100644 --- a/src/layouts/sponsor-id-layout.js +++ b/src/layouts/sponsor-id-layout.js @@ -1,10 +1,10 @@ import React from "react"; import { connect } from "react-redux"; import T from "i18n-react/dist/i18n-react"; +import { Breadcrumb } from "react-breadcrumbs"; import { Switch, Route } from "react-router-dom"; import EditSponsorPage from "../pages/sponsors/edit-sponsor-page"; import { getSponsor, resetSponsorForm } from "../actions/sponsor-actions"; -import { Breadcrumb } from "react-breadcrumbs"; import EditAdSponsorPage from "../pages/sponsors/edit-advertisement-sponsor-page"; import EditMaterialSponsorPage from "../pages/sponsors/edit-material-sponsor-page"; import EditSocialNetworkSponsorPage from "../pages/sponsors/edit-social-network-sponsor-page"; @@ -23,7 +23,7 @@ class SponsorIdLayout extends React.Component { } } - componentDidUpdate(prevProps, prevState, snapshot) { + componentDidUpdate(prevProps) { const oldId = prevProps.match.params.sponsor_id; const newId = this.props.match.params.sponsor_id; @@ -38,9 +38,9 @@ class SponsorIdLayout extends React.Component { render() { const { match, currentSponsor } = this.props; - let sponsorId = this.props.match.params.sponsor_id; + const sponsorId = this.props.match.params.sponsor_id; const breadcrumb = currentSponsor.id - ? currentSponsor.company.name + ? currentSponsor.company?.name : T.translate("general.new"); if (sponsorId && !currentSponsor.id) return
; From d594af3e66ee031b9b45b06448cb29759e0539e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Mon, 15 Dec 2025 11:29:17 -0300 Subject: [PATCH 03/22] fix: adjust inventory items metafield validation to enable add button (#704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: adjust inventory items metafield validation to enable add button Signed-off-by: Tomás Castillo * fix: adjust meta_field key to avoid reset Signed-off-by: Tomás Castillo * fix: adjust form reinitialization to avoid reset metafields, adjust validations Signed-off-by: Tomás Castillo --------- Signed-off-by: Tomás Castillo --- .../sponsors_inventory/inventory-list-page.js | 31 ++++--- .../popup/sponsor-inventory-popup.js | 90 ++++++++----------- 2 files changed, 55 insertions(+), 66 deletions(-) diff --git a/src/pages/sponsors_inventory/inventory-list-page.js b/src/pages/sponsors_inventory/inventory-list-page.js index 344bae974..0a4f666bd 100644 --- a/src/pages/sponsors_inventory/inventory-list-page.js +++ b/src/pages/sponsors_inventory/inventory-list-page.js @@ -69,6 +69,7 @@ const InventoryListPage = ({ const [searchTerm, setSearchTerm] = useState(""); const handleClose = () => { + resetInventoryItemForm(); setOpen(false); }; @@ -119,13 +120,13 @@ const InventoryListPage = ({ ); }; - const handleRowEdit = (row) => { - if (row) getInventoryItem(row.id); - setOpen(true); + const handleRowEdit = async (row) => { + if (row) { + getInventoryItem(row.id).then(() => setOpen(true)); + } }; const handleNewInventoryItem = () => { - resetInventoryItemForm(); setOpen(true); }; @@ -292,16 +293,18 @@ const InventoryListPage = ({
)} - + {open && ( + + )} ); }; diff --git a/src/pages/sponsors_inventory/popup/sponsor-inventory-popup.js b/src/pages/sponsors_inventory/popup/sponsor-inventory-popup.js index e59f3a254..700746357 100644 --- a/src/pages/sponsors_inventory/popup/sponsor-inventory-popup.js +++ b/src/pages/sponsors_inventory/popup/sponsor-inventory-popup.js @@ -69,8 +69,8 @@ const SponsorItemDialog = ({ name: "", type: "Text", is_required: false, - minimum_quantity: null, - maximum_quantity: null, + minimum_quantity: 0, + maximum_quantity: 0, values: [] } ], @@ -97,11 +97,12 @@ const SponsorItemDialog = ({ yup.object().shape({ name: yup .string() - .when(["values", "minimum_quantity", "maximum_quantity"], { - is: (values, minQty, maxQty) => { + .when(["type", "values", "minimum_quantity", "maximum_quantity"], { + is: (type, values, minQty, maxQty) => { // required only if has values or quantities const hasValues = values && values.length > 0; - const hasQuantities = minQty !== null || maxQty !== null; + const hasQuantities = + type === "Quantity" && (minQty != null || maxQty != null); return hasValues || hasQuantities; }, then: (schema) => @@ -149,7 +150,6 @@ const SponsorItemDialog = ({ }) ) }), - enableReinitialize: true, onSubmit: (values) => onSave(values) }); @@ -238,10 +238,9 @@ const SponsorItemDialog = ({ onClose(); }; - const isMetafieldIncomplete = (field) => { + const areMetafieldsIncomplete = () => { if (formik.errors.meta_fields) return true; - if (field.name === "") return true; - return false; + return formik.values.meta_fields.some((f) => f.name?.trim() === ""); }; return ( @@ -395,6 +394,7 @@ const SponsorItemDialog = ({ container spacing={2} sx={{ alignItems: "center" }} + // eslint-disable-next-line key={field} > @@ -494,48 +494,34 @@ const SponsorItemDialog = ({ sx={{ alignItems: "start", my: 2 }} > - - - + - - - + )} @@ -573,7 +559,7 @@ const SponsorItemDialog = ({ + + {({ push, remove }) => ( + <> + + a.order - b.order + )} + onReorder={handleReorder} + renderItem={(valueItem, index, provided, snapshot) => + renderValueItem( + valueItem, + index, + provided, + snapshot, + remove + ) + } + idKey="id" + updateOrderKey="order" + droppableId="sponsor-extra-question-values" + /> + + + + )} + )} From e0b75757fed36532445807c8e9c21fa4d8c5f775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Mon, 15 Dec 2025 12:37:58 -0300 Subject: [PATCH 09/22] fix: adjust default page on hide archive items (#716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/pages/sponsors/sponsor-form-item-list-page/index.js | 3 ++- src/pages/sponsors/sponsor-forms-list-page/index.js | 3 ++- src/pages/sponsors/sponsor-forms-tab/index.js | 5 +++-- src/pages/sponsors_inventory/form-template-list-page.js | 9 ++++++++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/pages/sponsors/sponsor-form-item-list-page/index.js b/src/pages/sponsors/sponsor-form-item-list-page/index.js index 8ab191468..8381ed860 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/index.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/index.js @@ -40,6 +40,7 @@ import ItemPopup from "./components/item-popup"; import InventoryPopup from "./components/inventory-popup"; import MuiTableEditable from "../../../components/mui/editable-table/mui-table-editable"; import { parsePrice } from "../../../utils/currency"; +import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants"; const SponsorFormItemListPage = ({ match, @@ -75,7 +76,7 @@ const SponsorFormItemListPage = ({ const handleHideArchivedForms = (ev) => { getSponsorFormItems( formId, - currentPage, + DEFAULT_CURRENT_PAGE, perPage, order, orderDir, diff --git a/src/pages/sponsors/sponsor-forms-list-page/index.js b/src/pages/sponsors/sponsor-forms-list-page/index.js index 804fbf18c..6e0bf2a0b 100644 --- a/src/pages/sponsors/sponsor-forms-list-page/index.js +++ b/src/pages/sponsors/sponsor-forms-list-page/index.js @@ -36,6 +36,7 @@ import SearchInput from "../../../components/mui/search-input"; import GlobalTemplatePopup from "./components/global-template/global-template-popup"; import FormTemplatePopup from "./components/form-template/form-template-popup"; import MuiTable from "../../../components/mui/table/mui-table"; +import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants"; const SponsorFormsListPage = ({ sponsorForms, @@ -99,7 +100,7 @@ const SponsorFormsListPage = ({ const handleHideArchivedForms = (ev) => { getSponsorForms( term, - currentPage, + DEFAULT_CURRENT_PAGE, perPage, order, orderDir, diff --git a/src/pages/sponsors/sponsor-forms-tab/index.js b/src/pages/sponsors/sponsor-forms-tab/index.js index 91f56a286..06920a737 100644 --- a/src/pages/sponsors/sponsor-forms-tab/index.js +++ b/src/pages/sponsors/sponsor-forms-tab/index.js @@ -37,6 +37,7 @@ import SearchInput from "../../../components/mui/search-input"; import MuiTable from "../../../components/mui/table/mui-table"; import AddSponsorFormTemplatePopup from "./components/add-sponsor-form-template-popup"; import CustomizedFormPopup from "./components/customized-form/customized-form-popup"; +import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants"; const SponsorFormsTab = ({ term, @@ -124,7 +125,7 @@ const SponsorFormsTab = ({ const handleHideArchivedForms = (ev) => { getSponsorManagedForms( term, - managedForms.currentPage, + DEFAULT_CURRENT_PAGE, managedForms.perPage, managedForms.order, managedForms.orderDir, @@ -132,7 +133,7 @@ const SponsorFormsTab = ({ ); getSponsorCustomizedForms( term, - customizedForms.currentPage, + DEFAULT_CURRENT_PAGE, customizedForms.perPage, customizedForms.order, customizedForms.orderDir, diff --git a/src/pages/sponsors_inventory/form-template-list-page.js b/src/pages/sponsors_inventory/form-template-list-page.js index e8882fe04..885477552 100644 --- a/src/pages/sponsors_inventory/form-template-list-page.js +++ b/src/pages/sponsors_inventory/form-template-list-page.js @@ -163,7 +163,14 @@ const FormTemplateListPage = ({ item.is_archived ? unarchiveFormTemplate(item) : archiveFormTemplate(item); const handleHideArchivedForms = (value) => { - getFormTemplates(term, currentPage, perPage, order, orderDir, value); + getFormTemplates( + term, + DEFAULT_CURRENT_PAGE, + perPage, + order, + orderDir, + value + ); }; const columns = [ From 8823dd38ea1613a1b6511791addcc6b6f31ef83d Mon Sep 17 00:00:00 2001 From: smarcet Date: Mon, 15 Dec 2025 15:04:53 -0300 Subject: [PATCH 10/22] chore: update uicore --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 366a39483..d435278ab 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "moment-duration-format": "^2.3.2", "moment-timezone": "^0.5.33", "node-sass": "^7.0.1", - "openstack-uicore-foundation": "4.2.17", + "openstack-uicore-foundation": "4.2.18", "p-limit": "^6.1.0", "path-browserify": "^1.0.1", "postcss-loader": "^6.2.1", diff --git a/yarn.lock b/yarn.lock index e3a993945..c9297f8cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10342,10 +10342,10 @@ open@^8.0.9: is-docker "^2.1.1" is-wsl "^2.2.0" -openstack-uicore-foundation@4.2.17: - version "4.2.17" - resolved "https://registry.npmjs.org/openstack-uicore-foundation/-/openstack-uicore-foundation-4.2.17.tgz#f4519aae7fd92a939b259bd2e8dc4e8bf5b1e716" - integrity sha512-fK+SrVESdmadAlyOxVTHXr+T3gqVUDJvyd6s1BTcbRrww174FN3OKUOWxnGsPvG3hJrNecJDo/At269zYHRH2A== +openstack-uicore-foundation@4.2.18: + version "4.2.18" + resolved "https://registry.npmjs.org/openstack-uicore-foundation/-/openstack-uicore-foundation-4.2.18.tgz#762fe648e44dc9e178d4429d6aa1130b03708221" + integrity sha512-bD0IRaH4qqAhYclx4uePQPqgmh2Iyn5hbQQi4Ab79JFe4wK/MTjCXzCJ4/AhSJfJkmOowovUyjJyfZY9SXvgfA== optionator@^0.9.1: version "0.9.4" From 33b85f04bd374895345c63369d5296908e03aa9e Mon Sep 17 00:00:00 2001 From: smarcet Date: Mon, 15 Dec 2025 15:23:31 -0300 Subject: [PATCH 11/22] chore: update uicore to 4.2.19 fix issues on md5 file upload generation --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index d435278ab..48106fb79 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "moment-duration-format": "^2.3.2", "moment-timezone": "^0.5.33", "node-sass": "^7.0.1", - "openstack-uicore-foundation": "4.2.18", + "openstack-uicore-foundation": "4.2.19", "p-limit": "^6.1.0", "path-browserify": "^1.0.1", "postcss-loader": "^6.2.1", diff --git a/yarn.lock b/yarn.lock index c9297f8cf..823369b55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10342,10 +10342,10 @@ open@^8.0.9: is-docker "^2.1.1" is-wsl "^2.2.0" -openstack-uicore-foundation@4.2.18: - version "4.2.18" - resolved "https://registry.npmjs.org/openstack-uicore-foundation/-/openstack-uicore-foundation-4.2.18.tgz#762fe648e44dc9e178d4429d6aa1130b03708221" - integrity sha512-bD0IRaH4qqAhYclx4uePQPqgmh2Iyn5hbQQi4Ab79JFe4wK/MTjCXzCJ4/AhSJfJkmOowovUyjJyfZY9SXvgfA== +openstack-uicore-foundation@4.2.19: + version "4.2.19" + resolved "https://registry.npmjs.org/openstack-uicore-foundation/-/openstack-uicore-foundation-4.2.19.tgz#40ae09230b483a3279592bdeb0e7c164af4e5c97" + integrity sha512-spRHx76SlFmJqUtsUz786pnX+oaco/gDH7LJ7u5MIj5c9VcCo9rkeBfwl7htCLaCC68msuRGwlrMJpkSsY6gxw== optionator@^0.9.1: version "0.9.4" From 3c316c3f3b6689dad9f84749ee020303b34cfa2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 16 Dec 2025 15:22:22 -0300 Subject: [PATCH 12/22] fix: change action to download room occupancy csv file (#677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/room-occupancy-actions.js | 43 +++++++++++++++++++-------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/actions/room-occupancy-actions.js b/src/actions/room-occupancy-actions.js index 5870e89ba..81970faad 100644 --- a/src/actions/room-occupancy-actions.js +++ b/src/actions/room-occupancy-actions.js @@ -3,14 +3,15 @@ import { createAction, deleteRequest, escapeFilterValue, - getCSV, getRequest, putRequest, startLoading, - stopLoading + stopLoading, + getRawCSV, + downloadFileByContent } from "openstack-uicore-foundation/lib/utils/actions"; import moment from "moment-timezone"; -import { getAccessTokenSafely } from "../utils/methods"; +import { getAccessTokenSafely, joinCVSChunks } from "../utils/methods"; import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, @@ -143,11 +144,17 @@ export const getEventsForOccupancyCSV = orderDir = DEFAULT_ORDER_DIR ) => async (dispatch, getState) => { - const { currentSummitState } = getState(); - const accessToken = await getAccessTokenSafely(); + const { currentSummitState, currentRoomOccupancyState } = getState(); + const { totalEvents } = currentRoomOccupancyState; + const csvMIME = "text/csv;charset=utf-8"; + const pageSize = 500; + const totalPages = Math.ceil(totalEvents / pageSize); const { currentSummit } = currentSummitState; const filter = []; const summitTZ = currentSummit.time_zone.name; + const cvsFiles = []; + const endpoint = `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/events/csv`; + const accessToken = await getAccessTokenSafely(); dispatch(startLoading()); @@ -174,7 +181,8 @@ export const getEventsForOccupancyCSV = } const params = { - access_token: accessToken + access_token: accessToken, + per_page: pageSize }; if (filter.length > 0) { @@ -192,13 +200,22 @@ export const getEventsForOccupancyCSV = const filename = `summit-${currentSummit.slug}-rooms-occupancy.csv`; - dispatch( - getCSV( - `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/events/csv`, - params, - filename - ) - ); + for (let i = 1; i <= totalPages; i++) { + cvsFiles.push(getRawCSV(endpoint, { ...params, page: i })); + } + + Promise.all(cvsFiles) + .then((files) => { + if (files.length > 0) { + const cvs = joinCVSChunks(files); + // then simulate the file download + downloadFileByContent(filename, cvs, csvMIME); + } + dispatch(stopLoading()); + }) + .catch(() => { + dispatch(stopLoading()); + }); }; export const getCurrentEventForOccupancy = From 253dff90d7c2f53f723b282dde7b725547ede5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 18 Dec 2025 11:35:14 -0300 Subject: [PATCH 13/22] fix: adjust item form validation, add edit cell validations (#712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: adjust validation on numbers at item form component Signed-off-by: Tomás Castillo * fix: adjust item form validation, add edit cell validations Signed-off-by: Tomás Castillo --------- Signed-off-by: Tomás Castillo --- .../mui/editable-table/mui-table-editable.js | 59 ++++++++++++++++--- .../components/item-form.js | 37 ++++++++---- .../sponsor-form-item-list-page/index.js | 47 ++++++++++++++- 3 files changed, 120 insertions(+), 23 deletions(-) diff --git a/src/components/mui/editable-table/mui-table-editable.js b/src/components/mui/editable-table/mui-table-editable.js index f21337f4c..9250aae74 100644 --- a/src/components/mui/editable-table/mui-table-editable.js +++ b/src/components/mui/editable-table/mui-table-editable.js @@ -23,19 +23,51 @@ import { } from "../../../utils/constants"; import showConfirmDialog from "../showConfirmDialog"; +const validateValue = (value, validation) => { + if (!validation) return { isValid: true }; + + // validate with yup schema + if ( + validation.schema && + typeof validation.schema.validateSync === "function" + ) { + try { + validation.schema.validateSync(value); + return { isValid: true, message: null }; + } catch (err) { + return { isValid: false, message: err.message }; + } + } + + return { isValid: true }; +}; + // Updated component to handle editable cells with hover edit icon -const EditableCell = ({ value, isEditing, onBlur }) => { +const EditableCell = ({ value, isEditing, onBlur, validation }) => { const [inputValue, setInputValue] = React.useState(value); const [isHovering, setIsHovering] = React.useState(false); + const [error, setError] = React.useState(null); React.useEffect(() => { setInputValue(value); + setError(null); }, [value]); + const handleValidationAndSave = (newValue) => { + const { isValid, message } = validateValue(newValue, validation); + + if (isValid) { + setError(null); + onBlur(newValue, true); + } else { + setError(message); + } + }; + const handleKeyDown = (e) => { if (e.key === "Enter") { e.preventDefault(); - onBlur(inputValue); + handleValidationAndSave(inputValue); } }; @@ -44,14 +76,19 @@ const EditableCell = ({ value, isEditing, onBlur }) => { setInputValue(e.target.value)} + onChange={(e) => { + setInputValue(e.target.value); + if (error) setError(null); + }} onBlur={() => { - onBlur(inputValue); + handleValidationAndSave(inputValue); }} onKeyDown={handleKeyDown} size="small" fullWidth variant="standard" + error={!!error} + helperText={error} /> ); } @@ -150,8 +187,8 @@ const MuiTableEditable = ({ }; // Handler for saving changes when editing is complete - const handleCellBlur = (rowId, columnKey, newValue) => { - if (onCellChange) { + const handleCellBlur = (rowId, columnKey, newValue, isValid) => { + if (onCellChange && isValid) { onCellChange(rowId, columnKey, newValue); } setEditingCell(null); @@ -229,9 +266,15 @@ const MuiTableEditable = ({ editingCell.rowId === row.id && editingCell.columnKey === col.columnKey } - onBlur={(newValue) => - handleCellBlur(row.id, col.columnKey, newValue) + onBlur={(newValue, isValid) => + handleCellBlur( + row.id, + col.columnKey, + newValue, + isValid + ) } + validation={col.validation} /> ) : col.render ? ( col.render(row) diff --git a/src/pages/sponsors/sponsor-form-item-list-page/components/item-form.js b/src/pages/sponsors/sponsor-form-item-list-page/components/item-form.js index fffd3183f..b442bbbac 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/components/item-form.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/components/item-form.js @@ -28,6 +28,19 @@ const buildInitialValues = (data) => { addIssAfterDateFieldValidator(); +const numberValidation = () => + yup.number().typeError(T.translate("validation.number")); + +const decimalValidation = () => + yup + .number() + .typeError(T.translate("validation.number")) + .min(0, T.translate("validation.number_positive")) + .test("max-decimals", T.translate("validation.two_decimals"), (value) => { + if (value === undefined || value === null) return true; + return /^\d+(\.\d{1,2})?$/.test(value.toString()); + }); + const ItemForm = ({ initialValues, onSubmit }) => { const formik = useFormik({ initialValues: buildInitialValues(initialValues), @@ -41,18 +54,18 @@ const ItemForm = ({ initialValues, onSubmit }) => { description: yup .string(T.translate("validation.string")) .required(T.translate("validation.required")), - early_bird_rate: yup - .number(T.translate("validation.number")) - .required(T.translate("validation.required")), - standard_rate: yup - .number(T.translate("validation.number")) - .required(T.translate("validation.required")), - onsite_rate: yup - .number(T.translate("validation.number")) - .required(T.translate("validation.required")), - default_quantity: yup - .number(T.translate("validation.number")) - .required(T.translate("validation.required")) + early_bird_rate: decimalValidation(), + standard_rate: decimalValidation(), + onsite_rate: decimalValidation(), + default_quantity: numberValidation() + .integer(T.translate("validation.integer")) + .min(0, T.translate("validation.number_positive")), + quantity_limit_per_sponsor: numberValidation() + .integer(T.translate("validation.integer")) + .min(0, T.translate("validation.number_positive")), + quantity_limit_per_show: numberValidation() + .integer(T.translate("validation.integer")) + .min(0, T.translate("validation.number_positive")) }), onSubmit, enableReinitialize: true diff --git a/src/pages/sponsors/sponsor-form-item-list-page/index.js b/src/pages/sponsors/sponsor-form-item-list-page/index.js index 8381ed860..344f46e00 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/index.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/index.js @@ -15,6 +15,7 @@ import React, { useEffect, useState } from "react"; import { Breadcrumb } from "react-breadcrumbs"; import { connect } from "react-redux"; import T from "i18n-react/dist/i18n-react"; +import * as yup from "yup"; import { Alert, Box, @@ -112,6 +113,37 @@ const SponsorFormItemListPage = ({ setOpenPopup("inventory"); }; + const rateCellValidation = () => + yup + .number() + // allow $ at the start + .transform((value, originalValue) => { + if (typeof originalValue === "string") { + const cleaned = originalValue.replace(/^\$/, ""); + return cleaned === "" ? undefined : parseFloat(cleaned); + } + return value; + }) + // check if there's letters or characters + .test({ + name: "valid-format", + message: T.translate("validation.number"), + test: (value, { originalValue }) => { + if ( + originalValue === undefined || + originalValue === null || + originalValue === "" + ) + return true; + return /^\$?-?\d+(\.\d+)?$/.test(originalValue); + } + }) + .min(0, T.translate("validation.number_positive")) + .test("max-decimals", T.translate("validation.two_decimals"), (value) => { + if (value === undefined || value === null) return true; + return /^\d+(\.\d{1,2})?$/.test(value.toString()); + }); + const columns = [ { columnKey: "code", @@ -127,19 +159,28 @@ const SponsorFormItemListPage = ({ columnKey: "early_bird_rate", header: T.translate("sponsor_form_item_list.early_bird_rate"), sortable: true, - editable: true + editable: true, + validation: { + schema: rateCellValidation() + } }, { columnKey: "standard_rate", header: T.translate("sponsor_form_item_list.standard_rate"), sortable: true, - editable: true + editable: true, + validation: { + schema: rateCellValidation() + } }, { columnKey: "onsite_rate", header: T.translate("sponsor_form_item_list.onsite_rate"), sortable: true, - editable: true + editable: true, + validation: { + schema: rateCellValidation() + } }, { columnKey: "default_quantity", From ec29d8084f26d1270afb261b94a5bce2a6d2ad33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 18 Dec 2025 11:36:48 -0300 Subject: [PATCH 14/22] fix: rename sponsor list column for tiers (#726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/pages/sponsors/sponsor-list-page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/sponsors/sponsor-list-page.js b/src/pages/sponsors/sponsor-list-page.js index 3fc35351f..e10100bc0 100644 --- a/src/pages/sponsors/sponsor-list-page.js +++ b/src/pages/sponsors/sponsor-list-page.js @@ -117,7 +117,7 @@ const SponsorListPage = ({ { columnKey: "company_name", header: T.translate("sponsor_list.company") }, { columnKey: "sponsorships", - header: T.translate("sponsor_list.sponsorship"), + header: T.translate("sponsor_list.sponsorships"), render: (row) => row.sponsorships.map((s) => ( From 9319963bc97dffffc2b56d00fee04a22cd1b3f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 18 Dec 2025 11:39:37 -0300 Subject: [PATCH 15/22] fix: reload sponsorship table after delete (#727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/sponsor-actions.js | 2 +- src/components/forms/sponsor-general-form/sponsorship.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/actions/sponsor-actions.js b/src/actions/sponsor-actions.js index 6d5f114d3..c48e8a77f 100644 --- a/src/actions/sponsor-actions.js +++ b/src/actions/sponsor-actions.js @@ -436,7 +436,7 @@ export const removeTierFromSponsor = dispatch(startLoading()); - deleteRequest( + return deleteRequest( null, createAction(SPONSOR_TIER_DELETED)({ sponsorshipId }), `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsorships/${sponsorshipId}`, diff --git a/src/components/forms/sponsor-general-form/sponsorship.js b/src/components/forms/sponsor-general-form/sponsorship.js index 701555a80..670667963 100644 --- a/src/components/forms/sponsor-general-form/sponsorship.js +++ b/src/components/forms/sponsor-general-form/sponsorship.js @@ -66,6 +66,12 @@ const Sponsorship = ({ onSponsorshipPaginate(currentPage, perPage, key, dir); }; + const handleSponsorshipDelete = (sponsorshipId) => { + onSponsorshipDelete(sponsorshipId).then(() => + onSponsorshipPaginate(DEFAULT_CURRENT_PAGE, perPage, order, orderDir) + ); + }; + const handleOpenManageAddonsPopup = (sponsorship) => { setSelectedSponsorship(sponsorship); onSponsorshipSelect(sponsorship); @@ -184,7 +190,7 @@ const Sponsorship = ({ orderField="order" perPage={perPage} currentPage={currentPage} - onDelete={onSponsorshipDelete} + onDelete={handleSponsorshipDelete} onPageChange={handlePageChange} onPerPageChange={handlePerPageChange} onSort={handleSort} From 5bcbe12741c23c6c6fda2fd4a933ff78df4d00c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 18 Dec 2025 11:45:06 -0300 Subject: [PATCH 16/22] fix: adjust validation on submit for manage addons popup (#730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../forms/sponsor-general-form/manage-tier-addons-popup.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/forms/sponsor-general-form/manage-tier-addons-popup.js b/src/components/forms/sponsor-general-form/manage-tier-addons-popup.js index 8a13e0685..594833426 100644 --- a/src/components/forms/sponsor-general-form/manage-tier-addons-popup.js +++ b/src/components/forms/sponsor-general-form/manage-tier-addons-popup.js @@ -116,6 +116,8 @@ const ManageTierAddonsPopup = ({ onSubmit(valuesToSave, sponsorship.id); }, + validateOnBlur: false, + validateOnChange: false, enableReinitialize: true }); From a0133987ef3e4569730571f13d9d8ce4bbb773c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 18 Dec 2025 11:47:09 -0300 Subject: [PATCH 17/22] fix: add sponsor forms list pagination (#723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/sponsor-forms-actions.js | 2 +- src/pages/sponsors/sponsor-forms-list-page/index.js | 12 +++++++++++- src/reducers/sponsors/sponsor-forms-list-reducer.js | 3 ++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/actions/sponsor-forms-actions.js b/src/actions/sponsor-forms-actions.js index 9253b92fc..83bb44467 100644 --- a/src/actions/sponsor-forms-actions.js +++ b/src/actions/sponsor-forms-actions.js @@ -131,7 +131,7 @@ export const getSponsorForms = createAction(RECEIVE_SPONSOR_FORMS), `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms`, authErrorHandler, - { order, orderDir, page, term, hideArchived } + { order, orderDir, page, perPage, term, hideArchived } )(params)(dispatch).then(() => { dispatch(stopLoading()); }); diff --git a/src/pages/sponsors/sponsor-forms-list-page/index.js b/src/pages/sponsors/sponsor-forms-list-page/index.js index 6e0bf2a0b..17829f18a 100644 --- a/src/pages/sponsors/sponsor-forms-list-page/index.js +++ b/src/pages/sponsors/sponsor-forms-list-page/index.js @@ -62,7 +62,16 @@ const SponsorFormsListPage = ({ const handlePageChange = (page) => { getSponsorForms(term, page, perPage, order, orderDir, hideArchived); }; - + const handlePerPageChange = (newPerPage) => { + getSponsorForms( + term, + currentPage, + newPerPage, + order, + orderDir, + hideArchived + ); + }; const handleSort = (key, dir) => { getSponsorForms(term, currentPage, perPage, key, dir, hideArchived); }; @@ -229,6 +238,7 @@ const SponsorFormsListPage = ({ T.translate("sponsor_forms.remove_form_warning", { name }) } onPageChange={handlePageChange} + onPerPageChange={handlePerPageChange} onSort={handleSort} onEdit={handleRowEdit} onArchive={handleArchiveItem} diff --git a/src/reducers/sponsors/sponsor-forms-list-reducer.js b/src/reducers/sponsors/sponsor-forms-list-reducer.js index c96e239b6..151b82a5a 100644 --- a/src/reducers/sponsors/sponsor-forms-list-reducer.js +++ b/src/reducers/sponsors/sponsor-forms-list-reducer.js @@ -78,7 +78,7 @@ const sponsorFormsListReducer = (state = DEFAULT_STATE, action) => { return DEFAULT_STATE; } case REQUEST_SPONSOR_FORMS: { - const { order, orderDir, page, term, hideArchived } = payload; + const { order, orderDir, page, perPage, term, hideArchived } = payload; return { ...state, @@ -86,6 +86,7 @@ const sponsorFormsListReducer = (state = DEFAULT_STATE, action) => { orderDir, sponsorForms: [], currentPage: page, + perPage, term, hideArchived }; From 080e2a0ea56a469e048c98decb8c3ba5540eb1e7 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Thu, 11 Dec 2025 14:47:55 -0300 Subject: [PATCH 18/22] feat: page templates grid and menu --- .env.example | 2 + src/actions/page-template-actions.js | 254 +++++++++++++++++ src/app.js | 1 + src/components/menu/index.js | 4 + src/i18n/en.json | 23 +- src/layouts/page-template-layout.js | 55 ++++ src/layouts/primary-layout.js | 2 + .../edit-page-template-page.js | 85 ++++++ .../page-template-list-page.js | 257 ++++++++++++++++++ .../page-template-list-reducer.js | 124 +++++++++ src/store.js | 4 +- 11 files changed, 809 insertions(+), 2 deletions(-) create mode 100644 src/actions/page-template-actions.js create mode 100644 src/layouts/page-template-layout.js create mode 100644 src/pages/sponsors_inventory/edit-page-template-page.js create mode 100644 src/pages/sponsors_inventory/page-template-list-page.js create mode 100644 src/reducers/sponsors_inventory/page-template-list-reducer.js diff --git a/.env.example b/.env.example index b71ae142b..1a059072b 100644 --- a/.env.example +++ b/.env.example @@ -17,6 +17,8 @@ SPONSOR_USERS_API_SCOPES="show-medata/read show-medata/write access-requests/rea EMAIL_SCOPES="clients/read templates/read templates/write emails/read" FILE_UPLOAD_SCOPES="files/upload" SCOPES="profile openid offline_access ${SPONSOR_USERS_API_SCOPES} ${PURCHASES_API_SCOPES} ${EMAIL_SCOPES} ${FILE_UPLOAD_SCOPES} ${SCOPES_BASE_REALM}/summits/delete-event ${SCOPES_BASE_REALM}/summits/write ${SCOPES_BASE_REALM}/summits/write-event ${SCOPES_BASE_REALM}/summits/read/all ${SCOPES_BASE_REALM}/summits/read ${SCOPES_BASE_REALM}/summits/publish-event ${SCOPES_BASE_REALM}/members/read ${SCOPES_BASE_REALM}/members/read/me ${SCOPES_BASE_REALM}/speakers/write ${SCOPES_BASE_REALM}/attendees/write ${SCOPES_BASE_REALM}/members/write ${SCOPES_BASE_REALM}/organizations/write ${SCOPES_BASE_REALM}/organizations/read ${SCOPES_BASE_REALM}/summits/write-presentation-materials ${SCOPES_BASE_REALM}/summits/registration-orders/update ${SCOPES_BASE_REALM}/summits/registration-orders/delete ${SCOPES_BASE_REALM}/summits/registration-orders/create/offline ${SCOPES_BASE_REALM}/summits/badge-scans/read entity-updates/publish ${SCOPES_BASE_REALM}/audit-logs/read" +SPONSOR_PAGES_API_URL=https://sponsor-pages-api.dev.fnopen.com +SPONSOR_PAGES_SCOPES=page-template/read page-template/write GOOGLE_API_KEY= ALLOWED_USER_GROUPS="super-admins administrators summit-front-end-administrators summit-room-administrators track-chairs-admins sponsors" APP_CLIENT_NAME = "openstack" diff --git a/src/actions/page-template-actions.js b/src/actions/page-template-actions.js new file mode 100644 index 000000000..44c341bd2 --- /dev/null +++ b/src/actions/page-template-actions.js @@ -0,0 +1,254 @@ +/** + * 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 T from "i18n-react/dist/i18n-react"; +import { + getRequest, + putRequest, + postRequest, + deleteRequest, + createAction, + stopLoading, + startLoading, + showMessage, + showSuccessMessage, + authErrorHandler, + escapeFilterValue +} from "openstack-uicore-foundation/lib/utils/actions"; +import history from "../history"; +import { getAccessTokenSafely } from "../utils/methods"; +import { + DEFAULT_CURRENT_PAGE, + DEFAULT_ORDER_DIR, + DEFAULT_PER_PAGE +} from "../utils/constants"; +import { snackbarErrorHandler } from "./base-actions"; + + + +export const ADD_PAGE_TEMPLATE = "ADD_PAGE_TEMPLATE"; +export const PAGE_TEMPLATE_ADDED = "PAGE_TEMPLATE_ADDED"; +export const PAGE_TEMPLATE_DELETED = "PAGE_TEMPLATE_DELETED"; +export const PAGE_TEMPLATE_UPDATED = "PAGE_TEMPLATE_UPDATED"; +export const RECEIVE_PAGE_TEMPLATE = "RECEIVE_PAGE_TEMPLATE"; +export const RECEIVE_PAGE_TEMPLATES = "RECEIVE_PAGE_TEMPLATES"; +export const REQUEST_PAGE_TEMPLATES = "REQUEST_PAGE_TEMPLATES"; +export const RESET_PAGE_TEMPLATE_FORM = "RESET_PAGE_TEMPLATE_FORM"; +export const UPDATE_PAGE_TEMPLATE = "UPDATE_PAGE_TEMPLATE"; +export const PAGE_TEMPLATE_ARCHIVED = "PAGE_TEMPLATE_ARCHIVED"; +export const PAGE_TEMPLATE_UNARCHIVED = "PAGE_TEMPLATE_UNARCHIVED"; + +export const getPageTemplates = + ( + term = null, + page = DEFAULT_CURRENT_PAGE, + perPage = DEFAULT_PER_PAGE, + order = "id", + orderDir = DEFAULT_ORDER_DIR, + hideArchived = false + ) => + async (dispatch) => { + const accessToken = await getAccessTokenSafely(); + const filter = []; + + dispatch(startLoading()); + + if (term) { + const escapedTerm = escapeFilterValue(term); + filter.push(`name=@${escapedTerm},code=@${escapedTerm}`); + } + + const params = { + page, + /* + expand: "modules", + fields: "id,code,name,modules,is_archived,modules.kind,modules.id,modules.content", + relations: "modules,modules.none", + */ + per_page: perPage, + access_token: accessToken + }; + + if (hideArchived) filter.push("is_archived==0"); + + 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_PAGE_TEMPLATES), + createAction(RECEIVE_PAGE_TEMPLATES), + `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates`, + authErrorHandler, + { order, orderDir, page, perPage, term, hideArchived } + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + }); + }; + +export const getPageTemplate = (formTemplateId) => async (dispatch) => { + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken, + expand: "materials,meta_fields,meta_fields.values" + }; + + return getRequest( + null, + createAction(RECEIVE_PAGE_TEMPLATE), + `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates/${formTemplateId}`, + authErrorHandler + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + }); +}; + +export const deletePageTemplate = (formTemplateId) => async (dispatch) => { + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + return deleteRequest( + null, + createAction(PAGE_TEMPLATE_DELETED)({ formTemplateId }), + `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates/${formTemplateId}`, + null, + authErrorHandler + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + }); +}; + +export const resetPageTemplateForm = () => (dispatch) => { + dispatch(createAction(RESET_PAGE_TEMPLATE_FORM)({})); +}; + +const normalizeEntity = (entity) => { + const normalizedEntity = { ...entity }; + normalizedEntity.meta_fields = normalizedEntity.meta_fields?.filter( + (mf) => mf.name + ); + normalizedEntity.materials = normalizedEntity.materials?.filter( + (mat) => mat.file_path + ); + return normalizedEntity; +}; + +export const savePageTemplate = (entity) => async (dispatch) => { + const accessToken = await getAccessTokenSafely(); + const params = { + access_token: accessToken, + expand: "materials,meta_fields,meta_fields.values" + }; + + dispatch(startLoading()); + + const normalizedEntity = normalizeEntity(entity); + + if (entity.id) { + return putRequest( + createAction(UPDATE_PAGE_TEMPLATE), + createAction(PAGE_TEMPLATE_UPDATED), + `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates/${entity.id}`, + normalizedEntity, + authErrorHandler, + entity + )(params)(dispatch) + .then(() => { + dispatch( + showSuccessMessage( + T.translate("edit_form_template.form_template_saved") + ) + ); + }) + .catch((err) => { + console.error(err); + }) + .finally(() => { + dispatch(stopLoading()); + }); + } + + const success_message = { + title: T.translate("general.done"), + html: T.translate("edit_form_template.form_template_created"), + type: "success" + }; + + return postRequest( + createAction(ADD_PAGE_TEMPLATE), + createAction(PAGE_TEMPLATE_ADDED), + `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates`, + normalizedEntity, + authErrorHandler, + entity + )(params)(dispatch) + .then(() => { + dispatch( + showMessage(success_message, () => { + history.push("/app/page-templates"); + }) + ); + }) + .catch((err) => { + console.error(err); + }) + .finally(() => { + dispatch(stopLoading()); + }); +}; + +/* ************************************** ARCHIVE ************************************** */ + +export const archivePageTemplate = (pageTemplateId) => async (dispatch) => { + const accessToken = await getAccessTokenSafely(); + const params = { access_token: accessToken }; + + return putRequest( + null, + createAction(PAGE_TEMPLATE_ARCHIVED), + `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates/${pageTemplateId}/archive`, + null, + snackbarErrorHandler + )(params)(dispatch); +}; + +export const unarchivePageTemplate = (pageTemplateId) => async (dispatch) => { + const accessToken = await getAccessTokenSafely(); + const params = { access_token: accessToken }; + + dispatch(startLoading()); + + return deleteRequest( + null, + createAction(PAGE_TEMPLATE_UNARCHIVED)({ pageTemplateId }), + `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates/${pageTemplateId}/archive`, + null, + snackbarErrorHandler + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + }); +}; diff --git a/src/app.js b/src/app.js index edf4be49d..eb5b5e2b2 100644 --- a/src/app.js +++ b/src/app.js @@ -81,6 +81,7 @@ window.MARKETING_API_BASE_URL = process.env.MARKETING_API_BASE_URL; window.EMAIL_API_BASE_URL = process.env.EMAIL_API_BASE_URL; window.PURCHASES_API_URL = process.env.PURCHASES_API_URL; window.SPONSOR_USERS_API_URL = process.env.SPONSOR_USERS_API_URL; +window.SPONSOR_PAGES_API_URL = process.env.SPONSOR_PAGES_API_URL; window.FILE_UPLOAD_API_BASE_URL = process.env.FILE_UPLOAD_API_BASE_URL; window.SIGNAGE_BASE_URL = process.env.SIGNAGE_BASE_URL; window.INVENTORY_API_BASE_URL = process.env.INVENTORY_API_BASE_URL; diff --git a/src/components/menu/index.js b/src/components/menu/index.js index 61177a4a4..83e492498 100644 --- a/src/components/menu/index.js +++ b/src/components/menu/index.js @@ -61,6 +61,10 @@ const getGlobalItems = () => [ { name: "form_templates", linkUrl: "form-templates" + }, + { + name: "page_templates", + linkUrl: "page-templates" } ] }, diff --git a/src/i18n/en.json b/src/i18n/en.json index dc388e52b..0590f60c0 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -187,7 +187,8 @@ "submission_invitations": "Submission Invitations", "sponsors_inventory": "Sponsors", "form_templates": "Form Templates", - "inventory": "Inventory" + "inventory": "Inventory", + "page_templates": "Pages" }, "schedule": { "schedule": "Schedule", @@ -3831,5 +3832,25 @@ "seat_type": "Select a Seat Type", "status": "Select a Status" } + }, + "page_template_list": { + "page_templates": "Page Templates", + "alert_info": "You can create or archive Pages from the list. To edit a Page click on the item's Edit botton.", + "code": "Code", + "name": "Name", + "info_mod": "Info Mod", + "upload_mod": "Upload Mod", + "download_mod": "Download Mod", + "hide_archived": "Hide archived pages", + "no_pages": "No pages found.", + "add_new": "New Page", + "add_template": "Using Template", + "delete_form_template_warning": "Are you sure you want to delete form template ", + "using_duplicate": "Using Duplicate", + "add_form_template": "New Form", + "add_using_global_template": "Using Global Template", + "placeholders": { + "search": "Search" + } } } diff --git a/src/layouts/page-template-layout.js b/src/layouts/page-template-layout.js new file mode 100644 index 000000000..ab4fc6e13 --- /dev/null +++ b/src/layouts/page-template-layout.js @@ -0,0 +1,55 @@ +/** + * 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 from "react"; +import { Switch, Route, withRouter } from "react-router-dom"; +import T from "i18n-react/dist/i18n-react"; +import { Breadcrumb } from "react-breadcrumbs"; +import Restrict from "../routes/restrict"; +import NoMatchPage from "../pages/no-match-page"; +import EditPageTemplatePage from "../pages/sponsors_inventory/edit-page-template-page"; +import PageTemplateListPage from "../pages/sponsors_inventory/page-template-list-page"; + +const PageTemplateLayout = ({ match }) => ( +
+ + + + + + + +
+); + +export default Restrict(withRouter(PageTemplateLayout), "page-template"); diff --git a/src/layouts/primary-layout.js b/src/layouts/primary-layout.js index 29f46f0b4..086398890 100644 --- a/src/layouts/primary-layout.js +++ b/src/layouts/primary-layout.js @@ -29,6 +29,7 @@ import MediaFileTypeLayout from "./media-file-type-layout"; import SponsoredProjectLayout from "./sponsored-project-layout"; import TagLayout from "./tag-layout"; import SponsorshipLayout from "./sponsorship-layout"; +import PageTemplateLayout from "./page-template-layout"; const PrimaryLayout = ({ match, currentSummit, location, member }) => { let extraClass = "container"; @@ -65,6 +66,7 @@ const PrimaryLayout = ({ match, currentSummit, location, member }) => { + { + const { + match, + entity, + errors, + getFormTemplate, + resetFormTemplateForm, + saveFormTemplate, + deleteFormTemplateMetaFieldType, + deleteFormTemplateMetaFieldTypeValue, + deleteFormTemplateMaterial + } = props; + const formTemplateId = match.params.form_template_id; + + useEffect(() => { + if (!formTemplateId) { + resetFormTemplateForm(); + } else { + getFormTemplate(formTemplateId); + } + }, [formTemplateId, getFormTemplate, resetFormTemplateForm]); + + const title = entity.id + ? T.translate("general.edit") + : T.translate("general.add"); + const breadcrumb = entity.id ? entity.name : T.translate("general.new"); + + return ( +
+ +

+ {title} {T.translate("edit_form_template.form_template")} +

+
+ +
+ ); +}; + +const mapStateToProps = ({ currentFormTemplateState }) => ({ + ...currentFormTemplateState +}); + +export default connect(mapStateToProps, { + getFormTemplate, + resetFormTemplateForm, + saveFormTemplate, + deleteFormTemplateMetaFieldType, + deleteFormTemplateMetaFieldTypeValue, + deleteFormTemplateMaterial +})(EditPageTemplatePage); diff --git a/src/pages/sponsors_inventory/page-template-list-page.js b/src/pages/sponsors_inventory/page-template-list-page.js new file mode 100644 index 000000000..893c03e05 --- /dev/null +++ b/src/pages/sponsors_inventory/page-template-list-page.js @@ -0,0 +1,257 @@ +/** + * 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 { + Alert, + Button, + Checkbox, + FormControlLabel, + FormGroup, + Grid2 +} from "@mui/material"; +import Box from "@mui/material/Box"; +import AddIcon from "@mui/icons-material/Add"; +import { connect } from "react-redux"; +import T from "i18n-react/dist/i18n-react"; +import { + archivePageTemplate, + getPageTemplates, + unarchivePageTemplate +} from "../../actions/page-template-actions"; +import MuiTable from "../../components/mui/table/mui-table"; +import SearchInput from "../../components/mui/search-input"; +import { DEFAULT_CURRENT_PAGE } from "../../utils/constants"; + +const PageTemplateListPage = ({ + pageTemplates, + currentPage, + perPage, + term, + order, + orderDir, + hideArchived, + totalPageTemplates, + getPageTemplates, + archivePageTemplate, + unarchivePageTemplate +}) => { + useEffect(() => { + getPageTemplates(); + }, []); + + const handlePageChange = (page) => { + getPageTemplates(term, page, perPage, order, orderDir, hideArchived); + }; + + const handlePerPageChange = (newPerPage) => { + getPageTemplates( + term, + DEFAULT_CURRENT_PAGE, + newPerPage, + order, + orderDir, + hideArchived + ); + }; + + const handleSort = (key, dir) => { + getPageTemplates(term, currentPage, perPage, key, dir, hideArchived); + }; + + const handleSearch = (searchTerm) => { + getPageTemplates( + searchTerm, + currentPage, + perPage, + order, + orderDir, + hideArchived + ); + }; + + const handleHideArchived = (ev) => { + getPageTemplates( + term, + currentPage, + perPage, + order, + orderDir, + ev.target.checked + ); + }; + + const handleNewPageTemplate = () => { + console.log("NEW PAGE"); + }; + + const handleClonePageTemplate = () => { + console.log("CLONE PAGE"); + }; + + const handleArchive = (item) => + item.is_archived + ? unarchivePageTemplate(item.id) + : archivePageTemplate(item.id); + + const handleEdit = (row) => { + console.log("EDIT", row); + }; + + const handleDelete = (row) => { + console.log("DELETE", row); + }; + + const columns = [ + { + columnKey: "code", + header: T.translate("page_template_list.code"), + sortable: true + }, + { + columnKey: "name", + header: T.translate("page_template_list.name"), + sortable: true + }, + { + columnKey: "info_mod", + header: T.translate("page_template_list.info_mod"), + sortable: false + }, + { + columnKey: "upload_mod", + header: T.translate("page_template_list.upload_mod"), + sortable: false + }, + { + columnKey: "download_mod", + header: T.translate("page_template_list.download_mod"), + sortable: false + } + ]; + + const tableOptions = { + sortCol: order, + sortDir: orderDir + }; + + return ( +
+

+ {T.translate("page_template_list.page_templates")} ({totalPageTemplates} + ) +

+ + {T.translate("page_template_list.alert_info")} + + + + + {totalPageTemplates} pages + + + + } + label={T.translate("page_template_list.hide_archived")} + /> + + + + + + + + + + + {pageTemplates.length === 0 && ( + + {T.translate("page_template_list.no_pages")} + + )} + + {pageTemplates.length > 0 && ( +
+ +
+ )} +
+
+ ); +}; + +const mapStateToProps = ({ pageTemplateListState }) => ({ + ...pageTemplateListState +}); + +export default connect(mapStateToProps, { + getPageTemplates, + archivePageTemplate, + unarchivePageTemplate +})(PageTemplateListPage); diff --git a/src/reducers/sponsors_inventory/page-template-list-reducer.js b/src/reducers/sponsors_inventory/page-template-list-reducer.js new file mode 100644 index 000000000..e2ff363d0 --- /dev/null +++ b/src/reducers/sponsors_inventory/page-template-list-reducer.js @@ -0,0 +1,124 @@ +/** + * 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 { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; +import { + REQUEST_PAGE_TEMPLATES, + RECEIVE_PAGE_TEMPLATES, + PAGE_TEMPLATE_DELETED, + PAGE_TEMPLATE_ARCHIVED, + PAGE_TEMPLATE_UNARCHIVED +} from "../../actions/page-template-actions"; + +const DEFAULT_STATE = { + pageTemplates: [], + term: null, + order: "name", + orderDir: 1, + currentPage: 1, + lastPage: 1, + perPage: 10, + totalPageTemplates: 0, + hideArchived: false +}; + +const pageTemplateListReducer = (state = DEFAULT_STATE, action = {}) => { + const { type, payload } = action; + switch (type) { + case LOGOUT_USER: { + return DEFAULT_STATE; + } + case REQUEST_PAGE_TEMPLATES: { + const { order, orderDir, page, perPage, ...rest } = payload; + + if ( + order !== state.order || + orderDir !== state.orderDir || + page !== state.currentPage + ) { + // if the change was in page or order, keep selection + return { + ...state, + order, + orderDir, + currentPage: page, + ...rest + }; + } + + return { + ...state, + order, + orderDir, + pageTemplates: [], + currentPage: page, + perPage, + ...rest + }; + } + case RECEIVE_PAGE_TEMPLATES: { + const { current_page, total, last_page } = payload.response; + + const pageTemplates = payload.response.data.map((a) => ({ + id: a.id, + code: a.code, + name: a.name, + info_mod: a.modules.filter((m) => m.kind === "Info").length, + upload_mod: a.modules.filter((m) => m.kind === "Upload").length, + download_mod: a.modules.filter((m) => m.kind === "Download").length, + is_archived: a.is_archived + })); + + return { + ...state, + pageTemplates, + currentPage: current_page, + totalPageTemplates: total, + lastPage: last_page + }; + } + case PAGE_TEMPLATE_DELETED: { + const { pageTemplateId } = payload; + return { + ...state, + pageTemplates: state.pageTemplates.filter( + (a) => a.id !== pageTemplateId + ) + }; + } + case PAGE_TEMPLATE_ARCHIVED: { + const updatedFormTemplate = payload.response; + + const updatedPageTemplates = state.pageTemplates.map((item) => + item.id === updatedFormTemplate.id + ? { ...item, is_archived: true } + : item + ); + return { ...state, pageTemplates: updatedPageTemplates }; + } + case PAGE_TEMPLATE_UNARCHIVED: { + const updatedFormTemplateId = payload; + + const updatedPageTemplates = state.pageTemplates.map((item) => + item.id === updatedFormTemplateId + ? { ...item, is_archived: false } + : item + ); + return { ...state, pageTemplates: updatedPageTemplates }; + } + default: + return state; + } +}; + +export default pageTemplateListReducer; diff --git a/src/store.js b/src/store.js index 64762f8ac..e61a3c172 100644 --- a/src/store.js +++ b/src/store.js @@ -158,6 +158,7 @@ import formTemplateReducer from "./reducers/sponsors_inventory/form-template-red import formTemplateListReducer from "./reducers/sponsors_inventory/form-template-list-reducer.js"; import formTemplateItemReducer from "./reducers/sponsors_inventory/form-template-item-reducer.js"; import formTemplateItemListReducer from "./reducers/sponsors_inventory/form-template-item-list-reducer.js"; +import pageTemplateListReducer from "./reducers/sponsors_inventory/page-template-list-reducer.js"; import sponsorSettingsReducer from "./reducers/sponsor_settings/sponsor-settings-reducer"; import eventRSVPListReducer from "./reducers/rsvps/event-rsvp-list-reducer.js"; import eventRSVPInvitationListReducer from "./reducers/rsvps/event-rsvp-invitation-list-reducer.js"; @@ -322,7 +323,8 @@ const reducers = persistCombineReducers(config, { currentFormTemplateListState: formTemplateListReducer, currentFormTemplateItemState: formTemplateItemReducer, currentFormTemplateItemListState: formTemplateItemListReducer, - sponsorSettingsState: sponsorSettingsReducer + sponsorSettingsState: sponsorSettingsReducer, + pageTemplateListState: pageTemplateListReducer }); const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; From 4f8ae97328b3509562ac3aa0fe9d75d736228036 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Thu, 11 Dec 2025 15:42:12 -0300 Subject: [PATCH 19/22] feat: pages grid - fix api fields --- src/actions/page-template-actions.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/actions/page-template-actions.js b/src/actions/page-template-actions.js index 44c341bd2..998932b7b 100644 --- a/src/actions/page-template-actions.js +++ b/src/actions/page-template-actions.js @@ -34,8 +34,6 @@ import { } from "../utils/constants"; import { snackbarErrorHandler } from "./base-actions"; - - export const ADD_PAGE_TEMPLATE = "ADD_PAGE_TEMPLATE"; export const PAGE_TEMPLATE_ADDED = "PAGE_TEMPLATE_ADDED"; export const PAGE_TEMPLATE_DELETED = "PAGE_TEMPLATE_DELETED"; @@ -70,11 +68,10 @@ export const getPageTemplates = const params = { page, - /* expand: "modules", - fields: "id,code,name,modules,is_archived,modules.kind,modules.id,modules.content", + fields: + "id,code,name,modules,is_archived,modules.kind,modules.id,modules.content", relations: "modules,modules.none", - */ per_page: perPage, access_token: accessToken }; From 64ce7b83b522c60d5c719262a950fde243f81ed9 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Fri, 12 Dec 2025 15:18:09 -0300 Subject: [PATCH 20/22] feat: sponsor pages - build crud popup --- src/actions/page-template-actions.js | 43 ++--- src/i18n/en.json | 8 + src/layouts/form-template-item-layout.js | 4 +- src/layouts/form-template-layout.js | 4 +- src/layouts/inventory-item-layout.js | 4 +- src/layouts/page-template-layout.js | 4 +- .../add-form-template-item-popup.js | 0 .../edit-form-template-item-page.js | 4 +- .../edit-form-template-page.js | 4 +- .../form-template-from-duplicate-popup.js | 0 .../form-template-item-list-page.js | 14 +- .../form-template-list-page.js | 12 +- .../form-templates}/form-template-popup.js | 2 +- .../sponsor-inventory-popup.js | 2 +- .../inventory}/edit-inventory-item-page.js | 4 +- .../inventory}/inventory-list-page.js | 8 +- .../edit-page-template-page.js | 4 +- .../page-template-list-page.js | 27 ++- .../page-templates/page-template-popup.js | 154 ++++++++++++++++++ .../shared}/meta-field-values.js | 0 .../sponsors/components/additional-input.js | 2 +- 21 files changed, 234 insertions(+), 70 deletions(-) rename src/pages/{sponsors_inventory/popup => sponsors-global/form-templates}/add-form-template-item-popup.js (100%) rename src/pages/{sponsors_inventory => sponsors-global/form-templates}/edit-form-template-item-page.js (96%) rename src/pages/{sponsors_inventory => sponsors-global/form-templates}/edit-form-template-page.js (95%) rename src/pages/{sponsors_inventory/popup => sponsors-global/form-templates}/form-template-from-duplicate-popup.js (100%) rename src/pages/{sponsors_inventory => sponsors-global/form-templates}/form-template-item-list-page.js (95%) rename src/pages/{sponsors_inventory => sponsors-global/form-templates}/form-template-list-page.js (96%) rename src/pages/{sponsors_inventory/popup => sponsors-global/form-templates}/form-template-popup.js (99%) rename src/pages/{sponsors_inventory/popup => sponsors-global/form-templates}/sponsor-inventory-popup.js (99%) rename src/pages/{sponsors_inventory => sponsors-global/inventory}/edit-inventory-item-page.js (95%) rename src/pages/{sponsors_inventory => sponsors-global/inventory}/inventory-list-page.js (96%) rename src/pages/{sponsors_inventory => sponsors-global/page-templates}/edit-page-template-page.js (95%) rename src/pages/{sponsors_inventory => sponsors-global/page-templates}/page-template-list-page.js (89%) create mode 100644 src/pages/sponsors-global/page-templates/page-template-popup.js rename src/pages/{sponsors_inventory/popup => sponsors-global/shared}/meta-field-values.js (100%) diff --git a/src/actions/page-template-actions.js b/src/actions/page-template-actions.js index 998932b7b..363230881 100644 --- a/src/actions/page-template-actions.js +++ b/src/actions/page-template-actions.js @@ -20,19 +20,16 @@ import { createAction, stopLoading, startLoading, - showMessage, - showSuccessMessage, authErrorHandler, escapeFilterValue } from "openstack-uicore-foundation/lib/utils/actions"; -import history from "../history"; import { getAccessTokenSafely } from "../utils/methods"; import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, DEFAULT_PER_PAGE } from "../utils/constants"; -import { snackbarErrorHandler } from "./base-actions"; +import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; export const ADD_PAGE_TEMPLATE = "ADD_PAGE_TEMPLATE"; export const PAGE_TEMPLATE_ADDED = "PAGE_TEMPLATE_ADDED"; @@ -145,20 +142,16 @@ export const resetPageTemplateForm = () => (dispatch) => { const normalizeEntity = (entity) => { const normalizedEntity = { ...entity }; - normalizedEntity.meta_fields = normalizedEntity.meta_fields?.filter( - (mf) => mf.name - ); - normalizedEntity.materials = normalizedEntity.materials?.filter( - (mat) => mat.file_path - ); + + normalizedEntity.modules = []; + return normalizedEntity; }; -export const savePageTemplate = (entity) => async (dispatch) => { +export const savePageTemplate = (entity) => async (dispatch, getState) => { const accessToken = await getAccessTokenSafely(); const params = { - access_token: accessToken, - expand: "materials,meta_fields,meta_fields.values" + access_token: accessToken }; dispatch(startLoading()); @@ -171,15 +164,17 @@ export const savePageTemplate = (entity) => async (dispatch) => { createAction(PAGE_TEMPLATE_UPDATED), `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates/${entity.id}`, normalizedEntity, - authErrorHandler, + snackbarErrorHandler, entity )(params)(dispatch) .then(() => { dispatch( - showSuccessMessage( - T.translate("edit_form_template.form_template_saved") - ) + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("page_template_list.page_crud.page_saved") + }) ); + getPageTemplates()(dispatch, getState); }) .catch((err) => { console.error(err); @@ -189,26 +184,22 @@ export const savePageTemplate = (entity) => async (dispatch) => { }); } - const success_message = { - title: T.translate("general.done"), - html: T.translate("edit_form_template.form_template_created"), - type: "success" - }; - return postRequest( createAction(ADD_PAGE_TEMPLATE), createAction(PAGE_TEMPLATE_ADDED), `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates`, normalizedEntity, - authErrorHandler, + snackbarErrorHandler, entity )(params)(dispatch) .then(() => { dispatch( - showMessage(success_message, () => { - history.push("/app/page-templates"); + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("page_template_list.page_crud.page_created") }) ); + getPageTemplates()(dispatch, getState); }) .catch((err) => { console.error(err); diff --git a/src/i18n/en.json b/src/i18n/en.json index 0590f60c0..a4761a75c 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -3851,6 +3851,14 @@ "add_using_global_template": "Using Global Template", "placeholders": { "search": "Search" + }, + "page_crud": { + "title": "Create New Page", + "add_info": "Add Info", + "add_doc": "Add Document Download", + "add_media": "Add Media Request", + "no_modules": "No modules added yet.", + "save": "Save Page" } } } diff --git a/src/layouts/form-template-item-layout.js b/src/layouts/form-template-item-layout.js index 2ee8c4a40..f3526ebde 100644 --- a/src/layouts/form-template-item-layout.js +++ b/src/layouts/form-template-item-layout.js @@ -16,8 +16,8 @@ import { Switch, Route, withRouter } from "react-router-dom"; import T from "i18n-react/dist/i18n-react"; import { Breadcrumb } from "react-breadcrumbs"; import Restrict from "../routes/restrict"; -import EditFormTemplateItemPage from "../pages/sponsors_inventory/edit-form-template-item-page"; -import FormTemplateItemListPage from "../pages/sponsors_inventory/form-template-item-list-page"; +import EditFormTemplateItemPage from "../pages/sponsors-global/form-templates/edit-form-template-item-page"; +import FormTemplateItemListPage from "../pages/sponsors-global/form-templates/form-template-item-list-page"; import NoMatchPage from "../pages/no-match-page"; const FormTemplateItemLayout = ({ match }) => ( diff --git a/src/layouts/form-template-layout.js b/src/layouts/form-template-layout.js index ead05f5d2..7a9f037ad 100644 --- a/src/layouts/form-template-layout.js +++ b/src/layouts/form-template-layout.js @@ -16,8 +16,8 @@ import { Switch, Route, withRouter } from "react-router-dom"; import T from "i18n-react/dist/i18n-react"; import { Breadcrumb } from "react-breadcrumbs"; import Restrict from "../routes/restrict"; -import FormTemplateListPage from "../pages/sponsors_inventory/form-template-list-page"; -import EditFormTemplatePage from "../pages/sponsors_inventory/edit-form-template-page"; +import FormTemplateListPage from "../pages/sponsors-global/form-templates/form-template-list-page"; +import EditFormTemplatePage from "../pages/sponsors-global/form-templates/edit-form-template-page"; import FormTemplateItemLayout from "./form-template-item-layout"; import NoMatchPage from "../pages/no-match-page"; diff --git a/src/layouts/inventory-item-layout.js b/src/layouts/inventory-item-layout.js index 025166cd3..b012fbe99 100644 --- a/src/layouts/inventory-item-layout.js +++ b/src/layouts/inventory-item-layout.js @@ -16,8 +16,8 @@ import { Switch, Route, withRouter } from "react-router-dom"; import T from "i18n-react/dist/i18n-react"; import { Breadcrumb } from "react-breadcrumbs"; import Restrict from "../routes/restrict"; -import InventoryListPage from "../pages/sponsors_inventory/inventory-list-page"; -import EditInventoryItemPage from "../pages/sponsors_inventory/edit-inventory-item-page"; +import InventoryListPage from "../pages/sponsors-global/inventory/inventory-list-page"; +import EditInventoryItemPage from "../pages/sponsors-global/inventory/edit-inventory-item-page"; import NoMatchPage from "../pages/no-match-page"; const InventoryItemLayout = ({ match }) => ( diff --git a/src/layouts/page-template-layout.js b/src/layouts/page-template-layout.js index ab4fc6e13..445381daa 100644 --- a/src/layouts/page-template-layout.js +++ b/src/layouts/page-template-layout.js @@ -17,8 +17,8 @@ import T from "i18n-react/dist/i18n-react"; import { Breadcrumb } from "react-breadcrumbs"; import Restrict from "../routes/restrict"; import NoMatchPage from "../pages/no-match-page"; -import EditPageTemplatePage from "../pages/sponsors_inventory/edit-page-template-page"; -import PageTemplateListPage from "../pages/sponsors_inventory/page-template-list-page"; +import EditPageTemplatePage from "../pages/sponsors-global/page-templates/edit-page-template-page"; +import PageTemplateListPage from "../pages/sponsors-global/page-templates/page-template-list-page"; const PageTemplateLayout = ({ match }) => (
diff --git a/src/pages/sponsors_inventory/popup/add-form-template-item-popup.js b/src/pages/sponsors-global/form-templates/add-form-template-item-popup.js similarity index 100% rename from src/pages/sponsors_inventory/popup/add-form-template-item-popup.js rename to src/pages/sponsors-global/form-templates/add-form-template-item-popup.js diff --git a/src/pages/sponsors_inventory/edit-form-template-item-page.js b/src/pages/sponsors-global/form-templates/edit-form-template-item-page.js similarity index 96% rename from src/pages/sponsors_inventory/edit-form-template-item-page.js rename to src/pages/sponsors-global/form-templates/edit-form-template-item-page.js index 8ea06f672..bc7389ce2 100644 --- a/src/pages/sponsors_inventory/edit-form-template-item-page.js +++ b/src/pages/sponsors-global/form-templates/edit-form-template-item-page.js @@ -15,7 +15,7 @@ import React, { useEffect } from "react"; import { connect } from "react-redux"; import { Breadcrumb } from "react-breadcrumbs"; import T from "i18n-react/dist/i18n-react"; -import InventoryItemForm from "../../components/forms/inventory-item-form"; +import InventoryItemForm from "../../../components/forms/inventory-item-form"; import { getFormTemplateItem, resetFormTemplateItemForm, @@ -23,7 +23,7 @@ import { deleteItemMetaFieldType, deleteItemMetaFieldTypeValue, deleteItemImage -} from "../../actions/form-template-item-actions"; +} from "../../../actions/form-template-item-actions"; const EditFormTemplateItemPage = (props) => { const { diff --git a/src/pages/sponsors_inventory/edit-form-template-page.js b/src/pages/sponsors-global/form-templates/edit-form-template-page.js similarity index 95% rename from src/pages/sponsors_inventory/edit-form-template-page.js rename to src/pages/sponsors-global/form-templates/edit-form-template-page.js index 495987f62..f026badd7 100644 --- a/src/pages/sponsors_inventory/edit-form-template-page.js +++ b/src/pages/sponsors-global/form-templates/edit-form-template-page.js @@ -15,7 +15,7 @@ import React, { useEffect } from "react"; import { connect } from "react-redux"; import { Breadcrumb } from "react-breadcrumbs"; import T from "i18n-react/dist/i18n-react"; -import FormTemplateForm from "../../components/forms/form-template-form"; +import FormTemplateForm from "../../../components/forms/form-template-form"; import { getFormTemplate, resetFormTemplateForm, @@ -23,7 +23,7 @@ import { deleteFormTemplateMetaFieldType, deleteFormTemplateMetaFieldTypeValue, deleteFormTemplateMaterial -} from "../../actions/form-template-actions"; +} from "../../../actions/form-template-actions"; const EditFormTemplatePage = (props) => { const { diff --git a/src/pages/sponsors_inventory/popup/form-template-from-duplicate-popup.js b/src/pages/sponsors-global/form-templates/form-template-from-duplicate-popup.js similarity index 100% rename from src/pages/sponsors_inventory/popup/form-template-from-duplicate-popup.js rename to src/pages/sponsors-global/form-templates/form-template-from-duplicate-popup.js diff --git a/src/pages/sponsors_inventory/form-template-item-list-page.js b/src/pages/sponsors-global/form-templates/form-template-item-list-page.js similarity index 95% rename from src/pages/sponsors_inventory/form-template-item-list-page.js rename to src/pages/sponsors-global/form-templates/form-template-item-list-page.js index 9eebcc662..16b6acf04 100644 --- a/src/pages/sponsors_inventory/form-template-item-list-page.js +++ b/src/pages/sponsors-global/form-templates/form-template-item-list-page.js @@ -27,7 +27,7 @@ import AddIcon from "@mui/icons-material/Add"; import IconButton from "@mui/material/IconButton"; import Tooltip from "@mui/material/Tooltip"; import ImageIcon from "@mui/icons-material/Image"; -import MuiTable from "../../components/mui/table/mui-table"; +import MuiTable from "../../../components/mui/table/mui-table"; import { cloneFromInventoryItem, deleteFormTemplateItem, @@ -39,12 +39,12 @@ import { deleteItemImage, unarchiveFormTemplateItem, archiveFormTemplateItem -} from "../../actions/form-template-item-actions"; -import { getFormTemplate } from "../../actions/form-template-actions"; -import AddFormTemplateItemDialog from "./popup/add-form-template-item-popup"; -import SponsorItemDialog from "./popup/sponsor-inventory-popup"; -import { getInventoryItems } from "../../actions/inventory-item-actions"; -import { DEFAULT_CURRENT_PAGE } from "../../utils/constants"; +} from "../../../actions/form-template-item-actions"; +import { getFormTemplate } from "../../../actions/form-template-actions"; +import AddFormTemplateItemDialog from "./add-form-template-item-popup"; +import SponsorItemDialog from "./sponsor-inventory-popup"; +import { getInventoryItems } from "../../../actions/inventory-item-actions"; +import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants"; const FormTemplateItemListPage = ({ formTemplateId, diff --git a/src/pages/sponsors_inventory/form-template-list-page.js b/src/pages/sponsors-global/form-templates/form-template-list-page.js similarity index 96% rename from src/pages/sponsors_inventory/form-template-list-page.js rename to src/pages/sponsors-global/form-templates/form-template-list-page.js index 885477552..515561cdc 100644 --- a/src/pages/sponsors_inventory/form-template-list-page.js +++ b/src/pages/sponsors-global/form-templates/form-template-list-page.js @@ -37,12 +37,12 @@ import { resetFormTemplateForm, saveFormTemplate, unarchiveFormTemplate -} from "../../actions/form-template-actions"; -import MuiTable from "../../components/mui/table/mui-table"; -import FormTemplateDialog from "./popup/form-template-popup"; -import history from "../../history"; -import FormTemplateFromDuplicateDialog from "./popup/form-template-from-duplicate-popup"; -import { DEFAULT_CURRENT_PAGE } from "../../utils/constants"; +} from "../../../actions/form-template-actions"; +import MuiTable from "../../../components/mui/table/mui-table"; +import FormTemplateDialog from "./form-template-popup"; +import history from "../../../history"; +import FormTemplateFromDuplicateDialog from "./form-template-from-duplicate-popup"; +import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants"; const FormTemplateListPage = ({ formTemplates, diff --git a/src/pages/sponsors_inventory/popup/form-template-popup.js b/src/pages/sponsors-global/form-templates/form-template-popup.js similarity index 99% rename from src/pages/sponsors_inventory/popup/form-template-popup.js rename to src/pages/sponsors-global/form-templates/form-template-popup.js index 88b97044d..d47e94488 100644 --- a/src/pages/sponsors_inventory/popup/form-template-popup.js +++ b/src/pages/sponsors-global/form-templates/form-template-popup.js @@ -20,7 +20,7 @@ import CloseIcon from "@mui/icons-material/Close"; import { useFormik, FormikProvider, FieldArray } from "formik"; import * as yup from "yup"; import showConfirmDialog from "../../../components/mui/showConfirmDialog"; -import MetaFieldValues from "./meta-field-values"; +import MetaFieldValues from "../shared/meta-field-values"; import MuiFormikTextField from "../../../components/mui/formik-inputs/mui-formik-textfield"; import FormikTextEditor from "../../../components/inputs/formik-text-editor"; import MuiFormikSelect from "../../../components/mui/formik-inputs/mui-formik-select"; diff --git a/src/pages/sponsors_inventory/popup/sponsor-inventory-popup.js b/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js similarity index 99% rename from src/pages/sponsors_inventory/popup/sponsor-inventory-popup.js rename to src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js index 700746357..5b87d555a 100644 --- a/src/pages/sponsors_inventory/popup/sponsor-inventory-popup.js +++ b/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js @@ -28,7 +28,7 @@ import { METAFIELD_TYPES } from "../../../utils/constants"; import showConfirmDialog from "../../../components/mui/showConfirmDialog"; -import MetaFieldValues from "./meta-field-values"; +import MetaFieldValues from "../shared/meta-field-values"; import MuiFormikTextField from "../../../components/mui/formik-inputs/mui-formik-textfield"; import useScrollToError from "../../../hooks/useScrollToError"; import MuiFormikSelect from "../../../components/mui/formik-inputs/mui-formik-select"; diff --git a/src/pages/sponsors_inventory/edit-inventory-item-page.js b/src/pages/sponsors-global/inventory/edit-inventory-item-page.js similarity index 95% rename from src/pages/sponsors_inventory/edit-inventory-item-page.js rename to src/pages/sponsors-global/inventory/edit-inventory-item-page.js index cecac7cc0..f51c73a81 100644 --- a/src/pages/sponsors_inventory/edit-inventory-item-page.js +++ b/src/pages/sponsors-global/inventory/edit-inventory-item-page.js @@ -15,7 +15,7 @@ import React, { useEffect } from "react"; import { connect } from "react-redux"; import { Breadcrumb } from "react-breadcrumbs"; import T from "i18n-react/dist/i18n-react"; -import InventoryItemForm from "../../components/forms/inventory-item-form"; +import InventoryItemForm from "../../../components/forms/inventory-item-form"; import { getInventoryItem, resetInventoryItemForm, @@ -23,7 +23,7 @@ import { deleteInventoryItemMetaFieldType, deleteInventoryItemMetaFieldTypeValue, deleteInventoryItemImage -} from "../../actions/inventory-item-actions"; +} from "../../../actions/inventory-item-actions"; const EditInventoryItemPage = (props) => { const { diff --git a/src/pages/sponsors_inventory/inventory-list-page.js b/src/pages/sponsors-global/inventory/inventory-list-page.js similarity index 96% rename from src/pages/sponsors_inventory/inventory-list-page.js rename to src/pages/sponsors-global/inventory/inventory-list-page.js index 0a4f666bd..48e446d7b 100644 --- a/src/pages/sponsors_inventory/inventory-list-page.js +++ b/src/pages/sponsors-global/inventory/inventory-list-page.js @@ -39,10 +39,10 @@ import { resetInventoryItemForm, saveInventoryItem, unarchiveInventoryItem -} from "../../actions/inventory-item-actions"; -import MuiTable from "../../components/mui/table/mui-table"; -import SponsorInventoryDialog from "./popup/sponsor-inventory-popup"; -import { DEFAULT_CURRENT_PAGE } from "../../utils/constants"; +} from "../../../actions/inventory-item-actions"; +import MuiTable from "../../../components/mui/table/mui-table"; +import SponsorInventoryDialog from "../form-templates/sponsor-inventory-popup"; +import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants"; const InventoryListPage = ({ inventoryItems, diff --git a/src/pages/sponsors_inventory/edit-page-template-page.js b/src/pages/sponsors-global/page-templates/edit-page-template-page.js similarity index 95% rename from src/pages/sponsors_inventory/edit-page-template-page.js rename to src/pages/sponsors-global/page-templates/edit-page-template-page.js index 99b041c69..ac2ff1c3e 100644 --- a/src/pages/sponsors_inventory/edit-page-template-page.js +++ b/src/pages/sponsors-global/page-templates/edit-page-template-page.js @@ -15,7 +15,7 @@ import React, { useEffect } from "react"; import { connect } from "react-redux"; import { Breadcrumb } from "react-breadcrumbs"; import T from "i18n-react/dist/i18n-react"; -import FormTemplateForm from "../../components/forms/form-template-form"; +import FormTemplateForm from "../../../components/forms/form-template-form"; import { getFormTemplate, resetFormTemplateForm, @@ -23,7 +23,7 @@ import { deleteFormTemplateMetaFieldType, deleteFormTemplateMetaFieldTypeValue, deleteFormTemplateMaterial -} from "../../actions/form-template-actions"; +} from "../../../actions/form-template-actions"; const EditPageTemplatePage = (props) => { const { diff --git a/src/pages/sponsors_inventory/page-template-list-page.js b/src/pages/sponsors-global/page-templates/page-template-list-page.js similarity index 89% rename from src/pages/sponsors_inventory/page-template-list-page.js rename to src/pages/sponsors-global/page-templates/page-template-list-page.js index 893c03e05..19821bdfc 100644 --- a/src/pages/sponsors_inventory/page-template-list-page.js +++ b/src/pages/sponsors-global/page-templates/page-template-list-page.js @@ -11,7 +11,7 @@ * limitations under the License. * */ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { Alert, Button, @@ -27,11 +27,13 @@ import T from "i18n-react/dist/i18n-react"; import { archivePageTemplate, getPageTemplates, + savePageTemplate, unarchivePageTemplate -} from "../../actions/page-template-actions"; -import MuiTable from "../../components/mui/table/mui-table"; -import SearchInput from "../../components/mui/search-input"; -import { DEFAULT_CURRENT_PAGE } from "../../utils/constants"; +} from "../../../actions/page-template-actions"; +import MuiTable from "../../../components/mui/table/mui-table"; +import SearchInput from "../../../components/mui/search-input"; +import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants"; +import PageTemplatePopup from "./page-template-popup"; const PageTemplateListPage = ({ pageTemplates, @@ -44,8 +46,11 @@ const PageTemplateListPage = ({ totalPageTemplates, getPageTemplates, archivePageTemplate, - unarchivePageTemplate + unarchivePageTemplate, + savePageTemplate }) => { + const [pageTemplateId, setPageTemplateId] = useState(null); + useEffect(() => { getPageTemplates(); }, []); @@ -92,7 +97,7 @@ const PageTemplateListPage = ({ }; const handleNewPageTemplate = () => { - console.log("NEW PAGE"); + setPageTemplateId("new"); }; const handleClonePageTemplate = () => { @@ -242,6 +247,11 @@ const PageTemplateListPage = ({
)}
+ setPageTemplateId(null)} + onSave={savePageTemplate} + /> ); }; @@ -253,5 +263,6 @@ const mapStateToProps = ({ pageTemplateListState }) => ({ export default connect(mapStateToProps, { getPageTemplates, archivePageTemplate, - unarchivePageTemplate + unarchivePageTemplate, + savePageTemplate })(PageTemplateListPage); diff --git a/src/pages/sponsors-global/page-templates/page-template-popup.js b/src/pages/sponsors-global/page-templates/page-template-popup.js new file mode 100644 index 000000000..6fc8f89c6 --- /dev/null +++ b/src/pages/sponsors-global/page-templates/page-template-popup.js @@ -0,0 +1,154 @@ +import React from "react"; +import T from "i18n-react/dist/i18n-react"; +import { connect } from "react-redux"; +import PropTypes from "prop-types"; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + Grid2, + IconButton, + Typography +} from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import CloseIcon from "@mui/icons-material/Close"; +import { FormikProvider, useFormik } from "formik"; +import * as yup from "yup"; +import MuiFormikTextField from "../../../components/mui/formik-inputs/mui-formik-textfield"; + +const PageTemplatePopup = ({ pageTemplate, open, onClose, onSave }) => { + const handleClose = () => { + onClose(); + }; + + const handleAddInfo = () => { + console.log("ADD INFO"); + }; + + const handleAddDocument = () => { + console.log("ADD DOCUMENT"); + }; + + const handleAddMedia = () => { + console.log("ADD MEDIA"); + }; + + const formik = useFormik({ + initialValues: { + ...pageTemplate + }, + validationSchema: yup.object().shape({ + code: yup.string().required(T.translate("validation.required")), + name: yup.string().required(T.translate("validation.required")) + }), + enableReinitialize: true, + onSubmit: (values) => { + onSave(values); + } + }); + + return ( + + + + {T.translate("page_template_list.page_crud.title")} + + handleClose()} sx={{ mr: 1 }}> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {T.translate("page_template_list.page_crud.no_modules")} + + + + + + + + + + ); +}; + +PageTemplatePopup.propTypes = { + open: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired +}; + +const mapStateToProps = ({ currentPageTemplateState }) => ({ + ...currentPageTemplateState +}); + +export default connect(mapStateToProps, {})(PageTemplatePopup); diff --git a/src/pages/sponsors_inventory/popup/meta-field-values.js b/src/pages/sponsors-global/shared/meta-field-values.js similarity index 100% rename from src/pages/sponsors_inventory/popup/meta-field-values.js rename to src/pages/sponsors-global/shared/meta-field-values.js diff --git a/src/pages/sponsors/components/additional-input.js b/src/pages/sponsors/components/additional-input.js index 6386f6795..96b9ed74e 100644 --- a/src/pages/sponsors/components/additional-input.js +++ b/src/pages/sponsors/components/additional-input.js @@ -14,7 +14,7 @@ import { import T from "i18n-react"; import DeleteIcon from "@mui/icons-material/Delete"; import AddIcon from "@mui/icons-material/Add"; -import MetaFieldValues from "../../sponsors_inventory/popup/meta-field-values"; +import MetaFieldValues from "../../sponsors-global/shared/meta-field-values"; import { METAFIELD_TYPES, METAFIELD_TYPES_WITH_OPTIONS From 28ed06e03b030fa8f8a86107f77b2c963d6c6269 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Fri, 12 Dec 2025 16:01:06 -0300 Subject: [PATCH 21/22] feat: sponsor pages - missing translation --- src/i18n/en.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index a4761a75c..fac92ee91 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -3858,7 +3858,9 @@ "add_doc": "Add Document Download", "add_media": "Add Media Request", "no_modules": "No modules added yet.", - "save": "Save Page" + "save": "Save Page", + "page_saved": "Page saved successfully.", + "page_created": "Page created successfully." } } } From fbbd53acfb5370b3d45d9de13e53fa1d38c2f75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 16 Dec 2025 17:06:53 -0300 Subject: [PATCH 22/22] feat: add new menu and page for summit sponsor pages, page template popup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/sponsor-pages-actions.js | 134 +++++++++++ src/components/menu/index.js | 5 + src/i18n/en.json | 35 +++ src/layouts/sponsor-layout.js | 22 ++ .../global-page/global-page-popup.js | 55 +++++ .../global-page/select-pages-dialog.js | 191 +++++++++++++++ .../sponsors/sponsor-pages-list-page/index.js | 223 ++++++++++++++++++ .../sponsors/sponsor-pages-list-reducer.js | 85 +++++++ src/store.js | 2 + 9 files changed, 752 insertions(+) create mode 100644 src/actions/sponsor-pages-actions.js create mode 100644 src/pages/sponsors/sponsor-pages-list-page/components/global-page/global-page-popup.js create mode 100644 src/pages/sponsors/sponsor-pages-list-page/components/global-page/select-pages-dialog.js create mode 100644 src/pages/sponsors/sponsor-pages-list-page/index.js create mode 100644 src/reducers/sponsors/sponsor-pages-list-reducer.js diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js new file mode 100644 index 000000000..e93e0042c --- /dev/null +++ b/src/actions/sponsor-pages-actions.js @@ -0,0 +1,134 @@ +/** + * 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, + postRequest, + startLoading, + stopLoading +} from "openstack-uicore-foundation/lib/utils/actions"; +import T from "i18n-react/dist/i18n-react"; +import { escapeFilterValue, getAccessTokenSafely } from "../utils/methods"; +import { + DEFAULT_CURRENT_PAGE, + DEFAULT_ORDER_DIR, + DEFAULT_PER_PAGE +} from "../utils/constants"; +import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; + +export const REQUEST_SPONSOR_PAGES = "REQUEST_SPONSOR_PAGES"; +export const RECEIVE_SPONSOR_PAGES = "RECEIVE_SPONSOR_PAGES"; + +export const GLOBAL_PAGE_CLONED = "GLOBAL_PAGE_CLONED"; + +export const getSponsorPages = + ( + term = "", + page = DEFAULT_CURRENT_PAGE, + perPage = DEFAULT_PER_PAGE, + order = "id", + orderDir = DEFAULT_ORDER_DIR, + hideArchived = false, + sponsorshipTypesId = [] + ) => + 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(`name=@${escapedTerm},code=@${escapedTerm}`); + } + + const params = { + page, + per_page: perPage, + access_token: accessToken, + expand: "sponsorship_types" + }; + + if (hideArchived) filter.push("is_archived==0"); + + if (sponsorshipTypesId?.length > 0) { + const formattedSponsorships = sponsorshipTypesId.join("&&"); + filter.push("applies_to_all_tiers==0"); + filter.push(`sponsorship_type_id_not_in==${formattedSponsorships}`); + } + + 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_PAGES), + createAction(RECEIVE_SPONSOR_PAGES), + `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/show-pages`, + authErrorHandler, + { order, orderDir, page, term, hideArchived } + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + }); + }; + +export const cloneGlobalPage = + (pagesIds, sponsorIds, allSponsors) => async (dispatch, getState) => { + const { currentSummitState } = getState(); + const accessToken = await getAccessTokenSafely(); + const { currentSummit } = currentSummitState; + + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + const normalizedEntity = { + page_template_ids: pagesIds, + sponsorship_types: sponsorIds, + apply_to_all_types: allSponsors + }; + + if (allSponsors) { + delete normalizedEntity.sponsorship_types; + } + + return postRequest( + null, + createAction(GLOBAL_PAGE_CLONED), + `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/show-pages/clone`, + normalizedEntity, + snackbarErrorHandler + )(params)(dispatch) + .then(() => { + dispatch(getSponsorForms()); + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("sponsor_pages.global_page_popup.success") + }) + ); + }) + .catch(() => {}); // need to catch promise reject + }; diff --git a/src/components/menu/index.js b/src/components/menu/index.js index 83e492498..0689c4f10 100644 --- a/src/components/menu/index.js +++ b/src/components/menu/index.js @@ -244,6 +244,11 @@ const getSummitItems = (summitId) => [ linkUrl: `summits/${summitId}/sponsors/forms`, accessRoute: "admin-sponsors" }, + { + name: "sponsor_pages", + linkUrl: `summits/${summitId}/sponsors/pages`, + accessRoute: "admin-sponsors" + }, { name: "sponsorship_list", linkUrl: `summits/${summitId}/sponsorships`, diff --git a/src/i18n/en.json b/src/i18n/en.json index fac92ee91..506fed838 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -154,6 +154,7 @@ "sponsors": "Sponsors", "sponsor_list": "Sponsor List", "sponsor_forms": "Forms", + "sponsor_pages": "Pages", "sponsorship_list": "Tiers", "sponsor_users": "Users", "sponsors_promocodes": "Promo Codes", @@ -2576,6 +2577,40 @@ "items_added": "Items added successfully." } }, + "sponsor_pages": { + "pages": "Pages", + "alert_info": "Note: These Pages will be visible to all sponsors of the selected level in this program.", + "using_template": "Using Template", + "new_page": "New Page", + "code_column_label": "Code", + "name_column_label": "Name", + "tier_column_label": "Tier", + "info_mod_column_label": "Info Mod", + "upload_mod_column_label": "Upload Mod", + "download_mod_column_label": "Download Mod", + "hide_archived": "Hide archived Pages", + "filter": "Filter", + "sort_by": "Sort By", + "no_sponsors_pages": "No pages found for this search criteria.", + "placeholders": { + "search": "Search..." + }, + "global_page_popup": { + "title": "Add Page Template", + "items_selected": "items selected", + "code": "Code", + "name": "name", + "info_mod": "Info Mod", + "download_mod": "Download Mod", + "upload_mod": "Upload Mod", + "add_selected": "Add Selected Page Template", + "success": "Page created successfully.", + "error": "There was a problem creating the forms, please try again.", + "placeholders": { + "search": "Search..." + } + } + }, "sponsor_users": { "users": "Users", "access_request": "access request", diff --git a/src/layouts/sponsor-layout.js b/src/layouts/sponsor-layout.js index fefd4f28d..85ed8dbb9 100644 --- a/src/layouts/sponsor-layout.js +++ b/src/layouts/sponsor-layout.js @@ -25,6 +25,7 @@ import SponsorSettingsPage from "../pages/sponsor_settings/sponsor-settings-page import SponsorFormsListPage from "../pages/sponsors/sponsor-forms-list-page"; import SponsorFormItemListPage from "../pages/sponsors/sponsor-form-item-list-page"; import SponsorUsersListPage from "../pages/sponsors/sponsor-users-list-page"; +import sponsorPagesListPage from "../pages/sponsors/sponsor-pages-list-page"; const SponsorLayout = ({ match }) => (
@@ -63,6 +64,27 @@ const SponsorLayout = ({ match }) => (
)} /> + ( +
+ + + + +
+ )} + /> { + const [stage, setStage] = useState("pages"); + const [selectedTemplates, setSelectedTemplates] = useState([]); + const dialogSize = stage === "pages" ? "md" : "sm"; + + const handleClose = () => { + setSelectedTemplates([]); + setStage("pages"); + onClose(); + }; + + const handleOnSelectTemplates = (templates) => { + setSelectedTemplates(templates); + setStage("sponsorships"); + }; + + const handleOnSave = (selectedTiers, allTiers) => { + cloneGlobalPage(selectedTemplates, selectedTiers, allTiers).finally(() => { + handleClose(); + }); + }; + + return ( + + {stage === "pages" && ( + + )} + {stage === "sponsorships" && ( + + )} + + ); +}; + +GlobalPagePopup.propTypes = { + open: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired +}; + +const mapStateToProps = () => ({}); + +export default connect(mapStateToProps, { + cloneGlobalPage +})(GlobalPagePopup); diff --git a/src/pages/sponsors/sponsor-pages-list-page/components/global-page/select-pages-dialog.js b/src/pages/sponsors/sponsor-pages-list-page/components/global-page/select-pages-dialog.js new file mode 100644 index 000000000..2a8e4a2b4 --- /dev/null +++ b/src/pages/sponsors/sponsor-pages-list-page/components/global-page/select-pages-dialog.js @@ -0,0 +1,191 @@ +import React, { useEffect, useState } from "react"; +import T from "i18n-react/dist/i18n-react"; +import PropTypes from "prop-types"; +import { connect } from "react-redux"; +import { + Box, + Button, + Checkbox, + DialogActions, + DialogContent, + DialogTitle, + Divider, + FormControlLabel, + Grid2, + IconButton, + Typography +} from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import SearchInput from "../../../../../components/mui/search-input"; +import { getPageTemplates } from "../../../../../actions/page-template-actions"; +import { DEFAULT_PER_PAGE } from "../../../../../utils/constants"; +import MuiInfiniteTable from "../../../../../components/mui/infinite-table"; + +const SelectPagesDialog = ({ + pageTemplates, + items, + currentPage, + term, + order, + orderDir, + total, + onSave, + onClose, + getPageTemplates +}) => { + const [selectedRows, setSelectedRows] = useState([]); + + useEffect(() => { + getPageTemplates("", 1, DEFAULT_PER_PAGE, "id", 1, true); + }, []); + + const handleSort = (key, dir) => { + getPageTemplates(term, 1, DEFAULT_PER_PAGE, key, dir, true); + }; + + const handleLoadMore = () => { + if (total > items.length) { + getPageTemplates( + term, + currentPage + 1, + DEFAULT_PER_PAGE, + order, + orderDir, + true + ); + } + }; + + const handleClose = () => { + setSelectedRows([]); + onClose(); + }; + + const handleOnCheck = (rowId, checked) => { + if (checked) { + setSelectedRows([...selectedRows, rowId]); + } else { + setSelectedRows(selectedRows.filter((r) => r !== rowId)); + } + }; + + const handleOnSearch = (searchTerm) => { + getPageTemplates(searchTerm, 1, DEFAULT_PER_PAGE, "id", 1, true); + }; + + const handleOnSave = () => { + onSave(selectedRows); + }; + + const columns = [ + { + columnKey: "select", + header: "", + width: 30, + align: "center", + render: (row) => ( + handleOnCheck(row.id, ev.target.checked)} + /> + } + /> + ) + }, + { + columnKey: "code", + header: T.translate("sponsor_pages.global_page_popup.code"), + sortable: true + }, + { + columnKey: "name", + header: T.translate("sponsor_pages.global_page_popup.name"), + sortable: true + }, + { + columnKey: "info_mod", + header: T.translate("sponsor_pages.global_page_popup.info_mod"), + sortable: false + }, + { + columnKey: "download_mod", + header: T.translate("sponsor_pages.global_page_popup.download_mod"), + sortable: false + }, + { + columnKey: "upload_mod", + header: T.translate("sponsor_pages.global_page_popup.upload_mod"), + sortable: false + } + ]; + + return ( + <> + + + {T.translate("sponsor_pages.global_page_popup.title")} + + handleClose()}> + + + + + + + + {selectedRows.length} items selected + + + + + + + {pageTemplates.length > 0 && ( + + + + )} + + + + + + + ); +}; + +SelectPagesDialog.propTypes = { + onClose: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired +}; + +const mapStateToProps = ({ pageTemplateListState }) => ({ + ...pageTemplateListState +}); + +export default connect(mapStateToProps, { + getPageTemplates +})(SelectPagesDialog); diff --git a/src/pages/sponsors/sponsor-pages-list-page/index.js b/src/pages/sponsors/sponsor-pages-list-page/index.js new file mode 100644 index 000000000..ad50767a8 --- /dev/null +++ b/src/pages/sponsors/sponsor-pages-list-page/index.js @@ -0,0 +1,223 @@ +/** + * 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, useState } from "react"; +import { connect } from "react-redux"; +import T from "i18n-react/dist/i18n-react"; +import { + Box, + Button, + Checkbox, + FormControlLabel, + FormGroup, + Grid2 +} from "@mui/material"; +import AddIcon from "@mui/icons-material/Add"; +import { getSponsorPages } from "../../../actions/sponsor-pages-actions"; +import CustomAlert from "../../../components/mui/custom-alert"; +import MuiTable from "../../../components/mui/table/mui-table"; +import GlobalPagePopup from "./components/global-page/global-page-popup"; + +const SponsorPagesListPage = ({ + sponsorPages, + currentPage, + perPage, + term, + order, + orderDir, + hideArchived, + totalCount, + getSponsorPages, + getSponsorForm +}) => { + const [openPopup, setOpenPopup] = useState(null); + + useEffect(() => { + getSponsorPages(); + }, []); + + const handlePageChange = (page) => { + getSponsorPages(term, page, perPage, order, orderDir, hideArchived); + }; + + const handleSort = (key, dir) => { + getSponsorPages(term, currentPage, perPage, key, dir, hideArchived); + }; + + const handleRowEdit = (row) => { + getSponsorForm(row.id).then(() => { + setOpenPopup("new"); + }); + }; + + const handleRowDelete = (itemId) => { + console.log("DELETE ITEM ID...", itemId); + // deleteSponsorForm(itemId); + }; + + const handleArchiveItem = (item) => console.log("archive ITEM...", item); + // item.is_archived + // ? unarchiveSponsorForm(item.id) + // : archiveSponsorForm(item.id); + + const handleHideArchivedForms = (ev) => { + getSponsorPages( + term, + currentPage, + perPage, + order, + orderDir, + ev.target.checked + ); + }; + + const columns = [ + { + columnKey: "code", + header: T.translate("sponsor_pages.code_column_label"), + sortable: true + }, + { + columnKey: "name", + header: T.translate("sponsor_pages.name_column_label"), + sortable: true + }, + { + columnKey: "tier", + header: T.translate("sponsor_pages.tier_column_label"), + sortable: false + }, + { + columnKey: "info_mod", + header: T.translate("sponsor_pages.info_mod_column_label"), + sortable: false + }, + { + columnKey: "upload_mod", + header: T.translate("sponsor_pages.upload_mod_column_label"), + sortable: false + }, + { + columnKey: "download_mod", + header: T.translate("sponsor_pages.download_mod_column_label"), + sortable: false + } + ]; + + const tableOptions = { + sortCol: order, + sortDir: orderDir, + disableProp: "is_archived" + }; + + return ( +
+

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

+ + + + + {totalCount} {T.translate("sponsor_pages.pages")} + + + + + + } + label={T.translate("sponsor_pages.hide_archived")} + /> + + + + + + + + + + + + {sponsorPages.length === 0 && ( +
{T.translate("sponsor_pages.no_sponsors_pages")}
+ )} + + {sponsorPages.length > 0 && ( +
+ +
+ )} + + setOpenPopup(null)} + /> + {/* setOpenPopup(null)} + /> */} +
+ ); +}; + +const mapStateToProps = ({ sponsorPagesListState }) => ({ + ...sponsorPagesListState +}); + +export default connect(mapStateToProps, { + getSponsorPages +})(SponsorPagesListPage); diff --git a/src/reducers/sponsors/sponsor-pages-list-reducer.js b/src/reducers/sponsors/sponsor-pages-list-reducer.js new file mode 100644 index 000000000..c835667b0 --- /dev/null +++ b/src/reducers/sponsors/sponsor-pages-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 { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; +import { + RECEIVE_SPONSOR_PAGES, + REQUEST_SPONSOR_PAGES +} from "../../actions/sponsor-pages-actions"; +import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; + +const DEFAULT_STATE = { + sponsorPages: [], + term: "", + order: "name", + orderDir: 1, + currentPage: 1, + lastPage: 1, + perPage: 10, + totalCount: 0, + hideArchived: false +}; + +const sponsorPagesListReducer = (state = DEFAULT_STATE, action) => { + const { type, payload } = action; + + switch (type) { + case SET_CURRENT_SUMMIT: + case LOGOUT_USER: { + return DEFAULT_STATE; + } + case REQUEST_SPONSOR_PAGES: { + const { order, orderDir, page, term, hideArchived } = payload; + + return { + ...state, + order, + orderDir, + sponsorPages: [], + currentPage: page, + term, + hideArchived + }; + } + case RECEIVE_SPONSOR_PAGES: { + const { + current_page: currentPage, + total, + last_page: lastPage + } = payload.response; + + const sponsorPages = payload.response.data.map((a) => ({ + id: a.id, + code: a.code, + name: a.name, + tier: a.sponsorship_types.map((s) => s.name).join(", "), + info_mod: a.modules.filter((m) => m.kind === "Info").length, + upload_mod: a.modules.filter((m) => m.kind === "Upload").length, + download_mod: a.modules.filter((m) => m.kind === "Download").length, + is_archived: a.is_archived + })); + + return { + ...state, + sponsorPages, + currentPage, + totalCount: total, + lastPage + }; + } + default: + return state; + } +}; + +export default sponsorPagesListReducer; diff --git a/src/store.js b/src/store.js index e61a3c172..784b3e1e3 100644 --- a/src/store.js +++ b/src/store.js @@ -165,6 +165,7 @@ import eventRSVPInvitationListReducer from "./reducers/rsvps/event-rsvp-invitati import eventRSVPReducer from "./reducers/events/event-rsvp-reducer.js"; import sponsorPageFormsListReducer from "./reducers/sponsors/sponsor-page-forms-list-reducer.js"; import sponsorCustomizedFormReducer from "./reducers/sponsors/sponsor-customized-form-reducer.js"; +import sponsorPagesListReducer from "./reducers/sponsors/sponsor-pages-list-reducer.js"; // default: localStorage if web, AsyncStorage if react-native @@ -248,6 +249,7 @@ const reducers = persistCombineReducers(config, { currentSponsorState: sponsorReducer, sponsorFormsListState: sponsorFormsListReducer, sponsorFormItemsListState: sponsorFormItemsListReducer, + sponsorPagesListState: sponsorPagesListReducer, sponsorUsersListState: sponsorUsersListReducer, sponsorPageFormsListState: sponsorPageFormsListReducer, sponsorCustomizedFormState: sponsorCustomizedFormReducer,