Skip to content

Commit eac4ce7

Browse files
unsettable fields in modifications by filters (#3228)
Signed-off-by: Mathieu DEHARBE <[email protected]>
1 parent 43f5bab commit eac4ce7

File tree

8 files changed

+116
-27
lines changed

8 files changed

+116
-27
lines changed

src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-constants.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
SIEMENS,
2323
} from '@gridsuite/commons-ui';
2424

25-
export const FIELD_OPTIONS = {
25+
export const FIELD_OPTIONS: Record<string, FieldOptionType> = {
2626
PROPERTY: {
2727
id: FieldType.FREE_PROPERTIES,
2828
label: 'Property',
@@ -33,6 +33,7 @@ export const FIELD_OPTIONS = {
3333
label: 'RatedNominalPowerText',
3434
unit: MEGA_VOLT_AMPERE,
3535
dataType: DataType.DOUBLE,
36+
settableToNone: true,
3637
},
3738
MINIMUM_ACTIVE_POWER: {
3839
id: FieldType.MINIMUM_ACTIVE_POWER,
@@ -102,6 +103,7 @@ export const FIELD_OPTIONS = {
102103
label: 'TransformerReactanceForm',
103104
unit: OHM,
104105
dataType: DataType.DOUBLE,
106+
settableToNone: true,
105107
},
106108
Q_PERCENT: {
107109
id: FieldType.Q_PERCENT,
@@ -146,24 +148,28 @@ export const FIELD_OPTIONS = {
146148
id: FieldType.LOW_VOLTAGE_LIMIT,
147149
label: 'LowVoltageLimit',
148150
unit: KILO_VOLT,
151+
settableToNone: true,
149152
dataType: DataType.DOUBLE,
150153
},
151154
HIGH_VOLTAGE_LIMIT: {
152155
id: FieldType.HIGH_VOLTAGE_LIMIT,
153156
label: 'HighVoltageLimit',
154157
unit: KILO_VOLT,
158+
settableToNone: true,
155159
dataType: DataType.DOUBLE,
156160
},
157161
LOW_SHORT_CIRCUIT_CURRENT_LIMIT: {
158162
id: FieldType.LOW_SHORT_CIRCUIT_CURRENT_LIMIT,
159163
label: 'LowShortCircuitCurrentLimit',
160164
unit: KILO_AMPERE,
165+
settableToNone: true,
161166
dataType: DataType.DOUBLE,
162167
},
163168
HIGH_SHORT_CIRCUIT_CURRENT_LIMIT: {
164169
id: FieldType.HIGH_SHORT_CIRCUIT_CURRENT_LIMIT,
165170
label: 'HighShortCircuitCurrentLimit',
166171
unit: KILO_AMPERE,
172+
settableToNone: true,
167173
dataType: DataType.DOUBLE,
168174
},
169175
ACTIVE_POWER: {
@@ -243,6 +249,7 @@ export const FIELD_OPTIONS = {
243249
label: 'RatedNominalPowerText',
244250
unit: MEGA_VOLT_AMPERE,
245251
dataType: DataType.DOUBLE,
252+
settableToNone: true,
246253
},
247254
TARGET_V: {
248255
id: FieldType.TARGET_V,
@@ -296,11 +303,13 @@ export const FIELD_OPTIONS = {
296303
id: FieldType.SELECTED_OPERATIONAL_LIMITS_GROUP_1,
297304
label: 'selectedOperationalLimitsGroup1',
298305
dataType: DataType.STRING,
306+
settableToNone: true,
299307
},
300308
SELECTED_OPERATIONAL_LIMITS_GROUP_2: {
301309
id: FieldType.SELECTED_OPERATIONAL_LIMITS_GROUP_2,
302310
label: 'selectedOperationalLimitsGroup2',
303311
dataType: DataType.STRING,
312+
settableToNone: true,
304313
},
305314
} as const satisfies Record<string, ReadonlyDeep<FieldOptionType>>;
306315

src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-form.tsx

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
66
*/
77

8-
import { FC, useMemo } from 'react';
8+
import { FC, useCallback, useMemo } from 'react';
99
import {
1010
AutocompleteInput,
1111
DirectoryItemsInput,
1212
ElementType,
1313
FloatInput,
1414
IntegerInput,
15+
Option,
1516
SelectInput,
1617
SwitchInput,
1718
TextInput,
@@ -26,6 +27,7 @@ import { DataType, FieldOptionType } from './assignment.type';
2627
import { areIdsEqual, comparatorStrIgnoreCase } from '../../../../../utils/utils';
2728
import { PredefinedProperties } from '../../../common/properties/property-utils';
2829
import GridItem from '../../../../commons/grid-item';
30+
import { useIntl } from 'react-intl';
2931

3032
interface AssignmentFormProps {
3133
name: string;
@@ -42,7 +44,8 @@ const AssignmentForm: FC<AssignmentFormProps> = ({
4244
equipmentFields,
4345
equipmentType,
4446
}) => {
45-
const { setValue } = useFormContext();
47+
const { setError, setValue } = useFormContext();
48+
const intl = useIntl();
4649

4750
const watchEditedField = useWatch({
4851
name: `${name}.${index}.${EDITED_FIELD}`,
@@ -52,6 +55,10 @@ const AssignmentForm: FC<AssignmentFormProps> = ({
5255
return equipmentFields?.find((fieldOption) => fieldOption?.id === watchEditedField)?.dataType;
5356
}, [watchEditedField, equipmentFields]);
5457

58+
const settable_to_none: boolean = useMemo(() => {
59+
return equipmentFields?.find((fieldOption) => fieldOption?.id === watchEditedField)?.settableToNone ?? false;
60+
}, [watchEditedField, equipmentFields]);
61+
5562
const watchPropertyName = useWatch({
5663
name: `${name}.${index}.${PROPERTY_NAME_FIELD}`,
5764
});
@@ -75,8 +82,42 @@ const AssignmentForm: FC<AssignmentFormProps> = ({
7582
setValue(`${name}.${index}.${VALUE_FIELD}`, dataType === DataType.BOOLEAN ? false : null);
7683
}
7784

85+
const emptyValueStr = useMemo(() => {
86+
return intl.formatMessage({ id: 'EmptyField' });
87+
}, [intl]);
88+
7889
const formatLabelWithUnit = useFormatLabelWithUnit();
7990

91+
const AutoCompleteSettableToNone = useCallback(
92+
({ numberOnly }: { numberOnly?: boolean }) => (
93+
<AutocompleteInput
94+
name={`${name}.${index}.${VALUE_FIELD}`}
95+
label={'ValueOrEmptyField'}
96+
options={[emptyValueStr]}
97+
size={'small'}
98+
onCheckNewValue={
99+
numberOnly
100+
? (option: Option | null) => {
101+
if (option && option !== emptyValueStr && Number.isNaN(Number(option))) {
102+
setError(`${name}.${index}.${VALUE_FIELD}`, {
103+
message: 'NumericValueOrEmptyField',
104+
});
105+
} else {
106+
setError(`${name}.${index}.${VALUE_FIELD}`, {
107+
message: '',
108+
});
109+
}
110+
return true;
111+
}
112+
: undefined
113+
}
114+
getOptionLabel={(option: Option) => (typeof option !== 'string' ? (option?.label ?? option) : option)}
115+
allowNewValue
116+
/>
117+
),
118+
[emptyValueStr, index, name, setError]
119+
);
120+
80121
const filtersField = (
81122
<DirectoryItemsInput
82123
name={`${name}.${index}.${FILTERS}`}
@@ -139,12 +180,20 @@ const AssignmentForm: FC<AssignmentFormProps> = ({
139180
}
140181

141182
if (dataType === DataType.STRING) {
142-
return <TextInput name={`${name}.${index}.${VALUE_FIELD}`} label={'Value'} clearable />;
183+
if (settable_to_none) {
184+
return <AutoCompleteSettableToNone />;
185+
} else {
186+
return <TextInput name={`${name}.${index}.${VALUE_FIELD}`} label={'Value'} clearable />;
187+
}
188+
}
189+
190+
if (dataType === DataType.DOUBLE && settable_to_none) {
191+
return <AutoCompleteSettableToNone numberOnly />;
143192
}
144193

145194
// by default is a numeric type
146195
return <FloatInput name={`${name}.${index}.${VALUE_FIELD}`} label="Value" />;
147-
}, [dataType, name, index, predefinedPropertiesValues, options]);
196+
}, [dataType, settable_to_none, name, index, predefinedPropertiesValues, options, AutoCompleteSettableToNone]);
148197

149198
return (
150199
<>

src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment-utils.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ import {
1515
} from '../../../../../utils/field-constants';
1616
import yup from 'components/utils/yup-config';
1717
import { Schema } from 'yup';
18-
import { Assignment, DataType, FieldValue } from './assignment.type';
18+
import { Assignment, DataType, FieldOptionType, FieldValue } from './assignment.type';
1919
import { FIELD_OPTIONS } from './assignment-constants';
2020

2121
export const getDataType = (fieldName?: string | null) => {
2222
return getFieldOption(fieldName)?.dataType;
2323
};
24+
export const getUnsettable = (fieldName?: string | null) => {
25+
return getFieldOption(fieldName)?.settableToNone;
26+
};
2427

25-
export const getFieldOption = (fieldName?: string | null) => {
28+
export const getFieldOption = (fieldName?: string | null): FieldOptionType | undefined => {
2629
return Object.values(FIELD_OPTIONS).find((fieldOption) => fieldOption.id === fieldName);
2730
};
2831

@@ -34,7 +37,7 @@ export const getAssignmentInitialValue = () => ({
3437
[VALUE_FIELD]: null,
3538
});
3639

37-
export function getAssignmentsSchema() {
40+
export function getAssignmentsSchema(emptyValueStr: string) {
3841
return yup
3942
.array()
4043
.of(
@@ -61,20 +64,25 @@ export function getAssignmentsSchema() {
6164
.mixed<FieldValue>()
6265
.when([EDITED_FIELD], ([editedField]) => {
6366
const dataType = getDataType(editedField);
64-
return getValueSchema(dataType);
67+
const unsettable = getUnsettable(editedField);
68+
return getValueSchema(emptyValueStr, dataType, unsettable);
6569
})
6670
.required(),
6771
})
6872
)
6973
.required();
7074
}
7175

72-
function getValueSchema(dataType?: DataType) {
76+
function getValueSchema(emptyValueStr: string, dataType?: DataType, settable_to_none?: boolean) {
7377
let schema: Schema;
7478
// set type
7579
switch (dataType) {
7680
case DataType.DOUBLE:
77-
schema = yup.number();
81+
schema = settable_to_none
82+
? yup.string().test('is-number-or-none', 'NumericValueOrEmptyField', (value) => {
83+
return value === emptyValueStr || !isNaN(Number(value));
84+
})
85+
: yup.number();
7886
break;
7987
case DataType.INTEGER:
8088
schema = yup.number().integer();
@@ -87,7 +95,8 @@ function getValueSchema(dataType?: DataType) {
8795
schema = yup.boolean();
8896
break;
8997
case DataType.STRING:
90-
return yup.string().nullable();
98+
schema = yup.string();
99+
break;
91100
default:
92101
schema = yup.number();
93102
}

src/components/dialogs/network-modifications/by-filter/by-assignment/assignment/assignment.type.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export type FieldOptionType = {
3232
label: string;
3333
unit?: string;
3434
dataType: DataType;
35+
// if settableToNone is true it is possible to save an empty value which means "unset".
36+
// undefined means false ie. cannot be set to empty/null/none
37+
settableToNone?: boolean;
3538
values?: Option[];
3639
outputConverter?: (value: number) => number | undefined;
3740
inputConverter?: (value: number) => number | undefined;

src/components/dialogs/network-modifications/by-filter/by-assignment/modification-by-assignment-dialog.tsx

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
FieldType,
1515
useSnackMessage,
1616
} from '@gridsuite/commons-ui';
17-
import { FC, useCallback, useEffect } from 'react';
17+
import { FC, useCallback, useEffect, useMemo } from 'react';
1818
import { FetchStatus } from '../../../../../services/utils';
1919
import { useForm } from 'react-hook-form';
2020
import { ModificationDialog } from '../../../commons/modificationDialog';
@@ -31,14 +31,7 @@ import {
3131
} from './assignment/assignment-utils';
3232
import { Assignment, ModificationByAssignment } from './assignment/assignment.type';
3333
import { DeepNullable } from '../../../../utils/ts-utils';
34-
35-
const formSchema = yup
36-
.object()
37-
.shape({
38-
[EQUIPMENT_TYPE_FIELD]: yup.string().required(),
39-
[ASSIGNMENTS]: getAssignmentsSchema(),
40-
})
41-
.required();
34+
import { useIntl } from 'react-intl';
4235

4336
const emptyFormData = {
4437
[EQUIPMENT_TYPE_FIELD]: '',
@@ -55,6 +48,19 @@ const ModificationByAssignmentDialog: FC<any> = ({
5548
}) => {
5649
const currentNodeUuid = currentNode.id;
5750
const { snackError } = useSnackMessage();
51+
const intl = useIntl();
52+
53+
const emptyValueStr = useMemo(() => {
54+
return intl.formatMessage({ id: 'EmptyField' });
55+
}, [intl]);
56+
57+
const formSchema = yup
58+
.object()
59+
.shape({
60+
[EQUIPMENT_TYPE_FIELD]: yup.string().required(),
61+
[ASSIGNMENTS]: getAssignmentsSchema(emptyValueStr),
62+
})
63+
.required();
5864

5965
// "DeepNullable" to allow deeply null values as default values for required values
6066
// ("undefined" is accepted here in RHF, but it conflicts with MUI behaviour which does not like undefined values)
@@ -79,7 +85,10 @@ const ModificationByAssignmentDialog: FC<any> = ({
7985
const fieldKey = assignment[EDITED_FIELD] as keyof typeof FieldType;
8086
const field = FieldType[fieldKey];
8187
const value = assignment[VALUE_FIELD];
82-
const valueConverted = convertInputValue(field, value);
88+
let valueConverted = convertInputValue(field, value);
89+
if (!valueConverted || valueConverted === '') {
90+
valueConverted = emptyValueStr;
91+
}
8392
return {
8493
...assignment,
8594
[VALUE_FIELD]: valueConverted,
@@ -90,7 +99,7 @@ const ModificationByAssignmentDialog: FC<any> = ({
9099
[ASSIGNMENTS]: assignments,
91100
});
92101
}
93-
}, [editData, reset]);
102+
}, [editData, intl, emptyValueStr, reset]);
94103

95104
const clear = useCallback(() => {
96105
reset(emptyFormData);
@@ -101,8 +110,12 @@ const ModificationByAssignmentDialog: FC<any> = ({
101110
const assignmentsList = formData[ASSIGNMENTS].map((assignment) => {
102111
const dataType = getDataType(assignment[EDITED_FIELD]);
103112
const fieldKey = assignment[EDITED_FIELD] as keyof typeof FieldType;
104-
const field = FieldType[fieldKey];
105-
const value = assignment[VALUE_FIELD];
113+
const field: FieldType = FieldType[fieldKey];
114+
let value = assignment[VALUE_FIELD];
115+
// "Empty" values have to be set to an empty string for the back :
116+
if (value === emptyValueStr) {
117+
value = '';
118+
}
106119
const valueConverted = convertOutputValue(field, value);
107120
return {
108121
...assignment,
@@ -124,7 +137,7 @@ const ModificationByAssignmentDialog: FC<any> = ({
124137
});
125138
});
126139
},
127-
[currentNodeUuid, editData, snackError, studyUuid]
140+
[currentNodeUuid, editData, emptyValueStr, snackError, studyUuid]
128141
);
129142

130143
return (

src/components/network/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const RESULTS_LOADING_DELAY = 500;
1212
export const LOAD_TYPES = [
1313
{ id: 'AUXILIARY', label: 'Auxiliary' },
1414
{ id: 'FICTITIOUS', label: 'Fictitious' },
15-
] as const;
15+
];
1616
// and the undefined/default one (not displayed)
1717
export const UNDEFINED_LOAD_TYPE = 'UNDEFINED';
1818

src/translations/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@
8181
"images": "Images",
8282
"NetworkVisualizations": "Network visualizations",
8383
"None": "None",
84+
"EmptyField": "Empty the field",
85+
"NumericValueOrEmptyField": "Numeric value or Empty the field",
86+
"ValueOrEmptyField": "Value or Empty the field",
8487
"NoneUpcomingOverload": "None",
8588
"Feeders": "Feeder",
8689
"NominalVoltage": "Nominal voltage",

src/translations/fr.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@
8181
"images": "Images",
8282
"NetworkVisualizations": "Images réseau",
8383
"None": "Aucun",
84+
"EmptyField": "Vider le champs",
85+
"NumericValueOrEmptyField": "Valeur numérique ou Vider le champs",
86+
"ValueOrEmptyField": "Valeur ou Vider le champs",
8487
"NoneUpcomingOverload": "Aucune",
8588
"Feeders": "Départs",
8689
"NominalVoltage": "Tension nominale",

0 commit comments

Comments
 (0)