From b1e30a0fa89f497ad2c5491e2c2862ff630e8c5c Mon Sep 17 00:00:00 2001 From: romanetar Date: Mon, 16 Dec 2024 14:25:41 +0100 Subject: [PATCH] feat: inventory item crud pages (WIP) --- .env.example | 4 +- src/actions/inventory-item-actions.js | 192 ++++++++++++++++++ src/app.js | 1 + src/components/form-repeater/index.js | 67 ++++++ src/components/forms/inventory-item-form.js | 175 ++++++++++++++++ src/components/menu/index.js | 11 + src/i18n/en.json | 23 ++- src/layouts/inventory-item-layout.js | 55 +++++ src/layouts/primary-layout.js | 5 + src/layouts/sponsor-inventory-layout.js | 42 ++++ .../edit-inventory-item-page.js | 73 +++++++ .../sponsors_inventory/inventory-list-page.js | 157 ++++++++++++++ .../inventory-item-list-reducer.js | 102 ++++++++++ .../inventory_items/inventory-item-reducer.js | 98 +++++++++ src/store.js | 16 +- 15 files changed, 1013 insertions(+), 8 deletions(-) create mode 100644 src/actions/inventory-item-actions.js create mode 100644 src/components/form-repeater/index.js create mode 100644 src/components/forms/inventory-item-form.js create mode 100644 src/layouts/inventory-item-layout.js create mode 100644 src/layouts/sponsor-inventory-layout.js create mode 100644 src/pages/sponsors_inventory/edit-inventory-item-page.js create mode 100644 src/pages/sponsors_inventory/inventory-list-page.js create mode 100644 src/reducers/inventory_items/inventory-item-list-reducer.js create mode 100644 src/reducers/inventory_items/inventory-item-reducer.js diff --git a/.env.example b/.env.example index f4a232a14..4fc27b7be 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,7 @@ REPORT_API_BASE_URL= MARKETING_API_BASE_URL= EMAIL_API_BASE_URL= FILE_UPLOAD_API_BASE_URL= +INVENTORY_API_BASE_URL= SIGNAGE_BASE_URL= PRINT_APP_URL=https://badge-print-app.dev.fnopen.com PUB_API_BASE_URL= @@ -12,7 +13,8 @@ OS_BASE_URL= SCOPES_BASE_REALM=${API_BASE_URL} EMAIL_SCOPES="clients/read templates/read templates/write emails/read" FILE_UPLOAD_SCOPES="files/upload" -SCOPES="profile openid offline_access ${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" +INVENTORY_API_SCOPES="inventory-items/get inventory-items/add inventory-items/update inventory-items/delete" +SCOPES="profile openid offline_access ${EMAIL_SCOPES} ${FILE_UPLOAD_SCOPES} ${INVENTORY_API_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" 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/inventory-item-actions.js b/src/actions/inventory-item-actions.js new file mode 100644 index 000000000..960130bab --- /dev/null +++ b/src/actions/inventory-item-actions.js @@ -0,0 +1,192 @@ +/** + * 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"; + +export const ADD_INVENTORY_ITEM = "ADD_INVENTORY_ITEM"; +export const CHANGE_INVENTORY_ITEM_SEARCH_TERM = + "CHANGE_INVENTORY_ITEM_SEARCH_TERM"; +export const INVENTORY_ITEM_ADDED = "INVENTORY_ITEM_ADDED"; +export const INVENTORY_ITEM_DELETED = "INVENTORY_ITEM_DELETED"; +export const INVENTORY_ITEM_UPDATED = "INVENTORY_ITEM_UPDATED"; +export const RECEIVE_INVENTORY_ITEM = "RECEIVE_INVENTORY_ITEM"; +export const RECEIVE_INVENTORY_ITEMS = "RECEIVE_INVENTORY_ITEMS"; +export const REQUEST_INVENTORY_ITEMS = "REQUEST_INVENTORY_ITEMS"; +export const RESET_INVENTORY_ITEM_FORM = "RESET_INVENTORY_ITEM_FORM"; +export const UPDATE_INVENTORY_ITEM = "UPDATE_INVENTORY_ITEM"; + +export const getInventoryItems = + ( + term = null, + page = DEFAULT_CURRENT_PAGE, + perPage = DEFAULT_PER_PAGE, + order = "id", + orderDir = DEFAULT_ORDER_DIR + ) => + async (dispatch) => { + const accessToken = await getAccessTokenSafely(); + const filter = []; + + dispatch(startLoading()); + + if (term) { + const escapedTerm = escapeFilterValue(term); + filter.push(`name=@${escapedTerm}`); + } + + const params = { + page, + fields: "id,code,name", + per_page: perPage, + access_token: accessToken + }; + + if (filter.length > 0) { + params["filter[]"] = filter; + } + + // order + if (order != null && orderDir != null) { + const orderDirSign = orderDir === 1 ? "+" : "-"; + params.order = `${orderDirSign}${order}`; + } + + return getRequest( + createAction(REQUEST_INVENTORY_ITEMS), + createAction(RECEIVE_INVENTORY_ITEMS), + `${window.INVENTORY_API_BASE_URL}/api/v1/inventory-items/`, + authErrorHandler, + { order, orderDir, page, term } + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + }); + }; + +export const getInventoryItem = (inventoryItemId) => async (dispatch) => { + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken, + expand: "images,meta_fields,meta_fields.values" + }; + + return getRequest( + null, + createAction(RECEIVE_INVENTORY_ITEM), + `${window.INVENTORY_API_BASE_URL}/api/v1/inventory-items/${inventoryItemId}/`, + authErrorHandler + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + }); +}; + +export const deleteInventoryItem = (inventoryItemId) => async (dispatch) => { + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + return deleteRequest( + null, + createAction(INVENTORY_ITEM_DELETED)({ inventoryItemId }), + `${window.INVENTORY_API_BASE_URL}/api/v1/inventory-items/${inventoryItemId}/`, + null, + authErrorHandler + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + }); +}; + +export const resetInventoryItemForm = () => (dispatch) => { + dispatch(createAction(RESET_INVENTORY_ITEM_FORM)({})); +}; + +export const saveInventoryItem = (entity) => async (dispatch) => { + const accessToken = await getAccessTokenSafely(); + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + const normalizedEntity = normalizeEntity(entity); + + if (entity.id) { + putRequest( + createAction(UPDATE_INVENTORY_ITEM), + createAction(INVENTORY_ITEM_UPDATED), + `${window.INVENTORY_API_BASE_URL}/api/v1/inventory-items/${entity.id}/`, + normalizedEntity, + authErrorHandler, + entity + )(params)(dispatch).then(() => { + dispatch( + showSuccessMessage( + T.translate("edit_inventory_item.inventory_item_saved") + ) + ); + }); + } else { + const success_message = { + title: T.translate("general.done"), + html: T.translate("edit_inventory_item.inventory_item_created"), + type: "success" + }; + + postRequest( + createAction(ADD_INVENTORY_ITEM), + createAction(INVENTORY_ITEM_ADDED), + `${window.INVENTORY_API_BASE_URL}/api/v1/inventory-items/`, + normalizedEntity, + authErrorHandler, + entity + )(params)(dispatch).then(() => { + dispatch( + showMessage(success_message, () => { + history.push("/app/sponsors-inventory"); + }) + ); + }); + } +}; + +const normalizeEntity = (entity) => { + const normalizedEntity = { ...entity }; + + return normalizedEntity; +}; diff --git a/src/app.js b/src/app.js index c65ec8c7c..2df3d4cae 100644 --- a/src/app.js +++ b/src/app.js @@ -71,6 +71,7 @@ window.REPORT_API_BASE_URL = process.env.REPORT_API_BASE_URL; window.MARKETING_API_BASE_URL = process.env.MARKETING_API_BASE_URL; window.EMAIL_API_BASE_URL = process.env.EMAIL_API_BASE_URL; window.FILE_UPLOAD_API_BASE_URL = process.env.FILE_UPLOAD_API_BASE_URL; +window.INVENTORY_API_BASE_URL = process.env.INVENTORY_API_BASE_URL; window.SIGNAGE_BASE_URL = process.env.SIGNAGE_BASE_URL; window.OAUTH2_CLIENT_ID = process.env.OAUTH2_CLIENT_ID; window.SCOPES = process.env.SCOPES; diff --git a/src/components/form-repeater/index.js b/src/components/form-repeater/index.js new file mode 100644 index 000000000..49744784e --- /dev/null +++ b/src/components/form-repeater/index.js @@ -0,0 +1,67 @@ +import React, { useState } from "react"; + +const FormRepeater = ({ renderContent }) => { + const [lines, setLines] = useState([{ id: Date.now(), value: "" }]); + + const handleAddLine = (id) => { + const newLine = { id: Date.now(), value: "" }; + const index = lines.findIndex((line) => line.id === id); + const updatedLines = [ + ...lines.slice(0, index + 1), + newLine, + ...lines.slice(index + 1) + ]; + setLines(updatedLines); + }; + + const handleRemoveLine = (id) => { + setLines(lines.filter((line) => line.id !== id)); + }; + + return ( +
+ {lines.map((line) => ( +
+
+ {renderContent(line, (value) => { + const updatedLines = lines.map((l) => + l.id === line.id ? { ...l, value } : l + ); + setLines(updatedLines); + })} +
+