Skip to content

Commit

Permalink
Improve gui criteria filter (#674)
Browse files Browse the repository at this point in the history
* Improve GUI Criteria Filter forms behaviour and design

Signed-off-by: basseche <[email protected]>
  • Loading branch information
basseche authored Jan 30, 2025
1 parent 10827de commit 5d90904
Show file tree
Hide file tree
Showing 14 changed files with 117 additions and 15 deletions.
4 changes: 2 additions & 2 deletions src/components/filter/expert/expertFilterConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,12 @@ export const OPERATOR_OPTIONS = {
IS_PART_OF: {
name: OperatorType.IS_PART_OF,
customName: OperatorType.IS_PART_OF,
label: 'isPartOf',
label: 'inFilter',
},
IS_NOT_PART_OF: {
name: OperatorType.IS_NOT_PART_OF,
customName: OperatorType.IS_NOT_PART_OF,
label: 'isNotPartOf',
label: 'notInFilter',
},
};

Expand Down
9 changes: 8 additions & 1 deletion src/components/filter/expert/stylesExpertFilter.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@
display: none;
}

.queryBuilder-branches .rule:hover .rule-remove {
visibility: visible;
}

.queryBuilder-branches .rule .rule-remove {
visibility: hidden;
}

.queryBuilder-branches .ruleGroup .ruleGroup::before,
.queryBuilder-branches .ruleGroup .ruleGroup::after {
left: calc(calc(-0.5rem - 1px) - 1px);
Expand All @@ -110,7 +118,6 @@
}

/* Justify layout */
.queryBuilder .ruleGroup-remove,
.queryBuilder .rule-remove {
margin-left: auto;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import { CombinatorSelectorProps } from 'react-querybuilder';
import { useCallback, useState } from 'react';
import { MaterialValueSelector } from '@react-querybuilder/material';
import { PopupConfirmationDialog } from '../../dialogs/popupConfirmationDialog/PopupConfirmationDialog';
import { useSelectAppearance } from '../../../hooks/useSelectAppearance';

export function CombinatorSelector(props: CombinatorSelectorProps) {
const { value, handleOnChange } = props;
const { options, value, handleOnChange } = props;
const [tempCombinator, setTempCombinator] = useState(value);
const [openPopup, setOpenPopup] = useState(false);

Expand All @@ -35,6 +36,7 @@ export function CombinatorSelector(props: CombinatorSelectorProps) {
setTempCombinator(newCombinator);
setOpenPopup(true);
}}
{...useSelectAppearance(options.length)}
/>
</>
);
Expand Down
4 changes: 3 additions & 1 deletion src/components/inputs/reactQueryBuilder/FieldSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { ValueSelectorProps } from 'react-querybuilder';
import { MaterialValueSelector } from '@react-querybuilder/material';
import { useSelectAppearance } from '../../../hooks/useSelectAppearance';

const ITEM_HEIGHT = 32; // default value from React query builder defaultNativeSelectStyles that can't be accessed
const ITEM_PADDING = 4;
Expand All @@ -21,5 +22,6 @@ const MenuProps = {
};

export function FieldSelector(props: Readonly<ValueSelectorProps>) {
return <MaterialValueSelector {...props} MenuProps={MenuProps} />;
const { options } = props;
return <MaterialValueSelector {...props} MenuProps={MenuProps} {...useSelectAppearance(options.length)} />;
}
19 changes: 15 additions & 4 deletions src/components/inputs/reactQueryBuilder/PropertyValueEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { OPERATOR_OPTIONS } from '../../filter/expert/expertFilterConstants';
import { FieldConstants } from '../../../utils/constants/fieldConstants';
import { usePredefinedProperties } from '../../../hooks/usePredefinedProperties';
import { EquipmentType } from '../../../utils';
import { useSelectAppearance } from '../../../hooks/useSelectAppearance';
import { useCustomFilterOptions } from '../../../hooks/useCustomFilterOptions';

const PROPERTY_VALUE_OPERATORS = [OPERATOR_OPTIONS.IN];

Expand Down Expand Up @@ -71,7 +73,7 @@ export function PropertyValueEditor(props: ExpertFilterPropertyProps) {
);

return (
<Grid container item spacing={1}>
<Grid container spacing={1} item>
<Grid item xs={5}>
<Autocomplete
value={propertyName ?? ''}
Expand All @@ -84,9 +86,10 @@ export function PropertyValueEditor(props: ExpertFilterPropertyProps) {
onChange(FieldConstants.PROPERTY_NAME, value);
}}
size="small"
filterOptions={useCustomFilterOptions()}
/>
</Grid>
<Grid item xs={2.5}>
<Grid item xs="auto">
<Select
value={propertyOperator ?? PROPERTY_VALUE_OPERATORS[0].customName}
size="small"
Expand All @@ -95,6 +98,7 @@ export function PropertyValueEditor(props: ExpertFilterPropertyProps) {
onChange={(event, value) => {
onChange(FieldConstants.PROPERTY_OPERATOR, value);
}}
{...useSelectAppearance(PROPERTY_VALUE_OPERATORS.length)}
>
{PROPERTY_VALUE_OPERATORS.map((operator) => (
<MenuItem key={operator.customName} value={operator.customName}>
Expand All @@ -103,19 +107,26 @@ export function PropertyValueEditor(props: ExpertFilterPropertyProps) {
))}
</Select>
</Grid>
<Grid item xs={4.5}>
<Grid item xs>
<Autocomplete
value={propertyValues ?? []}
options={predefinedValues ?? []}
title={valueEditorProps?.title}
multiple
renderInput={(params) => <TextField {...params} error={!valid} />}
renderInput={(params) => (
<TextField
{...params}
error={!valid}
placeholder={propertyValues?.length > 0 ? '' : intl.formatMessage({ id: 'valuesList' })}
/>
)}
freeSolo
autoSelect
onChange={(event, value) => {
onChange(FieldConstants.PROPERTY_VALUES, value);
}}
size="small"
filterOptions={useCustomFilterOptions()}
/>
</Grid>
</Grid>
Expand Down
14 changes: 13 additions & 1 deletion src/components/inputs/reactQueryBuilder/TextValueEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import { ValueEditorProps } from 'react-querybuilder';
import { MaterialValueEditor } from '@react-querybuilder/material';
import { Autocomplete, TextField } from '@mui/material';
import { useIntl } from 'react-intl';
import { useConvertValue } from './hooks/useConvertValue';
import { useValid } from './hooks/useValid';
import { useCustomFilterOptions } from '../../../hooks/useCustomFilterOptions';

export function TextValueEditor(props: ValueEditorProps) {
useConvertValue(props);
Expand All @@ -18,6 +20,9 @@ export function TextValueEditor(props: ValueEditorProps) {

const { value, handleOnChange, title } = props;
// The displayed component totally depends on the value type and not the operator. This way, we have smoother transition.
const customFilterOptions = useCustomFilterOptions();
const intl = useIntl();

if (!Array.isArray(value)) {
return <MaterialValueEditor {...props} />;
}
Expand All @@ -29,9 +34,16 @@ export function TextValueEditor(props: ValueEditorProps) {
onChange={(event, newValue: any) => handleOnChange(newValue)}
multiple
fullWidth
renderInput={(params) => <TextField {...params} error={!valid} />}
renderInput={(params) => (
<TextField
{...params}
error={!valid}
placeholder={value?.length > 0 ? '' : intl.formatMessage({ id: 'valuesList' })}
/>
)}
size="small"
title={title}
filterOptions={customFilterOptions}
/>
);
}
4 changes: 3 additions & 1 deletion src/components/inputs/reactQueryBuilder/ValueSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

import { ValueSelectorProps } from 'react-querybuilder';
import { MaterialValueSelector } from '@react-querybuilder/material';
import { useSelectAppearance } from '../../../hooks/useSelectAppearance';

export function ValueSelector(props: ValueSelectorProps) {
return <MaterialValueSelector {...props} />;
const { options } = props;
return <MaterialValueSelector {...props} {...useSelectAppearance(options.length)} sx={{ border: 'none' }} />;
}
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export * from './usePredefinedProperties';
export * from './usePrevious';
export * from './useSnackMessage';
export * from './useFormatLabelWithUnit';
export * from './useSelectAppearance';
31 changes: 31 additions & 0 deletions src/hooks/useCustomFilterOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (c) 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 { useCallback } from 'react';
import { createFilterOptions, FilterOptionsState } from '@mui/material';

/**
* Hook used to add custom filterOptions, use only when freeSolo = true
*/
export function useCustomFilterOptions() {
return useCallback((options: string[], params: FilterOptionsState<string>) => {
const filter = createFilterOptions<string>();
const filteredOptions = filter(options, params);
const { inputValue } = params;

const isExisting = options.some((option) => inputValue === option);
if (isExisting && options.length === 1 && options[0] === inputValue) {
// exact match : nothing to show
return [];
}

if (inputValue !== '' && !isExisting) {
filteredOptions.push(inputValue);
}
return filteredOptions;
}, []);
}
32 changes: 32 additions & 0 deletions src/hooks/useSelectAppearance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright (c) 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 { useMemo } from 'react';

/** Hook used to modify the appearance of Select into a readonly TextField,
by hiding display button and setting readOnly prop to true
if options list is only one element long.
P.S : Do not use on AutoComplete.
*/
export function useSelectAppearance(listLength: number) {
return useMemo(() => {
if (listLength === 1) {
return {
IconComponent: () => null,
sx: {
boxShadow: 'none',
'.MuiOutlinedInput-notchedOutline': { border: 'none' },
pointerEvents: 'none',
border: 'none',
},
readOnly: true,
disableUnderline: true,
};
}
return {};
}, [listLength]);
}
4 changes: 2 additions & 2 deletions src/translations/en/filterEn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const filterEn = {
not_exists: 'not exists',
between: 'between',
in: 'in',
isPartOf: 'is part of',
isNotPartOf: 'is not part of',
inFilter: 'in filter',
notInFilter: 'not in filter',
emptyRule: 'Filter contains an empty field',
incorrectRule: 'Filter contains an incorrect field',
obsoleteFilter: 'This filter is no longer supported. Please remove it or change its equipment type.',
Expand Down
1 change: 1 addition & 0 deletions src/translations/en/filterExpertEn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,5 @@ export const filterExpertEn = {
'The operator will be changed and will be applied to all the criteria already created in the group.',
lowShortCircuitCurrentLimit: 'Low short-circuit current limit',
highShortCircuitCurrentLimit: 'High short-circuit current limit',
valuesList: 'Values list',
};
1 change: 1 addition & 0 deletions src/translations/fr/filterExpertFr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,5 @@ export const filterExpertFr = {
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',
valuesList: 'Liste de valeurs',
};
4 changes: 2 additions & 2 deletions src/translations/fr/filterFr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const filterFr = {
not_exists: "n'existe pas",
between: 'entre',
in: 'dans',
isPartOf: 'fait partie de',
isNotPartOf: 'ne fait pas partie de',
inFilter: 'dans le filtre',
notInFilter: 'pas dans le filtre',
emptyRule: 'Le filtre contient un champ vide',
incorrectRule: 'Le filtre contient un champ incorrect',
obsoleteFilter: "Ce filtre n'est plus supporté. Veuillez le supprimer ou changer son type d'équipement.",
Expand Down

0 comments on commit 5d90904

Please sign in to comment.