418 lines
12 KiB
JavaScript
Raw Normal View History

2019-07-09 09:53:50 +02:00
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
2019-07-08 17:01:12 +02:00
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
2019-07-09 09:53:50 +02:00
import { capitalize, get, isEmpty, sortBy } from 'lodash';
import { FormattedMessage } from 'react-intl';
import {
ButtonDropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
} from 'reactstrap';
2019-07-09 19:38:39 +02:00
import {
PluginHeader,
PopUpWarning,
getQueryParameters,
} from 'strapi-helper-plugin';
2019-07-08 17:01:12 +02:00
import pluginId from '../../pluginId';
2019-07-09 18:11:09 +02:00
import { ListViewProvider } from '../../contexts/ListView';
2019-07-09 09:53:50 +02:00
import FilterLogo from '../../assets/images/icon_filter.png';
2019-07-08 20:27:38 +02:00
import Container from '../../components/Container';
2019-07-09 16:56:40 +02:00
import CustomTable from '../../components/CustomTable';
2019-07-09 12:31:18 +02:00
import FilterPicker from '../../components/FilterPicker';
2019-07-09 09:53:50 +02:00
import InputCheckbox from '../../components/InputCheckbox';
2019-07-08 20:27:38 +02:00
import Search from '../../components/Search';
2019-07-09 18:11:09 +02:00
import {
generateFiltersFromSearch,
generateSearchFromFilters,
} from '../../utils/search';
2019-07-09 09:53:50 +02:00
import { onChangeListLabels, resetListLabels } from '../Main/actions';
import { AddFilterCta, DropDownWrapper, Img, Wrapper } from './components';
2019-07-10 08:43:40 +02:00
import Footer from './Footer';
2019-07-09 19:38:39 +02:00
import {
getData,
onChangeBulk,
onChangeBulkSelectall,
onDeleteSeveralData,
resetProps,
toggleModalDeleteAll,
} from './actions';
2019-07-08 17:01:12 +02:00
import reducer from './reducer';
import saga from './saga';
import makeSelectListView from './selectors';
function ListView({
2019-07-08 20:27:38 +02:00
count,
2019-07-09 16:56:40 +02:00
data,
2019-07-08 20:27:38 +02:00
emitEvent,
2019-07-09 19:38:39 +02:00
entriesToDelete,
2019-07-08 20:27:38 +02:00
location: { search },
getData,
2019-07-08 17:01:12 +02:00
layouts,
2019-07-08 20:27:38 +02:00
isLoading,
history: { push },
2019-07-08 17:01:12 +02:00
match: {
params: { slug },
},
2019-07-09 19:38:39 +02:00
onChangeBulk,
onChangeBulkSelectall,
2019-07-09 09:53:50 +02:00
onChangeListLabels,
2019-07-09 19:38:39 +02:00
onDeleteSeveralData,
2019-07-09 09:53:50 +02:00
resetListLabels,
2019-07-08 20:27:38 +02:00
resetProps,
2019-07-09 19:38:39 +02:00
shouldRefetchData,
showWarningDeleteAll,
toggleModalDeleteAll,
2019-07-08 17:01:12 +02:00
}) {
strapi.useInjectReducer({ key: 'listView', reducer, pluginId });
strapi.useInjectSaga({ key: 'listView', saga, pluginId });
2019-07-09 16:56:40 +02:00
2019-07-09 18:11:09 +02:00
const getLayoutSettingRef = useRef();
2019-07-09 09:53:50 +02:00
const [isLabelPickerOpen, setLabelPickerState] = useState(false);
2019-07-09 12:31:18 +02:00
const [isFilterPickerOpen, setFilterPickerState] = useState(false);
2019-07-09 18:11:09 +02:00
2019-07-09 09:53:50 +02:00
getLayoutSettingRef.current = settingName =>
get(layouts, [slug, 'settings', settingName], '');
2019-07-08 20:27:38 +02:00
2019-07-09 18:11:09 +02:00
const getSearchParams = useCallback(
2019-07-08 20:27:38 +02:00
(updatedParams = {}) => {
return {
_limit:
2019-07-09 09:53:50 +02:00
getQueryParameters(search, '_limit') ||
getLayoutSettingRef.current('pageSize'),
2019-07-08 20:27:38 +02:00
_page: getQueryParameters(search, '_page') || 1,
_q: getQueryParameters(search, '_q') || '',
_sort:
getQueryParameters(search, '_sort') ||
2019-07-09 09:53:50 +02:00
`${getLayoutSettingRef.current(
'defaultSortBy'
)}:${getLayoutSettingRef.current('defaultSortOrder')}`,
2019-07-08 20:27:38 +02:00
source: getQueryParameters(search, 'source'),
2019-07-09 18:11:09 +02:00
...generateFiltersFromSearch(search),
2019-07-08 20:27:38 +02:00
...updatedParams,
};
},
2019-07-09 09:53:50 +02:00
[getLayoutSettingRef, search]
2019-07-08 20:27:38 +02:00
);
2019-07-08 17:01:12 +02:00
useEffect(() => {
2019-07-09 18:11:09 +02:00
getData(slug, getSearchParams());
2019-07-08 17:01:12 +02:00
2019-07-08 20:27:38 +02:00
return () => {
resetProps();
};
/* eslint-disable-next-line react-hooks/exhaustive-deps */
2019-07-09 19:38:39 +02:00
}, [slug, shouldRefetchData]);
2019-07-08 20:27:38 +02:00
2019-07-09 18:11:09 +02:00
const toggleLabelPickerState = () =>
setLabelPickerState(prevState => !prevState);
const toggleFilterPickerState = () =>
setFilterPickerState(prevState => !prevState);
2019-07-09 09:53:50 +02:00
2019-07-09 18:11:09 +02:00
// Helpers
const getMetaDatas = (path = []) =>
get(layouts, [slug, 'metadata', ...path], {});
const getListLayout = () => get(layouts, [slug, 'layouts', 'list'], []);
const getAllLabels = () => {
2019-07-09 09:53:50 +02:00
return sortBy(
2019-07-09 18:11:09 +02:00
Object.keys(getMetaDatas())
.filter(key => !isEmpty(getMetaDatas([key, 'list'])))
2019-07-09 09:53:50 +02:00
.map(label => ({
name: label,
2019-07-09 18:11:09 +02:00
value: getListLayout().includes(label),
2019-07-09 09:53:50 +02:00
})),
['label', 'name']
);
};
2019-07-09 18:11:09 +02:00
const getFirstSortableElement = (name = '') => {
return get(
getListLayout().filter(h => {
return h !== name && getMetaDatas([h, 'list', 'sortable']) === true;
}),
['0'],
'id'
);
2019-07-08 20:27:38 +02:00
};
2019-07-09 18:11:09 +02:00
const getTableHeaders = () => {
return getListLayout().map(label => {
return { ...getMetaDatas([label, 'list']), name: label };
});
};
2019-07-09 09:53:50 +02:00
const handleChangeListLabels = ({ name, value }) => {
2019-07-09 18:11:09 +02:00
const currentSort = getSearchParams()._sort;
2019-07-09 16:56:40 +02:00
2019-07-09 18:11:09 +02:00
if (value && getListLayout().length === 1) {
2019-07-09 16:56:40 +02:00
strapi.notification.error(
'content-manager.notification.error.displayedFields'
);
return;
}
if (currentSort.split(':')[0] === name && value) {
emitEvent('didChangeDisplayedFields');
handleChangeParams({
2019-07-09 18:11:09 +02:00
target: {
name: '_sort',
value: `${getFirstSortableElement(name)}:ASC`,
},
2019-07-09 16:56:40 +02:00
});
}
2019-07-09 09:53:50 +02:00
onChangeListLabels({
target: { name: `${slug}.${name}`, value: !value },
});
};
2019-07-09 18:11:09 +02:00
const handleChangeParams = ({ target: { name, value } }) => {
const updatedSearch = getSearchParams({ [name]: value });
const newSearch = generateSearchFromFilters(updatedSearch);
2019-07-10 08:43:40 +02:00
if (name === '_limit') {
emitEvent('willChangeNumberOfEntriesPerPage');
}
2019-07-09 18:11:09 +02:00
push({ search: newSearch });
resetProps();
getData(slug, updatedSearch);
};
const handleSubmit = () => {
emitEvent('didFilterEntries');
toggleFilterPickerState();
};
2019-07-09 12:31:18 +02:00
const filterPickerActions = [
{
label: `${pluginId}.components.FiltersPickWrapper.PluginHeader.actions.clearAll`,
kind: 'secondary',
onClick: () => {
toggleFilterPickerState();
},
},
{
label: `${pluginId}.components.FiltersPickWrapper.PluginHeader.actions.apply`,
kind: 'primary',
type: 'submit',
},
];
2019-07-09 18:11:09 +02:00
const pluginHeaderActions = [
{
id: 'addEntry',
label: 'content-manager.containers.List.addAnEntry',
labelValues: {
entity: capitalize(slug) || 'Content Manager',
},
kind: 'primaryAddShape',
onClick: () => {
emitEvent('willCreateEntry');
},
},
];
2019-07-09 16:56:40 +02:00
2019-07-08 17:01:12 +02:00
return (
2019-07-08 20:27:38 +02:00
<>
2019-07-09 18:11:09 +02:00
<ListViewProvider
2019-07-09 19:38:39 +02:00
data={data}
2019-07-10 08:43:40 +02:00
count={count}
2019-07-09 19:38:39 +02:00
entriesToDelete={entriesToDelete}
2019-07-09 18:11:09 +02:00
firstSortableElement={getFirstSortableElement()}
2019-07-09 19:38:39 +02:00
onChangeBulk={onChangeBulk}
onChangeBulkSelectall={onChangeBulkSelectall}
2019-07-09 18:11:09 +02:00
onChangeParams={handleChangeParams}
2019-07-09 19:38:39 +02:00
onDeleteSeveralData={onDeleteSeveralData}
2019-07-09 18:11:09 +02:00
searchParams={getSearchParams()}
slug={slug}
2019-07-09 19:38:39 +02:00
toggleModalDeleteAll={toggleModalDeleteAll}
2019-07-09 18:11:09 +02:00
>
<FilterPicker
actions={filterPickerActions}
isOpen={isFilterPickerOpen}
name={slug}
onSubmit={handleSubmit}
/>
<Container className="container-fluid">
{!isFilterPickerOpen && (
<PluginHeader
actions={pluginHeaderActions}
description={{
id:
count > 1
? `${pluginId}.containers.List.pluginHeaderDescription`
: `${pluginId}.containers.List.pluginHeaderDescription.singular`,
values: {
label: count,
},
}}
title={{
id: slug || 'Content Manager',
}}
withDescriptionAnim={isLoading}
/>
)}
{getLayoutSettingRef.current('searchable') && (
<Search
changeParams={handleChangeParams}
initValue={getQueryParameters(search, '_q') || ''}
model={slug}
value={getQueryParameters(search, '_q') || ''}
/>
)}
<Wrapper>
<div className="row">
<div className="col-10">
<AddFilterCta type="button" onClick={toggleFilterPickerState}>
<Img src={FilterLogo} alt="filter_logo" />
<FormattedMessage
id={`${pluginId}.components.AddFilterCTA.add`}
/>
</AddFilterCta>
</div>
<div className="col-2">
<DropDownWrapper>
<ButtonDropdown
isOpen={isLabelPickerOpen}
toggle={toggleLabelPickerState}
direction="left"
>
<DropdownToggle />
<DropdownMenu>
<FormattedMessage id="content-manager.containers.ListPage.displayedFields">
{msg => (
<DropdownItem
onClick={() => {
resetListLabels(slug);
2019-07-09 09:53:50 +02:00
}}
>
2019-07-09 18:11:09 +02:00
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}
>
<span>{msg}</span>
<FormattedMessage id="content-manager.containers.Edit.reset" />
</div>
</DropdownItem>
)}
</FormattedMessage>
{getAllLabels().map(label => {
//
return (
<DropdownItem
key={label.name}
toggle={false}
onClick={() => handleChangeListLabels(label)}
>
<div>
<InputCheckbox
onChange={() => handleChangeListLabels(label)}
name={label.name}
value={label.value}
/>
</div>
</DropdownItem>
);
})}
</DropdownMenu>
</ButtonDropdown>
</DropDownWrapper>
</div>
2019-07-09 09:53:50 +02:00
</div>
2019-07-09 18:11:09 +02:00
<div className="row" style={{ paddingTop: '36px' }}>
<div className="col-12">
<CustomTable
data={data}
headers={getTableHeaders()}
isBulkable={getLayoutSettingRef.current('bulkable')}
onChangeParams={handleChangeParams}
slug={slug}
/>
2019-07-10 08:43:40 +02:00
<Footer />
2019-07-09 18:11:09 +02:00
</div>
2019-07-09 16:56:40 +02:00
</div>
2019-07-09 18:11:09 +02:00
</Wrapper>
</Container>
2019-07-09 19:38:39 +02:00
<PopUpWarning
isOpen={showWarningDeleteAll}
toggleModal={toggleModalDeleteAll}
content={{
title: 'content-manager.popUpWarning.title',
// message: this.getPopUpDeleteAllMsg(),
cancel: 'content-manager.popUpWarning.button.cancel',
confirm: 'content-manager.popUpWarning.button.confirm',
}}
popUpWarningType="danger"
onConfirm={() => {
onDeleteSeveralData(
entriesToDelete,
slug,
getSearchParams().source
);
}}
/>
2019-07-09 18:11:09 +02:00
</ListViewProvider>
2019-07-08 20:27:38 +02:00
</>
2019-07-08 17:01:12 +02:00
);
}
ListView.defaultProps = {
layouts: {},
};
ListView.propTypes = {
2019-07-08 20:27:38 +02:00
count: PropTypes.number.isRequired,
2019-07-09 16:56:40 +02:00
data: PropTypes.array.isRequired,
2019-07-08 20:27:38 +02:00
emitEvent: PropTypes.func.isRequired,
2019-07-09 19:38:39 +02:00
entriesToDelete: PropTypes.array.isRequired,
2019-07-08 17:01:12 +02:00
layouts: PropTypes.object,
2019-07-08 20:27:38 +02:00
location: PropTypes.shape({
search: PropTypes.string.isRequired,
}),
getData: PropTypes.func.isRequired,
isLoading: PropTypes.bool.isRequired,
2019-07-09 09:53:50 +02:00
history: PropTypes.shape({
push: PropTypes.func.isRequired,
}),
2019-07-08 17:01:12 +02:00
match: PropTypes.shape({
params: PropTypes.shape({
slug: PropTypes.string.isRequired,
}),
}),
2019-07-09 19:38:39 +02:00
onChangeBulk: PropTypes.func.isRequired,
onChangeBulkSelectall: PropTypes.func.isRequired,
2019-07-09 09:53:50 +02:00
onChangeListLabels: PropTypes.func.isRequired,
2019-07-09 19:38:39 +02:00
onDeleteSeveralData: PropTypes.func.isRequired,
2019-07-09 09:53:50 +02:00
resetListLabels: PropTypes.func.isRequired,
2019-07-08 20:27:38 +02:00
resetProps: PropTypes.func.isRequired,
2019-07-09 19:38:39 +02:00
shouldRefetchData: PropTypes.bool.isRequired,
showWarningDeleteAll: PropTypes.bool.isRequired,
toggleModalDeleteAll: PropTypes.func.isRequired,
2019-07-08 17:01:12 +02:00
};
const mapStateToProps = makeSelectListView();
export function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
2019-07-08 20:27:38 +02:00
getData,
2019-07-09 19:38:39 +02:00
onChangeBulk,
onChangeBulkSelectall,
2019-07-09 09:53:50 +02:00
onChangeListLabels,
2019-07-09 19:38:39 +02:00
onDeleteSeveralData,
2019-07-09 09:53:50 +02:00
resetListLabels,
2019-07-08 20:27:38 +02:00
resetProps,
2019-07-09 19:38:39 +02:00
toggleModalDeleteAll,
2019-07-08 17:01:12 +02:00
},
dispatch
);
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps
);
export default compose(
withConnect,
memo
)(ListView);