161 lines
4.2 KiB
TypeScript
161 lines
4.2 KiB
TypeScript
import React, { useState, useReducer } from 'react';
|
|
import { Props } from './types';
|
|
import useThrottledEffect from '../../../hooks/useThrottledEffect';
|
|
import Button from '../Button';
|
|
import reducer from './reducer';
|
|
import Condition from './Condition';
|
|
import fieldTypes from './field-types';
|
|
import flattenTopLevelFields from '../../../../utilities/flattenTopLevelFields';
|
|
|
|
import './index.scss';
|
|
|
|
const baseClass = 'where-builder';
|
|
|
|
const validateWhereQuery = (query) => {
|
|
if (query.or.length > 0 && query.or[0].and && query.or[0].and.length > 0) {
|
|
return query;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const reduceFields = (fields) => flattenTopLevelFields(fields).reduce((reduced, field) => {
|
|
if (typeof fieldTypes[field.type] === 'object') {
|
|
const formattedField = {
|
|
label: field.label,
|
|
value: field.name,
|
|
...fieldTypes[field.type],
|
|
props: {
|
|
...field,
|
|
},
|
|
};
|
|
|
|
return [
|
|
...reduced,
|
|
formattedField,
|
|
];
|
|
}
|
|
|
|
return reduced;
|
|
}, []);
|
|
|
|
const WhereBuilder: React.FC<Props> = (props) => {
|
|
const {
|
|
collection,
|
|
collection: {
|
|
labels: {
|
|
plural,
|
|
} = {},
|
|
} = {},
|
|
handleChange,
|
|
} = props;
|
|
|
|
const [where, dispatchWhere] = useReducer(reducer, []);
|
|
const [reducedFields] = useState(() => reduceFields(collection.fields));
|
|
|
|
useThrottledEffect(() => {
|
|
let whereQuery = {
|
|
or: [],
|
|
};
|
|
|
|
if (where) {
|
|
whereQuery.or = where.map((or) => or.reduce((conditions, condition) => {
|
|
const { field, operator, value } = condition;
|
|
if (field && operator && value) {
|
|
return {
|
|
and: [
|
|
...conditions.and,
|
|
{
|
|
[field]: {
|
|
[operator]: value,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
return conditions;
|
|
}, {
|
|
and: [],
|
|
}));
|
|
}
|
|
|
|
whereQuery = validateWhereQuery(whereQuery);
|
|
|
|
if (typeof handleChange === 'function') handleChange(whereQuery);
|
|
}, 500, [where, handleChange]);
|
|
|
|
return (
|
|
<div className={baseClass}>
|
|
{where.length > 0 && (
|
|
<React.Fragment>
|
|
<div className={`${baseClass}__label`}>
|
|
Filter
|
|
{' '}
|
|
{plural}
|
|
{' '}
|
|
where
|
|
</div>
|
|
<ul className={`${baseClass}__or-filters`}>
|
|
{where.map((or, orIndex) => (
|
|
<li key={orIndex}>
|
|
{orIndex !== 0 && (
|
|
<div className={`${baseClass}__label`}>
|
|
Or
|
|
</div>
|
|
)}
|
|
<ul className={`${baseClass}__and-filters`}>
|
|
{or && or.map((_, andIndex) => (
|
|
<li key={andIndex}>
|
|
{andIndex !== 0 && (
|
|
<div className={`${baseClass}__label`}>
|
|
And
|
|
</div>
|
|
)}
|
|
<Condition
|
|
value={where[orIndex][andIndex]}
|
|
orIndex={orIndex}
|
|
andIndex={andIndex}
|
|
key={andIndex}
|
|
fields={reducedFields}
|
|
dispatch={dispatchWhere}
|
|
/>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<Button
|
|
className={`${baseClass}__add-or`}
|
|
icon="plus"
|
|
buttonStyle="icon-label"
|
|
iconPosition="left"
|
|
iconStyle="with-border"
|
|
onClick={() => dispatchWhere({ type: 'add' })}
|
|
>
|
|
Or
|
|
</Button>
|
|
</React.Fragment>
|
|
)}
|
|
{where.length === 0 && (
|
|
<div className={`${baseClass}__no-filters`}>
|
|
<div className={`${baseClass}__label`}>No filters set</div>
|
|
<Button
|
|
className={`${baseClass}__add-first-filter`}
|
|
icon="plus"
|
|
buttonStyle="icon-label"
|
|
iconPosition="left"
|
|
iconStyle="with-border"
|
|
onClick={() => dispatchWhere({ type: 'add' })}
|
|
>
|
|
Add filter
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default WhereBuilder;
|