diff --git a/src/actions/form-template-actions.js b/src/actions/form-template-actions.js
index 08ad83393..9122f1430 100644
--- a/src/actions/form-template-actions.js
+++ b/src/actions/form-template-actions.js
@@ -315,7 +315,7 @@ export const deleteFormTemplateMetaFieldTypeValue = (
valueId
) => {
const settings = {
- url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${templateId}/meta-field-types/${metaFieldId}/values`,
+ url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${templateId}/meta-field-types/${metaFieldId}/values/`,
deletedActionName: FORM_TEMPLATE_META_FIELD_VALUE_DELETED
};
return deleteMetaFieldTypeValue(metaFieldId, valueId, settings);
diff --git a/src/actions/form-template-item-actions.js b/src/actions/form-template-item-actions.js
index a7578fd2b..2e2900a12 100644
--- a/src/actions/form-template-item-actions.js
+++ b/src/actions/form-template-item-actions.js
@@ -375,7 +375,7 @@ export const deleteItemMetaFieldTypeValue = (
valueId
) => {
const settings = {
- url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${formTemplateId}/items/${formTemplateItemId}/meta-field-types/${metaFieldId}/values`,
+ url: `${window.INVENTORY_API_BASE_URL}/api/v1/form-templates/${formTemplateId}/items/${formTemplateItemId}/meta-field-types/${metaFieldId}/values/`,
deletedActionName: FORM_TEMPLATE_ITEM_META_FIELD_VALUE_DELETED
};
return deleteMetaFieldTypeValue(metaFieldId, valueId, settings);
diff --git a/src/actions/inventory-item-actions.js b/src/actions/inventory-item-actions.js
index d0d2422a1..dd066bb6d 100644
--- a/src/actions/inventory-item-actions.js
+++ b/src/actions/inventory-item-actions.js
@@ -352,7 +352,7 @@ export const deleteInventoryItemMetaFieldTypeValue = (
valueId
) => {
const settings = {
- url: `${window.INVENTORY_API_BASE_URL}/api/v1/inventory-items/${inventoryItemId}/meta-field-types/${metaFieldId}/values`,
+ url: `${window.INVENTORY_API_BASE_URL}/api/v1/inventory-items/${inventoryItemId}/meta-field-types/${metaFieldId}/values/`,
deletedActionName: INVENTORY_ITEM_META_FIELD_VALUE_DELETED
};
return deleteMetaFieldTypeValue(metaFieldId, valueId, settings);
diff --git a/src/actions/inventory-shared-actions.js b/src/actions/inventory-shared-actions.js
index 34bdc4af0..64de746a7 100644
--- a/src/actions/inventory-shared-actions.js
+++ b/src/actions/inventory-shared-actions.js
@@ -207,7 +207,7 @@ export const deleteMetaFieldTypeValue =
metaFieldId,
valueId
}),
- `${settings.url}${valueId}/`,
+ `${settings.url}${valueId}`,
null,
authErrorHandler
)(params)(dispatch).then(() => {
diff --git a/src/components/mui/__tests__/additional-input-list.test.js b/src/components/mui/__tests__/additional-input-list.test.js
new file mode 100644
index 000000000..923a7bd37
--- /dev/null
+++ b/src/components/mui/__tests__/additional-input-list.test.js
@@ -0,0 +1,348 @@
+import React from "react";
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { Formik, Form, useFormikContext } from "formik";
+import "@testing-library/jest-dom";
+import AdditionalInputList from "../formik-inputs/additional-input/additional-input-list";
+import showConfirmDialog from "../showConfirmDialog";
+
+// Mocks
+jest.mock("../showConfirmDialog", () => jest.fn());
+
+jest.mock(
+ "../formik-inputs/additional-input/additional-input",
+ () =>
+ function MockAdditionalInput({
+ item,
+ itemIdx,
+ onAdd,
+ onDelete,
+ isAddDisabled
+ }) {
+ return (
+
+ {item.name}
+ {item.type}
+
+
+
+ );
+ }
+);
+
+// Helper function to render the component with Formik
+const renderWithFormik = (props, initialValues = { meta_fields: [] }) =>
+ render(
+
+
+
+ );
+
+describe("AdditionalInputList", () => {
+ const defaultMetaField = {
+ id: 1,
+ name: "Field 1",
+ type: "Text",
+ is_required: false,
+ minimum_quantity: 0,
+ maximum_quantity: 0,
+ values: []
+ };
+
+ const defaultProps = {
+ name: "meta_fields",
+ onDelete: jest.fn(),
+ onDeleteValue: jest.fn(),
+ entityId: 1
+ };
+
+ const defaultInitialValues = {
+ meta_fields: [defaultMetaField]
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("Rendering", () => {
+ test("renders an AdditionalInput for each meta field", () => {
+ const multipleFields = {
+ meta_fields: [
+ { ...defaultMetaField, id: 1, name: "Field 1" },
+ { ...defaultMetaField, id: 2, name: "Field 2" },
+ { ...defaultMetaField, id: 3, name: "Field 3" }
+ ]
+ };
+
+ renderWithFormik(defaultProps, multipleFields);
+
+ expect(screen.getByTestId("additional-input-0")).toBeInTheDocument();
+ expect(screen.getByTestId("additional-input-1")).toBeInTheDocument();
+ expect(screen.getByTestId("additional-input-2")).toBeInTheDocument();
+ expect(screen.getByTestId("item-name-0")).toHaveTextContent("Field 1");
+ expect(screen.getByTestId("item-name-1")).toHaveTextContent("Field 2");
+ expect(screen.getByTestId("item-name-2")).toHaveTextContent("Field 3");
+ });
+
+ test("renders nothing when meta_fields is empty", () => {
+ renderWithFormik(defaultProps, { meta_fields: [] });
+
+ expect(
+ screen.queryByTestId("additional-input-0")
+ ).not.toBeInTheDocument();
+ });
+ });
+
+ describe("handleAddItem", () => {
+ test("adds a new empty meta field when onAdd is called", async () => {
+ const TestWrapper = () => {
+ const { values } = useFormikContext();
+ return (
+ <>
+
+ {values.meta_fields.length}
+ >
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId("field-count")).toHaveTextContent("1");
+
+ const addButton = screen.getByTestId("add-btn-0");
+ await userEvent.click(addButton);
+
+ await waitFor(() => {
+ expect(screen.getByTestId("field-count")).toHaveTextContent("2");
+ });
+ });
+ });
+
+ describe("handleRemove", () => {
+ test("shows confirmation dialog when delete is clicked", async () => {
+ showConfirmDialog.mockResolvedValue(false);
+
+ renderWithFormik(defaultProps, defaultInitialValues);
+
+ const deleteButton = screen.getByTestId("delete-btn-0");
+ await userEvent.click(deleteButton);
+
+ expect(showConfirmDialog).toHaveBeenCalledWith(
+ expect.objectContaining({
+ title: expect.any(String),
+ type: "warning"
+ })
+ );
+ });
+
+ test("calls API and removes from UI when item has id", async () => {
+ const mockOnDelete = jest.fn().mockResolvedValue();
+ showConfirmDialog.mockResolvedValue(true);
+
+ const TestWrapper = ({ onDelete }) => {
+ const { values } = useFormikContext();
+ return (
+ <>
+
+ {values.meta_fields.length}
+ >
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId("field-count")).toHaveTextContent("1");
+
+ const deleteButton = screen.getByTestId("delete-btn-0");
+ await userEvent.click(deleteButton);
+
+ await waitFor(() => {
+ expect(mockOnDelete).toHaveBeenCalledWith(1, 1); // entityId, item.id
+ });
+ });
+
+ test("removes from UI without API call when item has no id", async () => {
+ const mockOnDelete = jest.fn();
+ showConfirmDialog.mockResolvedValue(true);
+
+ const fieldWithoutId = {
+ name: "New Field",
+ type: "Text",
+ is_required: false,
+ values: []
+ };
+
+ const TestWrapper = ({ onDelete }) => {
+ const { values } = useFormikContext();
+ return (
+ <>
+
+ {values.meta_fields.length}
+ >
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ expect(screen.getByTestId("field-count")).toHaveTextContent("2");
+
+ // remove the second field (without id)
+ const deleteButton = screen.getByTestId("delete-btn-1");
+ await userEvent.click(deleteButton);
+
+ await waitFor(() => {
+ expect(mockOnDelete).not.toHaveBeenCalled();
+ expect(screen.getByTestId("field-count")).toHaveTextContent("1");
+ });
+ });
+
+ test("resets to default meta field when last item is deleted", async () => {
+ const mockOnDelete = jest.fn().mockResolvedValue();
+ showConfirmDialog.mockResolvedValue(true);
+
+ const TestWrapper = ({ onDelete }) => {
+ const { values } = useFormikContext();
+ return (
+ <>
+
+ {values.meta_fields.length}
+
+ {values.meta_fields[0]?.name || "empty"}
+
+ >
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ const deleteButton = screen.getByTestId("delete-btn-0");
+ await userEvent.click(deleteButton);
+
+ await waitFor(() => {
+ // should still have 1 field (the default empty one)
+ expect(screen.getByTestId("field-count")).toHaveTextContent("1");
+ // field should be reset to empty
+ expect(screen.getByTestId("first-field-name")).toHaveTextContent(
+ "empty"
+ );
+ });
+ });
+ });
+
+ describe("areMetafieldsIncomplete", () => {
+ test("disables add button when name is empty", () => {
+ const fieldWithEmptyName = { ...defaultMetaField, name: "" };
+
+ renderWithFormik(defaultProps, { meta_fields: [fieldWithEmptyName] });
+
+ expect(screen.getByTestId("add-btn-0")).toBeDisabled();
+ });
+
+ test("disables add button when type is empty", () => {
+ const fieldWithEmptyType = {
+ ...defaultMetaField,
+ name: "Field",
+ type: ""
+ };
+
+ renderWithFormik(defaultProps, { meta_fields: [fieldWithEmptyType] });
+
+ expect(screen.getByTestId("add-btn-0")).toBeDisabled();
+ });
+
+ test("disables add button when type with options has no values", () => {
+ const fieldWithNoValues = {
+ ...defaultMetaField,
+ name: "Field",
+ type: "CheckBoxList",
+ values: []
+ };
+
+ renderWithFormik(defaultProps, { meta_fields: [fieldWithNoValues] });
+
+ expect(screen.getByTestId("add-btn-0")).toBeDisabled();
+ });
+
+ test("disables add button when values are incomplete", () => {
+ const fieldWithIncompleteValues = {
+ ...defaultMetaField,
+ name: "Field",
+ type: "ComboBox",
+ values: [{ name: "Option", value: "" }] // value is empty
+ };
+
+ renderWithFormik(defaultProps, {
+ meta_fields: [fieldWithIncompleteValues]
+ });
+
+ expect(screen.getByTestId("add-btn-0")).toBeDisabled();
+ });
+
+ test("enables add button when all fields are complete", () => {
+ const completeField = {
+ ...defaultMetaField,
+ name: "Field",
+ type: "Text"
+ };
+
+ renderWithFormik(defaultProps, { meta_fields: [completeField] });
+
+ expect(screen.getByTestId("add-btn-0")).not.toBeDisabled();
+ });
+
+ test("enables add button when type with options has complete values", () => {
+ const completeFieldWithValues = {
+ ...defaultMetaField,
+ name: "Field",
+ type: "CheckBoxList",
+ values: [{ name: "Option 1", value: "opt1" }]
+ };
+
+ renderWithFormik(defaultProps, {
+ meta_fields: [completeFieldWithValues]
+ });
+
+ expect(screen.getByTestId("add-btn-0")).not.toBeDisabled();
+ });
+ });
+});
diff --git a/src/components/mui/__tests__/additional-input.test.js b/src/components/mui/__tests__/additional-input.test.js
new file mode 100644
index 000000000..de283043d
--- /dev/null
+++ b/src/components/mui/__tests__/additional-input.test.js
@@ -0,0 +1,253 @@
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { Formik, Form } from "formik";
+import "@testing-library/jest-dom";
+import AdditionalInput from "../formik-inputs/additional-input/additional-input";
+
+// Mocks
+jest.mock(
+ "../formik-inputs/additional-input/meta-field-values",
+ () =>
+ function MockMetaFieldValues() {
+ return MetaFieldValues
;
+ }
+);
+
+// Helper function to render the component with Formik
+const renderWithFormik = (props, initialValues = { meta_fields: [] }) =>
+ render(
+
+
+
+ );
+
+describe("AdditionalInput", () => {
+ const defaultItem = {
+ id: 1,
+ name: "Test Field",
+ type: "",
+ is_required: false,
+ minimum_quantity: 0,
+ maximum_quantity: 0,
+ values: []
+ };
+
+ const defaultProps = {
+ item: defaultItem,
+ itemIdx: 0,
+ baseName: "meta_fields",
+ onAdd: jest.fn(),
+ onDelete: jest.fn(),
+ onDeleteValue: jest.fn(),
+ entityId: 1,
+ isAddDisabled: false
+ };
+
+ const defaultInitialMetaFields = {
+ meta_fields: [defaultItem]
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("Rendering", () => {
+ test("renders name, type and is_required fields", () => {
+ renderWithFormik(defaultProps, defaultInitialMetaFields);
+
+ expect(
+ screen.getByPlaceholderText(
+ "additional_inputs.placeholders.meta_field_title"
+ )
+ ).toBeInTheDocument();
+ expect(screen.getByRole("combobox")).toBeInTheDocument();
+ expect(screen.getByRole("checkbox")).toBeInTheDocument();
+ });
+
+ test("renders add and delete buttons", () => {
+ renderWithFormik(defaultProps, defaultInitialMetaFields);
+
+ expect(
+ screen.getByRole("button", { name: /delete/i })
+ ).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /add/i })).toBeInTheDocument();
+ });
+ });
+
+ describe("Conditional rendering based on type", () => {
+ test("shows MetaFieldValues when type is CheckBoxList", () => {
+ const itemWithOptions = { ...defaultItem, type: "CheckBoxList" };
+
+ renderWithFormik(
+ { ...defaultProps, item: itemWithOptions },
+ { meta_fields: [itemWithOptions] }
+ );
+
+ expect(screen.getByTestId("meta-field-values")).toBeInTheDocument();
+ });
+
+ test("shows MetaFieldValues when type is ComboBox", () => {
+ const itemWithOptions = { ...defaultItem, type: "ComboBox" };
+
+ renderWithFormik(
+ { ...defaultProps, item: itemWithOptions },
+ { meta_fields: [itemWithOptions] }
+ );
+
+ expect(screen.getByTestId("meta-field-values")).toBeInTheDocument();
+ });
+
+ test("shows MetaFieldValues when type is RadioButtonList", () => {
+ const itemWithOptions = { ...defaultItem, type: "RadioButtonList" };
+
+ renderWithFormik(
+ { ...defaultProps, item: itemWithOptions },
+ { meta_fields: [itemWithOptions] }
+ );
+
+ expect(screen.getByTestId("meta-field-values")).toBeInTheDocument();
+ });
+
+ test("does not show MetaFieldValues when type is Text", () => {
+ renderWithFormik(defaultProps, defaultInitialMetaFields);
+
+ expect(screen.queryByTestId("meta-field-values")).not.toBeInTheDocument();
+ });
+
+ test("shows quantity fields when type is Quantity", () => {
+ const itemQuantity = { ...defaultItem, type: "Quantity" };
+
+ renderWithFormik(
+ { ...defaultProps, item: itemQuantity },
+ { meta_fields: [itemQuantity] }
+ );
+
+ expect(
+ screen.getByPlaceholderText(
+ "additional_inputs.placeholders.meta_field_minimum_quantity"
+ )
+ ).toBeInTheDocument();
+ expect(
+ screen.getByPlaceholderText(
+ "additional_inputs.placeholders.meta_field_maximum_quantity"
+ )
+ ).toBeInTheDocument();
+ });
+
+ test("does not show quantity fields when type is not Quantity", () => {
+ renderWithFormik(defaultProps, defaultInitialMetaFields);
+
+ expect(
+ screen.queryByPlaceholderText(
+ "additional_inputs.placeholders.meta_field_minimum_quantity"
+ )
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByPlaceholderText(
+ "additional_inputs.placeholders.meta_field_maximum_quantity"
+ )
+ ).not.toBeInTheDocument();
+ });
+ });
+
+ describe("Button interactions", () => {
+ test("calls onDelete with item and index when delete button is clicked", async () => {
+ const mockOnDelete = jest.fn();
+
+ renderWithFormik(
+ { ...defaultProps, onDelete: mockOnDelete },
+ defaultInitialMetaFields
+ );
+
+ const deleteButton = screen.getByRole("button", { name: /delete/i });
+ await userEvent.click(deleteButton);
+
+ expect(mockOnDelete).toHaveBeenCalledWith(defaultItem, 0);
+ });
+
+ test("calls onAdd when add button is clicked", async () => {
+ const mockOnAdd = jest.fn();
+
+ renderWithFormik(
+ { ...defaultProps, onAdd: mockOnAdd },
+ defaultInitialMetaFields
+ );
+
+ const addButton = screen.getByRole("button", { name: /add/i });
+ await userEvent.click(addButton);
+
+ expect(mockOnAdd).toHaveBeenCalled();
+ });
+
+ test("disables add button when isAddDisabled is true", () => {
+ renderWithFormik(
+ { ...defaultProps, isAddDisabled: true },
+ defaultInitialMetaFields
+ );
+
+ const addButton = screen.getByRole("button", { name: /add/i });
+ expect(addButton).toBeDisabled();
+ });
+
+ test("enables add button when isAddDisabled is false", () => {
+ renderWithFormik(
+ { ...defaultProps, isAddDisabled: false },
+ defaultInitialMetaFields
+ );
+
+ const addButton = screen.getByRole("button", { name: /add/i });
+ expect(addButton).not.toBeDisabled();
+ });
+ });
+
+ describe("Error display", () => {
+ test("shows values error when touched and has error", () => {
+ const itemWithOptions = { ...defaultItem, type: "CheckBoxList" };
+
+ render(
+
+
+
+ );
+
+ expect(
+ screen.getByText("At least one option required")
+ ).toBeInTheDocument();
+ });
+
+ test("does not show values error when not touched", () => {
+ const itemWithOptions = { ...defaultItem, type: "CheckBoxList" };
+
+ render(
+
+
+
+ );
+
+ expect(
+ screen.queryByText("At least one option required")
+ ).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/components/mui/__tests__/meta-field-values.test.js b/src/components/mui/__tests__/meta-field-values.test.js
new file mode 100644
index 000000000..36b8950ed
--- /dev/null
+++ b/src/components/mui/__tests__/meta-field-values.test.js
@@ -0,0 +1,319 @@
+import React from "react";
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { Formik, Form, useFormikContext } from "formik";
+import "@testing-library/jest-dom";
+import MetaFieldValues from "../formik-inputs/additional-input/meta-field-values";
+import showConfirmDialog from "../showConfirmDialog";
+
+// Mocks
+jest.mock("../showConfirmDialog", () => jest.fn());
+
+jest.mock(
+ "../dnd-list",
+ () =>
+ function MockDragAndDropList({ items, renderItem }) {
+ return (
+
+ {items.map((item, index) => (
+
+ {renderItem(item, index, {}, { isDragging: false })}
+
+ ))}
+
+ );
+ }
+);
+
+// Helper function to render the component with Formik
+const renderWithFormik = (props, initialValues = { meta_fields: [] }) =>
+ render(
+
+
+
+ );
+
+describe("MetaFieldValues", () => {
+ const defaultField = {
+ id: 1,
+ name: "Test Field",
+ type: "CheckBoxList",
+ values: [
+ { id: 101, name: "Option 1", value: "opt1", is_default: false, order: 1 },
+ { id: 102, name: "Option 2", value: "opt2", is_default: true, order: 2 }
+ ]
+ };
+
+ const defaultProps = {
+ field: defaultField,
+ fieldIndex: 0,
+ baseName: "meta_fields",
+ onMetaFieldTypeValueDeleted: jest.fn(),
+ entityId: 1
+ };
+
+ const defaultInitialValues = {
+ meta_fields: [defaultField]
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("Rendering", () => {
+ test("renders all field values sorted by order prop", () => {
+ const fieldWithUnorderedValues = {
+ ...defaultField,
+ values: [
+ { id: 103, name: "Option 3", value: "opt3", order: 3 },
+ { id: 101, name: "Option 1", value: "opt1", order: 1 },
+ { id: 102, name: "Option 2", value: "opt2", order: 2 }
+ ]
+ };
+
+ renderWithFormik(
+ { ...defaultProps, field: fieldWithUnorderedValues },
+ { meta_fields: [fieldWithUnorderedValues] }
+ );
+
+ // verify all values are rendered
+ const items = screen.getAllByPlaceholderText(
+ "edit_inventory_item.placeholders.meta_field_name"
+ );
+ expect(items).toHaveLength(3);
+
+ // verify the values are rendered using the order prop
+ expect(items[0]).toHaveValue("Option 1");
+ expect(items[1]).toHaveValue("Option 2");
+ expect(items[2]).toHaveValue("Option 3");
+ });
+ });
+
+ describe("handleAddValue", () => {
+ test("adds a new empty value when add button is clicked", async () => {
+ // Componente wrapper que sincroniza field con Formik
+ const TestWrapper = () => {
+ const { values } = useFormikContext();
+ const field = values.meta_fields[0];
+ return (
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ // Verificar cantidad inicial
+ const initialInputs = screen.getAllByPlaceholderText(
+ "edit_inventory_item.placeholders.meta_field_name"
+ );
+ expect(initialInputs).toHaveLength(2);
+
+ // Click en agregar
+ const addButton = screen.getByRole("button", { name: /add/i });
+ await userEvent.click(addButton);
+
+ // Esperar actualización
+ await waitFor(() => {
+ const updatedInputs = screen.getAllByPlaceholderText(
+ "edit_inventory_item.placeholders.meta_field_name"
+ );
+ expect(updatedInputs).toHaveLength(3);
+ });
+ });
+ });
+
+ describe("isMetafieldValueIncomplete", () => {
+ test("disables add button when a value name is empty", () => {
+ const fieldWithIncomplete = {
+ ...defaultField,
+ values: [
+ { id: 101, name: "", value: "opt1", is_default: false, order: 1 }
+ ]
+ };
+
+ renderWithFormik(
+ { ...defaultProps, field: fieldWithIncomplete },
+ { meta_fields: [fieldWithIncomplete] }
+ );
+
+ const addButton = screen.getByRole("button", { name: /add/i });
+ expect(addButton).toBeDisabled();
+ });
+
+ test("disables add button when a value is empty", () => {
+ const fieldWithIncomplete = {
+ ...defaultField,
+ values: [
+ { id: 101, name: "Option", value: "", is_default: false, order: 1 }
+ ]
+ };
+
+ renderWithFormik(
+ { ...defaultProps, field: fieldWithIncomplete },
+ { meta_fields: [fieldWithIncomplete] }
+ );
+
+ const addButton = screen.getByRole("button", { name: /add/i });
+ expect(addButton).toBeDisabled();
+ });
+
+ test("enables add button when all values are complete", () => {
+ renderWithFormik(defaultProps, defaultInitialValues);
+
+ const addButton = screen.getByRole("button", { name: /add/i });
+ expect(addButton).not.toBeDisabled();
+ });
+
+ test("enables add button when there are no values", () => {
+ const fieldWithNoValues = { ...defaultField, values: [] };
+
+ renderWithFormik(
+ { ...defaultProps, field: fieldWithNoValues },
+ { meta_fields: [fieldWithNoValues] }
+ );
+
+ const addButton = screen.getByRole("button", { name: /add/i });
+ expect(addButton).not.toBeDisabled();
+ });
+ });
+
+ describe("handleDefaultChange", () => {
+ test("only one value can be default at a time", async () => {
+ renderWithFormik(defaultProps, defaultInitialValues);
+
+ const checkboxes = screen.getAllByRole("checkbox");
+
+ // Option 2 is default
+ expect(checkboxes[1]).toBeChecked();
+ expect(checkboxes[0]).not.toBeChecked();
+
+ // click on Option 1
+ await userEvent.click(checkboxes[0]);
+
+ // Option 1 should be checked and Option 2 unchecked
+ expect(checkboxes[0]).toBeChecked();
+ expect(checkboxes[1]).not.toBeChecked();
+ });
+ });
+
+ describe("handleRemoveValue", () => {
+ test("shows confirmation dialog when remove is clicked", async () => {
+ showConfirmDialog.mockResolvedValue(false);
+ renderWithFormik(defaultProps, defaultInitialValues);
+
+ const closeIcons = screen.getAllByTestId("CloseIcon");
+ const closeButton = closeIcons[0].closest("button");
+ await userEvent.click(closeButton);
+
+ expect(showConfirmDialog).toHaveBeenCalledWith(
+ expect.objectContaining({
+ title: expect.any(String),
+ type: "warning"
+ })
+ );
+ });
+
+ test("calls API and removes from UI when value has id", async () => {
+ const mockOnDelete = jest.fn().mockResolvedValue();
+ showConfirmDialog.mockResolvedValue(true);
+
+ const TestWrapper = ({ onDelete }) => {
+ const { values } = useFormikContext();
+ const field = values.meta_fields[0];
+ return (
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ expect(screen.getAllByTestId("CloseIcon")).toHaveLength(2);
+
+ const closeButton = screen
+ .getAllByTestId("CloseIcon")[0]
+ .closest("button");
+ await userEvent.click(closeButton);
+
+ await waitFor(() => {
+ expect(mockOnDelete).toHaveBeenCalledWith(1, 1, 101);
+ expect(screen.getAllByTestId("CloseIcon")).toHaveLength(1);
+ });
+ });
+
+ test("removes from UI without API call when value has no id", async () => {
+ const mockOnDelete = jest.fn();
+ showConfirmDialog.mockResolvedValue(true);
+
+ const fieldWithoutIds = {
+ ...defaultField,
+ values: [
+ { name: "Option 1", value: "opt1", is_default: false, order: 1 },
+ { name: "Option 2", value: "opt2", is_default: false, order: 2 }
+ ]
+ };
+
+ const TestWrapper = ({ onDelete }) => {
+ const { values } = useFormikContext();
+ const field = values.meta_fields[0];
+ return (
+
+ );
+ };
+
+ render(
+
+
+
+ );
+
+ expect(screen.getAllByTestId("CloseIcon")).toHaveLength(2);
+
+ const closeButton = screen
+ .getAllByTestId("CloseIcon")[0]
+ .closest("button");
+ await userEvent.click(closeButton);
+
+ await waitFor(() => {
+ expect(mockOnDelete).not.toHaveBeenCalled();
+ expect(screen.getAllByTestId("CloseIcon")).toHaveLength(1);
+ });
+ });
+ });
+});
diff --git a/src/components/mui/formik-inputs/additional-input/additional-input-list.js b/src/components/mui/formik-inputs/additional-input/additional-input-list.js
new file mode 100644
index 000000000..541732502
--- /dev/null
+++ b/src/components/mui/formik-inputs/additional-input/additional-input-list.js
@@ -0,0 +1,98 @@
+import React from "react";
+import { useFormikContext, getIn } from "formik";
+import T from "i18n-react";
+import AdditionalInput from "./additional-input";
+import showConfirmDialog from "../../showConfirmDialog";
+import { METAFIELD_TYPES_WITH_OPTIONS } from "../../../../utils/constants";
+
+const DEFAULT_META_FIELD = {
+ name: "",
+ type: "",
+ is_required: false,
+ minimum_quantity: 0,
+ maximum_quantity: 0,
+ values: []
+};
+
+const AdditionalInputList = ({ name, onDelete, onDeleteValue, entityId }) => {
+ const { values, setFieldValue, errors } = useFormikContext();
+
+ const metaFields = values[name] || [];
+
+ const handleAddItem = () => {
+ setFieldValue(name, [...metaFields, { ...DEFAULT_META_FIELD }]);
+ };
+
+ const handleRemove = async (item, index) => {
+ const isConfirmed = await showConfirmDialog({
+ title: T.translate("general.are_you_sure"),
+ text: `${T.translate("additional_inputs.delete_meta_field_warning")} ${
+ item.name
+ }`,
+ type: "warning",
+ confirmButtonColor: "#DD6B55",
+ confirmButtonText: T.translate("general.yes_delete")
+ });
+
+ if (!isConfirmed) return;
+
+ const removeFromUI = () => {
+ const newValues = metaFields.filter((_, idx) => idx !== index);
+ if (newValues.length === 0) {
+ newValues.push({ ...DEFAULT_META_FIELD });
+ }
+ setFieldValue(name, newValues);
+ };
+
+ if (item.id && onDelete) {
+ onDelete(entityId, item.id)
+ .then(() => removeFromUI())
+ .catch((err) => console.error("Error deleting field from API", err));
+ } else {
+ removeFromUI();
+ }
+ };
+
+ const areMetafieldsIncomplete = () => {
+ const fieldErrors = getIn(errors, name);
+ if (fieldErrors && Array.isArray(fieldErrors)) {
+ const hasRealErrors = fieldErrors.some(
+ (err) => err && Object.keys(err).length > 0
+ );
+ if (hasRealErrors) return true;
+ }
+
+ return metaFields.some((field) => {
+ if (!field.name?.trim() || !field.type) return true;
+ if (METAFIELD_TYPES_WITH_OPTIONS.includes(field.type)) {
+ if (!field.values || field.values.length === 0) return true;
+ const hasIncompleteValues = field.values.some(
+ (v) => !v.name?.trim() || !v.value?.trim()
+ );
+ if (hasIncompleteValues) return true;
+ }
+
+ return false;
+ });
+ };
+
+ return (
+ <>
+ {metaFields.map((item, itemIdx) => (
+
+ ))}
+ >
+ );
+};
+
+export default AdditionalInputList;
diff --git a/src/components/mui/formik-inputs/additional-input/additional-input.js b/src/components/mui/formik-inputs/additional-input/additional-input.js
new file mode 100644
index 000000000..f1ef1f1fc
--- /dev/null
+++ b/src/components/mui/formik-inputs/additional-input/additional-input.js
@@ -0,0 +1,185 @@
+import React from "react";
+import {
+ Box,
+ Button,
+ Divider,
+ FormHelperText,
+ Grid2,
+ InputLabel,
+ MenuItem
+} from "@mui/material";
+import { useFormikContext, getIn } from "formik";
+import T from "i18n-react/dist/i18n-react";
+import DeleteIcon from "@mui/icons-material/Delete";
+import AddIcon from "@mui/icons-material/Add";
+import MetaFieldValues from "./meta-field-values";
+import MuiFormikTextField from "../mui-formik-textfield";
+import MuiFormikSelect from "../mui-formik-select";
+import MuiFormikCheckbox from "../mui-formik-checkbox";
+import {
+ METAFIELD_TYPES,
+ METAFIELD_TYPES_WITH_OPTIONS
+} from "../../../../utils/constants";
+
+const AdditionalInput = ({
+ item,
+ itemIdx,
+ baseName,
+ onAdd,
+ onDelete,
+ onDeleteValue,
+ entityId,
+ isAddDisabled
+}) => {
+ const { errors, touched, values } = useFormikContext();
+
+ const buildFieldName = (fieldName) => `${baseName}[${itemIdx}].${fieldName}`;
+ const currentType = getIn(values, buildFieldName("type"));
+
+ const fieldErrors = getIn(errors, `${baseName}[${itemIdx}]`);
+ const fieldTouched = getIn(touched, `${baseName}[${itemIdx}]`);
+
+ const showValuesError =
+ fieldTouched?.values &&
+ fieldErrors?.values &&
+ typeof fieldErrors.values === "string";
+
+ return (
+
+
+
+
+
+
+ {T.translate("additional_inputs.meta_field_title")}
+
+
+
+
+
+ {T.translate("additional_inputs.meta_field_type")}
+
+
+ {METAFIELD_TYPES.map((fieldType) => (
+
+ ))}
+
+
+
+
+
+
+ {METAFIELD_TYPES_WITH_OPTIONS.includes(currentType) && (
+ <>
+
+
+ {showValuesError && (
+
+ {fieldErrors.values}
+
+ )}
+ >
+ )}
+ {currentType === "Quantity" && (
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default AdditionalInput;
diff --git a/src/components/mui/formik-inputs/additional-input/meta-field-values.js b/src/components/mui/formik-inputs/additional-input/meta-field-values.js
new file mode 100644
index 000000000..32da68b4e
--- /dev/null
+++ b/src/components/mui/formik-inputs/additional-input/meta-field-values.js
@@ -0,0 +1,182 @@
+import React from "react";
+import T from "i18n-react/dist/i18n-react";
+import { useFormikContext } from "formik";
+import { Box, Button, Grid2, Divider, IconButton } from "@mui/material";
+import CloseIcon from "@mui/icons-material/Close";
+import AddIcon from "@mui/icons-material/Add";
+import DragAndDropList from "../../dnd-list";
+import showConfirmDialog from "../../showConfirmDialog";
+import MuiFormikTextField from "../mui-formik-textfield";
+import MuiFormikCheckbox from "../mui-formik-checkbox";
+
+const MetaFieldValues = ({
+ field,
+ fieldIndex,
+ baseName = "meta_fields",
+ onMetaFieldTypeValueDeleted,
+ entityId
+}) => {
+ const { values, setFieldValue } = useFormikContext();
+
+ const metaFields = values[baseName] || [];
+ const sortedValues = [...field.values].sort((a, b) => a.order - b.order);
+
+ const buildValueFieldName = (valueIndex, fieldName) =>
+ `${baseName}[${fieldIndex}].values[${valueIndex}].${fieldName}`;
+
+ const onReorder = (newValues) => {
+ const newMetaFields = [...metaFields];
+ newMetaFields[fieldIndex].values = newValues;
+ setFieldValue(baseName, newMetaFields);
+ };
+
+ const handleAddValue = () => {
+ const newFields = [...metaFields];
+ newFields[fieldIndex].values.push({
+ value: "",
+ name: "",
+ is_default: false
+ });
+ setFieldValue(baseName, newFields);
+ };
+
+ const handleDefaultChange = (valueIndex, checked) => {
+ const newFields = [...metaFields];
+ if (checked) {
+ newFields[fieldIndex].values.forEach((v) => {
+ v.is_default = false;
+ });
+ }
+ newFields[fieldIndex].values[valueIndex].is_default = checked;
+ setFieldValue(baseName, newFields);
+ };
+
+ const isMetafieldValueIncomplete = () => {
+ if (field.values.length > 0) {
+ return field.values.some((v) => !v.name?.trim() || !v.value?.trim());
+ }
+ return false;
+ };
+
+ const handleRemoveValue = async (metaFieldValue, valueIndex) => {
+ const isConfirmed = await showConfirmDialog({
+ title: T.translate("general.are_you_sure"),
+ text: `${T.translate("meta_field_values_list.delete_value_warning")} ${
+ metaFieldValue.name
+ }`,
+ type: "warning",
+ confirmButtonColor: "#DD6B55",
+ confirmButtonText: T.translate("general.yes_delete")
+ });
+
+ if (!isConfirmed) return;
+
+ const removeValueFromFields = () => {
+ const newFields = [...metaFields];
+ newFields[fieldIndex].values = newFields[fieldIndex].values.filter(
+ (_, index) => index !== valueIndex
+ );
+ setFieldValue(baseName, newFields);
+ };
+
+ if (field.id && metaFieldValue.id && onMetaFieldTypeValueDeleted) {
+ onMetaFieldTypeValueDeleted(entityId, field.id, metaFieldValue.id).then(
+ () => removeValueFromFields()
+ );
+ } else {
+ removeValueFromFields();
+ }
+ };
+
+ const renderMetaFieldValue = (val, sortedIndex, provided, snapshot) => {
+ const originalIndex = field.values.findIndex(
+ (v) => (v.id && v.id === val.id) || v === val
+ );
+ const valueIndex = originalIndex !== -1 ? originalIndex : sortedIndex;
+
+ return (
+
+
+
+
+
+
+ handleRemoveValue(val, valueIndex)}
+ aria-label="remove value"
+ >
+
+
+ )
+ }}
+ />
+
+
+
+ handleDefaultChange(valueIndex, e.target.checked)
+ }
+ />
+
+
+
+
+ );
+ };
+
+ return (
+
+
+
+ }
+ disabled={isMetafieldValueIncomplete()}
+ onClick={handleAddValue}
+ variant="text"
+ >
+ {T.translate("edit_inventory_item.meta_field_add_value")}
+
+
+
+ );
+};
+
+export default MetaFieldValues;
diff --git a/src/components/mui/formik-inputs/mui-formik-select.js b/src/components/mui/formik-inputs/mui-formik-select.js
index 226737a18..f8c2aa75e 100644
--- a/src/components/mui/formik-inputs/mui-formik-select.js
+++ b/src/components/mui/formik-inputs/mui-formik-select.js
@@ -10,7 +10,13 @@ import {
import ClearIcon from "@mui/icons-material/Clear";
import { useField } from "formik";
-const MuiFormikSelect = ({ name, children, isClearable, ...rest }) => {
+const MuiFormikSelect = ({
+ name,
+ placeholder,
+ children,
+ isClearable,
+ ...rest
+}) => {
const [field, meta, helpers] = useField(name);
const handleClear = (ev) => {
@@ -25,6 +31,12 @@ const MuiFormikSelect = ({ name, children, isClearable, ...rest }) => {
// eslint-disable-next-line react/jsx-props-no-spreading
{...field}
displayEmpty
+ renderValue={(selected) => {
+ if (!selected || selected === "") {
+ return {placeholder};
+ }
+ return selected;
+ }}
endAdornment={
isClearable && field.value ? (
@@ -49,6 +61,7 @@ const MuiFormikSelect = ({ name, children, isClearable, ...rest }) => {
MuiFormikSelect.propTypes = {
name: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
+ placeholder: PropTypes.string,
isClearable: PropTypes.bool
};
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 3ae951780..11b4004c5 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -2482,7 +2482,13 @@
"meta_field_type": "Field Type",
"meta_field_required": "Required",
"delete_meta_field_warning": "Are you sure you want to delete meta field ",
- "delete_value_warning": "Are you sure you want to delete meta field value "
+ "delete_value_warning": "Are you sure you want to delete meta field value ",
+ "placeholders": {
+ "meta_field_title": "Field Title",
+ "meta_field_type": "Select...",
+ "meta_field_maximum_quantity": "Maximum",
+ "meta_field_minimum_quantity": "Minimum"
+ }
},
"sponsor_forms": {
"forms": "Forms",
diff --git a/src/pages/sponsors/components/additional-input-list.js b/src/pages/sponsors/components/additional-input-list.js
deleted file mode 100644
index 5575e7e60..000000000
--- a/src/pages/sponsors/components/additional-input-list.js
+++ /dev/null
@@ -1,130 +0,0 @@
-import React from "react";
-import _ from "lodash";
-import { useField } from "formik";
-import T from "i18n-react";
-import AdditionalInput from "./additional-input";
-import showConfirmDialog from "../../../components/mui/showConfirmDialog";
-
-const AdditionalInputList = ({ name, onDelete, onDeleteValue }) => {
- // eslint-disable-next-line no-unused-vars
- const [field, meta, helpers] = useField(name);
-
- const handleChange = (itemIdx, fieldName, fieldValue) => {
- const newValues = _.cloneDeep(field.value);
- newValues[itemIdx][fieldName] = fieldValue;
- helpers.setValue(newValues);
- };
-
- const handleRemove = async (item, index) => {
- const isConfirmed = await showConfirmDialog({
- title: T.translate("general.are_you_sure"),
- text: `${T.translate("additional_inputs.delete_meta_field_warning")} ${
- item.name
- }`,
- type: "warning",
- confirmButtonColor: "#DD6B55",
- confirmButtonText: T.translate("general.yes_delete")
- });
-
- if (isConfirmed) {
- const removeFromUI = () => {
- const newValues = field.value.filter((val, idx) => idx !== index);
-
- if (newValues.length === 0)
- newValues.push({
- name: "",
- type: "Text",
- is_required: false,
- values: []
- });
-
- helpers.setValue(newValues);
- };
-
- if (item.id && onDelete) {
- onDelete(item.id).then(() => {
- removeFromUI();
- });
- } else {
- removeFromUI();
- }
- }
- };
-
- const handleRemoveValue = async (item, itemValue, valueIndex, itemIndex) => {
- const isConfirmed = await showConfirmDialog({
- title: T.translate("general.are_you_sure"),
- text: `${T.translate("additional_inputs.delete_value_warning")} ${
- itemValue.name
- }`,
- type: "warning",
- confirmButtonColor: "#DD6B55",
- confirmButtonText: T.translate("general.yes_delete")
- });
-
- if (isConfirmed) {
- const removeFromUI = () => {
- const newValues = _.cloneDeep(field.value);
- newValues[itemIndex].values = newValues[itemIndex].values.filter(
- (val, idx) => idx !== valueIndex
- );
- helpers.setValue(newValues);
- };
-
- if (item.id && itemValue.id && onDeleteValue) {
- onDeleteValue(item.id, itemValue.id).then(() => {
- removeFromUI();
- });
- } else {
- removeFromUI();
- }
- }
- };
-
- const handleAddValue = (index) => {
- const newValues = _.cloneDeep(field.value);
- newValues[index].values.push({ value: "", is_default: false });
- helpers.setValue(newValues);
- };
-
- const handleValueChange = (itemIdx, valueIdx, key, value) => {
- const newValues = _.cloneDeep(field.value);
- newValues[itemIdx].values[valueIdx][key] = value;
- helpers.setValue(newValues);
- };
-
- const handleAddItem = () => {
- helpers.setValue([
- ...field.value,
- { name: "", type: "Text", is_required: false, values: [] }
- ]);
- };
-
- const handleReorderValues = (itemIdx, newItemValues) => {
- const newValues = _.cloneDeep(field.value);
- newValues[itemIdx].values = newItemValues;
- helpers.setValue(newValues);
- };
-
- return (
- <>
- {field.value.map((item, itemIdx) => (
-
- ))}
- >
- );
-};
-
-export default AdditionalInputList;
diff --git a/src/pages/sponsors/components/additional-input.js b/src/pages/sponsors/components/additional-input.js
deleted file mode 100644
index 6386f6795..000000000
--- a/src/pages/sponsors/components/additional-input.js
+++ /dev/null
@@ -1,144 +0,0 @@
-import React from "react";
-import {
- Box,
- Button,
- Checkbox,
- Divider,
- FormControl,
- FormControlLabel,
- Grid2,
- MenuItem,
- Select,
- TextField
-} from "@mui/material";
-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 {
- METAFIELD_TYPES,
- METAFIELD_TYPES_WITH_OPTIONS
-} from "../../../utils/constants";
-
-const AdditionalInput = ({
- item,
- itemIdx,
- onChange,
- onChangeValue,
- onAdd,
- onAddValue,
- onDelete,
- onDeleteValue,
- onReorderValue
-}) => (
-
-
-
-
-
- onChange(itemIdx, "name", ev.target.value)}
- fullWidth
- />
-
-
-
-
-
-
-
- onChange(itemIdx, "is_required", ev.target.checked)
- }
- />
- }
- label={T.translate("additional_inputs.meta_field_required")}
- />
-
-
-
- {METAFIELD_TYPES_WITH_OPTIONS.includes(item.type) && (
- <>
-
-
- >
- )}
-
-
-
-
-
-
-
-
-
-);
-
-export default AdditionalInput;
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 d81959364..53bd5f8db 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
@@ -19,7 +19,7 @@ import {
decimalValidation
} from "../../../../utils/yup";
import MuiFormikTextField from "../../../../components/mui/formik-inputs/mui-formik-textfield";
-import AdditionalInputList from "../../components/additional-input-list";
+import AdditionalInputList from "../../../../components/mui/formik-inputs/additional-input/additional-input-list";
import useScrollToError from "../../../../hooks/useScrollToError";
import MuiFormikUpload from "../../../../components/mui/formik-inputs/mui-formik-upload";
import MuiFormikPriceField from "../../../../components/mui/formik-inputs/mui-formik-pricefield";
diff --git a/src/pages/sponsors/sponsor-forms-list-page/components/form-template/form-template-form.js b/src/pages/sponsors/sponsor-forms-list-page/components/form-template/form-template-form.js
index 22747a7e0..b72e0ef07 100644
--- a/src/pages/sponsors/sponsor-forms-list-page/components/form-template/form-template-form.js
+++ b/src/pages/sponsors/sponsor-forms-list-page/components/form-template/form-template-form.js
@@ -17,7 +17,7 @@ import { addIssAfterDateFieldValidator } from "../../../../../utils/yup";
import DropdownCheckbox from "../../../../../components/mui/dropdown-checkbox";
import MuiFormikTextField from "../../../../../components/mui/formik-inputs/mui-formik-textfield";
import MuiFormikDatepicker from "../../../../../components/mui/formik-inputs/mui-formik-datepicker";
-import AdditionalInputList from "../../../components/additional-input-list";
+import AdditionalInputList from "../../../../../components/mui/formik-inputs/additional-input/additional-input-list";
import useScrollToError from "../../../../../hooks/useScrollToError";
import FormikTextEditor from "../../../../../components/inputs/formik-text-editor";
diff --git a/src/pages/sponsors/sponsor-forms-tab/components/customized-form/customized-form.js b/src/pages/sponsors/sponsor-forms-tab/components/customized-form/customized-form.js
index 87fdbcd90..f91575aa6 100644
--- a/src/pages/sponsors/sponsor-forms-tab/components/customized-form/customized-form.js
+++ b/src/pages/sponsors/sponsor-forms-tab/components/customized-form/customized-form.js
@@ -16,7 +16,7 @@ import { FormikProvider, useFormik } from "formik";
import { addIssAfterDateFieldValidator } from "../../../../../utils/yup";
import MuiFormikTextField from "../../../../../components/mui/formik-inputs/mui-formik-textfield";
import MuiFormikDatepicker from "../../../../../components/mui/formik-inputs/mui-formik-datepicker";
-import AdditionalInputList from "../../../components/additional-input-list";
+import AdditionalInputList from "../../../../../components/mui/formik-inputs/additional-input/additional-input-list";
import useScrollToError from "../../../../../hooks/useScrollToError";
import FormikTextEditor from "../../../../../components/inputs/formik-text-editor";
import { querySponsorAddons } from "../../../../../actions/sponsor-actions";
diff --git a/src/pages/sponsors_inventory/inventory-list-page.js b/src/pages/sponsors_inventory/inventory-list-page.js
index 0a4f666bd..61a99d497 100644
--- a/src/pages/sponsors_inventory/inventory-list-page.js
+++ b/src/pages/sponsors_inventory/inventory-list-page.js
@@ -131,17 +131,18 @@ const InventoryListPage = ({
};
const handleInventorySave = (item) => {
- saveInventoryItem(item).then(() =>
- getInventoryItems(
- term,
- currentPage,
- perPage,
- order,
- orderDir,
- hideArchived
+ saveInventoryItem(item)
+ .then(() =>
+ getInventoryItems(
+ term,
+ currentPage,
+ perPage,
+ order,
+ orderDir,
+ hideArchived
+ )
)
- );
- setOpen(false);
+ .finally(() => setOpen(false));
};
const handleArchiveItem = (item) =>
diff --git a/src/pages/sponsors_inventory/popup/form-template-popup.js b/src/pages/sponsors_inventory/popup/form-template-popup.js
index 88b97044d..8bbf3ecff 100644
--- a/src/pages/sponsors_inventory/popup/form-template-popup.js
+++ b/src/pages/sponsors_inventory/popup/form-template-popup.js
@@ -7,29 +7,20 @@ import {
DialogContent,
DialogTitle,
Button,
- MenuItem,
InputLabel,
Box,
IconButton,
Divider,
Grid2
} from "@mui/material";
-import AddIcon from "@mui/icons-material/Add";
-import DeleteIcon from "@mui/icons-material/Delete";
import CloseIcon from "@mui/icons-material/Close";
-import { useFormik, FormikProvider, FieldArray } from "formik";
+import { useFormik, FormikProvider } from "formik";
import * as yup from "yup";
-import showConfirmDialog from "../../../components/mui/showConfirmDialog";
-import MetaFieldValues from "./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";
-import MuiFormikCheckbox from "../../../components/mui/formik-inputs/mui-formik-checkbox";
+import AdditionalInputList from "../../../components/mui/formik-inputs/additional-input/additional-input-list";
import useScrollToError from "../../../hooks/useScrollToError";
-import {
- METAFIELD_TYPES,
- METAFIELD_TYPES_WITH_OPTIONS
-} from "../../../utils/constants";
+import { METAFIELD_TYPES } from "../../../utils/constants";
const FormTemplateDialog = ({
open,
@@ -99,30 +90,6 @@ const FormTemplateDialog = ({
useScrollToError(formik);
- const handleRemoveFieldType = async (fieldType, index, removeFormik) => {
- const isConfirmed = await showConfirmDialog({
- title: T.translate("general.are_you_sure"),
- text: `${T.translate("edit_form_template.delete_meta_field_warning")} ${
- fieldType.name
- }`,
- type: "warning",
- confirmButtonColor: "#DD6B55",
- confirmButtonText: T.translate("general.yes_delete")
- });
-
- if (!isConfirmed) return;
-
- if (fieldType.id) {
- onMetaFieldTypeDeleted(initialEntity.id, fieldType.id).then(() =>
- removeFormik(index)
- );
- } else {
- removeFormik(index);
- }
- };
-
- const buildFieldName = (base, index, field) => `${base}[${index}].${field}`;
-
const handleClose = () => {
formik.resetForm();
onClose();
@@ -196,210 +163,12 @@ const FormTemplateDialog = ({
-
- {({ push, remove }) => (
- <>
- {formik.values.meta_fields.map((field, fieldIndex) => (
-
-
-
-
-
-
- {T.translate(
- "edit_form_template.meta_field_title"
- )}
-
-
-
-
-
- {T.translate(
- "edit_form_template.meta_field_type"
- )}
-
-
- {METAFIELD_TYPES.map((field_type) => (
-
- ))}
-
-
-
-
-
-
- {METAFIELD_TYPES_WITH_OPTIONS.includes(
- field.type
- ) && (
- <>
-
-
- >
- )}
- {field.type === "Quantity" && (
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
-
-
-
-
-
-
-
-
- ))}
- >
- )}
-
+
diff --git a/src/pages/sponsors_inventory/popup/meta-field-values.js b/src/pages/sponsors_inventory/popup/meta-field-values.js
deleted file mode 100644
index b0e96f91a..000000000
--- a/src/pages/sponsors_inventory/popup/meta-field-values.js
+++ /dev/null
@@ -1,211 +0,0 @@
-import React from "react";
-import T from "i18n-react/dist/i18n-react";
-import { useFormikContext } from "formik";
-import {
- Box,
- Button,
- Checkbox,
- FormControlLabel,
- FormGroup,
- IconButton,
- TextField,
- Divider,
- Grid2
-} from "@mui/material";
-import CloseIcon from "@mui/icons-material/Close";
-import AddIcon from "@mui/icons-material/Add";
-import DragAndDropList from "../../../components/mui/dnd-list";
-import showConfirmDialog from "../../../components/mui/showConfirmDialog";
-
-const MetaFieldValues = ({
- field,
- fieldIndex,
- onMetaFieldTypeValueDeleted,
- initialEntity
-}) => {
- const { values, setFieldValue } = useFormikContext();
-
- const sortedValues = [...field.values].sort((a, b) => a.order - b.order);
-
- const onReorder = (newValues) => {
- const newMetaFields = [...values.meta_fields];
- newMetaFields[fieldIndex].values = newValues;
- setFieldValue("meta_fields", newMetaFields);
- };
-
- const handleFieldValueChange = (fieldIndex, valueIndex, key, value) => {
- const newFields = [...values.meta_fields];
- if (key === "is_default" && value === true) {
- newFields[fieldIndex].values.forEach((v) => {
- v.is_default = false;
- });
- }
- newFields[fieldIndex].values[valueIndex][key] = value;
- setFieldValue("meta_fields", newFields);
- };
-
- const handleAddValue = (index) => {
- const newFields = [...values.meta_fields];
- newFields[index].values.push({ value: "", name: "", is_default: false });
- setFieldValue("meta_fields", newFields);
- };
-
- const isMetafieldValueIncomplete = (index) => {
- const metafield = values.meta_fields[index];
- if (metafield.values.length > 0) {
- return metafield.values.some((f) => f.name === "" || f.value === "");
- }
- return false;
- };
-
- const handleRemoveValue = async (
- metaField,
- metaFieldValue,
- valueIndex,
- fieldIndex
- ) => {
- const isConfirmed = await showConfirmDialog({
- title: T.translate("general.are_you_sure"),
- text: `${T.translate("meta_field_values_list.delete_value_warning")} ${
- metaFieldValue.name
- }`,
- type: "warning",
- confirmButtonColor: "#DD6B55",
- confirmButtonText: T.translate("general.yes_delete")
- });
-
- if (isConfirmed) {
- const removeValueFromFields = () => {
- const newFields = [...values.meta_fields];
- newFields[fieldIndex].values = newFields[fieldIndex].values.filter(
- (_, index) => index !== valueIndex
- );
- setFieldValue("meta_fields", newFields);
- };
-
- if (metaField.id && metaFieldValue.id && onMetaFieldTypeValueDeleted) {
- onMetaFieldTypeValueDeleted(
- initialEntity.id,
- metaField.id,
- metaFieldValue.id
- ).then(() => {
- removeValueFromFields();
- });
- } else {
- removeValueFromFields();
- }
- }
- };
-
- const renderMetaFieldValue = (val, valueIndex, provided, snapshot) => (
- <>
-
-
-
- handleFieldValueChange(
- fieldIndex,
- valueIndex,
- "name",
- e.target.value
- )
- }
- fullWidth
- />
-
-
-
- handleFieldValueChange(
- fieldIndex,
- valueIndex,
- "value",
- e.target.value
- )
- }
- fullWidth
- slotProps={{
- input: {
- endAdornment: (
-
- handleRemoveValue(field, val, valueIndex, fieldIndex)
- }
- >
-
-
- )
- }
- }}
- />
-
-
-
-
- handleFieldValueChange(
- fieldIndex,
- valueIndex,
- "is_default",
- e.target.checked
- )
- }
- />
- }
- label={T.translate("edit_inventory_item.meta_field_is_default")}
- />
-
-
-
-
- >
- );
-
- return (
-
-
-
- }
- disabled={isMetafieldValueIncomplete(fieldIndex)}
- onClick={() => handleAddValue(fieldIndex)}
- >
- {T.translate("edit_inventory_item.meta_field_add_value")}
-
-
-
- );
-};
-
-export default MetaFieldValues;
diff --git a/src/pages/sponsors_inventory/popup/sponsor-inventory-popup.js b/src/pages/sponsors_inventory/popup/sponsor-inventory-popup.js
index c079e005a..bee244952 100644
--- a/src/pages/sponsors_inventory/popup/sponsor-inventory-popup.js
+++ b/src/pages/sponsors_inventory/popup/sponsor-inventory-popup.js
@@ -1,7 +1,7 @@
import React from "react";
import T from "i18n-react/dist/i18n-react";
import PropTypes from "prop-types";
-import { FieldArray, FormikProvider, useFormik } from "formik";
+import { FormikProvider, useFormik } from "formik";
import * as yup from "yup";
import {
Dialog,
@@ -9,7 +9,6 @@ import {
DialogContent,
DialogTitle,
Button,
- MenuItem,
InputLabel,
Box,
IconButton,
@@ -17,8 +16,6 @@ import {
Grid2,
FormHelperText
} from "@mui/material";
-import AddIcon from "@mui/icons-material/Add";
-import DeleteIcon from "@mui/icons-material/Delete";
import CloseIcon from "@mui/icons-material/Close";
import { UploadInputV2 } from "openstack-uicore-foundation/lib/components";
import {
@@ -27,16 +24,14 @@ import {
MAX_INVENTORY_IMAGES_UPLOAD_QTY,
METAFIELD_TYPES
} from "../../../utils/constants";
-import showConfirmDialog from "../../../components/mui/showConfirmDialog";
-import MetaFieldValues from "./meta-field-values";
import MuiFormikTextField from "../../../components/mui/formik-inputs/mui-formik-textfield";
import MuiFormikPriceField from "../../../components/mui/formik-inputs/mui-formik-pricefield.js";
import useScrollToError from "../../../hooks/useScrollToError";
-import MuiFormikSelect from "../../../components/mui/formik-inputs/mui-formik-select";
-import MuiFormikCheckbox from "../../../components/mui/formik-inputs/mui-formik-checkbox";
import FormikTextEditor from "../../../components/inputs/formik-text-editor";
+import AdditionalInputList from "../../../components/mui/formik-inputs/additional-input/additional-input-list";
import { numberValidation, decimalValidation } from "../../../utils/yup";
+
const SponsorItemDialog = ({
open,
onClose,
@@ -152,47 +147,6 @@ const SponsorItemDialog = ({
useScrollToError(formik);
- const handleRemoveFieldType = async (fieldType, index, removeFormik) => {
- const isConfirmed = await showConfirmDialog({
- title: T.translate("general.are_you_sure"),
- text: `${T.translate("edit_inventory_item.delete_meta_field_warning")} ${
- fieldType.name
- }`,
- type: "warning",
- confirmButtonColor: "#DD6B55",
- confirmButtonText: T.translate("general.yes_delete")
- });
-
- if (!isConfirmed) return;
-
- const removeOrResetField = () => {
- if (formik.values.meta_fields.length === 1) {
- formik.setFieldValue("meta_fields", [
- {
- name: "",
- type: "Text",
- is_required: false,
- minimum_quantity: 0,
- maximum_quantity: 0,
- values: []
- }
- ]);
- } else {
- removeFormik(index);
- }
- };
-
- if (fieldType.id) {
- onMetaFieldTypeDeleted(initialEntity.id, fieldType.id)
- .then(() => removeOrResetField())
- .catch((err) => console.log("Error at delete field from API", err));
- } else {
- removeOrResetField();
- }
- };
-
- const buildFieldName = (base, index, field) => `${base}[${index}].${field}`;
-
const handleImageUploadComplete = (response) => {
if (response) {
const image = {
@@ -227,11 +181,6 @@ const SponsorItemDialog = ({
onClose();
};
- const areMetafieldsIncomplete = () => {
- if (formik.errors.meta_fields) return true;
- return formik.values.meta_fields.some((f) => f.name?.trim() === "");
- };
-
return (