From a30a3a4e2e83f1698ac30610316b86d774c6e723 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 22 May 2025 01:02:29 +0200 Subject: [PATCH 1/9] prettier --- .../reactHookForm/agGridTable/BottomRightButtons.tsx | 7 +------ .../agGridTable/csvUploader/CsvUploader.tsx | 11 ++--------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/components/inputs/reactHookForm/agGridTable/BottomRightButtons.tsx b/src/components/inputs/reactHookForm/agGridTable/BottomRightButtons.tsx index cd456d23..b8f5cc8f 100644 --- a/src/components/inputs/reactHookForm/agGridTable/BottomRightButtons.tsx +++ b/src/components/inputs/reactHookForm/agGridTable/BottomRightButtons.tsx @@ -59,12 +59,7 @@ export function BottomRightButtons({ {csvProps && ( setUploaderOpen(true)}> - + diff --git a/src/components/inputs/reactHookForm/agGridTable/csvUploader/CsvUploader.tsx b/src/components/inputs/reactHookForm/agGridTable/csvUploader/CsvUploader.tsx index 7e0e7d77..66247254 100644 --- a/src/components/inputs/reactHookForm/agGridTable/csvUploader/CsvUploader.tsx +++ b/src/components/inputs/reactHookForm/agGridTable/csvUploader/CsvUploader.tsx @@ -201,17 +201,10 @@ export function CsvUploader({ - + {acceptedFile ? acceptedFile.name - : intl.formatMessage({ - id: 'uploadMessage', - })} + : intl.formatMessage({ id: 'uploadMessage' })} )} From 72e5125073227d026914df64f2896c5b117184a6 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 22 May 2025 01:02:10 +0200 Subject: [PATCH 2/9] rework yup-config --- demo/src/app.jsx | 2 + package-lock.json | 9 ++- package.json | 5 +- .../criteriaBased/criteriaBasedUtils.ts | 3 +- .../DescriptionModificationDialog.tsx | 2 +- .../elementSaveDialog/ElementSaveDialog.tsx | 4 +- .../filter/FilterCreationDialog.tsx | 6 +- src/components/filter/HeaderFilterForm.tsx | 4 +- .../expert/ExpertFilterEditionDialog.tsx | 6 +- .../filter/expert/ExpertFilterForm.tsx | 14 ----- .../ExplicitNamingFilterEditionDialog.tsx | 5 +- .../ExplicitNamingFilterForm.tsx | 6 +- .../reactHookForm/numbers/RangeInput.tsx | 3 +- .../limitreductions/columns-definitions.ts | 5 +- .../loadflow/load-flow-parameters-utils.ts | 4 +- .../loadflow/use-load-flow-parameters-form.ts | 5 +- src/hooks/index.ts | 1 + src/hooks/useYupIntl.ts | 60 +++++++++++++++++++ src/translations/en/filterExpertEn.ts | 2 - src/translations/fr/filterExpertFr.ts | 2 - src/utils/index.ts | 1 - src/utils/yupConfig.ts | 22 ------- 22 files changed, 98 insertions(+), 73 deletions(-) create mode 100644 src/hooks/useYupIntl.ts delete mode 100644 src/utils/yupConfig.ts diff --git a/demo/src/app.jsx b/demo/src/app.jsx index f4100148..ed7e9a09 100644 --- a/demo/src/app.jsx +++ b/demo/src/app.jsx @@ -103,6 +103,7 @@ import { networkModificationsFr, logout, equipmentStyles, + useYupIntl, } from '../../src'; const messages = { @@ -307,6 +308,7 @@ function AppContent({ language, onLanguageClick }) { const navigate = useNavigate(); const location = useLocation(); const intl = useIntl(); + useYupIntl(); const [searchDisabled, setSearchDisabled] = useState(false); const [userManager, setUserManager] = useState({ instance: null, diff --git a/package-lock.json b/package-lock.json index fb140083..421a0b3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,8 @@ "react-querybuilder": "^8.2.0", "reconnecting-websocket": "^4.4.0", "type-fest": "^4.34.1", - "uuid": "^11.0.5" + "uuid": "^11.0.5", + "yup-locales": "^1.2.28" }, "devDependencies": { "@babel/helper-builder-react-jsx": "^7.25.9", @@ -17177,6 +17178,12 @@ "type-fest": "^2.19.0" } }, + "node_modules/yup-locales": { + "version": "1.2.28", + "resolved": "https://registry.npmjs.org/yup-locales/-/yup-locales-1.2.28.tgz", + "integrity": "sha512-q2p5XDVThFXTLOOV1DgMbpMOfrxfZTjYSkS1WRWco6YZJOeHrbuM3ISytrLaYa8TiX/YPtE9tuQmMiUyTBZDIw==", + "license": "MIT" + }, "node_modules/yup/node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", diff --git a/package.json b/package.json index 1e4f1e2e..29548d4a 100644 --- a/package.json +++ b/package.json @@ -42,9 +42,9 @@ "@react-querybuilder/material": "^8.2.0", "autosuggest-highlight": "^3.3.4", "clsx": "^2.1.1", - "mui-nested-menu": "^4.0.0", "jwt-decode": "^4.0.0", "localized-countries": "^2.0.0", + "mui-nested-menu": "^4.0.0", "oidc-client": "^1.11.5", "prop-types": "^15.8.1", "react-csv-downloader": "^3.3.0", @@ -53,7 +53,8 @@ "react-querybuilder": "^8.2.0", "reconnecting-websocket": "^4.4.0", "type-fest": "^4.34.1", - "uuid": "^11.0.5" + "uuid": "^11.0.5", + "yup-locales": "^1.2.28" }, "peerDependencies": { "@emotion/react": "^11.14.0", diff --git a/src/components/contingencyList/criteriaBased/criteriaBasedUtils.ts b/src/components/contingencyList/criteriaBased/criteriaBasedUtils.ts index f4456eb8..abfe9e10 100644 --- a/src/components/contingencyList/criteriaBased/criteriaBasedUtils.ts +++ b/src/components/contingencyList/criteriaBased/criteriaBasedUtils.ts @@ -5,9 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { type ObjectSchema } from 'yup'; +import yup, { type ObjectSchema } from 'yup'; import { FieldConstants } from '../../../utils/constants/fieldConstants'; -import yup from '../../../utils/yupConfig'; import { DEFAULT_RANGE_VALUE, getRangeInputSchema, diff --git a/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx b/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx index 28f86bfa..44cf382e 100644 --- a/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx +++ b/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx @@ -9,7 +9,7 @@ import { useCallback } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { Box } from '@mui/material'; -import yup from '../../../utils/yupConfig'; +import yup from 'yup'; import { FieldConstants } from '../../../utils/constants/fieldConstants'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { CustomMuiDialog } from '../customMuiDialog/CustomMuiDialog'; diff --git a/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx b/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx index 060a6b97..fc79e876 100644 --- a/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx +++ b/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx @@ -8,10 +8,10 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { UUID } from 'crypto'; import { useCallback, useEffect, useState } from 'react'; -import { Grid, Box, Button, CircularProgress, Typography } from '@mui/material'; +import { Box, Button, CircularProgress, Grid, Typography } from '@mui/material'; import { SubmitHandler, useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from '../../../utils/yupConfig'; +import yup from 'yup'; import { TreeViewFinderNodeProps } from '../../treeViewFinder'; import { DescriptionField, RadioInput, UniqueNameInput } from '../../inputs'; import { DirectoryItemSelector } from '../../directoryItemSelector'; diff --git a/src/components/filter/FilterCreationDialog.tsx b/src/components/filter/FilterCreationDialog.tsx index 4299b644..8227f38c 100644 --- a/src/components/filter/FilterCreationDialog.tsx +++ b/src/components/filter/FilterCreationDialog.tsx @@ -6,9 +6,10 @@ */ import { useCallback, useMemo } from 'react'; -import { FieldValues, Resolver, useForm } from 'react-hook-form'; +import { type FieldValues, type Resolver, useForm } from 'react-hook-form'; +import yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; -import { UUID } from 'crypto'; +import type { UUID } from 'crypto'; import { saveExpertFilter, saveExplicitNamingFilter } from './utils/filterApi'; import { useSnackMessage } from '../../hooks/useSnackMessage'; import { CustomMuiDialog } from '../dialogs/customMuiDialog/CustomMuiDialog'; @@ -17,7 +18,6 @@ import { getExplicitNamingFilterEmptyFormData, } from './explicitNaming/ExplicitNamingFilterForm'; import { FieldConstants } from '../../utils/constants/fieldConstants'; -import yup from '../../utils/yupConfig'; import { FilterForm } from './FilterForm'; import { expertFilterSchema, getExpertFilterEmptyFormData } from './expert/ExpertFilterForm'; import { FilterType } from './constants/FilterConstants'; diff --git a/src/components/filter/HeaderFilterForm.tsx b/src/components/filter/HeaderFilterForm.tsx index 3aab6046..f8a62386 100644 --- a/src/components/filter/HeaderFilterForm.tsx +++ b/src/components/filter/HeaderFilterForm.tsx @@ -6,10 +6,10 @@ */ import { Grid } from '@mui/material'; -import { UUID } from 'crypto'; +import type { UUID } from 'crypto'; +import yup from 'yup'; import { ElementType, FieldConstants, MAX_CHAR_DESCRIPTION } from '../../utils'; import { DescriptionField, UniqueNameInput } from '../inputs'; -import yup from '../../utils/yupConfig'; export const filterStyles = { textField: { diff --git a/src/components/filter/expert/ExpertFilterEditionDialog.tsx b/src/components/filter/expert/ExpertFilterEditionDialog.tsx index ebc00d51..b7440d82 100644 --- a/src/components/filter/expert/ExpertFilterEditionDialog.tsx +++ b/src/components/filter/expert/ExpertFilterEditionDialog.tsx @@ -5,16 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { yupResolver } from '@hookform/resolvers/yup'; import { useCallback, useEffect, useState } from 'react'; +import yup from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; import { useForm } from 'react-hook-form'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { FetchStatus } from '../../../utils/constants/fetchStatus'; import { FieldConstants } from '../../../utils/constants/fieldConstants'; -import yup from '../../../utils/yupConfig'; import { CustomMuiDialog } from '../../dialogs/customMuiDialog/CustomMuiDialog'; import { FilterType, NO_ITEM_SELECTION_FOR_COPY } from '../constants/FilterConstants'; -import { FilterEditionProps } from '../filter.type'; +import type { FilterEditionProps } from '../filter.type'; import { FilterForm } from '../FilterForm'; import { saveExpertFilter } from '../utils/filterApi'; import { expertFilterSchema } from './ExpertFilterForm'; diff --git a/src/components/filter/expert/ExpertFilterForm.tsx b/src/components/filter/expert/ExpertFilterForm.tsx index 772ce11f..7d741074 100644 --- a/src/components/filter/expert/ExpertFilterForm.tsx +++ b/src/components/filter/expert/ExpertFilterForm.tsx @@ -6,7 +6,6 @@ */ import { useCallback, useMemo } from 'react'; - import type { RuleGroupTypeAny } from 'react-querybuilder'; import { formatQuery } from 'react-querybuilder'; import './stylesExpertFilter.css'; @@ -23,7 +22,6 @@ import { OPERATOR_OPTIONS, RULES, } from './expertFilterConstants'; - import { FieldConstants } from '../../../utils/constants/fieldConstants'; import { InputWithPopupConfirmation } from '../../inputs/reactHookForm/selectInputs/InputWithPopupConfirmation'; import { SelectInput } from '../../inputs/reactHookForm/selectInputs/SelectInput'; @@ -34,18 +32,6 @@ import { FieldType } from '../../../utils/types/fieldType'; import { useFormatLabelWithUnit } from '../../../hooks/useFormatLabelWithUnit'; import { filterStyles } from '../HeaderFilterForm'; -yup.setLocale({ - mixed: { - required: 'YupRequired', - notType: ({ type }) => { - if (type === 'number') { - return 'YupNotTypeNumber'; - } - return 'YupNotTypeDefault'; - }, - }, -}); - function isSupportedEquipmentType(equipmentType: string): boolean { return Object.values(EXPERT_FILTER_EQUIPMENTS) .map((equipments) => equipments.id) diff --git a/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx b/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx index 2983338a..24a29e50 100644 --- a/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx +++ b/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx @@ -8,15 +8,14 @@ import { yupResolver } from '@hookform/resolvers/yup'; import PropTypes from 'prop-types'; import { useCallback, useEffect, useState } from 'react'; -import { SubmitHandler, useForm, UseFormReturn } from 'react-hook-form'; +import yup from 'yup'; +import { type SubmitHandler, useForm, type UseFormReturn } from 'react-hook-form'; import { v4 as uuid4 } from 'uuid'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { FieldConstants } from '../../../utils/constants/fieldConstants'; -import yup from '../../../utils/yupConfig'; import { CustomMuiDialog } from '../../dialogs/customMuiDialog/CustomMuiDialog'; import { saveExplicitNamingFilter } from '../utils/filterApi'; import { explicitNamingFilterSchema } from './ExplicitNamingFilterForm'; - import { FetchStatus } from '../../../utils/constants/fetchStatus'; import { FilterForm } from '../FilterForm'; import { FilterType, NO_ITEM_SELECTION_FOR_COPY } from '../constants/FilterConstants'; diff --git a/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx b/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx index 0f021125..a5d9d502 100644 --- a/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx +++ b/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx @@ -7,12 +7,12 @@ import { useCallback, useEffect, useMemo } from 'react'; import { useIntl } from 'react-intl'; import { useFormContext, useWatch } from 'react-hook-form'; +import yup from 'yup'; import { Box } from '@mui/material'; -import { ValueParserParams } from 'ag-grid-community'; +import type { ValueParserParams } from 'ag-grid-community'; import { v4 as uuid4 } from 'uuid'; -import { UUID } from 'crypto'; +import type { UUID } from 'crypto'; import { FieldConstants } from '../../../utils/constants/fieldConstants'; -import yup from '../../../utils/yupConfig'; import { CustomAgGridTable } from '../../inputs/reactHookForm/agGridTable/CustomAgGridTable'; import { SelectInput } from '../../inputs/reactHookForm/selectInputs/SelectInput'; import { Generator, Load } from '../../../utils/types/equipmentTypes'; diff --git a/src/components/inputs/reactHookForm/numbers/RangeInput.tsx b/src/components/inputs/reactHookForm/numbers/RangeInput.tsx index 13af7956..27fce50f 100644 --- a/src/components/inputs/reactHookForm/numbers/RangeInput.tsx +++ b/src/components/inputs/reactHookForm/numbers/RangeInput.tsx @@ -7,10 +7,9 @@ import { useWatch } from 'react-hook-form'; import { FormattedMessage } from 'react-intl'; import { useMemo } from 'react'; -import { type ObjectSchema } from 'yup'; +import yup, { type ObjectSchema } from 'yup'; import { FormControl, Grid, InputLabel, type Theme } from '@mui/material'; import { FloatInput } from './FloatInput'; -import yup from '../../../../utils/yupConfig'; import { MuiSelectInput } from '../selectInputs/MuiSelectInput'; import { FieldConstants } from '../../../../utils/constants/fieldConstants'; diff --git a/src/components/parameters/common/limitreductions/columns-definitions.ts b/src/components/parameters/common/limitreductions/columns-definitions.ts index d94f904c..8f69032a 100644 --- a/src/components/parameters/common/limitreductions/columns-definitions.ts +++ b/src/components/parameters/common/limitreductions/columns-definitions.ts @@ -5,9 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { NumberSchema } from 'yup'; -import { UUID } from 'crypto'; -import yup from '../../../../utils/yupConfig'; +import yup, { type NumberSchema } from 'yup'; +import type { UUID } from 'crypto'; import { PARAM_SA_FLOW_PROPORTIONAL_THRESHOLD, PARAM_SA_HIGH_VOLTAGE_ABSOLUTE_THRESHOLD, diff --git a/src/components/parameters/loadflow/load-flow-parameters-utils.ts b/src/components/parameters/loadflow/load-flow-parameters-utils.ts index 927d1728..b3ae8a51 100644 --- a/src/components/parameters/loadflow/load-flow-parameters-utils.ts +++ b/src/components/parameters/loadflow/load-flow-parameters-utils.ts @@ -5,7 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { UseFormReturn } from 'react-hook-form'; +import { type UseFormReturn } from 'react-hook-form'; +import yup from 'yup'; import { ILimitReductionsByVoltageLevel, IST_FORM, @@ -36,7 +37,6 @@ import { WRITE_SLACK_BUS, } from './constants'; import { toFormValuesLimitReductions } from '../common/limitreductions/limit-reductions-form-util'; -import yup from '../../../utils/yupConfig'; import { ParameterType, SpecificParameterInfos } from '../../../utils/types/parameters.type'; import { SpecificParametersPerProvider } from '../../../utils/types/loadflow.type'; import { DESCRIPTION_INPUT, NAME } from '../../inputs'; diff --git a/src/components/parameters/loadflow/use-load-flow-parameters-form.ts b/src/components/parameters/loadflow/use-load-flow-parameters-form.ts index e76d7117..54727eda 100644 --- a/src/components/parameters/loadflow/use-load-flow-parameters-form.ts +++ b/src/components/parameters/loadflow/use-load-flow-parameters-form.ts @@ -8,8 +8,8 @@ import { FieldErrors, useForm, UseFormReturn } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { Dispatch, SetStateAction, SyntheticEvent, useCallback, useEffect, useMemo, useState } from 'react'; -import { ObjectSchema } from 'yup'; -import { UUID } from 'crypto'; +import yup, { type ObjectSchema } from 'yup'; +import type { UUID } from 'crypto'; import { getCommonLoadFlowParametersFormSchema, getDefaultSpecificParamsValues, @@ -38,7 +38,6 @@ import { PARAM_PROVIDER_OPENLOADFLOW, SPECIFIC_PARAMETERS, } from './constants'; -import yup from '../../../utils/yupConfig'; import { toFormValuesLimitReductions } from '../common/limitreductions/limit-reductions-form-util'; import { DESCRIPTION_INPUT, NAME } from '../../inputs'; import { updateParameter } from '../../../services'; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 6c51fe92..dceacac4 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -17,3 +17,4 @@ export * from './useSnackMessage'; export * from './useFormatLabelWithUnit'; export * from './useSelectAppearance'; export * from './use-parameters-backend'; +export * from './useYupIntl'; diff --git a/src/hooks/useYupIntl.ts b/src/hooks/useYupIntl.ts new file mode 100644 index 00000000..06dbe033 --- /dev/null +++ b/src/hooks/useYupIntl.ts @@ -0,0 +1,60 @@ +/* + * Copyright © 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { useEffect } from 'react'; +import { IntlShape, useIntl } from 'react-intl'; +import { type LocaleObject, setLocale } from 'yup'; +import { en as yupEn, fr as yupFr } from 'yup-locales'; +import { GsLangUser, LANG_ENGLISH, LANG_FRENCH } from '../utils'; + +function mapLang(lng: GsLangUser): LocaleObject { + switch (lng) { + case LANG_FRENCH: + return { + ...yupFr, + mixed: { + ...yupFr.mixed, + notType: ({ type }) => { + if (type === 'number') { + return "Ce champ n'accepte que des valeurs numériques"; + } + // @ts-expect-error it's a function in lib sources + return yupFr.mixed?.notType(type); + // return '"La valeur du champ n'est pas au bon format'; + }, + }, + }; + case LANG_ENGLISH: + default: + return { + ...yupEn, + mixed: { + ...yupEn.mixed, + notType: ({ type }) => { + if (type === 'number') { + return 'This field only accepts numeric values'; + } + // @ts-expect-error it's a function in lib sources + return yupFr.mixed?.notType(type); + // return 'Field value format is incorrect'; + }, + }, + }; + } +} + +/** + * Hook to automatically {@link setLocale} with {@link useIntl}. + * This permit to have Yup schema validation return translated messages. + * @param alterLocale extension point to further modify default locale + */ +export function useYupIntl(alterLocale: (lo: LocaleObject, intl: IntlShape) => LocaleObject = (lo) => lo) { + const intl = useIntl(); + useEffect(() => { + setLocale(alterLocale(mapLang(intl.locale as GsLangUser), intl)); + }, [alterLocale, intl]); +} diff --git a/src/translations/en/filterExpertEn.ts b/src/translations/en/filterExpertEn.ts index f7465e61..6fbf26fd 100644 --- a/src/translations/en/filterExpertEn.ts +++ b/src/translations/en/filterExpertEn.ts @@ -183,8 +183,6 @@ export const filterExpertEn = { greaterOrEqual: '>=', lessThan: '<', lessOrEqual: '<=', - YupNotTypeNumber: 'This field only accepts numeric values', - YupNotTypeDefault: 'Field value format is incorrect', changeOperatorMessage: 'The operator will be changed and will be applied to all the criteria already created in the group.', lowShortCircuitCurrentLimit: 'Low short-circuit current limit', diff --git a/src/translations/fr/filterExpertFr.ts b/src/translations/fr/filterExpertFr.ts index 9086b821..18d4aa94 100644 --- a/src/translations/fr/filterExpertFr.ts +++ b/src/translations/fr/filterExpertFr.ts @@ -183,8 +183,6 @@ export const filterExpertFr = { greaterOrEqual: '>=', lessThan: '<', lessOrEqual: '<=', - YupNotTypeNumber: "Ce champ n'accepte que des valeurs numériques", - YupNotTypeDefault: "La valeur du champ n'est pas au bon format", changeOperatorMessage: "L'opérateur sera modifié et s'appliquera sur tous les critères déjà créés dans le groupe.", lowShortCircuitCurrentLimit: 'Limite ICC min', highShortCircuitCurrentLimit: 'Limite ICC max', diff --git a/src/utils/index.ts b/src/utils/index.ts index e650d0b0..e450952d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -13,4 +13,3 @@ export * from './mapper'; export * from './constants/notificationsProvider'; export * from './styles'; export * from './types'; -export { default as yupConfig } from './yupConfig'; diff --git a/src/utils/yupConfig.ts b/src/utils/yupConfig.ts deleted file mode 100644 index 66d8ed3a..00000000 --- a/src/utils/yupConfig.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2023, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import * as yup from 'yup'; - -yup.setLocale({ - mixed: { - required: 'YupRequired', - notType: ({ type }) => { - if (type === 'number') { - return 'YupNotTypeNumber'; - } - return 'YupNotTypeDefault'; - }, - }, -}); - -export default yup; From d5752feed16e7f772e24e4aab2bf7dac1925c14c Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 22 May 2025 01:03:18 +0200 Subject: [PATCH 3/9] remove utility function --- .../autocompleteInputs/AutocompleteInput.tsx | 18 +++++++----- .../inputs/reactHookForm/text/TextInput.tsx | 29 ++++++++++--------- .../inputs/reactHookForm/utils/functions.tsx | 16 ++-------- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx b/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx index c31fd197..11e05d43 100644 --- a/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx +++ b/src/components/inputs/reactHookForm/autocompleteInputs/AutocompleteInput.tsx @@ -7,7 +7,7 @@ import { Autocomplete, AutocompleteProps, TextField, TextFieldProps } from '@mui/material'; import { useController } from 'react-hook-form'; -import { genHelperError, identity, isFieldRequired } from '../utils/functions'; +import { identity, isFieldRequired } from '../utils/functions'; import { FieldLabel } from '../utils/FieldLabel'; import { useCustomFormContext } from '../provider/useCustomFormContext'; import { Option } from '../../../../utils/types/types'; @@ -104,14 +104,18 @@ export function AutocompleteInput({ })} inputRef={ref} inputProps={{ ...inputProps, readOnly }} + error={error !== undefined} helperText={ - + error !== undefined ? ( + error.message + ) : ( + + ) } - {...genHelperError(error?.message)} {...formProps} {...rest} /> diff --git a/src/components/inputs/reactHookForm/text/TextInput.tsx b/src/components/inputs/reactHookForm/text/TextInput.tsx index 915e90f9..9211e4ff 100644 --- a/src/components/inputs/reactHookForm/text/TextInput.tsx +++ b/src/components/inputs/reactHookForm/text/TextInput.tsx @@ -5,16 +5,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { ReactElement } from 'react'; -import { IconButton, InputAdornment, TextField, TextFieldProps } from '@mui/material'; +import { type ReactElement } from 'react'; +import { IconButton, InputAdornment, TextField, type TextFieldProps } from '@mui/material'; import { Clear as ClearIcon } from '@mui/icons-material'; import { useController } from 'react-hook-form'; -import { TextFieldWithAdornment, TextFieldWithAdornmentProps } from '../utils/TextFieldWithAdornment'; +import { TextFieldWithAdornment, type TextFieldWithAdornmentProps } from '../utils/TextFieldWithAdornment'; import { FieldLabel } from '../utils/FieldLabel'; -import { genHelperError, identity, isFieldRequired } from '../utils/functions'; +import { identity, isFieldRequired } from '../utils/functions'; import { useCustomFormContext } from '../provider/useCustomFormContext'; - -import { Input } from '../../../../utils/types/types'; +import type { Input } from '../../../../utils/types/types'; import { HelperPreviousValue } from '../utils/HelperPreviousValue'; export interface TextInputProps { @@ -115,15 +114,19 @@ export function TextInput({ adornment && { handleClearValue, })} + error={error !== undefined} helperText={ - + error !== undefined ? ( + error.message + ) : ( + + ) } - {...genHelperError(error?.message)} {...formProps} {...(adornment && { ...finalAdornment })} disabled={disabled} diff --git a/src/components/inputs/reactHookForm/utils/functions.tsx b/src/components/inputs/reactHookForm/utils/functions.tsx index c31a0236..6069c022 100644 --- a/src/components/inputs/reactHookForm/utils/functions.tsx +++ b/src/components/inputs/reactHookForm/utils/functions.tsx @@ -4,22 +4,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { FormattedMessage } from 'react-intl'; -import { getIn, SchemaDescription } from 'yup'; -import { ReactElement } from 'react'; +import { getIn, type SchemaDescription } from 'yup'; +import { type ReactElement } from 'react'; import { Grid } from '@mui/material'; -export function genHelperError(...errors: any[]) { - const inError = errors.find((e) => e); - if (inError) { - return { - error: true, - helperText: , - }; - } - return {}; -} - export function identity(x: any) { return x; } From 9083d1fb1f2387fd7085e3b02f99924d518851d5 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 22 May 2025 11:29:58 +0200 Subject: [PATCH 4/9] yup now output i18n messages --- .../DescriptionModificationDialog.tsx | 26 ++- .../elementSaveDialog/ElementSaveDialog.tsx | 68 +++---- .../filter/FilterCreationDialog.tsx | 37 ++-- src/components/filter/HeaderFilterForm.tsx | 18 +- .../expert/ExpertFilterEditionDialog.tsx | 26 ++- .../ExplicitNamingFilterEditionDialog.tsx | 31 +-- .../ExplicitNamingFilterForm.tsx | 180 +++++++++--------- .../errorManagement/ErrorInput.tsx | 33 +--- .../reactHookForm/text/UniqueNameInput.tsx | 8 +- .../limitreductions/columns-definitions.ts | 82 ++++---- .../loadflow/load-flow-parameters-utils.ts | 17 +- .../loadflow/use-load-flow-parameters-form.ts | 55 ++++-- 12 files changed, 293 insertions(+), 288 deletions(-) diff --git a/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx b/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx index 44cf382e..3e124ca1 100644 --- a/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx +++ b/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx @@ -5,7 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; +import { useIntl } from 'react-intl'; import { SubmitHandler, useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { Box } from '@mui/material'; @@ -24,11 +25,6 @@ export interface DescriptionModificationDialogProps { updateElement: (uuid: string, data: Record) => Promise; } -const schema = yup.object().shape({ - [FieldConstants.DESCRIPTION]: yup.string().max(MAX_CHAR_DESCRIPTION, 'descriptionLimitError'), -}); -type SchemaType = yup.InferType; - export function DescriptionModificationDialog({ elementUuid, description, @@ -37,13 +33,23 @@ export function DescriptionModificationDialog({ updateElement, }: Readonly) { const { snackError } = useSnackMessage(); + const intl = useIntl(); - const emptyFormData = { - [FieldConstants.DESCRIPTION]: description ?? '', - }; + const schema = useMemo( + () => + yup.object().shape({ + [FieldConstants.DESCRIPTION]: yup + .string() + .max(MAX_CHAR_DESCRIPTION, intl.formatMessage({ id: 'descriptionLimitError' })), + }), + [intl] + ); + type SchemaType = yup.InferType; const methods = useForm({ - defaultValues: emptyFormData, + defaultValues: { + [FieldConstants.DESCRIPTION]: description ?? '', + }, resolver: yupResolver(schema), }); diff --git a/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx b/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx index fc79e876..92038b8a 100644 --- a/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx +++ b/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx @@ -7,7 +7,7 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { UUID } from 'crypto'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { Box, Button, CircularProgress, Grid, Typography } from '@mui/material'; import { SubmitHandler, useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; @@ -89,17 +89,6 @@ export type ElementSaveDialogProps = { } ); -const schema = yup - .object() - .shape({ - [FieldConstants.NAME]: yup.string().trim().required(), - [FieldConstants.DESCRIPTION]: yup.string().optional().max(MAX_CHAR_DESCRIPTION, 'descriptionLimitError'), - [FieldConstants.OPERATION_TYPE]: yup.string().oneOf(Object.values(OperationType)).required(), - }) - .required(); - -type SchemaType = yup.InferType; - const emptyFormData: FormData = { [FieldConstants.NAME]: '', [FieldConstants.DESCRIPTION]: '', @@ -132,6 +121,24 @@ export function ElementSaveDialog({ TreeViewFinderNodeProps & { parentFolderId: UUID; fullPath: string } >(); + const schema = useMemo( + () => + yup + .object() + .shape({ + [FieldConstants.NAME]: yup.string().trim().required(), + [FieldConstants.DESCRIPTION]: yup + .string() + .optional() + .max(MAX_CHAR_DESCRIPTION, intl.formatMessage({ id: 'descriptionLimitError' })), + [FieldConstants.OPERATION_TYPE]: yup.string().oneOf(Object.values(OperationType)).required(), + }) + .required(), + [intl] + ); + + type SchemaType = yup.InferType; + // Form handling with conditional defaultValues const formMethods = useForm({ defaultValues: { @@ -170,10 +177,7 @@ export function ElementSaveDialog({ }); const dateTime = getCurrentDateTime(); reset( - { - ...emptyFormData, - [FieldConstants.NAME]: `${formattedMessage}-${dateTime}`, - }, + { ...emptyFormData, [FieldConstants.NAME]: `${formattedMessage}-${dateTime}` }, { keepDefaultValues: true } ); } @@ -184,18 +188,12 @@ export function ElementSaveDialog({ if (open && isCreateMode && studyUuid) { fetchDirectoryElementPath(studyUuid).then((res) => { if (!res || res.length < 2) { - snackError({ - messageTxt: 'unknown study directory', - headerId: 'studyDirectoryFetchingError', - }); + snackError({ messageTxt: 'unknown study directory', headerId: 'studyDirectoryFetchingError' }); return; } const parentFolderIndex = res.length - 2; const { elementUuid, elementName } = res[parentFolderIndex]; - setDestinationFolder({ - id: elementUuid, - name: elementName, - }); + setDestinationFolder({ id: elementUuid, name: elementName }); }); } }, [studyUuid, open, snackError, isCreateMode]); @@ -203,10 +201,7 @@ export function ElementSaveDialog({ // Set initial directory for creation if provided useEffect(() => { if (open && isCreateMode && initDirectory) { - setDestinationFolder({ - id: initDirectory.elementUuid, - name: initDirectory.elementName, - }); + setDestinationFolder({ id: initDirectory.elementUuid, name: initDirectory.elementName }); } }, [initDirectory, open, isCreateMode]); @@ -229,10 +224,7 @@ export function ElementSaveDialog({ const { id, name, description, parents } = items[0]; if (!parents) { - snackError({ - messageTxt: 'errorNoParent', - headerId: 'error', - }); + snackError({ messageTxt: 'errorNoParent', headerId: 'error' }); return; } const fullPath = [...parents.map((parent) => parent.name), name].join('/'); @@ -327,9 +319,7 @@ export function ElementSaveDialog({ ]} formProps={{ sx: { - '& .MuiFormControlLabel-root': { - marginRight: 15, - }, + '& .MuiFormControlLabel-root': { marginRight: 15 }, }, }} /> @@ -357,12 +347,8 @@ export function ElementSaveDialog({ types={isCreateMode ? [ElementType.DIRECTORY] : [type]} onlyLeaves={isCreateMode ? false : undefined} multiSelect={false} - validationButtonText={intl.formatMessage({ - id: 'validate', - })} - title={intl.formatMessage({ - id: isCreateMode ? 'showSelectDirectoryDialog' : selectorTitleId, - })} + validationButtonText={intl.formatMessage({ id: 'validate' })} + title={intl.formatMessage({ id: isCreateMode ? 'showSelectDirectoryDialog' : selectorTitleId })} /> ); diff --git a/src/components/filter/FilterCreationDialog.tsx b/src/components/filter/FilterCreationDialog.tsx index 8227f38c..e94e5105 100644 --- a/src/components/filter/FilterCreationDialog.tsx +++ b/src/components/filter/FilterCreationDialog.tsx @@ -6,6 +6,7 @@ */ import { useCallback, useMemo } from 'react'; +import { useIntl } from 'react-intl'; import { type FieldValues, type Resolver, useForm } from 'react-hook-form'; import yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; @@ -14,8 +15,8 @@ import { saveExpertFilter, saveExplicitNamingFilter } from './utils/filterApi'; import { useSnackMessage } from '../../hooks/useSnackMessage'; import { CustomMuiDialog } from '../dialogs/customMuiDialog/CustomMuiDialog'; import { - explicitNamingFilterSchema, getExplicitNamingFilterEmptyFormData, + getExplicitNamingFilterSchema, } from './explicitNaming/ExplicitNamingFilterForm'; import { FieldConstants } from '../../utils/constants/fieldConstants'; import { FilterForm } from './FilterForm'; @@ -33,18 +34,6 @@ const emptyFormData = { ...getExpertFilterEmptyFormData(), }; -// we use both schemas then we can change the type of filter without losing the filled form fields -const formSchema = yup - .object() - .shape({ - [FieldConstants.NAME]: yup.string().trim().required('nameEmpty'), - [FieldConstants.DESCRIPTION]: yup.string().max(MAX_CHAR_DESCRIPTION, 'descriptionLimitError'), - [FieldConstants.EQUIPMENT_TYPE]: yup.string().required(), - ...explicitNamingFilterSchema, - ...expertFilterSchema, - }) - .required(); - export interface FilterCreationDialogProps { open: boolean; onClose: () => void; @@ -66,6 +55,28 @@ export function FilterCreationDialog({ filterType, }: FilterCreationDialogProps) { const { snackError } = useSnackMessage(); + const intl = useIntl(); + + // we use both schemas then we can change the type of filter without losing the filled form fields + const formSchema = useMemo( + () => + yup + .object() + .shape({ + [FieldConstants.NAME]: yup + .string() + .trim() + .required(intl.formatMessage({ id: 'nameEmpty' })), + [FieldConstants.DESCRIPTION]: yup + .string() + .max(MAX_CHAR_DESCRIPTION, intl.formatMessage({ id: 'descriptionLimitError' })), + [FieldConstants.EQUIPMENT_TYPE]: yup.string().required(), + ...getExplicitNamingFilterSchema(intl), + ...expertFilterSchema, + }) + .required(), + [intl] + ); const formMethods = useForm({ defaultValues: emptyFormData, diff --git a/src/components/filter/HeaderFilterForm.tsx b/src/components/filter/HeaderFilterForm.tsx index f8a62386..fed2470d 100644 --- a/src/components/filter/HeaderFilterForm.tsx +++ b/src/components/filter/HeaderFilterForm.tsx @@ -8,6 +8,7 @@ import { Grid } from '@mui/material'; import type { UUID } from 'crypto'; import yup from 'yup'; +import { type IntlShape } from 'react-intl'; import { ElementType, FieldConstants, MAX_CHAR_DESCRIPTION } from '../../utils'; import { DescriptionField, UniqueNameInput } from '../inputs'; @@ -32,11 +33,18 @@ export interface FilterFormProps { }; } -export const HeaderFilterSchema = { - [FieldConstants.NAME]: yup.string().trim().required('nameEmpty'), - [FieldConstants.EQUIPMENT_TYPE]: yup.string().required(), - [FieldConstants.DESCRIPTION]: yup.string().max(MAX_CHAR_DESCRIPTION, 'descriptionLimitError'), -}; +export function getHeaderFilterSchema(intl: IntlShape) { + return { + [FieldConstants.NAME]: yup + .string() + .trim() + .required(intl.formatMessage({ id: 'nameEmpty' })), + [FieldConstants.EQUIPMENT_TYPE]: yup.string().required(), + [FieldConstants.DESCRIPTION]: yup + .string() + .max(MAX_CHAR_DESCRIPTION, intl.formatMessage({ id: 'descriptionLimitError' })), + }; +} export function HeaderFilterForm({ creation, activeDirectory }: Readonly) { return ( diff --git a/src/components/filter/expert/ExpertFilterEditionDialog.tsx b/src/components/filter/expert/ExpertFilterEditionDialog.tsx index b7440d82..0f74d2dc 100644 --- a/src/components/filter/expert/ExpertFilterEditionDialog.tsx +++ b/src/components/filter/expert/ExpertFilterEditionDialog.tsx @@ -5,7 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useIntl } from 'react-intl'; import yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import { useForm } from 'react-hook-form'; @@ -19,18 +20,10 @@ import { FilterForm } from '../FilterForm'; import { saveExpertFilter } from '../utils/filterApi'; import { expertFilterSchema } from './ExpertFilterForm'; import { importExpertRules } from './expertFilterUtils'; -import { HeaderFilterSchema } from '../HeaderFilterForm'; +import { getHeaderFilterSchema } from '../HeaderFilterForm'; import { catchErrorHandler } from '../../../services'; import { EXPERT_FILTER_QUERY } from './expertFilterConstants'; -const formSchema = yup - .object() - .shape({ - ...HeaderFilterSchema, - ...expertFilterSchema, - }) - .required(); - export function ExpertFilterEditionDialog({ id, name, @@ -45,9 +38,22 @@ export function ExpertFilterEditionDialog({ language, description, }: Readonly) { + const intl = useIntl(); const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + ...getHeaderFilterSchema(intl), + ...expertFilterSchema, + }) + .required(), + [intl] + ); + // default values are set via reset when we fetch data const formMethods = useForm({ resolver: yupResolver(formSchema), diff --git a/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx b/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx index 24a29e50..d24853d9 100644 --- a/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx +++ b/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx @@ -7,32 +7,23 @@ import { yupResolver } from '@hookform/resolvers/yup'; import PropTypes from 'prop-types'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import yup from 'yup'; import { type SubmitHandler, useForm, type UseFormReturn } from 'react-hook-form'; import { v4 as uuid4 } from 'uuid'; +import { useIntl } from 'react-intl'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { FieldConstants } from '../../../utils/constants/fieldConstants'; import { CustomMuiDialog } from '../../dialogs/customMuiDialog/CustomMuiDialog'; import { saveExplicitNamingFilter } from '../utils/filterApi'; -import { explicitNamingFilterSchema } from './ExplicitNamingFilterForm'; +import { getExplicitNamingFilterSchema } from './ExplicitNamingFilterForm'; import { FetchStatus } from '../../../utils/constants/fetchStatus'; import { FilterForm } from '../FilterForm'; import { FilterType, NO_ITEM_SELECTION_FOR_COPY } from '../constants/FilterConstants'; import { FilterEditionProps } from '../filter.type'; -import { HeaderFilterSchema } from '../HeaderFilterForm'; +import { getHeaderFilterSchema } from '../HeaderFilterForm'; import { FILTER_EQUIPMENTS_ATTRIBUTES } from './ExplicitNamingFilterConstants'; -const formSchema = yup - .object() - .shape({ - ...HeaderFilterSchema, - ...explicitNamingFilterSchema, - }) - .required(); - -type FormSchemaType = yup.InferType; - export function ExplicitNamingFilterEditionDialog({ id, name, @@ -47,9 +38,23 @@ export function ExplicitNamingFilterEditionDialog({ language, description, }: Readonly) { + const intl = useIntl(); const { snackError } = useSnackMessage(); const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE); + const formSchema = useMemo( + () => + yup + .object() + .shape({ + ...getHeaderFilterSchema(intl), + ...getExplicitNamingFilterSchema(intl), + }) + .required(), + [intl] + ); + type FormSchemaType = yup.InferType; + // default values are set via reset when we fetch data const formMethods: UseFormReturn = useForm({ resolver: yupResolver(formSchema), diff --git a/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx b/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx index a5d9d502..eaeaa5ed 100644 --- a/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx +++ b/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { useCallback, useEffect, useMemo } from 'react'; -import { useIntl } from 'react-intl'; +import { IntlShape, useIntl } from 'react-intl'; import { useFormContext, useWatch } from 'react-hook-form'; import yup from 'yup'; import { Box } from '@mui/material'; @@ -30,40 +30,52 @@ import { unscrollableDialogStyles } from '../../dialogs'; import { FILTER_EQUIPMENTS_ATTRIBUTES } from './ExplicitNamingFilterConstants'; import { filterStyles } from '../HeaderFilterForm'; -function isGeneratorOrLoad(equipmentType: string): boolean { +function isGeneratorOrLoad(equipmentType: string) { return equipmentType === Generator.type || equipmentType === Load.type; } -export const explicitNamingFilterSchema = { - [FILTER_EQUIPMENTS_ATTRIBUTES]: yup - .array() - .of( - yup.object().shape({ - [FieldConstants.EQUIPMENT_ID]: yup.string().nullable(), - [DISTRIBUTION_KEY]: yup.number().nullable(), - }) - ) - // we remove empty lines - .compact((row) => !row[DISTRIBUTION_KEY] && !row[FieldConstants.EQUIPMENT_ID]) - .when([FieldConstants.FILTER_TYPE], { - is: FilterType.EXPLICIT_NAMING.id, - then: (schema) => - schema.min(1, 'emptyFilterError').when([FieldConstants.EQUIPMENT_TYPE], { - is: (equipmentType: string) => isGeneratorOrLoad(equipmentType), - then: (innerSchema) => - innerSchema - .test('noKeyWithoutId', 'distributionKeyWithMissingIdError', (array) => { - return !array!.some((row) => !row[FieldConstants.EQUIPMENT_ID]); - }) - .test('ifOneKeyThenKeyEverywhere', 'missingDistributionKeyError', (array) => { - return !( - array!.some((row) => row[DISTRIBUTION_KEY]) && - array!.some((row) => !row[DISTRIBUTION_KEY]) - ); - }), - }), - }), -}; +export function getExplicitNamingFilterSchema(intl: IntlShape) { + return { + [FILTER_EQUIPMENTS_ATTRIBUTES]: yup + .array() + .of( + yup.object().shape({ + [FieldConstants.EQUIPMENT_ID]: yup.string().nullable(), + [DISTRIBUTION_KEY]: yup.number().nullable(), + }) + ) + // we remove empty lines + .compact((row) => !row[DISTRIBUTION_KEY] && !row[FieldConstants.EQUIPMENT_ID]) + .when([FieldConstants.FILTER_TYPE], { + is: FilterType.EXPLICIT_NAMING.id, + then: (schema) => + schema + .min(1, intl.formatMessage({ id: 'emptyFilterError' })) + .when([FieldConstants.EQUIPMENT_TYPE], { + is: (equipmentType: string) => isGeneratorOrLoad(equipmentType), + then: (innerSchema) => + innerSchema + .test( + 'noKeyWithoutId', + intl.formatMessage({ id: 'distributionKeyWithMissingIdError' }), + (array) => { + return !array!.some((row) => !row[FieldConstants.EQUIPMENT_ID]); + } + ) + .test( + 'ifOneKeyThenKeyEverywhere', + intl.formatMessage({ id: 'missingDistributionKeyError' }), + (array) => { + return !( + array!.some((row) => row[DISTRIBUTION_KEY]) && + array!.some((row) => !row[DISTRIBUTION_KEY]) + ); + } + ), + }), + }), + }; +} interface FilterTableRow { [FieldConstants.AG_GRID_ROW_UUID]: string; @@ -73,7 +85,7 @@ interface FilterTableRow { function makeDefaultRowData(): FilterTableRow { return { - [FieldConstants.AG_GRID_ROW_UUID]: uuid4(), + [FieldConstants.AG_GRID_ROW_UUID]: uuid4() as UUID, [FieldConstants.EQUIPMENT_ID]: '', [DISTRIBUTION_KEY]: null, }; @@ -89,6 +101,18 @@ export function getExplicitNamingFilterEmptyFormData() { }; } +const defaultColDef = { suppressMovable: true } as const; + +function getDataFromCsvFile(csvData: any) { + return csvData + ? csvData.map((value: any) => ({ + [FieldConstants.AG_GRID_ROW_UUID]: uuid4(), + [FieldConstants.EQUIPMENT_ID]: value[0]?.trim(), + [DISTRIBUTION_KEY]: toFloatOrNullValue(value[1]?.trim()), + })) + : []; +} + export interface FilterForExplicitConversionProps { id: UUID; equipmentType: string; @@ -101,18 +125,12 @@ interface ExplicitNamingFilterFormProps { export function ExplicitNamingFilterForm({ sourceFilterForExplicitNamingConversion }: ExplicitNamingFilterFormProps) { const intl = useIntl(); const { snackError } = useSnackMessage(); - const { getValues, setValue } = useFormContext(); - const watchEquipmentType = useWatch({ - name: FieldConstants.EQUIPMENT_TYPE, - }); - + const watchEquipmentType = useWatch({ name: FieldConstants.EQUIPMENT_TYPE }); useEffect(() => { if (watchEquipmentType && !((watchEquipmentType as EquipmentType) in FILTER_EQUIPMENTS)) { - snackError({ - headerId: 'obsoleteFilter', - }); + snackError({ headerId: 'obsoleteFilter' }); } }, [snackError, watchEquipmentType]); @@ -127,9 +145,7 @@ export function ExplicitNamingFilterForm({ sourceFilterForExplicitNamingConversi const columnDefs = useMemo(() => { const newColumnDefs: any[] = [ { - headerName: intl.formatMessage({ - id: FieldConstants.EQUIPMENT_ID, - }), + headerName: intl.formatMessage({ id: FieldConstants.EQUIPMENT_ID }), rowDrag: true, field: FieldConstants.EQUIPMENT_ID, editable: true, @@ -151,13 +167,6 @@ export function ExplicitNamingFilterForm({ sourceFilterForExplicitNamingConversi return newColumnDefs; }, [intl, forGeneratorOrLoad]); - const defaultColDef = useMemo( - () => ({ - suppressMovable: true, - }), - [] - ); - const csvFileHeaders = useMemo(() => { const newCsvFileHeaders = [intl.formatMessage({ id: FieldConstants.EQUIPMENT_ID })]; if (forGeneratorOrLoad) { @@ -166,50 +175,39 @@ export function ExplicitNamingFilterForm({ sourceFilterForExplicitNamingConversi return newCsvFileHeaders; }, [intl, forGeneratorOrLoad]); - const getDataFromCsvFile = useCallback((csvData: any) => { - if (csvData) { - return csvData.map((value: any) => { - return { - [FieldConstants.AG_GRID_ROW_UUID]: uuid4(), - [FieldConstants.EQUIPMENT_ID]: value[0]?.trim(), - [DISTRIBUTION_KEY]: toFloatOrNullValue(value[1]?.trim()), - }; - }); - } - return []; - }, []); - - const openConfirmationPopup = () => { - return getValues(FILTER_EQUIPMENTS_ATTRIBUTES).some( - (row: FilterTableRow) => row[DISTRIBUTION_KEY] || row[FieldConstants.EQUIPMENT_ID] - ); - }; + const openConfirmationPopup = useCallback( + () => + getValues(FILTER_EQUIPMENTS_ATTRIBUTES).some( + (row: FilterTableRow) => row[DISTRIBUTION_KEY] || row[FieldConstants.EQUIPMENT_ID] + ), + [getValues] + ); - const handleResetOnConfirmation = () => { + const handleResetOnConfirmation = useCallback(() => { setValue(FILTER_EQUIPMENTS_ATTRIBUTES, makeDefaultTableRows()); - }; + }, [setValue]); - const onStudySelected = (studyUuid: UUID) => { - exportFilter(studyUuid, sourceFilterForExplicitNamingConversion?.id) - .then((matchingEquipments: any) => { - setValue( - FILTER_EQUIPMENTS_ATTRIBUTES, - matchingEquipments.length === 0 - ? makeDefaultTableRows() - : matchingEquipments.map((equipment: any) => ({ - [FieldConstants.AG_GRID_ROW_UUID]: uuid4(), - [FieldConstants.EQUIPMENT_ID]: equipment.id, - [DISTRIBUTION_KEY]: equipment.distributionKey, - })) - ); - }) - .catch((error: any) => - snackError({ - messageTxt: error.message, - headerId: 'convertIntoExplicitNamingFilterError', + const onStudySelected = useCallback( + (studyUuid: UUID) => { + exportFilter(studyUuid, sourceFilterForExplicitNamingConversion?.id) + .then((matchingEquipments: any) => { + setValue( + FILTER_EQUIPMENTS_ATTRIBUTES, + matchingEquipments.length === 0 + ? makeDefaultTableRows() + : matchingEquipments.map((equipment: any) => ({ + [FieldConstants.AG_GRID_ROW_UUID]: uuid4(), + [FieldConstants.EQUIPMENT_ID]: equipment.id, + [DISTRIBUTION_KEY]: equipment.distributionKey, + })) + ); }) - ); - }; + .catch((error: any) => + snackError({ messageTxt: error.message, headerId: 'convertIntoExplicitNamingFilterError' }) + ); + }, + [setValue, snackError, sourceFilterForExplicitNamingConversion?.id] + ); return ( <> diff --git a/src/components/inputs/reactHookForm/errorManagement/ErrorInput.tsx b/src/components/inputs/reactHookForm/errorManagement/ErrorInput.tsx index 1bd231bb..1e856cc7 100644 --- a/src/components/inputs/reactHookForm/errorManagement/ErrorInput.tsx +++ b/src/components/inputs/reactHookForm/errorManagement/ErrorInput.tsx @@ -5,20 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import React, { MutableRefObject, useEffect, useRef } from 'react'; -import { FormattedMessage } from 'react-intl'; +import { type ComponentType, type MutableRefObject, type ReactNode, useEffect, useRef } from 'react'; import { useController } from 'react-hook-form'; -export type ErrorMessage = - | { - id: string; - value: string; - } - | string; - export interface ErrorInputProps { name: string; - InputField: ({ message }: { message: string | React.ReactNode }) => React.ReactNode; + InputField: ComponentType<{ message: ReactNode }>; } export function ErrorInput({ name, InputField }: ErrorInputProps) { @@ -31,23 +23,6 @@ export function ErrorInput({ name, InputField }: ErrorInputProps) { const errorRef: MutableRefObject = useRef(null); - const errorProps = (errorMsg: ErrorMessage | undefined) => { - if (typeof errorMsg === 'string') { - return { - id: errorMsg, - }; - } - if (typeof errorMsg === 'object') { - return { - id: errorMsg.id, - values: { - value: errorMsg.value, - }, - }; - } - return {}; - }; - useEffect(() => { // the scroll should be done only when the form is submitting if (error && errorRef.current) { @@ -62,9 +37,7 @@ export function ErrorInput({ name, InputField }: ErrorInputProps) { return ( errorMsg && (
- {InputField({ - message: , - })} +
) ); diff --git a/src/components/inputs/reactHookForm/text/UniqueNameInput.tsx b/src/components/inputs/reactHookForm/text/UniqueNameInput.tsx index 0164deeb..18bd4183 100644 --- a/src/components/inputs/reactHookForm/text/UniqueNameInput.tsx +++ b/src/components/inputs/reactHookForm/text/UniqueNameInput.tsx @@ -148,10 +148,6 @@ export function UniqueNameInput({ onManualChangeCallback?.(); }; - const translatedLabel = ; - - const translatedError = error && ; - const showOk = value?.trim() && !isValidating && !error; const endAdornment = ( @@ -168,14 +164,14 @@ export function UniqueNameInput({ value={value} name={name} inputRef={ref} - label={translatedLabel} + label={} type="text" autoFocus={autoFocus} margin="dense" sx={sx} fullWidth={fullWidth} error={!!error} - helperText={translatedError} + helperText={error?.message} InputProps={{ endAdornment }} {...formProps} /> diff --git a/src/components/parameters/common/limitreductions/columns-definitions.ts b/src/components/parameters/common/limitreductions/columns-definitions.ts index 8f69032a..194782c4 100644 --- a/src/components/parameters/common/limitreductions/columns-definitions.ts +++ b/src/components/parameters/common/limitreductions/columns-definitions.ts @@ -7,6 +7,7 @@ import yup, { type NumberSchema } from 'yup'; import type { UUID } from 'crypto'; +import { type IntlShape } from 'react-intl'; import { PARAM_SA_FLOW_PROPORTIONAL_THRESHOLD, PARAM_SA_HIGH_VOLTAGE_ABSOLUTE_THRESHOLD, @@ -87,68 +88,69 @@ export const COLUMNS_DEFINITIONS_LIMIT_REDUCTIONS: IColumnsDef[] = [ ]; /* TODO: a cleaner solution can be done by using yup.array() - Instead of creating a schema for each limit duration individually, - we can use yup.array() to define an array of limit durations directly. */ -const getLimitDurationsFormSchema = (nbLimits: number) => { - const limitDurationsFormSchema: Record = {}; - for (let i = 0; i < nbLimits; i++) { - limitDurationsFormSchema[LIMIT_DURATION_FORM + i] = yup - .number() - .min(0, 'RealPercentage') - .max(1, 'RealPercentage') - .nullable() - .required(); - } - return limitDurationsFormSchema; -}; + * Instead of creating a schema for each limit duration individually, + * we can use yup.array() to define an array of limit durations directly. */ +function getLimitDurationsFormSchema(intl: IntlShape, nbLimits: number) { + return Array.from({ length: nbLimits }).reduce( + (acc, _, idx) => { + acc[`${LIMIT_DURATION_FORM}${idx}`] = yup + .number() + .min(0, intl.formatMessage({ id: 'RealPercentage' })) + .max(1, intl.formatMessage({ id: 'RealPercentage' })) + .nullable() + .required(); + return acc; + }, + {} as Record<`${typeof LIMIT_DURATION_FORM}${number}`, NumberSchema> + ); +} -export const getLimitReductionsFormSchema = (nbTemporaryLimits: number) => { +export function getLimitReductionsFormSchema(intl: IntlShape, nbTemporaryLimits: number) { return yup .object() .shape({ [LIMIT_REDUCTIONS_FORM]: yup.array().of( yup.object().shape({ [VOLTAGE_LEVELS_FORM]: yup.string(), - [IST_FORM]: yup.number().min(0, 'RealPercentage').max(1, 'RealPercentage').nullable().required(), - ...getLimitDurationsFormSchema(nbTemporaryLimits), + [IST_FORM]: yup + .number() + .min(0, intl.formatMessage({ id: 'RealPercentage' })) + .max(1, intl.formatMessage({ id: 'RealPercentage' })) + .nullable() + .required(), + ...getLimitDurationsFormSchema(intl, nbTemporaryLimits), }) ), }) .required(); -}; +} -export const getSAParametersFromSchema = (limitReductions?: ILimitReductionsByVoltageLevel[]) => { - const providerSchema = yup.object().shape({ +export function getSAParametersFromSchema(intl: IntlShape, limitReductions?: ILimitReductionsByVoltageLevel[]) { + return yup.object().shape({ + // providerSchema fields [PARAM_SA_PROVIDER]: yup.string().required(), - }); - - const limitReductionsSchema = getLimitReductionsFormSchema( - limitReductions?.length ? limitReductions[0].temporaryLimitReductions.length : 0 - ); - - const thresholdsSchema = yup.object().shape({ + // limitReductionsSchema fields + ...getLimitReductionsFormSchema( + intl, + limitReductions?.length ? limitReductions[0].temporaryLimitReductions.length : 0 + ).fields, + // thresholdsSchema fields [PARAM_SA_FLOW_PROPORTIONAL_THRESHOLD]: yup .number() - .min(0, 'NormalizedPercentage') - .max(100, 'NormalizedPercentage') + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) .required(), [PARAM_SA_LOW_VOLTAGE_PROPORTIONAL_THRESHOLD]: yup .number() - .min(0, 'NormalizedPercentage') - .max(100, 'NormalizedPercentage') + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) .required(), [PARAM_SA_LOW_VOLTAGE_ABSOLUTE_THRESHOLD]: yup.number().required(), [PARAM_SA_HIGH_VOLTAGE_PROPORTIONAL_THRESHOLD]: yup .number() - .min(0, 'NormalizedPercentage') - .max(100, 'NormalizedPercentage') + .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) + .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) .required(), [PARAM_SA_HIGH_VOLTAGE_ABSOLUTE_THRESHOLD]: yup.number().required(), }); - - return yup.object().shape({ - ...providerSchema.fields, - ...limitReductionsSchema.fields, - ...thresholdsSchema.fields, - }); -}; +} diff --git a/src/components/parameters/loadflow/load-flow-parameters-utils.ts b/src/components/parameters/loadflow/load-flow-parameters-utils.ts index b3ae8a51..ae694af1 100644 --- a/src/components/parameters/loadflow/load-flow-parameters-utils.ts +++ b/src/components/parameters/loadflow/load-flow-parameters-utils.ts @@ -7,6 +7,7 @@ import { type UseFormReturn } from 'react-hook-form'; import yup from 'yup'; +import { type IntlShape } from 'react-intl'; import { ILimitReductionsByVoltageLevel, IST_FORM, @@ -58,7 +59,7 @@ export const getBasicLoadFlowParametersFormSchema = () => { }); }; -export const getAdvancedLoadFlowParametersFormSchema = () => { +export function getAdvancedLoadFlowParametersFormSchema(intl: IntlShape) { return yup.object().shape({ [VOLTAGE_INIT_MODE]: yup.string().required(), [USE_REACTIVE_LIMITS]: yup.boolean().required(), @@ -71,10 +72,10 @@ export const getAdvancedLoadFlowParametersFormSchema = () => { [DC_POWER_FACTOR]: yup .number() .required() - .positive('dcPowerFactorGreaterThan0') - .max(1, 'dcPowerFactorLessOrEqualThan1'), + .positive(intl.formatMessage({ id: 'dcPowerFactorGreaterThan0' })) + .max(1, intl.formatMessage({ id: 'dcPowerFactorLessOrEqualThan1' })), }); -}; +} export const getDialogLoadFlowParametersFormSchema = (name: string | null) => { const shape: { [key: string]: yup.AnySchema } = {}; @@ -85,19 +86,19 @@ export const getDialogLoadFlowParametersFormSchema = (name: string | null) => { return shape; }; -export const getCommonLoadFlowParametersFormSchema = () => { +export function getCommonLoadFlowParametersFormSchema(intl: IntlShape) { return yup.object().shape({ [COMMON_PARAMETERS]: yup.object().shape({ ...getBasicLoadFlowParametersFormSchema().fields, - ...getAdvancedLoadFlowParametersFormSchema().fields, + ...getAdvancedLoadFlowParametersFormSchema(intl).fields, }), }); -}; +} export const getSpecificLoadFlowParametersFormSchema = (specificParameters: SpecificParameterInfos[]) => { const shape: { [key: string]: yup.AnySchema } = {}; - specificParameters?.forEach((param: SpecificParameterInfos) => { + specificParameters?.forEach((param) => { switch (param.type) { case ParameterType.STRING: shape[param.name] = yup.string().required(); diff --git a/src/components/parameters/loadflow/use-load-flow-parameters-form.ts b/src/components/parameters/loadflow/use-load-flow-parameters-form.ts index 54727eda..10efb1cc 100644 --- a/src/components/parameters/loadflow/use-load-flow-parameters-form.ts +++ b/src/components/parameters/loadflow/use-load-flow-parameters-form.ts @@ -5,11 +5,20 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { FieldErrors, useForm, UseFormReturn } from 'react-hook-form'; +import { type FieldErrors, useForm, type UseFormReturn } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -import { Dispatch, SetStateAction, SyntheticEvent, useCallback, useEffect, useMemo, useState } from 'react'; +import { + type Dispatch, + type SetStateAction, + type SyntheticEvent, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; import yup, { type ObjectSchema } from 'yup'; import type { UUID } from 'crypto'; +import { useIntl } from 'react-intl'; import { getCommonLoadFlowParametersFormSchema, getDefaultSpecificParamsValues, @@ -20,16 +29,16 @@ import { setSpecificParameters, TabValues, } from './load-flow-parameters-utils'; -import { LoadFlowParametersInfos, SpecificParametersPerProvider } from '../../../utils/types/loadflow.type'; +import type { LoadFlowParametersInfos, SpecificParametersPerProvider } from '../../../utils/types/loadflow.type'; import { ParameterType, - SpecificParameterInfos, - UseParametersBackendReturnProps, + type SpecificParameterInfos, + type UseParametersBackendReturnProps, } from '../../../utils/types/parameters.type'; import { ComputingType, PROVIDER } from '../common'; import { getLimitReductionsFormSchema, - ILimitReductionsByVoltageLevel, + type ILimitReductionsByVoltageLevel, LIMIT_REDUCTIONS_FORM, } from '../common/limitreductions/columns-definitions'; import { @@ -63,13 +72,14 @@ export interface UseLoadFlowParametersFormReturn { onSaveDialog: (formData: Record) => void; } -export const useLoadFlowParametersForm = ( +export function useLoadFlowParametersForm( parametersBackend: UseParametersBackendReturnProps, enableDeveloperMode: boolean, parametersUuid: UUID | null, name: string | null, description: string | null -): UseLoadFlowParametersFormReturn => { +): UseLoadFlowParametersFormReturn { + const intl = useIntl(); const [ providers, provider, @@ -108,18 +118,21 @@ export const useLoadFlowParametersForm = ( })); }, [currentProvider, specificParamsDescriptions]); - const formSchema = useMemo(() => { - return yup.object({ - ...getDialogLoadFlowParametersFormSchema(name), - [PROVIDER]: yup.string().required(), - [PARAM_LIMIT_REDUCTION]: yup.number().nullable(), - ...getCommonLoadFlowParametersFormSchema().fields, - ...getLimitReductionsFormSchema( - params?.limitReductions ? params.limitReductions[0]?.temporaryLimitReductions.length : 0 - ).fields, - ...getSpecificLoadFlowParametersFormSchema(specificParameters).fields, - }); - }, [name, params?.limitReductions, specificParameters]); + const formSchema = useMemo( + () => + yup.object({ + ...getDialogLoadFlowParametersFormSchema(name), + [PROVIDER]: yup.string().required(), + [PARAM_LIMIT_REDUCTION]: yup.number().nullable(), + ...getCommonLoadFlowParametersFormSchema(intl).fields, + ...getLimitReductionsFormSchema( + intl, + params?.limitReductions ? params.limitReductions[0]?.temporaryLimitReductions.length : 0 + ).fields, + ...getSpecificLoadFlowParametersFormSchema(specificParameters).fields, + }), + [intl, name, params?.limitReductions, specificParameters] + ); const formMethods = useForm({ defaultValues: { @@ -321,4 +334,4 @@ export const useLoadFlowParametersForm = ( onSaveInline, onSaveDialog, }; -}; +} From 072b14c9fb3a1564507f5e53c70432a878730989 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 22 May 2025 11:30:33 +0200 Subject: [PATCH 5/9] `ReactNode` include `string` --- .../reactHookForm/errorManagement/FieldErrorAlert.tsx | 8 ++++---- .../inputs/reactHookForm/errorManagement/MidFormError.tsx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/inputs/reactHookForm/errorManagement/FieldErrorAlert.tsx b/src/components/inputs/reactHookForm/errorManagement/FieldErrorAlert.tsx index ce441ef8..8aecce5a 100644 --- a/src/components/inputs/reactHookForm/errorManagement/FieldErrorAlert.tsx +++ b/src/components/inputs/reactHookForm/errorManagement/FieldErrorAlert.tsx @@ -6,11 +6,11 @@ */ import { Alert, Grid } from '@mui/material'; -import React from 'react'; +import { type ReactNode } from 'react'; -export interface FieldErrorAlertProps { - message: string | React.ReactNode; -} +export type FieldErrorAlertProps = { + message: ReactNode; +}; // component to display alert when a specific rhf field is in error // this component needs to be isolated to avoid too many rerenders diff --git a/src/components/inputs/reactHookForm/errorManagement/MidFormError.tsx b/src/components/inputs/reactHookForm/errorManagement/MidFormError.tsx index a75d7b58..c5966a39 100644 --- a/src/components/inputs/reactHookForm/errorManagement/MidFormError.tsx +++ b/src/components/inputs/reactHookForm/errorManagement/MidFormError.tsx @@ -5,10 +5,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { type ReactNode } from 'react'; import { Box } from '@mui/material'; -import { ReactNode } from 'react'; -export type MidFormErrorProps = { message: string | ReactNode }; +export type MidFormErrorProps = { message: ReactNode }; // component to display error message in the middle of dialog export function MidFormError({ message }: Readonly) { From 76d31bd18043fd6913bb2b3ff3d213a4e8a91ec5 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 22 May 2025 11:56:27 +0200 Subject: [PATCH 6/9] Missing translation key Move from gridstudy-app --- src/translations/en/parameters.ts | 1 + src/translations/fr/parameters.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/translations/en/parameters.ts b/src/translations/en/parameters.ts index cf116d62..14d5471f 100644 --- a/src/translations/en/parameters.ts +++ b/src/translations/en/parameters.ts @@ -53,6 +53,7 @@ export const parametersEn = { descLfCountriesToBalance: 'Countries participating in balancing', editParameters: 'Edit parameters', RealPercentage: 'This value must be between 0 and 1', + NormalizedPercentage: 'This percentage must be between 0 and 100', General: 'General', LimitReductions: 'Limit reductions', diff --git a/src/translations/fr/parameters.ts b/src/translations/fr/parameters.ts index 6913b4f1..ef7f78de 100644 --- a/src/translations/fr/parameters.ts +++ b/src/translations/fr/parameters.ts @@ -55,6 +55,7 @@ export const parametersFr = { descLfCountriesToBalance: 'Pays participant à la compensation', editParameters: 'Éditer les paramètres', RealPercentage: 'Cette valeur doit être comprise entre 0 et 1', + NormalizedPercentage: 'Ce pourcentage doit être compris entre 0 et 100', General: 'Général', LimitReductions: 'Abattements', From 1a9daf40d942cbe0b9e917c8aac8c16f58dfef79 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 22 May 2025 12:56:33 +0200 Subject: [PATCH 7/9] Fix yup default import --- .../contingencyList/criteriaBased/criteriaBasedUtils.ts | 3 ++- src/components/dialogs/customMuiDialog/CustomMuiDialog.tsx | 2 +- .../DescriptionModificationDialog.tsx | 2 +- src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx | 2 +- src/components/filter/FilterCreationDialog.tsx | 2 +- src/components/filter/HeaderFilterForm.tsx | 2 +- src/components/filter/expert/ExpertFilterEditionDialog.tsx | 2 +- .../explicitNaming/ExplicitNamingFilterEditionDialog.tsx | 2 +- .../filter/explicitNaming/ExplicitNamingFilterForm.tsx | 2 +- src/components/inputs/reactHookForm/numbers/RangeInput.tsx | 3 ++- .../inputs/reactHookForm/provider/CustomFormProvider.tsx | 2 +- .../parameters/common/limitreductions/columns-definitions.ts | 3 ++- .../parameters/loadflow/load-flow-parameters-utils.ts | 2 +- .../parameters/loadflow/use-load-flow-parameters-form.ts | 3 ++- 14 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/components/contingencyList/criteriaBased/criteriaBasedUtils.ts b/src/components/contingencyList/criteriaBased/criteriaBasedUtils.ts index abfe9e10..8a01bfac 100644 --- a/src/components/contingencyList/criteriaBased/criteriaBasedUtils.ts +++ b/src/components/contingencyList/criteriaBased/criteriaBasedUtils.ts @@ -5,7 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup, { type ObjectSchema } from 'yup'; +import * as yup from 'yup'; +import type { ObjectSchema } from 'yup'; import { FieldConstants } from '../../../utils/constants/fieldConstants'; import { DEFAULT_RANGE_VALUE, diff --git a/src/components/dialogs/customMuiDialog/CustomMuiDialog.tsx b/src/components/dialogs/customMuiDialog/CustomMuiDialog.tsx index 40d7fe05..667a7ab0 100644 --- a/src/components/dialogs/customMuiDialog/CustomMuiDialog.tsx +++ b/src/components/dialogs/customMuiDialog/CustomMuiDialog.tsx @@ -9,7 +9,7 @@ import { type MouseEvent, type ReactNode, useCallback, useState } from 'react'; import { FieldErrors, FieldValues, SubmitHandler, UseFormReturn } from 'react-hook-form'; import { FormattedMessage } from 'react-intl'; import { Dialog, DialogActions, DialogContent, DialogTitle, Grid, LinearProgress } from '@mui/material'; -import { type ObjectSchema } from 'yup'; +import type { ObjectSchema } from 'yup'; import { SubmitButton } from '../../inputs/reactHookForm/utils/SubmitButton'; import { CancelButton } from '../../inputs/reactHookForm/utils/CancelButton'; import { CustomFormProvider } from '../../inputs/reactHookForm/provider/CustomFormProvider'; diff --git a/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx b/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx index 3e124ca1..11643044 100644 --- a/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx +++ b/src/components/dialogs/descriptionModificationDialog/DescriptionModificationDialog.tsx @@ -10,7 +10,7 @@ import { useIntl } from 'react-intl'; import { SubmitHandler, useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import { Box } from '@mui/material'; -import yup from 'yup'; +import * as yup from 'yup'; import { FieldConstants } from '../../../utils/constants/fieldConstants'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; import { CustomMuiDialog } from '../customMuiDialog/CustomMuiDialog'; diff --git a/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx b/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx index 92038b8a..30c99e19 100644 --- a/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx +++ b/src/components/dialogs/elementSaveDialog/ElementSaveDialog.tsx @@ -11,7 +11,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { Box, Button, CircularProgress, Grid, Typography } from '@mui/material'; import { SubmitHandler, useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -import yup from 'yup'; +import * as yup from 'yup'; import { TreeViewFinderNodeProps } from '../../treeViewFinder'; import { DescriptionField, RadioInput, UniqueNameInput } from '../../inputs'; import { DirectoryItemSelector } from '../../directoryItemSelector'; diff --git a/src/components/filter/FilterCreationDialog.tsx b/src/components/filter/FilterCreationDialog.tsx index e94e5105..8387ff77 100644 --- a/src/components/filter/FilterCreationDialog.tsx +++ b/src/components/filter/FilterCreationDialog.tsx @@ -8,7 +8,7 @@ import { useCallback, useMemo } from 'react'; import { useIntl } from 'react-intl'; import { type FieldValues, type Resolver, useForm } from 'react-hook-form'; -import yup from 'yup'; +import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import type { UUID } from 'crypto'; import { saveExpertFilter, saveExplicitNamingFilter } from './utils/filterApi'; diff --git a/src/components/filter/HeaderFilterForm.tsx b/src/components/filter/HeaderFilterForm.tsx index fed2470d..f34b0c38 100644 --- a/src/components/filter/HeaderFilterForm.tsx +++ b/src/components/filter/HeaderFilterForm.tsx @@ -7,7 +7,7 @@ import { Grid } from '@mui/material'; import type { UUID } from 'crypto'; -import yup from 'yup'; +import * as yup from 'yup'; import { type IntlShape } from 'react-intl'; import { ElementType, FieldConstants, MAX_CHAR_DESCRIPTION } from '../../utils'; import { DescriptionField, UniqueNameInput } from '../inputs'; diff --git a/src/components/filter/expert/ExpertFilterEditionDialog.tsx b/src/components/filter/expert/ExpertFilterEditionDialog.tsx index 0f74d2dc..59ac3ff6 100644 --- a/src/components/filter/expert/ExpertFilterEditionDialog.tsx +++ b/src/components/filter/expert/ExpertFilterEditionDialog.tsx @@ -7,7 +7,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useIntl } from 'react-intl'; -import yup from 'yup'; +import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import { useForm } from 'react-hook-form'; import { useSnackMessage } from '../../../hooks/useSnackMessage'; diff --git a/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx b/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx index d24853d9..a6f5cbc1 100644 --- a/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx +++ b/src/components/filter/explicitNaming/ExplicitNamingFilterEditionDialog.tsx @@ -8,7 +8,7 @@ import { yupResolver } from '@hookform/resolvers/yup'; import PropTypes from 'prop-types'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import yup from 'yup'; +import * as yup from 'yup'; import { type SubmitHandler, useForm, type UseFormReturn } from 'react-hook-form'; import { v4 as uuid4 } from 'uuid'; import { useIntl } from 'react-intl'; diff --git a/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx b/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx index eaeaa5ed..4966cb47 100644 --- a/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx +++ b/src/components/filter/explicitNaming/ExplicitNamingFilterForm.tsx @@ -7,7 +7,7 @@ import { useCallback, useEffect, useMemo } from 'react'; import { IntlShape, useIntl } from 'react-intl'; import { useFormContext, useWatch } from 'react-hook-form'; -import yup from 'yup'; +import * as yup from 'yup'; import { Box } from '@mui/material'; import type { ValueParserParams } from 'ag-grid-community'; import { v4 as uuid4 } from 'uuid'; diff --git a/src/components/inputs/reactHookForm/numbers/RangeInput.tsx b/src/components/inputs/reactHookForm/numbers/RangeInput.tsx index 27fce50f..9ba57c44 100644 --- a/src/components/inputs/reactHookForm/numbers/RangeInput.tsx +++ b/src/components/inputs/reactHookForm/numbers/RangeInput.tsx @@ -7,7 +7,8 @@ import { useWatch } from 'react-hook-form'; import { FormattedMessage } from 'react-intl'; import { useMemo } from 'react'; -import yup, { type ObjectSchema } from 'yup'; +import * as yup from 'yup'; +import type { ObjectSchema } from 'yup'; import { FormControl, Grid, InputLabel, type Theme } from '@mui/material'; import { FloatInput } from './FloatInput'; import { MuiSelectInput } from '../selectInputs/MuiSelectInput'; diff --git a/src/components/inputs/reactHookForm/provider/CustomFormProvider.tsx b/src/components/inputs/reactHookForm/provider/CustomFormProvider.tsx index 6ea7cf50..9064b454 100644 --- a/src/components/inputs/reactHookForm/provider/CustomFormProvider.tsx +++ b/src/components/inputs/reactHookForm/provider/CustomFormProvider.tsx @@ -8,7 +8,7 @@ import { createContext, PropsWithChildren, useMemo } from 'react'; import { type FieldValues, FormProvider, type UseFormReturn } from 'react-hook-form'; import * as yup from 'yup'; -import { type ObjectSchema } from 'yup'; +import type { ObjectSchema } from 'yup'; import { getSystemLanguage } from '../../../../hooks'; type CustomFormContextProps = { diff --git a/src/components/parameters/common/limitreductions/columns-definitions.ts b/src/components/parameters/common/limitreductions/columns-definitions.ts index 194782c4..b6b0218e 100644 --- a/src/components/parameters/common/limitreductions/columns-definitions.ts +++ b/src/components/parameters/common/limitreductions/columns-definitions.ts @@ -5,7 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import yup, { type NumberSchema } from 'yup'; +import * as yup from 'yup'; +import type { NumberSchema } from 'yup'; import type { UUID } from 'crypto'; import { type IntlShape } from 'react-intl'; import { diff --git a/src/components/parameters/loadflow/load-flow-parameters-utils.ts b/src/components/parameters/loadflow/load-flow-parameters-utils.ts index ae694af1..319f7ec0 100644 --- a/src/components/parameters/loadflow/load-flow-parameters-utils.ts +++ b/src/components/parameters/loadflow/load-flow-parameters-utils.ts @@ -6,7 +6,7 @@ */ import { type UseFormReturn } from 'react-hook-form'; -import yup from 'yup'; +import * as yup from 'yup'; import { type IntlShape } from 'react-intl'; import { ILimitReductionsByVoltageLevel, diff --git a/src/components/parameters/loadflow/use-load-flow-parameters-form.ts b/src/components/parameters/loadflow/use-load-flow-parameters-form.ts index 10efb1cc..b536a143 100644 --- a/src/components/parameters/loadflow/use-load-flow-parameters-form.ts +++ b/src/components/parameters/loadflow/use-load-flow-parameters-form.ts @@ -16,7 +16,8 @@ import { useMemo, useState, } from 'react'; -import yup, { type ObjectSchema } from 'yup'; +import * as yup from 'yup'; +import type { ObjectSchema } from 'yup'; import type { UUID } from 'crypto'; import { useIntl } from 'react-intl'; import { From 89316cd2b6cd4a1fd0b7a531f75a806a5de6cafd Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 22 May 2025 16:02:32 +0200 Subject: [PATCH 8/9] missing translation --- src/translations/en/filterEn.ts | 2 ++ src/translations/fr/filterFr.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/translations/en/filterEn.ts b/src/translations/en/filterEn.ts index 0bad46aa..2fda455d 100644 --- a/src/translations/en/filterEn.ts +++ b/src/translations/en/filterEn.ts @@ -40,6 +40,8 @@ export const filterEn = { FreeProps: 'Equipment properties', SubstationFreeProps: 'Equipment substation properties', YupRequired: 'This field is required', + YupNotTypeNumber: 'This field only accepts numeric values', + YupNotTypeDefault: 'Field value format is incorrect', filterPropertiesNameUniquenessError: 'It is not possible to add multiple filters for the same property', emptyFilterError: 'Filter should contain at least one equipment', distributionKeyWithMissingIdError: 'Missing ID with defined distribution key', diff --git a/src/translations/fr/filterFr.ts b/src/translations/fr/filterFr.ts index b0845cc6..91a5dcb3 100644 --- a/src/translations/fr/filterFr.ts +++ b/src/translations/fr/filterFr.ts @@ -40,6 +40,8 @@ export const filterFr = { FreeProps: "Propriétés de l'ouvrage", SubstationFreeProps: "Propriétés du site de l'ouvrage", YupRequired: 'Ce champ doit être renseigné', + YupNotTypeNumber: "Ce champ n'accepte que des valeurs numériques", + YupNotTypeDefault: "La valeur du champ n'est pas au bon format", filterPropertiesNameUniquenessError: "Il n'est pas possible d'ajouter plusieurs filtres pour la même propriété", emptyFilterError: 'Le filtre doit contenir au moins un ouvrage', distributionKeyWithMissingIdError: 'ID manquant avec une clé de répartition définie', From 887300938eb2d65e9321dba78b4d60661dc5acf1 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 23 May 2025 06:18:27 +0200 Subject: [PATCH 9/9] missing `nullable` for gridstudy --- .../common/limitreductions/columns-definitions.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/parameters/common/limitreductions/columns-definitions.ts b/src/components/parameters/common/limitreductions/columns-definitions.ts index b6b0218e..dcd2e1ae 100644 --- a/src/components/parameters/common/limitreductions/columns-definitions.ts +++ b/src/components/parameters/common/limitreductions/columns-definitions.ts @@ -140,18 +140,21 @@ export function getSAParametersFromSchema(intl: IntlShape, limitReductions?: ILi .number() .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) + .nullable() .required(), [PARAM_SA_LOW_VOLTAGE_PROPORTIONAL_THRESHOLD]: yup .number() .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) + .nullable() .required(), - [PARAM_SA_LOW_VOLTAGE_ABSOLUTE_THRESHOLD]: yup.number().required(), + [PARAM_SA_LOW_VOLTAGE_ABSOLUTE_THRESHOLD]: yup.number().nullable().required(), [PARAM_SA_HIGH_VOLTAGE_PROPORTIONAL_THRESHOLD]: yup .number() .min(0, intl.formatMessage({ id: 'NormalizedPercentage' })) .max(100, intl.formatMessage({ id: 'NormalizedPercentage' })) + .nullable() .required(), - [PARAM_SA_HIGH_VOLTAGE_ABSOLUTE_THRESHOLD]: yup.number().required(), + [PARAM_SA_HIGH_VOLTAGE_ABSOLUTE_THRESHOLD]: yup.number().nullable().required(), }); }