diff --git a/src/fireedge/src/client/components/FormStepper/Stepper.js b/src/fireedge/src/client/components/FormStepper/Stepper.js
index da8a1c0c61..11f63127ef 100644
--- a/src/fireedge/src/client/components/FormStepper/Stepper.js
+++ b/src/fireedge/src/client/components/FormStepper/Stepper.js
@@ -15,18 +15,28 @@
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import PropTypes from 'prop-types'
-
-import { Box, Button, Typography } from '@mui/material'
+import {
+ Box,
+ Button,
+ Typography,
+ List,
+ ListItem,
+ ListItemText,
+ Paper,
+ IconButton,
+ Popover,
+} from '@mui/material'
import Step from '@mui/material/Step'
+import { NavArrowDown, NavArrowUp } from 'iconoir-react'
import StepButton from '@mui/material/StepButton'
import StepConnector, {
stepConnectorClasses,
} from '@mui/material/StepConnector'
+import { useState } from 'react'
import StepIcon, { stepIconClasses } from '@mui/material/StepIcon'
import StepLabel from '@mui/material/StepLabel'
import Stepper from '@mui/material/Stepper'
import { styled } from '@mui/styles'
-
import { SubmitButton } from 'client/components/FormControl'
import { Translate } from 'client/components/HOC'
import { SCHEMES, T } from 'client/constants'
@@ -66,6 +76,25 @@ const ConnectorStyled = styled(StepConnector)(({ theme }) => ({
},
}))
+const ErrorListContainer = styled(Paper)(({ theme }) => ({
+ maxHeight: 200,
+ overflowY: 'auto',
+ padding: theme.spacing(1),
+ backgroundColor: theme.palette.background.default,
+}))
+
+const ErrorSummaryContainer = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ cursor: 'pointer',
+ padding: theme.spacing(1),
+ borderRadius: theme.shape.borderRadius,
+ '&:hover': {
+ backgroundColor: theme.palette.action.hover,
+ },
+}))
+
const StepIconStyled = styled(StepIcon)(({ theme }) => ({
color: theme.palette.text.hint,
display: 'block',
@@ -97,57 +126,117 @@ const CustomStepper = ({
handleBack,
errors,
isSubmitting,
-}) => (
- <>
- }
- >
- {steps?.map(({ id, label }, stepIdx) => (
- stepIdx}>
- handleStep(stepIdx)}
- disabled={activeStep + 1 < stepIdx}
- optional={
- errors[id] && (
-
-
-
- )
- }
- data-cy={`step-${id}`}
- >
-
-
-
-
-
- ))}
-
-
-
- }
- />
-
- >
-)
+ {steps?.map(({ id, label }, stepIdx) => {
+ const errorData = errors[id]
+ const hasError = Boolean(errorData?.message)
+ const individualMessages = errorData?.individualErrorMessages || []
+
+ return (
+ stepIdx}>
+ handleStep(stepIdx)}
+ disabled={activeStep + 1 < stepIdx}
+ data-cy={`step-${id}`}
+ >
+
+
+
+
+ {hasError && (
+ <>
+ handleClick(event, stepIdx)}
+ >
+
+ {`${errorData.message[0].replace(
+ '%s',
+ errorData.message[1]
+ )}`}
+
+
+ {currentStep === stepIdx && open ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ {individualMessages.flat().map((msg, index) => (
+
+
+
+ ))}
+
+
+
+ >
+ )}
+
+ )
+ })}
+
+
+
+
+ }
+ />
+
+ >
+ )
+}
CustomStepper.propTypes = {
steps: PropTypes.arrayOf(
diff --git a/src/fireedge/src/client/components/FormStepper/index.js b/src/fireedge/src/client/components/FormStepper/index.js
index 0df91a2257..672a44bb50 100644
--- a/src/fireedge/src/client/components/FormStepper/index.js
+++ b/src/fireedge/src/client/components/FormStepper/index.js
@@ -208,14 +208,28 @@ const FormStepper = ({
const setErrors = ({ inner = [], message = { word: 'Error' } } = {}) => {
const errorsByPath = groupBy(inner, 'path') ?? {}
- const totalErrors = Object.keys(errorsByPath).length
+ const totalErrors = Object.values(errorsByPath).reduce((count, value) => {
+ if (Array.isArray(value)) {
+ const filteredValue = value?.filter(Boolean) || []
+
+ return count + filteredValue?.length || 0
+ }
+
+ return count
+ }, 0)
const translationError =
totalErrors > 0 ? [T.ErrorsOcurred, totalErrors] : Object.values(message)
- setError(stepId, { type: 'manual', message: translationError })
+ const individualErrorMessages = inner.map((error) => error?.message ?? '')
- inner?.forEach(({ path, type, errors: innerMessage }) => {
+ setError(stepId, {
+ type: 'manual',
+ message: translationError,
+ individualErrorMessages,
+ })
+
+ inner?.forEach(({ path, type, errors: innerMessage }, index) => {
setError(`${stepId}.${path}`, { type, message: innerMessage })
})
}
diff --git a/src/fireedge/src/client/components/Forms/ServiceTemplate/CreateForm/Steps/Roles/rolesPanel.js b/src/fireedge/src/client/components/Forms/ServiceTemplate/CreateForm/Steps/Roles/rolesPanel.js
index cde67fdfa6..1ebeddc557 100644
--- a/src/fireedge/src/client/components/Forms/ServiceTemplate/CreateForm/Steps/Roles/rolesPanel.js
+++ b/src/fireedge/src/client/components/Forms/ServiceTemplate/CreateForm/Steps/Roles/rolesPanel.js
@@ -40,9 +40,9 @@ const RoleVmVmPanel = ({ roles, onChange, selectedRoleIndex }) => {
onChange(updatedRole)
}
- const handleTextFieldChange = (event) => {
+ const handleTextFieldChange = (event, number = false) => {
const { name, value } = event.target
- handleInputChange(name, parseInt(value, 10))
+ handleInputChange(name, number ? parseInt(value, 10) : value)
}
const handleAutocompleteChange = (event, value) => {
@@ -79,7 +79,7 @@ const RoleVmVmPanel = ({ roles, onChange, selectedRoleIndex }) => {
label={Tr(T.NumberOfVms)}
name="CARDINALITY"
value={selectedRole?.CARDINALITY || 0}
- onChange={handleTextFieldChange}
+ onChange={(event) => handleTextFieldChange(event, true)}
disabled={isDisabled}
InputProps={{
inputProps: {
diff --git a/src/fireedge/src/client/utils/translation.js b/src/fireedge/src/client/utils/translation.js
index 77900928b1..896612c219 100644
--- a/src/fireedge/src/client/utils/translation.js
+++ b/src/fireedge/src/client/utils/translation.js
@@ -99,9 +99,9 @@ const buildTranslationLocale = () => {
setLocale({
mixed: {
- default: () => T['validation.mixed.default'],
- required: () => T['validation.mixed.required'],
- defined: () => T['validation.mixed.defined'],
+ default: ({ path }) => `${path} ${T['validation.mixed.default']}`,
+ required: ({ path }) => `${path} ${T['validation.mixed.required']}`,
+ defined: ({ path }) => `${path} ${T['validation.mixed.defined']}`,
oneOf: ({ values }) => ({ word: T['validation.mixed.oneOf'], values }),
notOneOf: ({ values }) => ({
word: T['validation.mixed.notOneOf'],