ukrbublik / react-awesome-query-builder

User-friendly query builder for React
https://ukrbublik.github.io/react-awesome-query-builder
MIT License
1.97k stars 494 forks source link

Add custom control #1061

Open karthikCrmwebx opened 3 months ago

karthikCrmwebx commented 3 months ago

How can we add the value and filed drop down for the type select [in the below code for option set] ? can we add our custom component to the query builder?

Screenshot 2024-06-10 at 8 20 27 PM Screenshot 2024-06-10 at 8 21 30 PM
import React, { useState, useEffect, ReactPropTypes } from 'react';
import { Utils as QbUtils, Query, Builder, BasicConfig, JsonTree } from '@react-awesome-query-builder/ui';
import '@react-awesome-query-builder/ui/css/styles.css';
import { AntdConfig, AntdWidgets } from '@react-awesome-query-builder/antd';
import './styles.css';
import { EntityFields } from './types';
import { useDispatch } from 'react-redux';
import { getOptionSet } from 'src/store/optionSet';
import { checkFieldIsOfAnyOptionSet } from 'src/Config/CommonUtils';
interface QueryBuilderProps {
  fields: EntityFields;
  onQuery: (query: string) => void;
  jsonTree?: JsonTree;
  saveJsonTree: (jsonTree: JsonTree) => void;
  rebuildBuilder?: boolean;
}
// or import '@react-awesome-query-builder/ui/css/compact_styles.css';
const InitialConfig = BasicConfig;
const queryValue: any = { id: QbUtils.uuid(), type: 'group' };
const config: BasicConfig = {
  ...InitialConfig,
  settings: {
    ...InitialConfig.settings,
    renderField: (props: any) => <AntdWidgets.FieldTreeSelect {...props} />,
  },
};
const renderBuilder = (props: any) => (
  <div className='query-builder-container' style={{ padding: '10px' }}>
    <div className='query-builder qb-lite'>
      <Builder {...props} />
    </div>
  </div>
);
const columnTypes: { [key: string]: string } = {
  bigint: 'number',
  bit: 'boolean',
  datetime: 'datetime',
  decimal: 'number',
  nvarchar: 'text',
  'nvarchar(250)': 'text',
  uniqueidentifier: 'text',

};

const controlTypes :{ [key: string]: string } = {
  OptionSet : 'select',
  MultiSelectionOptionSet:'select',
  TwoOptions:'select'
}

const QueryBuilder = ({ fields, jsonTree, onQuery, saveJsonTree, rebuildBuilder }: QueryBuilderProps) => {  
  const [fieldsData, setFieldsData] = useState({ ...config });

  useEffect(() => {

    const queryFields = Object.entries(fields).reduce(
       (result, [entityTableName, data]) => ({
        ...result,
        [`[${entityTableName}]`]: {
          label: data.entityName,
          type: '!struct',
          subfields: data.fields.reduce(
             (result, field) => {              
              return {
                ...result,
                [`[${field.fieldName}]`]: {
                  type: checkFieldIsOfAnyOptionSet(field.controlType)
                    ? controlTypes[field.controlType]
                    : columnTypes[(field.columnType || field.controlType).toLowerCase()],
                  label: field.displayName,
                  fieldSettings: checkFieldIsOfAnyOptionSet(field.controlType)
                    ? {
                        listValues: field?.fieldSettings
                        ,
                      }
                    : {},
                  valueSources: checkFieldIsOfAnyOptionSet(field.controlType)
                    ? ["value"]
                    : undefined,
                },
              };},
            {}
          ),
        },
      }),
      {}
    );
    setFieldsData((prev: any) => ({ ...prev, fields: { ...queryFields } }));
  }, [fields]);
  useEffect(() => {
    setFstate({
      tree: QbUtils.checkTree(QbUtils.loadTree(jsonTree || queryValue), fieldsData),
      config: fieldsData,
    });
  }, [fieldsData, rebuildBuilder]);
  const [fstate, setFstate] = useState({
    tree: QbUtils.checkTree(QbUtils.loadTree(jsonTree || queryValue), fieldsData),
    config: fieldsData,
  });
  const onChange = (immutableTree: any, config: any) => {
    // Tip: for better performance you can apply `throttle` - see `examples/demo`
    // setFstate({tree: immutableTree, config: config});

    const str = QbUtils.sqlFormat(immutableTree, config) || '';
    onQuery(str);
    const jsonTree = QbUtils.getTree(immutableTree);
    saveJsonTree(jsonTree);
    // `jsonTree` can be saved to backend, and later loaded to `queryValue`
  };

  return (
    <div className='query-wrapper'>
      {fieldsData.fields && (
        <Query {...fieldsData} value={fstate.tree} onChange={onChange} renderBuilder={renderBuilder} />
      )}
    </div>
  );
};
export default QueryBuilder;
ukrbublik commented 3 months ago

Do you want to add value/type switcher (as on 2nd screenshot) for "custom drop down" (on screenshot 1)?

Or completely different component, not built-in value/type switcher?

karthikCrmwebx commented 3 months ago

both are required for our use cases.

ukrbublik commented 3 months ago

As for 1st case, there should be 2+ fields of type select in your config. And you should use valueSources: ["value", "field"] , not valueSources: ["value"]

karthikCrmwebx commented 3 months ago

can you elaborate more or give give some sample since getting "Object literal may only specify known properties, but 'valueSources' does not exist in type 'SelectWidget<Config, SelectWidgetProps>'. Did you mean to write 'valueSrc'?ts(2561) index.d.ts(1241, 3): The expected type comes from property 'select' which is declared here on type 'CoreWidgets'"

ukrbublik commented 3 months ago

I mean these lines in your code

valueSources: checkFieldIsOfAnyOptionSet(field.controlType)
                ? ["value"]
                : undefined,

Please try without it

karthikCrmwebx commented 3 months ago

tried but still getting same

ukrbublik commented 3 months ago

Could you please create a sandbox to reproduce your issue? You can take this as example https://github.com/ukrbublik/react-awesome-query-builder/tree/master/packages/sandbox_simple

karthikCrmwebx commented 3 months ago

how about adding our custom component to the query builder?

ukrbublik commented 3 months ago

You can use renderBeforeWidget in config.settings

karthikCrmwebx commented 3 months ago

any example can you share

karthikCrmwebx commented 3 months ago

how can we add valueSources : ["value", "field", "ourCustomComponent"] on select of ourCustomComponent, my components should render

ukrbublik commented 3 months ago

valueSources array can only have values: value, field, func

ukrbublik commented 3 months ago

Please share a sandbox to demonstrate your issue and requested change

karthikCrmwebx commented 3 months ago

https://codesandbox.io/p/sandbox/modest-tdd-ywnc6g?file=%2Fsrc%2Fdemo%2Findex.jsx

click on add rule and select optSet values

ukrbublik commented 3 months ago

If you want to compare [optset_values] with other field, you should have any other field in config with type select, eg:

Screenshot 2024-06-19 at 18 33 38
ukrbublik commented 3 months ago

You can render custom select like this:

  renderBeforeWidget: (props) => {
    const {
      selectedField,
      selectedOperator,
    } = props;
    if (selectedField?.includes("[optset_values]") && selectedOperator) {
      return (
        <select
          onChange={({ target: { value } }) => {
            console.log(value);
          }}
        >
          <option value="one">one</option>
          <option value="two">two</option>
        </select>
      );
    }
  },

But right now there is no way to store meta data for rule in store (as you can inspect from the props of renderBeforeWidget). I can add support of this in the future. If it suits you, could you describe your case? Like why do you need to display custom select before the value widget, how do you plan to use its value, store its value, what format? Thanks

karthikCrmwebx commented 3 months ago

thanks for that.

if we have render renderBeforeWidget:(props: any) => <CustomComponent {...props} /> can we stop rendering the highlighted value and enter value in the same sandbox

Screenshot 2024-06-19 at 9 23 19 PM
ukrbublik commented 3 months ago

No. But instead of renderBeforeWidget you can use a custom component (widget) for your field [optset_values]

ukrbublik commented 3 months ago

https://codesandbox.io/p/sandbox/broken-flower-r9hqf9?file=%2Fsrc%2Fdemo%2Fconfig.jsx%3A109%2C20

// use widget select_custom
const fields = {
...
      "[optset_values]": {
        type: "select",
        preferWidgets: ["select_custom"],
...
};

// add widget select_custom
const widgets = {
...
  select_custom: {
    type: "select",
    factory: (props) => {
      console.log("select_custom prtops", props);
      return (
        <div>
          {"custom select"}
          <select
            value={props.value}
            onChange={({ target: { value } }) => {
              props.setValue(value);
            }}
          >
            {props.listValues.map(({ value, title }) => (
              <option value={value}>{title}</option>
            ))}
          </select>
        </div>
      );
    },
  },
};

// allow operators for widget select_custom
const types = {
...
  select: merge({}, InitialConfig.types.select, {
    widgets: {
      select_custom: {
        operators: ["select_equals"],
      },
    },
  }),
};
karthikCrmwebx commented 3 months ago

cannot access your sandbox can you provide access

karthikCrmwebx commented 3 months ago

how can we have multi-select