Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve gui criteria filter #674

Merged
merged 20 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading