mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 03:43:34 +00:00 
			
		
		
		
	AttributeFilter in CM
This commit is contained in:
		
							parent
							
								
									f3c76c29d8
								
							
						
					
					
						commit
						321ed4041c
					
				@ -0,0 +1,63 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import moment from 'moment';
 | 
			
		||||
import { DateTime } from '@buffetjs/custom';
 | 
			
		||||
import { DatePicker, InputText, InputNumber, Select, TimePicker } from '@buffetjs/core';
 | 
			
		||||
import { DateWrapper } from './components';
 | 
			
		||||
 | 
			
		||||
function GenericInput({ type, onChange, value, ...rest }) {
 | 
			
		||||
  switch (type) {
 | 
			
		||||
    case 'boolean':
 | 
			
		||||
      return <Select onChange={e => onChange(e.target.value)} value={value} {...rest} />;
 | 
			
		||||
 | 
			
		||||
    case 'date':
 | 
			
		||||
    case 'timestamp':
 | 
			
		||||
    case 'timestampUpdate': {
 | 
			
		||||
      const momentValue = moment(value);
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        <DateWrapper type={type}>
 | 
			
		||||
          <DatePicker onChange={e => onChange(e.target.value._d)} value={momentValue} {...rest} />
 | 
			
		||||
        </DateWrapper>
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case 'datetime': {
 | 
			
		||||
      const momentValue = moment(value);
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        <DateWrapper type={type}>
 | 
			
		||||
          <DateTime onChange={e => onChange(e.target.value)} value={momentValue} {...rest} />
 | 
			
		||||
        </DateWrapper>
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    case 'enumeration':
 | 
			
		||||
      return <Select onChange={e => onChange(e.target.value)} value={value} {...rest} />;
 | 
			
		||||
 | 
			
		||||
    case 'integer':
 | 
			
		||||
    case 'decimal':
 | 
			
		||||
    case 'float':
 | 
			
		||||
      return <InputNumber onChange={e => onChange(e.target.value)} value={value} {...rest} />;
 | 
			
		||||
 | 
			
		||||
    case 'time':
 | 
			
		||||
      return <TimePicker onChange={e => onChange(e.target.value)} value={value} {...rest} />;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * "biginteger" type falls into this section
 | 
			
		||||
     */
 | 
			
		||||
    default:
 | 
			
		||||
      return <InputText onChange={e => onChange(e.target.value)} value={value} {...rest} />;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
GenericInput.defaultProps = {
 | 
			
		||||
  value: undefined,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
GenericInput.propTypes = {
 | 
			
		||||
  onChange: PropTypes.func.isRequired,
 | 
			
		||||
  type: PropTypes.string.isRequired,
 | 
			
		||||
  value: PropTypes.any,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default GenericInput;
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
import { Button } from '@buffetjs/core';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
 | 
			
		||||
export const StyledButton = styled(Button)`
 | 
			
		||||
  width: 100%;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const FormWrapper = styled.form`
 | 
			
		||||
  min-width: 330px;
 | 
			
		||||
  max-width: 400px;
 | 
			
		||||
  padding: 13px 15px;
 | 
			
		||||
 | 
			
		||||
  & > * + * {
 | 
			
		||||
    margin-top: 11px;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const DateWrapper = styled.div`
 | 
			
		||||
  display: ${({ type }) => (type === 'datetime' ? 'flex' : 'block')};
 | 
			
		||||
 | 
			
		||||
  input {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
@ -0,0 +1,47 @@
 | 
			
		||||
import get from 'lodash/get';
 | 
			
		||||
import { useRBACProvider, findMatchingPermissions } from '@strapi/helper-plugin';
 | 
			
		||||
 | 
			
		||||
const NOT_ALLOWED_FILTERS = ['json', 'component', 'media', 'richtext', 'dynamiczone'];
 | 
			
		||||
 | 
			
		||||
const useAllowedAttributes = (contentType, slug) => {
 | 
			
		||||
  const { allPermissions } = useRBACProvider();
 | 
			
		||||
 | 
			
		||||
  let timestamps = get(contentType, ['options', 'timestamps']);
 | 
			
		||||
 | 
			
		||||
  if (!Array.isArray(timestamps)) {
 | 
			
		||||
    timestamps = [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const readPermissionsForSlug = findMatchingPermissions(allPermissions, [
 | 
			
		||||
    {
 | 
			
		||||
      action: 'plugins::content-manager.explorer.read',
 | 
			
		||||
      subject: slug,
 | 
			
		||||
    },
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const readPermissionForAttr = get(readPermissionsForSlug, ['0', 'properties', 'fields'], []);
 | 
			
		||||
  const attributesArray = Object.keys(get(contentType, ['attributes']), {});
 | 
			
		||||
  const allowedAttributes = attributesArray
 | 
			
		||||
    .filter(attr => {
 | 
			
		||||
      const current = get(contentType, ['attributes', attr], {});
 | 
			
		||||
 | 
			
		||||
      if (!current.type) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (NOT_ALLOWED_FILTERS.includes(current.type)) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!readPermissionForAttr.includes(attr) && attr !== 'id' && !timestamps.includes(attr)) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    })
 | 
			
		||||
    .sort();
 | 
			
		||||
 | 
			
		||||
  return allowedAttributes;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default useAllowedAttributes;
 | 
			
		||||
@ -0,0 +1,114 @@
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { Picker, Select } from '@buffetjs/core';
 | 
			
		||||
import {
 | 
			
		||||
  FilterIcon,
 | 
			
		||||
  getFilterType as comparatorsForType,
 | 
			
		||||
  useTracking,
 | 
			
		||||
  useQueryParams,
 | 
			
		||||
} from '@strapi/helper-plugin';
 | 
			
		||||
import { FormattedMessage } from 'react-intl';
 | 
			
		||||
import useAllowedAttributes from './hooks/useAllowedAttributes';
 | 
			
		||||
import getTrad from '../../utils/getTrad';
 | 
			
		||||
import formatAttribute from './utils/formatAttribute';
 | 
			
		||||
import getAttributeType from './utils/getAttributeType';
 | 
			
		||||
import GenericInput from './GenericInput';
 | 
			
		||||
import { StyledButton, FormWrapper } from './components';
 | 
			
		||||
 | 
			
		||||
const AttributeFilter = ({ contentType, slug, metaData }) => {
 | 
			
		||||
  const { trackUsage } = useTracking();
 | 
			
		||||
  const [{ query }, setQuery] = useQueryParams();
 | 
			
		||||
 | 
			
		||||
  const allowedAttributes = useAllowedAttributes(contentType, slug);
 | 
			
		||||
  const [attribute, setAttribute] = useState(allowedAttributes[0]);
 | 
			
		||||
 | 
			
		||||
  const attributeType = getAttributeType(attribute, contentType, metaData);
 | 
			
		||||
  const comparators = comparatorsForType(attributeType);
 | 
			
		||||
  const [comparator, setComparator] = useState(comparators[0].value);
 | 
			
		||||
 | 
			
		||||
  const [value, setValue] = useState();
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Picker
 | 
			
		||||
      renderButtonContent={() => (
 | 
			
		||||
        <>
 | 
			
		||||
          <FilterIcon />
 | 
			
		||||
          <FormattedMessage id="app.utils.filters" />
 | 
			
		||||
        </>
 | 
			
		||||
      )}
 | 
			
		||||
      renderSectionContent={onToggle => {
 | 
			
		||||
        const handleSubmit = e => {
 | 
			
		||||
          e.preventDefault();
 | 
			
		||||
 | 
			
		||||
          const formattedAttribute = formatAttribute(attribute, metaData);
 | 
			
		||||
 | 
			
		||||
          /**
 | 
			
		||||
           * When dealing with a "=" comparator, the filter should have a shape of {'attributeName': 'some value}
 | 
			
		||||
           * otherwise, it should look like { 'attributeName_comparatorName' : 'some value' }
 | 
			
		||||
           */
 | 
			
		||||
          const newFilter =
 | 
			
		||||
            comparator === '='
 | 
			
		||||
              ? { [formattedAttribute]: value }
 | 
			
		||||
              : { [`${formattedAttribute}${comparator}`]: value };
 | 
			
		||||
 | 
			
		||||
          /**
 | 
			
		||||
           * Pushing the filter in the URL for later refreshes or fast access
 | 
			
		||||
           */
 | 
			
		||||
          const _where = query._where || [];
 | 
			
		||||
          _where.push(newFilter);
 | 
			
		||||
          setQuery({ ...query, _where, page: 1 });
 | 
			
		||||
 | 
			
		||||
          /**
 | 
			
		||||
           * Tracking stuff
 | 
			
		||||
           */
 | 
			
		||||
          const useRelation = _where.some(obj => Object.keys(obj)[0].includes('.'));
 | 
			
		||||
          trackUsage('didFilterEntries', { useRelation });
 | 
			
		||||
 | 
			
		||||
          /**
 | 
			
		||||
           * Reset to initial state
 | 
			
		||||
           */
 | 
			
		||||
          setAttribute(allowedAttributes[0]);
 | 
			
		||||
          setComparator(comparators[0].value);
 | 
			
		||||
          setValue(undefined);
 | 
			
		||||
 | 
			
		||||
          onToggle();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
          <FormWrapper onSubmit={handleSubmit}>
 | 
			
		||||
            <Select
 | 
			
		||||
              onChange={e => setAttribute(e.target.value)}
 | 
			
		||||
              name="ct-filter"
 | 
			
		||||
              options={allowedAttributes}
 | 
			
		||||
              value={attribute}
 | 
			
		||||
            />
 | 
			
		||||
            <Select
 | 
			
		||||
              onChange={e => setComparator(e.target.value)}
 | 
			
		||||
              name="comparator"
 | 
			
		||||
              value={comparator}
 | 
			
		||||
              options={comparators.map(comparator => (
 | 
			
		||||
                <FormattedMessage id={comparator.id} key={comparator.value}>
 | 
			
		||||
                  {msg => <option value={comparator.value}>{msg}</option>}
 | 
			
		||||
                </FormattedMessage>
 | 
			
		||||
              ))}
 | 
			
		||||
            />
 | 
			
		||||
            <GenericInput name="input" onChange={setValue} type={attributeType} value={value} />
 | 
			
		||||
            <StyledButton icon type="submit">
 | 
			
		||||
              <FormattedMessage
 | 
			
		||||
                id={getTrad('components.FiltersPickWrapper.PluginHeader.actions.apply')}
 | 
			
		||||
              />
 | 
			
		||||
            </StyledButton>
 | 
			
		||||
          </FormWrapper>
 | 
			
		||||
        );
 | 
			
		||||
      }}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AttributeFilter.propTypes = {
 | 
			
		||||
  contentType: PropTypes.object.isRequired,
 | 
			
		||||
  metaData: PropTypes.object.isRequired,
 | 
			
		||||
  slug: PropTypes.string.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default AttributeFilter;
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
import get from 'lodash/get';
 | 
			
		||||
 | 
			
		||||
const formatAttribute = (attributeName, metaData) => {
 | 
			
		||||
  const mainField = get(metaData, [attributeName, 'list', 'mainField', 'name']);
 | 
			
		||||
 | 
			
		||||
  if (mainField) {
 | 
			
		||||
    return `${attributeName}.${mainField}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return attributeName;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default formatAttribute;
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
import get from 'lodash/get';
 | 
			
		||||
 | 
			
		||||
const getAttributeType = (attributeName, contentType, metaData) => {
 | 
			
		||||
  let attributeType = get(contentType, ['attributes', attributeName, 'type'], '');
 | 
			
		||||
 | 
			
		||||
  if (attributeType === 'relation') {
 | 
			
		||||
    attributeType = get(metaData, [attributeName, 'list', 'mainField', 'schema', 'type'], 'string');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return attributeType === 'string' ? 'text' : attributeType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default getAttributeType;
 | 
			
		||||
@ -1,56 +0,0 @@
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
 | 
			
		||||
/* eslint-disable indent */
 | 
			
		||||
const Wrapper = styled.div`
 | 
			
		||||
  margin-top: -6px;
 | 
			
		||||
  > div {
 | 
			
		||||
    padding-top: 2px;
 | 
			
		||||
    &:not(:first-of-type) {
 | 
			
		||||
      padding-top: 9px;
 | 
			
		||||
      padding-bottom: 2px;
 | 
			
		||||
      &:last-of-type:nth-of-type(even) {
 | 
			
		||||
        padding-bottom: 11px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const Span = styled.span`
 | 
			
		||||
  vertical-align: text-top;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
 | 
			
		||||
  &:after {
 | 
			
		||||
    margin-left: 2px;
 | 
			
		||||
    content: '\f077';
 | 
			
		||||
    font-family: FontAwesome;
 | 
			
		||||
    font-size: 10px;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const Flex = styled.div`
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: flex-end;
 | 
			
		||||
  padding: 0 0 10px 30px !important;
 | 
			
		||||
  margin-top: -10px;
 | 
			
		||||
  color: #c3c5c8;
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const Div = styled.div`
 | 
			
		||||
  width: calc(100% + 60px);
 | 
			
		||||
  margin: ${props => (props.show ? '-100px -30px 30px' : `-${props.number}px -30px 103px`)};
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  box-shadow: 3px 2px 4px #e3e9f3;
 | 
			
		||||
  padding: 18px 30px 0px 30px;
 | 
			
		||||
  transition: ${props => {
 | 
			
		||||
    if (props.anim) {
 | 
			
		||||
      return props.show
 | 
			
		||||
        ? 'margin-top .3s ease-out, margin-bottom .2s ease-out'
 | 
			
		||||
        : 'margin .3s ease-in';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return '';
 | 
			
		||||
  }};
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export { Div, Flex, Span, Wrapper };
 | 
			
		||||
@ -1,256 +0,0 @@
 | 
			
		||||
import React, { memo, useCallback, useMemo, useReducer, useRef } from 'react';
 | 
			
		||||
import { withRouter } from 'react-router';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { capitalize, get } from 'lodash';
 | 
			
		||||
import { Collapse } from 'reactstrap';
 | 
			
		||||
import { FormattedMessage } from 'react-intl';
 | 
			
		||||
import {
 | 
			
		||||
  PluginHeader,
 | 
			
		||||
  getFilterType,
 | 
			
		||||
  useRBACProvider,
 | 
			
		||||
  findMatchingPermissions,
 | 
			
		||||
  useTracking,
 | 
			
		||||
} from '@strapi/helper-plugin';
 | 
			
		||||
import { formatFiltersToQuery, getTrad } from '../../utils';
 | 
			
		||||
import Container from '../Container';
 | 
			
		||||
import FilterPickerOption from '../FilterPickerOption';
 | 
			
		||||
import { Flex, Span, Wrapper } from './components';
 | 
			
		||||
import init from './init';
 | 
			
		||||
import reducer, { initialState } from './reducer';
 | 
			
		||||
 | 
			
		||||
const NOT_ALLOWED_FILTERS = ['json', 'component', 'media', 'richtext', 'dynamiczone'];
 | 
			
		||||
 | 
			
		||||
function FilterPicker({
 | 
			
		||||
  contentType,
 | 
			
		||||
  filters,
 | 
			
		||||
  isOpen,
 | 
			
		||||
  metadatas,
 | 
			
		||||
  name,
 | 
			
		||||
  toggleFilterPickerState,
 | 
			
		||||
  setQuery,
 | 
			
		||||
  slug,
 | 
			
		||||
}) {
 | 
			
		||||
  const { trackUsage } = useTracking();
 | 
			
		||||
  const trackUsageRef = useRef(trackUsage);
 | 
			
		||||
  const { allPermissions } = useRBACProvider();
 | 
			
		||||
  const readActionAllowedFields = useMemo(() => {
 | 
			
		||||
    const matchingPermissions = findMatchingPermissions(allPermissions, [
 | 
			
		||||
      {
 | 
			
		||||
        action: 'plugins::content-manager.explorer.read',
 | 
			
		||||
        subject: slug,
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    return get(matchingPermissions, ['0', 'properties', 'fields'], []);
 | 
			
		||||
  }, [allPermissions, slug]);
 | 
			
		||||
 | 
			
		||||
  let timestamps = get(contentType, ['options', 'timestamps']);
 | 
			
		||||
 | 
			
		||||
  if (!Array.isArray(timestamps)) {
 | 
			
		||||
    timestamps = [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const actions = [
 | 
			
		||||
    {
 | 
			
		||||
      label: getTrad('components.FiltersPickWrapper.PluginHeader.actions.clearAll'),
 | 
			
		||||
      kind: 'secondary',
 | 
			
		||||
      onClick: () => {
 | 
			
		||||
        toggleFilterPickerState();
 | 
			
		||||
        setQuery({ _where: [] }, 'remove');
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      label: getTrad('components.FiltersPickWrapper.PluginHeader.actions.apply'),
 | 
			
		||||
      kind: 'primary',
 | 
			
		||||
      type: 'submit',
 | 
			
		||||
    },
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  const allowedAttributes = Object.keys(get(contentType, ['attributes']), {})
 | 
			
		||||
    .filter(attr => {
 | 
			
		||||
      const current = get(contentType, ['attributes', attr], {});
 | 
			
		||||
 | 
			
		||||
      if (!readActionAllowedFields.includes(attr) && attr !== 'id' && !timestamps.includes(attr)) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return !NOT_ALLOWED_FILTERS.includes(current.type) && current.type !== undefined;
 | 
			
		||||
    })
 | 
			
		||||
    .sort()
 | 
			
		||||
    .map(attr => {
 | 
			
		||||
      const current = get(contentType, ['attributes', attr], {});
 | 
			
		||||
 | 
			
		||||
      return { name: attr, type: current.type, options: current.enum || null };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  const [state, dispatch] = useReducer(reducer, initialState, () =>
 | 
			
		||||
    init(initialState, allowedAttributes[0] || {})
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const modifiedData = state.get('modifiedData').toJS();
 | 
			
		||||
  const handleChange = ({ target: { name, value } }) => {
 | 
			
		||||
    dispatch({
 | 
			
		||||
      type: 'ON_CHANGE',
 | 
			
		||||
      keys: name.split('.'),
 | 
			
		||||
      value,
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const renderTitle = () => (
 | 
			
		||||
    <FormattedMessage id={getTrad('components.FiltersPickWrapper.PluginHeader.title.filter')}>
 | 
			
		||||
      {message => (
 | 
			
		||||
        <span>
 | 
			
		||||
          {capitalize(name)} - 
 | 
			
		||||
          <span>{message}</span>
 | 
			
		||||
        </span>
 | 
			
		||||
      )}
 | 
			
		||||
    </FormattedMessage>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const initialFilter = useMemo(() => {
 | 
			
		||||
    const type = get(allowedAttributes, [0, 'type'], '');
 | 
			
		||||
    const [filter] = getFilterType(type);
 | 
			
		||||
 | 
			
		||||
    let value = '';
 | 
			
		||||
 | 
			
		||||
    switch (type) {
 | 
			
		||||
      case 'boolean': {
 | 
			
		||||
        value = 'true';
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 'number': {
 | 
			
		||||
        value = 0;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 'enumeration': {
 | 
			
		||||
        value = get(allowedAttributes, [0, 'options', 0], '');
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      default: {
 | 
			
		||||
        value = '';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const initFilter = {
 | 
			
		||||
      name: get(allowedAttributes, [0, 'name'], ''),
 | 
			
		||||
      filter: filter.value,
 | 
			
		||||
      value,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return initFilter;
 | 
			
		||||
  }, [allowedAttributes]);
 | 
			
		||||
 | 
			
		||||
  // Set the filters when the collapse is opening
 | 
			
		||||
  const handleEntering = () => {
 | 
			
		||||
    const currentFilters = filters;
 | 
			
		||||
    const initialFilters = currentFilters.length ? currentFilters : [initialFilter];
 | 
			
		||||
 | 
			
		||||
    dispatch({
 | 
			
		||||
      type: 'SET_FILTERS',
 | 
			
		||||
      initialFilters,
 | 
			
		||||
      attributes: get(contentType, 'attributes', {}),
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const addFilter = () => {
 | 
			
		||||
    dispatch({
 | 
			
		||||
      type: 'ADD_FILTER',
 | 
			
		||||
      filter: initialFilter,
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleSubmit = useCallback(
 | 
			
		||||
    e => {
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      const nextFilters = formatFiltersToQuery(modifiedData, metadatas);
 | 
			
		||||
      const useRelation = nextFilters._where.some(obj => Object.keys(obj)[0].includes('.'));
 | 
			
		||||
 | 
			
		||||
      trackUsageRef.current('didFilterEntries', { useRelation });
 | 
			
		||||
      setQuery({ ...nextFilters, page: 1 });
 | 
			
		||||
      toggleFilterPickerState();
 | 
			
		||||
    },
 | 
			
		||||
    [modifiedData, setQuery, toggleFilterPickerState, metadatas]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const handleRemoveFilter = index => {
 | 
			
		||||
    if (index === 0 && modifiedData.length === 1) {
 | 
			
		||||
      toggleFilterPickerState();
 | 
			
		||||
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dispatch({
 | 
			
		||||
      type: 'REMOVE_FILTER',
 | 
			
		||||
      index,
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getAttributeType = useCallback(
 | 
			
		||||
    filter => {
 | 
			
		||||
      const attributeType = get(contentType, ['attributes', filter.name, 'type'], '');
 | 
			
		||||
 | 
			
		||||
      if (attributeType === 'relation') {
 | 
			
		||||
        return get(metadatas, [filter.name, 'list', 'mainField', 'schema', 'type'], 'string');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return attributeType;
 | 
			
		||||
    },
 | 
			
		||||
    [contentType, metadatas]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Collapse isOpen={isOpen} onEntering={handleEntering}>
 | 
			
		||||
      <Container style={{ backgroundColor: 'white', paddingBottom: 0 }}>
 | 
			
		||||
        <form onSubmit={handleSubmit}>
 | 
			
		||||
          <PluginHeader
 | 
			
		||||
            actions={actions}
 | 
			
		||||
            title={renderTitle}
 | 
			
		||||
            description={{
 | 
			
		||||
              id: getTrad('components.FiltersPickWrapper.PluginHeader.description'),
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <Wrapper>
 | 
			
		||||
            {modifiedData.map((filter, key) => (
 | 
			
		||||
              <FilterPickerOption
 | 
			
		||||
                {...filter}
 | 
			
		||||
                allowedAttributes={allowedAttributes}
 | 
			
		||||
                index={key}
 | 
			
		||||
                modifiedData={modifiedData}
 | 
			
		||||
                onChange={handleChange}
 | 
			
		||||
                onClickAddFilter={addFilter}
 | 
			
		||||
                onRemoveFilter={handleRemoveFilter}
 | 
			
		||||
                type={getAttributeType(filter)}
 | 
			
		||||
                showAddButton={key === modifiedData.length - 1}
 | 
			
		||||
                // eslint-disable-next-line react/no-array-index-key
 | 
			
		||||
                key={key}
 | 
			
		||||
              />
 | 
			
		||||
            ))}
 | 
			
		||||
          </Wrapper>
 | 
			
		||||
          <Flex>
 | 
			
		||||
            <Span onClick={toggleFilterPickerState}>
 | 
			
		||||
              <FormattedMessage id="content-manager.components.FiltersPickWrapper.hide" />
 | 
			
		||||
               
 | 
			
		||||
            </Span>
 | 
			
		||||
          </Flex>
 | 
			
		||||
        </form>
 | 
			
		||||
      </Container>
 | 
			
		||||
    </Collapse>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FilterPicker.defaultProps = {
 | 
			
		||||
  name: '',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
FilterPicker.propTypes = {
 | 
			
		||||
  contentType: PropTypes.object.isRequired,
 | 
			
		||||
  filters: PropTypes.array.isRequired,
 | 
			
		||||
  isOpen: PropTypes.bool.isRequired,
 | 
			
		||||
  metadatas: PropTypes.object.isRequired,
 | 
			
		||||
  name: PropTypes.string,
 | 
			
		||||
  setQuery: PropTypes.func.isRequired,
 | 
			
		||||
  slug: PropTypes.string.isRequired,
 | 
			
		||||
  toggleFilterPickerState: PropTypes.func.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default withRouter(memo(FilterPicker));
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
import { fromJS } from 'immutable';
 | 
			
		||||
import { getFilterType } from '@strapi/helper-plugin';
 | 
			
		||||
import { get } from 'lodash';
 | 
			
		||||
 | 
			
		||||
function init(initialState, { name, type, options }) {
 | 
			
		||||
  // Create the first filter
 | 
			
		||||
  const [filter] = getFilterType(type);
 | 
			
		||||
  let value = '';
 | 
			
		||||
 | 
			
		||||
  if (type === 'boolean') {
 | 
			
		||||
    value = 'true';
 | 
			
		||||
  } else if (type === 'number') {
 | 
			
		||||
    value = 0;
 | 
			
		||||
  } else if (type === 'enumeration') {
 | 
			
		||||
    value = get(options, [0], '');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const initialFilter = { name, filter: filter.value, value };
 | 
			
		||||
 | 
			
		||||
  return initialState
 | 
			
		||||
    .update('initialData', () => fromJS([initialFilter]))
 | 
			
		||||
    .update('modifiedData', () => fromJS([initialFilter]));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default init;
 | 
			
		||||
@ -1,58 +0,0 @@
 | 
			
		||||
import { fromJS } from 'immutable';
 | 
			
		||||
 | 
			
		||||
const initialState = fromJS({
 | 
			
		||||
  attributes: {},
 | 
			
		||||
  initialData: [],
 | 
			
		||||
  modifiedData: [],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function reducer(state, action) {
 | 
			
		||||
  switch (action.type) {
 | 
			
		||||
    case 'ADD_FILTER':
 | 
			
		||||
      return state.update('modifiedData', list => list.push(fromJS(action.filter)));
 | 
			
		||||
    case 'ON_CHANGE': {
 | 
			
		||||
      const [index, key] = action.keys;
 | 
			
		||||
 | 
			
		||||
      return state
 | 
			
		||||
        .updateIn(['modifiedData', ...action.keys], () => {
 | 
			
		||||
          if (action.value && action.value._isAMomentObject === true) {
 | 
			
		||||
            return action.value.toISOString();
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return action.value;
 | 
			
		||||
        })
 | 
			
		||||
        .updateIn(['modifiedData', index, 'value'], value => {
 | 
			
		||||
          if (key === 'name') {
 | 
			
		||||
            const attribute = state.getIn(['attributes', action.value]);
 | 
			
		||||
            const attributeType = attribute.get('type');
 | 
			
		||||
 | 
			
		||||
            if (attributeType === 'boolean') {
 | 
			
		||||
              return 'true';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (attributeType === 'enumeration') {
 | 
			
		||||
              return attribute.getIn(['enum', '0']) || '';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return '';
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return value;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    case 'REMOVE_FILTER':
 | 
			
		||||
      return state.removeIn(['modifiedData', action.index]);
 | 
			
		||||
    case 'RESET_FILTERS':
 | 
			
		||||
      return initialState;
 | 
			
		||||
    case 'SET_FILTERS':
 | 
			
		||||
      return state
 | 
			
		||||
        .update('attributes', () => fromJS(action.attributes))
 | 
			
		||||
        .update('initialData', () => fromJS(action.initialFilters))
 | 
			
		||||
        .update('modifiedData', () => fromJS(action.initialFilters));
 | 
			
		||||
    default:
 | 
			
		||||
      return state;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default reducer;
 | 
			
		||||
export { initialState };
 | 
			
		||||
@ -1,58 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * InputWithAutoFocus that programatically manage the autofocus of another one
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { memo } from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
 | 
			
		||||
import { DateTime } from '@buffetjs/custom';
 | 
			
		||||
import { DatePicker, InputText, InputNumber, Select, TimePicker } from '@buffetjs/core';
 | 
			
		||||
import { InputWrapperDate } from './components';
 | 
			
		||||
 | 
			
		||||
const getInputType = attrType => {
 | 
			
		||||
  switch (attrType) {
 | 
			
		||||
    case 'boolean':
 | 
			
		||||
      return Select;
 | 
			
		||||
    case 'date':
 | 
			
		||||
    case 'timestamp':
 | 
			
		||||
    case 'timestampUpdate':
 | 
			
		||||
      return DatePicker;
 | 
			
		||||
    case 'datetime':
 | 
			
		||||
      return DateTime;
 | 
			
		||||
    case 'enumeration':
 | 
			
		||||
      return Select;
 | 
			
		||||
    case 'integer':
 | 
			
		||||
    case 'biginteger':
 | 
			
		||||
    case 'decimal':
 | 
			
		||||
    case 'float':
 | 
			
		||||
      return InputNumber;
 | 
			
		||||
    case 'time':
 | 
			
		||||
      return TimePicker;
 | 
			
		||||
    default:
 | 
			
		||||
      return InputText;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function Input({ type, ...rest }) {
 | 
			
		||||
  const Component = getInputType(type);
 | 
			
		||||
  let style = type !== 'time' ? { width: '210px' } : {};
 | 
			
		||||
 | 
			
		||||
  if (['integer', 'biginteger', 'float', 'decimal'].includes(type)) {
 | 
			
		||||
    style = { marginRight: '15px' };
 | 
			
		||||
  }
 | 
			
		||||
  const styles = type === 'boolean' ? { minWidth: '100px', maxWidth: '200px' } : style;
 | 
			
		||||
  const wrapperStyle = { marginRight: '15px' };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <InputWrapperDate type={type || 'text'} style={wrapperStyle}>
 | 
			
		||||
      <Component {...rest} style={styles} autoComplete="off" />
 | 
			
		||||
    </InputWrapperDate>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Input.propTypes = {
 | 
			
		||||
  type: PropTypes.string.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default memo(Input);
 | 
			
		||||
@ -1,14 +0,0 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { FormattedMessage } from 'react-intl';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
 | 
			
		||||
const Option = ({ id, value }) => {
 | 
			
		||||
  return <FormattedMessage id={id}>{msg => <option value={value}>{msg}</option>}</FormattedMessage>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Option.propTypes = {
 | 
			
		||||
  id: PropTypes.string.isRequired,
 | 
			
		||||
  value: PropTypes.string.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Option;
 | 
			
		||||
@ -1,60 +0,0 @@
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
 | 
			
		||||
const Wrapper = styled.div`
 | 
			
		||||
  min-height: 38px;
 | 
			
		||||
  border-left: ${props => props.borderLeft && '3px solid #007EFF'};
 | 
			
		||||
  padding-left: ${props => (props.borderLeft ? '10px' : '13px')};
 | 
			
		||||
  margin-bottom: 0px !important;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const InputWrapper = styled.div`
 | 
			
		||||
  display: flex;
 | 
			
		||||
  input,
 | 
			
		||||
  select {
 | 
			
		||||
    margin: 0px 5px !important;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const InputWrapperDate = styled.div`
 | 
			
		||||
  margin-right: 10px;
 | 
			
		||||
  span {
 | 
			
		||||
    left: 5px;
 | 
			
		||||
  }
 | 
			
		||||
  .rc-input-number-handler-wrap {
 | 
			
		||||
    right: -5px !important;
 | 
			
		||||
  }
 | 
			
		||||
  .rc-input-number-input-wrap {
 | 
			
		||||
    max-width: 210px;
 | 
			
		||||
    overflow: visible;
 | 
			
		||||
  }
 | 
			
		||||
  > div {
 | 
			
		||||
    width: 210px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ${({ type }) => {
 | 
			
		||||
    if (type === 'datetime') {
 | 
			
		||||
      return `
 | 
			
		||||
      > div {
 | 
			
		||||
        width: 300px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      `;
 | 
			
		||||
    }
 | 
			
		||||
  }}
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const Input = styled.input`
 | 
			
		||||
  height: 3.4rem;
 | 
			
		||||
  margin-top: 0.9rem;
 | 
			
		||||
  padding-left: 1rem;
 | 
			
		||||
  background-size: 0 !important;
 | 
			
		||||
  border: 1px solid #e3e9f3;
 | 
			
		||||
  border-radius: 0.25rem;
 | 
			
		||||
  line-height: 3.4rem;
 | 
			
		||||
  font-size: 1.3rem;
 | 
			
		||||
  font-family: 'Lato' !important;
 | 
			
		||||
  box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05);
 | 
			
		||||
`;
 | 
			
		||||
export { InputWrapper, Wrapper, InputWrapperDate, Input };
 | 
			
		||||
@ -1,101 +0,0 @@
 | 
			
		||||
import React, { memo } from 'react';
 | 
			
		||||
import { get, isEmpty } from 'lodash';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { CircleButton, getFilterType } from '@strapi/helper-plugin';
 | 
			
		||||
import { Select } from '@buffetjs/core';
 | 
			
		||||
 | 
			
		||||
import { InputWrapper, Wrapper } from './components';
 | 
			
		||||
import Input from './Input';
 | 
			
		||||
import Option from './Option';
 | 
			
		||||
 | 
			
		||||
const styles = {
 | 
			
		||||
  select: {
 | 
			
		||||
    minWidth: '170px',
 | 
			
		||||
    maxWidth: '200px',
 | 
			
		||||
  },
 | 
			
		||||
  selectMiddle: {
 | 
			
		||||
    minWidth: '130px',
 | 
			
		||||
    maxWidth: '200px',
 | 
			
		||||
    marginLeft: '10px',
 | 
			
		||||
    marginRight: '10px',
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function FilterPickerOption({
 | 
			
		||||
  allowedAttributes,
 | 
			
		||||
  modifiedData,
 | 
			
		||||
  index,
 | 
			
		||||
  onChange,
 | 
			
		||||
  onClickAddFilter,
 | 
			
		||||
  onRemoveFilter,
 | 
			
		||||
  value,
 | 
			
		||||
  showAddButton,
 | 
			
		||||
  type,
 | 
			
		||||
}) {
 | 
			
		||||
  const filtersOptions = getFilterType(type);
 | 
			
		||||
  const currentFilterName = get(modifiedData, [index, 'name'], '');
 | 
			
		||||
  const currentFilterData = allowedAttributes.find(attr => attr.name === currentFilterName);
 | 
			
		||||
  const options = get(currentFilterData, ['options'], null) || ['true', 'false'];
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Wrapper borderLeft={!isEmpty(value)}>
 | 
			
		||||
      <InputWrapper>
 | 
			
		||||
        <CircleButton type="button" isRemoveButton onClick={() => onRemoveFilter(index)} />
 | 
			
		||||
        <Select
 | 
			
		||||
          onChange={e => {
 | 
			
		||||
            // Change the attribute
 | 
			
		||||
            onChange(e);
 | 
			
		||||
            // Change the default filter so it reset to the common one which is '='
 | 
			
		||||
            onChange({ target: { name: `${index}.filter`, value: '=' } });
 | 
			
		||||
          }}
 | 
			
		||||
          name={`${index}.name`}
 | 
			
		||||
          value={currentFilterName}
 | 
			
		||||
          options={allowedAttributes.map(attr => attr.name)}
 | 
			
		||||
          style={styles.select}
 | 
			
		||||
        />
 | 
			
		||||
        <Select
 | 
			
		||||
          onChange={onChange}
 | 
			
		||||
          name={`${index}.filter`}
 | 
			
		||||
          options={filtersOptions.map(option => (
 | 
			
		||||
            <Option {...option} key={option.value} />
 | 
			
		||||
          ))}
 | 
			
		||||
          style={styles.selectMiddle}
 | 
			
		||||
          value={get(modifiedData, [index, 'filter'], '')}
 | 
			
		||||
        />
 | 
			
		||||
        <Input
 | 
			
		||||
          type={type}
 | 
			
		||||
          name={`${index}.value`}
 | 
			
		||||
          value={get(modifiedData, [index, 'value'], '')}
 | 
			
		||||
          options={options}
 | 
			
		||||
          onChange={onChange}
 | 
			
		||||
        />
 | 
			
		||||
        {showAddButton && <CircleButton type="button" onClick={onClickAddFilter} />}
 | 
			
		||||
      </InputWrapper>
 | 
			
		||||
    </Wrapper>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FilterPickerOption.defaultProps = {
 | 
			
		||||
  allowedAttributes: [],
 | 
			
		||||
  modifiedData: [],
 | 
			
		||||
  index: -1,
 | 
			
		||||
  onChange: () => {},
 | 
			
		||||
  onClickAddFilter: () => {},
 | 
			
		||||
  onRemoveFilter: () => {},
 | 
			
		||||
  value: null,
 | 
			
		||||
  type: 'string',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
FilterPickerOption.propTypes = {
 | 
			
		||||
  allowedAttributes: PropTypes.array,
 | 
			
		||||
  modifiedData: PropTypes.array,
 | 
			
		||||
  index: PropTypes.number,
 | 
			
		||||
  onChange: PropTypes.func,
 | 
			
		||||
  onClickAddFilter: PropTypes.func,
 | 
			
		||||
  onRemoveFilter: PropTypes.func,
 | 
			
		||||
  showAddButton: PropTypes.bool.isRequired,
 | 
			
		||||
  type: PropTypes.string,
 | 
			
		||||
  value: PropTypes.any,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default memo(FilterPickerOption);
 | 
			
		||||
@ -1,46 +1,10 @@
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { Button, FilterIcon as Filter } from '@strapi/helper-plugin';
 | 
			
		||||
import RemoveIcon from '../../assets/images/icon-cross-blue.svg';
 | 
			
		||||
 | 
			
		||||
const Wrapper = styled.div`
 | 
			
		||||
  padding-top: 1px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const FilterIcon = styled(Filter)`
 | 
			
		||||
  padding: 0 !important;
 | 
			
		||||
  margin: auto !important;
 | 
			
		||||
  > g {
 | 
			
		||||
    stroke: #282b2c;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const AddFilterCta = styled(Button)`
 | 
			
		||||
  display: flex;
 | 
			
		||||
  height: 30px;
 | 
			
		||||
  margin-right: 10px;
 | 
			
		||||
  padding: 0 10px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  background-color: #ffffff;
 | 
			
		||||
  border: 1px solid #e3e9f3;
 | 
			
		||||
  border-radius: 2px;
 | 
			
		||||
  line-height: 28px;
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  font-family: Lato;
 | 
			
		||||
  -webkit-font-smoothing: antialiased;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: #f7f8f8;
 | 
			
		||||
  }
 | 
			
		||||
  &:focus,
 | 
			
		||||
  &:active {
 | 
			
		||||
    outline: 0;
 | 
			
		||||
  }
 | 
			
		||||
  > span {
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const Img = styled.img`
 | 
			
		||||
  height: 7px;
 | 
			
		||||
  margin: auto;
 | 
			
		||||
@ -124,15 +88,4 @@ const Remove = styled.span`
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  AddFilterCta,
 | 
			
		||||
  FilterIcon,
 | 
			
		||||
  FooterWrapper,
 | 
			
		||||
  Img,
 | 
			
		||||
  Label,
 | 
			
		||||
  SelectWrapper,
 | 
			
		||||
  FilterWrapper,
 | 
			
		||||
  Separator,
 | 
			
		||||
  Remove,
 | 
			
		||||
  Wrapper,
 | 
			
		||||
};
 | 
			
		||||
export { FooterWrapper, Img, Label, SelectWrapper, FilterWrapper, Separator, Remove, Wrapper };
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 | 
			
		||||
import { connect } from 'react-redux';
 | 
			
		||||
import { bindActionCreators, compose } from 'redux';
 | 
			
		||||
import { get, isEmpty } from 'lodash';
 | 
			
		||||
import { FormattedMessage, useIntl } from 'react-intl';
 | 
			
		||||
import { useIntl } from 'react-intl';
 | 
			
		||||
import { useHistory, useLocation } from 'react-router-dom';
 | 
			
		||||
import { Header } from '@buffetjs/custom';
 | 
			
		||||
import { Flex, Padded } from '@buffetjs/core';
 | 
			
		||||
@ -26,11 +26,10 @@ import permissions from '../../../permissions';
 | 
			
		||||
import { formatFiltersFromQuery, getRequestUrl, getTrad } from '../../utils';
 | 
			
		||||
import Container from '../../components/Container';
 | 
			
		||||
import CustomTable from '../../components/CustomTable';
 | 
			
		||||
import FilterPicker from '../../components/FilterPicker';
 | 
			
		||||
import Search from '../../components/Search';
 | 
			
		||||
import ListViewProvider from '../../components/ListViewProvider';
 | 
			
		||||
import InjectionZoneList from '../../components/InjectionZoneList';
 | 
			
		||||
import { AddFilterCta, FilterIcon, Wrapper } from './components';
 | 
			
		||||
import { Wrapper } from './components';
 | 
			
		||||
import FieldPicker from './FieldPicker';
 | 
			
		||||
import Filter from './Filter';
 | 
			
		||||
import Footer from './Footer';
 | 
			
		||||
@ -51,6 +50,7 @@ import {
 | 
			
		||||
} from './actions';
 | 
			
		||||
import makeSelectListView from './selectors';
 | 
			
		||||
import { getAllAllowedHeaders, getFirstSortableHeader, buildQueryString } from './utils';
 | 
			
		||||
import AttributeFilter from '../../components/AttributeFilter';
 | 
			
		||||
 | 
			
		||||
const cmPermissions = permissions.contentManager;
 | 
			
		||||
 | 
			
		||||
@ -395,16 +395,6 @@ function ListView({
 | 
			
		||||
        toggleModalDeleteAll={handleToggleModalDeleteAll}
 | 
			
		||||
        setQuery={setQuery}
 | 
			
		||||
      >
 | 
			
		||||
        <FilterPicker
 | 
			
		||||
          contentType={contentType}
 | 
			
		||||
          filters={filters}
 | 
			
		||||
          isOpen={isFilterPickerOpen}
 | 
			
		||||
          metadatas={metadatas}
 | 
			
		||||
          name={label}
 | 
			
		||||
          toggleFilterPickerState={toggleFilterPickerState}
 | 
			
		||||
          setQuery={setQuery}
 | 
			
		||||
          slug={slug}
 | 
			
		||||
        />
 | 
			
		||||
        <Container className="container-fluid">
 | 
			
		||||
          {!isFilterPickerOpen && <Header {...headerProps} isLoading={isLoading && canRead} />}
 | 
			
		||||
          {isSearchable && canRead && (
 | 
			
		||||
@ -432,10 +422,13 @@ function ListView({
 | 
			
		||||
                  <div className="row" style={{ marginLeft: 0, marginRight: 0 }}>
 | 
			
		||||
                    {isFilterable && (
 | 
			
		||||
                      <>
 | 
			
		||||
                        <AddFilterCta type="button" onClick={toggleFilterPickerState}>
 | 
			
		||||
                          <FilterIcon />
 | 
			
		||||
                          <FormattedMessage id="app.utils.filters" />
 | 
			
		||||
                        </AddFilterCta>
 | 
			
		||||
                        <Padded right size="sm">
 | 
			
		||||
                          <AttributeFilter
 | 
			
		||||
                            contentType={contentType}
 | 
			
		||||
                            slug={slug}
 | 
			
		||||
                            metaData={metadatas}
 | 
			
		||||
                          />
 | 
			
		||||
                        </Padded>
 | 
			
		||||
                        {filters.map(({ filter: filterName, name, value }, key) => (
 | 
			
		||||
                          <Filter
 | 
			
		||||
                            contentType={contentType}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user