mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
4.11k stars 1.27k forks source link

[data grid] Format value shown in the filter button tooltip #5492

Open michGreg opened 2 years ago

michGreg commented 2 years ago

Order ID 💳

44899

Duplicates

Latest version

The problem in depth 🔍

I set valueOptions wich is array of objects and operator isAnyOf, but when i read filterModel it is changed to array of values. The issue is that info tooltip of filtered values shows values instead of labels. Is there any way how to read tooltip of labels instead of values or disable tooltip info?

Column setting

{
    disableColumnMenu: true,
    field: 'signalStrength',
    filterOperators: [{
      value: 'isAnyOf',
      InputComponent: GridFilterInputMultipleSingleSelect,
    }],
    headerName: formatMessage(messages.DevicesDataGrid.columns.signalStrength),
    hideable: true,
    renderCell: (device) => (
      <DeviceSignalStrengthStatus
        signalStrength={device.value.signalStrength}
      />
    ),
    sortable: true,
    valueOptions: [
      { label: 'Very strong', value: '1' },
      { label: 'Strong', value: '2' },
      { label: 'Good', value: '3' },
      { label: 'Poor', value: '4' },
      { label: 'Unknown', value: '5' },
    ],
  }

Tooltip info image

Filter model image

Your environment 🌎

`npx @mui/envinfo` ``` Don't forget to mention which browser you used. Output from `npx @mui/envinfo` goes here. ```
m4theushw commented 2 years ago

We have this same problem with every operator. In the tooltip, we display the value as it is, without any formatting. Another example is the date operator which doesn't format the value to the user's locale. The date should be displayed as dd/mm/YYYY to me.

image

I propose to add a function to GridFilterOperator to format the value of the filter item to another string which it's safe to be displayed in the UI.

diff --git a/packages/grid/x-data-grid/src/models/gridFilterOperator.ts b/packages/grid/x-data-grid/src/models/gridFilterOperator.ts
index 5fc3586d8..ccc50308c 100644
--- a/packages/grid/x-data-grid/src/models/gridFilterOperator.ts
+++ b/packages/grid/x-data-grid/src/models/gridFilterOperator.ts
@@ -42,4 +42,10 @@ export interface GridFilterOperator<R extends GridValidRowModel = any, V = any,
    * The props to pass to the input component in the filter panel for this filter operator.
    */
   InputComponentProps?: Record<string, any>;
+  /**
+   * Converts the value of a filter item to a human-readable form.
+   * @param {GridFilterItem['value']} value The filter item value.
+   * @returns {string} The value formatted to be displayed in the UI.
+   */
+  getValueAsString?: (value: GridFilterItem['value']) => string;
 }

Then, in the filter button, we call it if it's available:

diff --git a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx
index 3206f2833..ef0dde140 100644
--- a/packages/grid/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx
+++ b/packages/grid/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx
@@ -76,6 +76,13 @@ const GridToolbarFilterButton = React.forwardRef<HTMLButtonElement, GridToolbarF
           .getLocaleText(`filterOperator${capitalize(item.operatorValue!)}` as GridTranslationKeys)!
           .toString();

+      const getFilterItemValue = (item: GridFilterItem): string => {
+        const { getValueAsString } = lookup[item.columnField!].filterOperators!.find(
+          (operator) => operator.value === item.operatorValue,
+        )!;
+        return getValueAsString ? getValueAsString(item.value) : item.value;
+      };
+
       return (
         <div>
           {apiRef.current.getLocaleText('toolbarFiltersTooltipActive')(activeFilters.length)}
@@ -85,7 +92,7 @@ const GridToolbarFilterButton = React.forwardRef<HTMLButtonElement, GridToolbarF
                 <li key={index}>
                   {`${lookup[item.columnField!].headerName || item.columnField}
                   ${getOperatorLabel(item)}
-                  ${item.value}`}
+                  ${getFilterItemValue(item)}`}
                 </li>
               )),
             }))}

Is there any way how to read tooltip of labels instead of values or disable tooltip info?

You can disable the tooltip by providing a custom filter button. Here's an example: https://codesandbox.io/s/customtoolbargrid-demo-mui-x-forked-miy29h?file=/demo.tsx

TiagoPortfolio commented 2 years ago

Hi all!

I was about to create an issue but found this one while looking for duplicates. @m4theushw, do you know when this will be available?

Cheers!

ithrforu commented 1 year ago

I have the same problem in DataGridPremium.

TiagoPortfolio commented 1 year ago

Hi all!

With PR #6956 merged, to customize the value displayed I have to do something like this:

filterOperators: getGridSingleSelectOperators().map((operator) => {
  return {
    ...operator,
    getValueAsString: (value) => {
      // Handle anyOf operator
      if (Array.isArray(value)) {
        return value.map((v) => getStatusLabel(v)).join(', ')
      }
      return getStatusLabel(value)
    },
  }
}),

I think this is an overcomplicated solution because, in most use cases, the users want to see the same label of the selected options in the filter tooltip. Wouldn't it be better, for singleSelect type, to just display the label on the filter tooltip as the default behavior to avoid this boilerplate code? In my opinion, it doesn't make sense, for singleSelect, that in the options we see the label of the option image but in the tooltip, we see the value of the option image.

Let me know what you all think! :)

ithrforu commented 1 year ago

Hello, @TiagoPortfolio. I faced the same problem and see the followings solution options:

  1. Call getValueAsString here with something like value.map((v) => valueOptionsMap.get(v).label).join(', ') by default for multiselect/singleselect.

  2. (it's still difficult but can be controlled) Another option is changing getValueAsString signature (packages/grid/x-data-grid/src/models/gridFilterOperator.ts#L46) from GridFilterItem['value'] to GridFilterItem. Then, for example, for a multiselect, the following variant will be possible:

       const getValueAsString = ({value, columnField}) => {
         //  Columns can be taken from arguments of getGridSelectOperators wrapper or context.
         const valueOptions = columns.reduce((acc, prev) => {
           if (prev.field === columnField && prev.valueOptions !== undefined) {
             acc.push(prev.valueOptions);
             return acc;
           }
           return acc;
         }, []);
    
         const valueOptionsMap = new Map<string, Exclude<ValueOptions, string | number>>(
           valueOptions.flat().map((item) => [item.value, item])
         );
    
         return value.map((v) => valueOptionsMap.get(v).label).join(', ');
       },
    
       const gridClientMultiSelectOperators: GridFilterOperator[] = [
         {
             label: t('contains'),
             value: 'contains',
             getValueAsString,
         },
         {
             label: t('not contains'),
             value: 'not contains',
             getValueAsString,
         }
         ...
       ]
       ...

I hope @m4theushw could help us.

pedaars commented 2 months ago

is there any update to this issue?

jerichardson-r7 commented 5 days ago

Any futher update to this?