mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 02:44:55 +00:00
Merge pull request #5290 from strapi/front-media-lib-search
Front Media lib search & sort by
This commit is contained in:
commit
54a36e6b36
@ -15,9 +15,16 @@ const colors = {
|
||||
'gray-lighter': '#eceeef',
|
||||
'gray-lightest': '#f7f7f9',
|
||||
brightGrey: '#f0f3f8',
|
||||
darkGrey: '#e3e9f3',
|
||||
lightGrey: '#fafafa',
|
||||
lightestGrey: '#fbfbfb',
|
||||
mediumGrey: '#F2F3F4',
|
||||
grey: '#9ea7b8',
|
||||
greyDark: '#292b2c',
|
||||
greyAlpha: 'rgba(227, 233, 243, 0.5)',
|
||||
lightBlue: '#E6F0FB',
|
||||
mediumBlue: '#007EFF',
|
||||
darkBlue: '#AED4FB',
|
||||
|
||||
content: {
|
||||
background: '#fafafb',
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
const sizes = {
|
||||
borderRadius: '2px',
|
||||
header: {
|
||||
height: '6rem',
|
||||
},
|
||||
@ -14,7 +15,6 @@ const sizes = {
|
||||
// TODO
|
||||
md: '30px',
|
||||
},
|
||||
borderRadius: '2px',
|
||||
};
|
||||
|
||||
export default sizes;
|
||||
|
||||
@ -6,7 +6,14 @@ import SearchInfo from '../SearchInfo';
|
||||
import Clear from './Clear';
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
const HeaderSearch = ({ label, onChange, onClear, placeholder, value }) => {
|
||||
const HeaderSearch = ({
|
||||
label,
|
||||
name,
|
||||
onChange,
|
||||
onClear,
|
||||
placeholder,
|
||||
value,
|
||||
}) => {
|
||||
return (
|
||||
<Wrapper>
|
||||
<div>
|
||||
@ -14,6 +21,7 @@ const HeaderSearch = ({ label, onChange, onClear, placeholder, value }) => {
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
type="text"
|
||||
@ -32,6 +40,7 @@ const HeaderSearch = ({ label, onChange, onClear, placeholder, value }) => {
|
||||
|
||||
HeaderSearch.defaultProps = {
|
||||
label: '',
|
||||
name: '',
|
||||
onChange: () => {},
|
||||
onClear: () => {},
|
||||
placeholder: 'Search for an entry',
|
||||
@ -40,6 +49,7 @@ HeaderSearch.defaultProps = {
|
||||
|
||||
HeaderSearch.propTypes = {
|
||||
label: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onClear: PropTypes.func,
|
||||
placeholder: PropTypes.string,
|
||||
|
||||
@ -87,6 +87,9 @@ export {
|
||||
useGlobalContext,
|
||||
} from './contexts/GlobalContext';
|
||||
|
||||
// Hooks
|
||||
export { default as useQuery } from './hooks/useQuery';
|
||||
|
||||
// Utils
|
||||
export { default as auth } from './utils/auth';
|
||||
export { default as cleanData } from './utils/cleanData';
|
||||
@ -101,6 +104,9 @@ export { default as request } from './utils/request';
|
||||
export { default as storeData } from './utils/storeData';
|
||||
export { default as templateObject } from './utils/templateObject';
|
||||
export { default as getYupInnerErrors } from './utils/getYupInnerErrors';
|
||||
export { default as generateFiltersFromSearch } from './utils/generateFiltersFromSearch';
|
||||
export { default as generateSearchFromFilters } from './utils/generateSearchFromFilters';
|
||||
export { default as generateSearchFromObject } from './utils/generateSearchFromObject';
|
||||
|
||||
// SVGS
|
||||
export { default as LayoutIcon } from './svgs/Layout';
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { isEmpty, toString } from 'lodash';
|
||||
/**
|
||||
* Generate filters object from string
|
||||
* @param {String} search
|
||||
@ -51,28 +50,4 @@ const generateFiltersFromSearch = search => {
|
||||
}, []);
|
||||
};
|
||||
|
||||
const generateSearchFromFilters = filters => {
|
||||
return Object.keys(filters)
|
||||
.filter(key => !isEmpty(toString(filters[key])))
|
||||
.map(key => {
|
||||
let ret = `${key}=${filters[key]}`;
|
||||
|
||||
if (key === 'filters') {
|
||||
const formattedFilters = filters[key]
|
||||
.reduce((acc, curr) => {
|
||||
const key =
|
||||
curr.filter === '=' ? curr.name : `${curr.name}${curr.filter}`;
|
||||
acc.push(`${key}=${curr.value}`);
|
||||
|
||||
return acc;
|
||||
}, [])
|
||||
.join('&');
|
||||
ret = formattedFilters;
|
||||
}
|
||||
|
||||
return ret;
|
||||
})
|
||||
.join('&');
|
||||
};
|
||||
|
||||
export { generateFiltersFromSearch, generateSearchFromFilters };
|
||||
export default generateFiltersFromSearch;
|
||||
@ -0,0 +1,27 @@
|
||||
import { isEmpty, toString } from 'lodash';
|
||||
|
||||
const generateSearchFromFilters = filters => {
|
||||
return Object.keys(filters)
|
||||
.filter(key => !isEmpty(toString(filters[key])))
|
||||
.map(key => {
|
||||
let ret = `${key}=${filters[key]}`;
|
||||
|
||||
if (key === 'filters') {
|
||||
const formattedFilters = filters[key]
|
||||
.reduce((acc, curr) => {
|
||||
const key =
|
||||
curr.filter === '=' ? curr.name : `${curr.name}${curr.filter}`;
|
||||
acc.push(`${key}=${curr.value}`);
|
||||
|
||||
return acc;
|
||||
}, [])
|
||||
.join('&');
|
||||
ret = formattedFilters;
|
||||
}
|
||||
|
||||
return ret;
|
||||
})
|
||||
.join('&');
|
||||
};
|
||||
|
||||
export default generateSearchFromFilters;
|
||||
@ -0,0 +1,47 @@
|
||||
import generateFiltersFromSearch from '../generateFiltersFromSearch';
|
||||
|
||||
describe('HELPER PLUGIN | utils | generateFiltersFromSearch', () => {
|
||||
it('should generate an array of filters', () => {
|
||||
const search =
|
||||
'?_sort=id:ASC&bool=true&big_number_ne=1&created_at_lt=2019-08-01T00:00:00Z&date_lte=2019-08-02T00:00:00Z&decimal_number_gt=2&enum_ne=noon&float_number_gte=3';
|
||||
const expected = [
|
||||
{
|
||||
name: 'bool',
|
||||
filter: '=',
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
name: 'big_number',
|
||||
filter: '_ne',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
filter: '_lt',
|
||||
value: '2019-08-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
filter: '_lte',
|
||||
value: '2019-08-02T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'decimal_number',
|
||||
filter: '_gt',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
name: 'enum',
|
||||
filter: '_ne',
|
||||
value: 'noon',
|
||||
},
|
||||
{
|
||||
name: 'float_number',
|
||||
filter: '_gte',
|
||||
value: '3',
|
||||
},
|
||||
];
|
||||
|
||||
expect(generateFiltersFromSearch(search)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,52 @@
|
||||
import generateSearchFromFilters from '../generateSearchFromFilters';
|
||||
|
||||
describe('HELPER PLUGIN | utils | generateSearchFromFilters', () => {
|
||||
it('should return a string with all the applied filters', () => {
|
||||
const data = {
|
||||
_limit: 10,
|
||||
_sort: 'id:ASC',
|
||||
_page: 2,
|
||||
filters: [
|
||||
{
|
||||
name: 'bool',
|
||||
filter: '=',
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
name: 'big_number',
|
||||
filter: '_ne',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
filter: '_lt',
|
||||
value: '2019-08-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
filter: '_lte',
|
||||
value: '2019-08-02T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'decimal_number',
|
||||
filter: '_gt',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
name: 'enum',
|
||||
filter: '_ne',
|
||||
value: 'noon',
|
||||
},
|
||||
{
|
||||
name: 'float_number',
|
||||
filter: '_gte',
|
||||
value: '3',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const expected =
|
||||
'_limit=10&_sort=id:ASC&_page=2&bool=true&big_number_ne=1&created_at_lt=2019-08-01T00:00:00Z&date_lte=2019-08-02T00:00:00Z&decimal_number_gt=2&enum_ne=noon&float_number_gte=3';
|
||||
expect(generateSearchFromFilters(data)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,44 @@
|
||||
import generateSearchFromObject from '../generateSearchFromObject';
|
||||
|
||||
describe('HELPER PLUGIN | utils | generateSearchFromObject', () => {
|
||||
it('should return a string containing the _limit, _start and order', () => {
|
||||
const search = { _page: 1, _limit: 10, _sort: 'city:ASC' };
|
||||
const expected = '_limit=10&_sort=city:ASC&_start=0';
|
||||
|
||||
expect(generateSearchFromObject(search)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should remove the _q param from the search if it is empty', () => {
|
||||
const search = { _page: 1, _limit: 10, _sort: 'city:ASC', _q: '' };
|
||||
const expected = '_limit=10&_sort=city:ASC&_start=0';
|
||||
|
||||
expect(generateSearchFromObject(search)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should not add the filters if it is empty', () => {
|
||||
const search = {
|
||||
_page: 1,
|
||||
_limit: 10,
|
||||
_sort: 'city:ASC',
|
||||
_q: '',
|
||||
filters: [],
|
||||
};
|
||||
const expected = '_limit=10&_sort=city:ASC&_start=0';
|
||||
|
||||
expect(generateSearchFromObject(search)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle the filters correctly', () => {
|
||||
const search = {
|
||||
_limit: 10,
|
||||
_page: 1,
|
||||
_q: '',
|
||||
_sort: 'city:ASC',
|
||||
filters: [{ name: 'city', filter: '=', value: 'test' }],
|
||||
};
|
||||
|
||||
const expected = '_limit=10&_sort=city:ASC&city=test&_start=0';
|
||||
|
||||
expect(generateSearchFromObject(search)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@ -7,20 +7,21 @@ import { FormattedMessage } from 'react-intl';
|
||||
import { Header } from '@buffetjs/custom';
|
||||
import {
|
||||
PopUpWarning,
|
||||
generateFiltersFromSearch,
|
||||
generateSearchFromFilters,
|
||||
generateSearchFromObject,
|
||||
getQueryParameters,
|
||||
useGlobalContext,
|
||||
request,
|
||||
} from 'strapi-helper-plugin';
|
||||
|
||||
import pluginId from '../../pluginId';
|
||||
import DisplayedFieldsDropdown from '../../components/DisplayedFieldsDropdown';
|
||||
import Container from '../../components/Container';
|
||||
import CustomTable from '../../components/CustomTable';
|
||||
import FilterPicker from '../../components/FilterPicker';
|
||||
import Search from '../../components/Search';
|
||||
import {
|
||||
generateFiltersFromSearch,
|
||||
generateSearchFromFilters,
|
||||
} from '../../utils/search';
|
||||
|
||||
import ListViewProvider from '../ListViewProvider';
|
||||
import { onChangeListLabels, resetListLabels } from '../Main/actions';
|
||||
import { AddFilterCta, FilterIcon, Wrapper } from './components';
|
||||
@ -39,7 +40,6 @@ import {
|
||||
import reducer from './reducer';
|
||||
import makeSelectListView from './selectors';
|
||||
import getRequestUrl from '../../utils/getRequestUrl';
|
||||
import generateSearchFromObject from './utils/generateSearchFromObject';
|
||||
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
import generateSearchFromObject from '../generateSearchFromObject';
|
||||
|
||||
describe('CONTENT MANAGER | containers | ListView | utils', () => {
|
||||
describe('generateSearchFromObject', () => {
|
||||
it('should return a string containing the _limit, _start and order', () => {
|
||||
const search = { _page: 1, _limit: 10, _sort: 'city:ASC' };
|
||||
const expected = '_limit=10&_sort=city:ASC&_start=0';
|
||||
|
||||
expect(generateSearchFromObject(search)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should remove the _q param from the search if it is empty', () => {
|
||||
const search = { _page: 1, _limit: 10, _sort: 'city:ASC', _q: '' };
|
||||
const expected = '_limit=10&_sort=city:ASC&_start=0';
|
||||
|
||||
expect(generateSearchFromObject(search)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should not add the filters if it is empty', () => {
|
||||
const search = {
|
||||
_page: 1,
|
||||
_limit: 10,
|
||||
_sort: 'city:ASC',
|
||||
_q: '',
|
||||
filters: [],
|
||||
};
|
||||
const expected = '_limit=10&_sort=city:ASC&_start=0';
|
||||
|
||||
expect(generateSearchFromObject(search)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle the filters correctly', () => {
|
||||
const search = {
|
||||
_limit: 10,
|
||||
_page: 1,
|
||||
_q: '',
|
||||
_sort: 'city:ASC',
|
||||
filters: [{ name: 'city', filter: '=', value: 'test' }],
|
||||
};
|
||||
|
||||
const expected = '_limit=10&_sort=city:ASC&city=test&_start=0';
|
||||
|
||||
expect(generateSearchFromObject(search)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,102 +0,0 @@
|
||||
import {
|
||||
generateFiltersFromSearch,
|
||||
generateSearchFromFilters,
|
||||
} from '../search';
|
||||
|
||||
describe('Content Manager | utils | search', () => {
|
||||
describe('generateFiltersFromSearch', () => {
|
||||
it('should generate an array of filters', () => {
|
||||
const search =
|
||||
'?_sort=id:ASC&bool=true&big_number_ne=1&created_at_lt=2019-08-01T00:00:00Z&date_lte=2019-08-02T00:00:00Z&decimal_number_gt=2&enum_ne=noon&float_number_gte=3';
|
||||
const expected = [
|
||||
{
|
||||
name: 'bool',
|
||||
filter: '=',
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
name: 'big_number',
|
||||
filter: '_ne',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
filter: '_lt',
|
||||
value: '2019-08-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
filter: '_lte',
|
||||
value: '2019-08-02T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'decimal_number',
|
||||
filter: '_gt',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
name: 'enum',
|
||||
filter: '_ne',
|
||||
value: 'noon',
|
||||
},
|
||||
{
|
||||
name: 'float_number',
|
||||
filter: '_gte',
|
||||
value: '3',
|
||||
},
|
||||
];
|
||||
|
||||
expect(generateFiltersFromSearch(search)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateSearchFromFilters', () => {
|
||||
it('should return a string with all the applied filters', () => {
|
||||
const data = {
|
||||
_limit: 10,
|
||||
_sort: 'id:ASC',
|
||||
_page: 2,
|
||||
filters: [
|
||||
{
|
||||
name: 'bool',
|
||||
filter: '=',
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
name: 'big_number',
|
||||
filter: '_ne',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
filter: '_lt',
|
||||
value: '2019-08-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
filter: '_lte',
|
||||
value: '2019-08-02T00:00:00Z',
|
||||
},
|
||||
{
|
||||
name: 'decimal_number',
|
||||
filter: '_gt',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
name: 'enum',
|
||||
filter: '_ne',
|
||||
value: 'noon',
|
||||
},
|
||||
{
|
||||
name: 'float_number',
|
||||
filter: '_gte',
|
||||
value: '3',
|
||||
},
|
||||
],
|
||||
};
|
||||
const expected =
|
||||
'_limit=10&_sort=id:ASC&_page=2&bool=true&big_number_ne=1&created_at_lt=2019-08-01T00:00:00Z&date_lte=2019-08-02T00:00:00Z&decimal_number_gt=2&enum_ne=noon&float_number_gte=3';
|
||||
expect(generateSearchFromFilters(data)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -14,10 +14,9 @@ import { AttributeIcon } from '@buffetjs/core';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useGlobalContext } from 'strapi-helper-plugin';
|
||||
import { useGlobalContext, useQuery } from 'strapi-helper-plugin';
|
||||
import getTrad from '../../utils/getTrad';
|
||||
import makeSearch from '../../utils/makeSearch';
|
||||
import useQuery from '../../hooks/useQuery';
|
||||
import Button from './Button';
|
||||
import Card from './Card';
|
||||
|
||||
|
||||
@ -2,8 +2,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { components } from 'react-select';
|
||||
import { upperFirst } from 'lodash';
|
||||
import { useQuery } from 'strapi-helper-plugin';
|
||||
import useDataManager from '../../hooks/useDataManager';
|
||||
import useQuery from '../../hooks/useQuery';
|
||||
import Category from './Category';
|
||||
import Ul from './Ul';
|
||||
|
||||
|
||||
@ -3,10 +3,10 @@ import PropTypes from 'prop-types';
|
||||
import { components } from 'react-select';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { get } from 'lodash';
|
||||
import { useQuery } from 'strapi-helper-plugin';
|
||||
import { Checkbox, CheckboxWrapper, Label } from '@buffetjs/styles';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import useDataManager from '../../hooks/useDataManager';
|
||||
import useQuery from '../../hooks/useQuery';
|
||||
import getTrad from '../../utils/getTrad';
|
||||
import UpperFirst from '../UpperFirst';
|
||||
import SubUl from './SubUl';
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
ModalForm,
|
||||
getYupInnerErrors,
|
||||
useGlobalContext,
|
||||
useQuery,
|
||||
InputsIndex,
|
||||
} from 'strapi-helper-plugin';
|
||||
import { Button } from '@buffetjs/core';
|
||||
@ -16,7 +17,6 @@ import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { get, has, isEmpty, set, toLower, toString, upperFirst } from 'lodash';
|
||||
import pluginId from '../../pluginId';
|
||||
import useQuery from '../../hooks/useQuery';
|
||||
import useDataManager from '../../hooks/useDataManager';
|
||||
import AttributeOption from '../../components/AttributeOption';
|
||||
import BooleanBox from '../../components/BooleanBox';
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
import styled from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import { themePropTypes } from 'strapi-helper-plugin';
|
||||
|
||||
const Wrapper = styled.ul`
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
left: 0;
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
min-width: 230px;
|
||||
z-index: 1;
|
||||
list-style-type: none;
|
||||
font-size: ${({ theme }) => theme.main.fontSizes.md};
|
||||
background-color: ${({ theme }) => theme.main.colors.white};
|
||||
border: 1px solid ${({ theme }) => theme.main.colors.darkGrey};
|
||||
box-shadow: 0 2px 4px ${({ theme }) => theme.main.colors.greyAlpha};
|
||||
${({ isShown }) => isShown && 'display: block;'}
|
||||
`;
|
||||
|
||||
Wrapper.defaultProps = {
|
||||
isShown: false,
|
||||
};
|
||||
|
||||
Wrapper.propTypes = {
|
||||
isShown: PropTypes.bool,
|
||||
...themePropTypes,
|
||||
};
|
||||
|
||||
export default Wrapper;
|
||||
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Wrapper from './Wrapper';
|
||||
import SortListItem from '../SortListItem';
|
||||
|
||||
const SortList = ({ isShown, list, onClick, selectedItem }) => {
|
||||
return (
|
||||
<Wrapper isShown={isShown}>
|
||||
{Object.keys(list).map(item => {
|
||||
return (
|
||||
<SortListItem
|
||||
key={item}
|
||||
label={item}
|
||||
value={list[item]}
|
||||
onClick={onClick}
|
||||
selectedItem={selectedItem}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
SortList.defaultProps = {
|
||||
list: {},
|
||||
isShown: false,
|
||||
onClick: () => {},
|
||||
selectedItem: null,
|
||||
};
|
||||
|
||||
SortList.propTypes = {
|
||||
list: PropTypes.object,
|
||||
isShown: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
selectedItem: PropTypes.string,
|
||||
};
|
||||
|
||||
export default SortList;
|
||||
@ -0,0 +1,29 @@
|
||||
import styled from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import { themePropTypes } from 'strapi-helper-plugin';
|
||||
|
||||
const SortListItem = styled.li`
|
||||
padding: 0 14px;
|
||||
height: 27px;
|
||||
line-height: 27px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: ${({ theme }) => theme.main.colors.mediumGrey};
|
||||
}
|
||||
${({ isActive, theme }) =>
|
||||
isActive &&
|
||||
`
|
||||
background-color: ${theme.main.colors.mediumGrey};
|
||||
`}
|
||||
`;
|
||||
|
||||
SortListItem.defaultProps = {
|
||||
isActive: false,
|
||||
};
|
||||
|
||||
SortListItem.propTypes = {
|
||||
isActive: PropTypes.bool,
|
||||
...themePropTypes,
|
||||
};
|
||||
|
||||
export default SortListItem;
|
||||
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import getTrad from '../../utils/getTrad';
|
||||
|
||||
import Wrapper from './Wrapper';
|
||||
|
||||
const SortListItem = ({ onClick, selectedItem, label, value }) => {
|
||||
const handleClick = () => {
|
||||
onClick(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper isActive={selectedItem === value} onClick={handleClick}>
|
||||
<FormattedMessage id={getTrad(`sort.${label}`)} />
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
SortListItem.defaultProps = {
|
||||
selectedItem: null,
|
||||
label: '',
|
||||
onClick: () => {},
|
||||
value: null,
|
||||
};
|
||||
|
||||
SortListItem.propTypes = {
|
||||
selectedItem: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
export default SortListItem;
|
||||
@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import { themePropTypes } from 'strapi-helper-plugin';
|
||||
|
||||
import Text from '../Text';
|
||||
|
||||
const SortButton = styled(props => (
|
||||
<Text
|
||||
as="button"
|
||||
fontWeight="semiBold"
|
||||
color={props.isActive ? 'mediumBlue' : 'greyDark'}
|
||||
{...props}
|
||||
/>
|
||||
))`
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
line-height: 30px;
|
||||
background-color: ${({ theme }) => theme.main.colors.white};
|
||||
border: 1px solid ${({ theme }) => theme.main.colors.darkGrey};
|
||||
border-radius: ${({ theme }) => theme.main.sizes.borderRadius};
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
${({ isActive, theme }) =>
|
||||
isActive
|
||||
? `
|
||||
background-color: ${theme.main.colors.lightBlue};
|
||||
border: 1px solid ${theme.main.colors.darkBlue};
|
||||
`
|
||||
: `
|
||||
&:hover {
|
||||
background-color: ${theme.main.colors.lightestGrey};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
SortButton.defaultProps = {
|
||||
isActive: false,
|
||||
};
|
||||
|
||||
SortButton.propTypes = {
|
||||
isActive: PropTypes.bool,
|
||||
...themePropTypes,
|
||||
};
|
||||
|
||||
export default SortButton;
|
||||
@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export default Wrapper;
|
||||
@ -1,14 +1,58 @@
|
||||
import styled from 'styled-components';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const SortPicker = styled.div`
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
background: #ffffff;
|
||||
line-height: 30px;
|
||||
border: 1px solid #e3e9f3;
|
||||
border-radius: 2px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
`;
|
||||
import getTrad from '../../utils/getTrad';
|
||||
|
||||
import Wrapper from './Wrapper';
|
||||
import SortButton from './SortButton';
|
||||
import SortList from '../SortList';
|
||||
|
||||
const SortPicker = ({ onChange, value }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const orders = {
|
||||
created_at_asc: 'created_at:ASC',
|
||||
created_at_desc: 'created_at:DESC',
|
||||
name_asc: 'name:ASC',
|
||||
name_desc: 'name:DESC',
|
||||
updated_at_asc: 'updated_at:ASC',
|
||||
updated_at_desc: 'updated_at:DESC',
|
||||
};
|
||||
|
||||
const handleChange = value => {
|
||||
onChange({ target: { name: '_sort', value } });
|
||||
|
||||
hangleToggle();
|
||||
};
|
||||
|
||||
const hangleToggle = () => {
|
||||
setIsOpen(v => !v);
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<SortButton onClick={hangleToggle} isActive={isOpen}>
|
||||
<FormattedMessage id={getTrad('sort.label')} />
|
||||
</SortButton>
|
||||
<SortList
|
||||
isShown={isOpen}
|
||||
list={orders}
|
||||
selectedItem={value}
|
||||
onClick={handleChange}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
SortPicker.defaultProps = {
|
||||
onChange: () => {},
|
||||
value: null,
|
||||
};
|
||||
|
||||
SortPicker.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
export default SortPicker;
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
import React, { useReducer, useState } from 'react';
|
||||
import React, { useReducer, useState, useEffect } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Header } from '@buffetjs/custom';
|
||||
import { HeaderSearch, useGlobalContext } from 'strapi-helper-plugin';
|
||||
import {
|
||||
HeaderSearch,
|
||||
useGlobalContext,
|
||||
useQuery,
|
||||
generateSearchFromFilters,
|
||||
} from 'strapi-helper-plugin';
|
||||
import getTrad from '../../utils/getTrad';
|
||||
import Container from '../../components/Container';
|
||||
import ControlsWrapper from '../../components/ControlsWrapper';
|
||||
@ -17,18 +23,56 @@ import AddFilterCTA from '../../components/AddFilterCTA';
|
||||
const HomePage = () => {
|
||||
const { formatMessage } = useGlobalContext();
|
||||
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const { data, dataToDelete, _q } = reducerState.toJS();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { push } = useHistory();
|
||||
const query = useQuery();
|
||||
const { data, dataToDelete } = reducerState.toJS();
|
||||
const pluginName = formatMessage({ id: getTrad('plugin.name') });
|
||||
|
||||
useEffect(() => {
|
||||
// TODO - Retrieve data
|
||||
dispatch({
|
||||
type: 'GET_DATA_SUCCEEDED',
|
||||
data: [],
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getSearchParams = () => {
|
||||
const params = {};
|
||||
query.forEach((value, key) => {
|
||||
params[key] = value;
|
||||
});
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
const getUpdatedSearchParams = updatedParams => {
|
||||
return {
|
||||
...getSearchParams(),
|
||||
...updatedParams,
|
||||
};
|
||||
};
|
||||
|
||||
const getQueryValue = key => {
|
||||
const queryParams = getSearchParams();
|
||||
|
||||
return queryParams[key];
|
||||
};
|
||||
|
||||
const handleChangeParams = ({ target: { name, value } }) => {
|
||||
const updatedSearch = getUpdatedSearchParams({ [name]: value });
|
||||
const newSearch = generateSearchFromFilters(updatedSearch);
|
||||
|
||||
push({ search: encodeURI(newSearch) });
|
||||
};
|
||||
|
||||
const handleClearSearch = () => {
|
||||
handleChangeParams({ target: { name: '_q', value: '' } });
|
||||
};
|
||||
|
||||
const handleClickToggleModal = () => {
|
||||
setIsOpen(prev => !prev);
|
||||
};
|
||||
const handleClearSearch = () => {
|
||||
dispatch({
|
||||
type: 'ON_CLEAR_SEARCH',
|
||||
});
|
||||
};
|
||||
|
||||
const headerProps = {
|
||||
title: {
|
||||
@ -65,18 +109,19 @@ const HomePage = () => {
|
||||
<Header {...headerProps} />
|
||||
<HeaderSearch
|
||||
label={pluginName}
|
||||
// TODO: search
|
||||
onChange={() => {}}
|
||||
onChange={handleChangeParams}
|
||||
onClear={handleClearSearch}
|
||||
placeholder={formatMessage({ id: getTrad('search.placeholder') })}
|
||||
value={_q}
|
||||
name="_q"
|
||||
value={getQueryValue('_q') || ''}
|
||||
/>
|
||||
|
||||
<ControlsWrapper>
|
||||
<SelectAll />
|
||||
<SortPicker>
|
||||
<span> Sort By</span>
|
||||
</SortPicker>
|
||||
<SortPicker
|
||||
onChange={handleChangeParams}
|
||||
value={getQueryValue('_sort') || null}
|
||||
/>
|
||||
<AddFilterCTA />
|
||||
</ControlsWrapper>
|
||||
<ListEmpty onClick={handleClickToggleModal} />
|
||||
|
||||
@ -3,14 +3,12 @@ import { fromJS } from 'immutable';
|
||||
const initialState = fromJS({
|
||||
data: [],
|
||||
dataToDelete: [],
|
||||
// TODO: set to empty string
|
||||
_q: 'super asset',
|
||||
});
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case 'ON_CLEAR_SEARCH':
|
||||
return state.update('_q', () => '');
|
||||
case 'GET_DATA_SUCCEEDED':
|
||||
return state.update('data', () => action.data);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@ -27,5 +27,12 @@
|
||||
"plugin.description.long": "Media file management.",
|
||||
"plugin.description.short": "Media file management.",
|
||||
"search.placeholder": "Search for an asset...",
|
||||
"sort.label": "Sort by",
|
||||
"sort.created_at_asc": "Most recent uploads",
|
||||
"sort.created_at_desc": "Oldest uploads",
|
||||
"sort.name_asc": "Alphabetical order (A to Z)",
|
||||
"sort.name_desc": "Reverse alphabetical order (Z to A)",
|
||||
"sort.updated_at_asc": "Most recent updates",
|
||||
"sort.updated_at_desc": "Oldest updates",
|
||||
"window.confirm.close-modal": "Are you sure? You have some files that have not been uploaded yet."
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user