514 lines
15 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-31 16:03:59 +02:00
import { capitalize, get, sortBy } from 'lodash';
2019-07-09 09:53:50 +02:00
import { FormattedMessage } from 'react-intl';
2019-11-07 10:48:02 +01:00
import { Header } from '@buffetjs/custom';
import {
PopUpWarning,
getQueryParameters,
useGlobalContext,
request,
2019-11-07 10:48:02 +01:00
} from 'strapi-helper-plugin';
2019-07-08 17:01:12 +02:00
import pluginId from '../../pluginId';
import DisplayedFieldsDropdown from '../../components/DisplayedFieldsDropdown';
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-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-10-31 11:57:40 +01:00
import ListViewProvider from '../ListViewProvider';
2019-07-09 09:53:50 +02:00
import { onChangeListLabels, resetListLabels } from '../Main/actions';
2019-12-13 15:37:34 +01:00
import { AddFilterCta, FilterIcon, Wrapper } from './components';
2019-07-10 12:20:25 +02:00
import Filter from './Filter';
2019-07-10 08:43:40 +02:00
import Footer from './Footer';
2019-07-09 19:38:39 +02:00
import {
getDataSucceeded,
2019-07-09 19:38:39 +02:00
onChangeBulk,
onChangeBulkSelectall,
onDeleteDataSucceeded,
onDeleteSeveralDataSucceeded,
2019-07-09 19:38:39 +02:00
resetProps,
2019-07-10 09:12:48 +02:00
toggleModalDelete,
2019-07-09 19:38:39 +02:00
toggleModalDeleteAll,
} from './actions';
2019-07-08 17:01:12 +02:00
import reducer from './reducer';
import makeSelectListView from './selectors';
import getRequestUrl from '../../utils/getRequestUrl';
import generateSearchFromObject from './utils/generateSearchFromObject';
2019-07-08 17:01:12 +02:00
/* eslint-disable react/no-array-index-key */
2019-07-08 17:01:12 +02:00
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-10 17:31:49 +02:00
location: { pathname, search },
getDataSucceeded,
2019-07-08 17:01:12 +02:00
layouts,
2019-07-08 20:27:38 +02:00
history: { push },
2019-07-09 19:38:39 +02:00
onChangeBulk,
onChangeBulkSelectall,
2019-07-09 09:53:50 +02:00
onChangeListLabels,
onDeleteDataSucceeded,
onDeleteSeveralDataSucceeded,
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,
2019-07-10 09:12:48 +02:00
showWarningDelete,
2019-10-24 17:08:52 +02:00
slug,
2019-07-10 09:12:48 +02:00
toggleModalDelete,
2019-07-09 19:38:39 +02:00
showWarningDeleteAll,
toggleModalDeleteAll,
2019-07-08 17:01:12 +02:00
}) {
strapi.useInjectReducer({ key: 'listView', reducer, pluginId });
2019-11-07 10:48:02 +01:00
const { formatMessage } = useGlobalContext();
2019-07-09 18:11:09 +02:00
const getLayoutSettingRef = useRef();
const getDataRef = 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-10 09:12:48 +02:00
const [idToDelete, setIdToDelete] = useState(null);
2019-10-28 22:30:45 +01:00
const contentTypePath = [slug, 'contentType'];
2019-07-09 18:11:09 +02:00
getDataRef.current = async (uid, params) => {
try {
const generatedSearch = generateSearchFromObject(params);
const [{ count }, data] = await Promise.all([
request(getRequestUrl(`explorer/${uid}/count?${generatedSearch}`), {
method: 'GET',
}),
request(getRequestUrl(`explorer/${uid}?${generatedSearch}`), {
method: 'GET',
}),
]);
getDataSucceeded(count, data);
} catch (err) {
strapi.notification.error(`${pluginId}.error.model.fetch`);
}
};
2019-07-09 09:53:50 +02:00
getLayoutSettingRef.current = settingName =>
2019-10-28 22:30:45 +01:00
get(layouts, [...contentTypePath, '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-10 11:12:30 +02:00
filters: 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
);
const handleConfirmDeleteData = useCallback(async () => {
try {
emitEvent('willDeleteEntry');
await request(getRequestUrl(`explorer/${slug}/${idToDelete}`), {
method: 'DELETE',
});
strapi.notification.success(`${pluginId}.success.record.delete`);
// Close the modal and refetch data
onDeleteDataSucceeded();
emitEvent('didDeleteEntry');
} catch (err) {
strapi.notification.error(`${pluginId}.error.record.delete`);
}
}, [emitEvent, idToDelete, onDeleteDataSucceeded, slug]);
const handleConfirmDeleteAllData = useCallback(async () => {
const params = Object.assign(entriesToDelete);
try {
await request(getRequestUrl(`explorer/deleteAll/${slug}`), {
method: 'DELETE',
params,
});
onDeleteSeveralDataSucceeded();
} catch (err) {
strapi.notification.error(`${pluginId}.error.record.delete`);
}
}, [entriesToDelete, onDeleteSeveralDataSucceeded, slug]);
2019-07-08 17:01:12 +02:00
useEffect(() => {
getDataRef.current(slug, getSearchParams());
2019-07-08 17:01:12 +02:00
2019-07-08 20:27:38 +02:00
return () => {
resetProps();
2019-07-15 17:29:13 +02:00
setFilterPickerState(false);
2019-07-08 20:27:38 +02:00
};
// 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
const toggleLabelPickerState = () => {
if (!isLabelPickerOpen) {
emitEvent('willChangeListFieldsSettings');
}
2019-07-09 18:11:09 +02:00
setLabelPickerState(prevState => !prevState);
};
const toggleFilterPickerState = () => {
if (!isFilterPickerOpen) {
emitEvent('willFilterEntries');
}
2019-07-09 18:11:09 +02:00
setFilterPickerState(prevState => !prevState);
};
2019-07-09 09:53:50 +02:00
2019-07-09 18:11:09 +02:00
// Helpers
const getMetaDatas = (path = []) =>
2019-10-28 22:30:45 +01:00
get(layouts, [...contentTypePath, 'metadatas', ...path], {});
2019-11-21 14:24:11 +01:00
2019-10-28 22:30:45 +01:00
const getListLayout = () =>
get(layouts, [...contentTypePath, 'layouts', 'list'], []);
2019-11-21 12:18:08 +01:00
2019-11-27 10:36:49 +01:00
const getListSchema = () => get(layouts, [...contentTypePath, 'schema'], {});
2019-11-21 12:18:08 +01:00
const getName = () => {
2019-12-10 11:11:55 +01:00
return get(getListSchema(), ['info', 'name'], '');
2019-11-21 12:18:08 +01:00
};
2019-07-09 18:11:09 +02:00
const getAllLabels = () => {
2019-07-09 09:53:50 +02:00
return sortBy(
2019-07-09 18:11:09 +02:00
Object.keys(getMetaDatas())
2019-07-31 16:03:59 +02:00
.filter(
key =>
2019-11-20 17:33:49 +01:00
![
'json',
'component',
'dynamiczone',
'relation',
'richtext',
2019-11-27 10:36:49 +01:00
].includes(get(getListSchema(), ['attributes', key, 'type'], ''))
2019-07-31 16:03:59 +02:00
)
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-11-20 17:33:49 +01:00
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-11-26 10:39:26 +01:00
2019-07-09 09:53:50 +02:00
onChangeListLabels({
2019-11-21 14:24:11 +01:00
target: {
name,
slug,
value: !value,
},
2019-07-09 09:53:50 +02:00
});
};
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();
getDataRef.current(slug, updatedSearch);
2019-07-09 18:11:09 +02:00
};
2019-07-10 09:12:48 +02:00
const handleClickDelete = id => {
setIdToDelete(id);
toggleModalDelete();
};
2019-07-10 17:31:49 +02:00
const handleSubmit = (filters = []) => {
2019-07-09 18:11:09 +02:00
emitEvent('didFilterEntries');
toggleFilterPickerState();
2019-07-10 17:31:49 +02:00
handleChangeParams({ target: { name: 'filters', value: filters } });
2019-07-09 18:11:09 +02:00
};
2019-07-09 12:31:18 +02:00
const filterPickerActions = [
{
label: `${pluginId}.components.FiltersPickWrapper.PluginHeader.actions.clearAll`,
kind: 'secondary',
onClick: () => {
toggleFilterPickerState();
2019-07-10 17:31:49 +02:00
handleChangeParams({ target: { name: 'filters', value: [] } });
2019-07-09 12:31:18 +02:00
},
},
{
label: `${pluginId}.components.FiltersPickWrapper.PluginHeader.actions.apply`,
kind: 'primary',
type: 'submit',
},
];
2019-11-06 12:06:17 +01:00
const headerAction = [
2019-07-09 18:11:09 +02:00
{
2020-01-06 01:52:26 +01:00
label: formatMessage(
2019-11-07 10:48:02 +01:00
{
id: 'content-manager.containers.List.addAnEntry',
},
{
2019-11-21 12:18:08 +01:00
entity: capitalize(getName()) || 'Content Manager',
2019-11-07 10:48:02 +01:00
}
2019-11-06 12:06:17 +01:00
),
2019-07-09 18:11:09 +02:00
onClick: () => {
emitEvent('willCreateEntry');
2019-07-10 17:31:49 +02:00
push({
pathname: `${pathname}/create`,
search: `redirectUrl=${pathname}${search}`,
});
2019-07-09 18:11:09 +02:00
},
2019-11-06 12:06:17 +01:00
color: 'primary',
type: 'button',
icon: true,
2019-11-28 19:23:10 +01:00
style: {
paddingLeft: 15,
paddingRight: 15,
fontWeight: 600,
},
2019-07-09 18:11:09 +02:00
},
];
2019-07-09 16:56:40 +02:00
2019-11-06 12:06:17 +01:00
const headerProps = {
2019-11-07 10:48:02 +01:00
title: {
2019-11-21 12:18:08 +01:00
label: getName() || 'Content Manager',
2019-11-07 10:48:02 +01:00
},
content: formatMessage(
{
id:
count > 1
? `${pluginId}.containers.List.pluginHeaderDescription`
: `${pluginId}.containers.List.pluginHeaderDescription.singular`,
},
{ label: count }
2019-11-06 12:06:17 +01:00
),
2019-11-07 10:48:02 +01:00
actions: headerAction,
2019-11-06 12:06:17 +01: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-10 11:12:30 +02:00
emitEvent={emitEvent}
2019-07-09 18:11:09 +02:00
firstSortableElement={getFirstSortableElement()}
2019-11-21 12:18:08 +01:00
label={getName()}
2019-07-09 19:38:39 +02:00
onChangeBulk={onChangeBulk}
onChangeBulkSelectall={onChangeBulkSelectall}
2019-07-09 18:11:09 +02:00
onChangeParams={handleChangeParams}
2019-07-10 09:12:48 +02:00
onClickDelete={handleClickDelete}
2019-11-27 10:36:49 +01:00
schema={getListSchema()}
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}
2019-11-21 12:18:08 +01:00
name={getName()}
2019-07-10 17:31:49 +02:00
toggleFilterPickerState={toggleFilterPickerState}
2019-07-09 18:11:09 +02:00
onSubmit={handleSubmit}
/>
<Container className="container-fluid">
2019-11-06 12:06:17 +01:00
{!isFilterPickerOpen && <Header {...headerProps} />}
2019-07-09 18:11:09 +02:00
{getLayoutSettingRef.current('searchable') && (
<Search
changeParams={handleChangeParams}
initValue={getQueryParameters(search, '_q') || ''}
2019-11-21 12:18:08 +01:00
model={getName()}
2019-07-09 18:11:09 +02:00
value={getQueryParameters(search, '_q') || ''}
/>
)}
<Wrapper>
2019-11-07 10:48:02 +01:00
<div className="row" style={{ marginBottom: '5px' }}>
2019-07-09 18:11:09 +02:00
<div className="col-10">
2019-07-10 12:20:25 +02:00
<div className="row" style={{ marginLeft: 0, marginRight: 0 }}>
2019-07-10 17:07:19 +02:00
{getLayoutSettingRef.current('filterable') && (
<>
<AddFilterCta
type="button"
onClick={toggleFilterPickerState}
>
2019-12-13 15:37:34 +01:00
<FilterIcon />
<FormattedMessage id="app.utils.filters" />
2019-07-10 17:07:19 +02:00
</AddFilterCta>
{getSearchParams().filters.map((filter, key) => (
<Filter
{...filter}
changeParams={handleChangeParams}
filters={getSearchParams().filters}
index={key}
2019-11-27 10:36:49 +01:00
schema={getListSchema()}
2019-07-10 17:07:19 +02:00
key={key}
toggleFilterPickerState={toggleFilterPickerState}
isFilterPickerOpen={isFilterPickerOpen}
/>
))}
</>
)}
2019-07-10 12:20:25 +02:00
</div>
2019-07-09 18:11:09 +02:00
</div>
<div className="col-2">
<DisplayedFieldsDropdown
isOpen={isLabelPickerOpen}
items={getAllLabels()}
onChange={handleChangeListLabels}
onClickReset={() => {
resetListLabels(slug);
}}
2019-10-15 15:12:53 +02:00
slug={slug}
toggle={toggleLabelPickerState}
/>
2019-07-09 18:11:09 +02:00
</div>
2019-07-09 09:53:50 +02:00
</div>
2019-11-28 19:23:10 +01:00
<div className="row" style={{ paddingTop: '12px' }}>
2019-07-09 18:11:09 +02:00
<div className="col-12">
<CustomTable
data={data}
headers={getTableHeaders()}
isBulkable={getLayoutSettingRef.current('bulkable')}
onChangeParams={handleChangeParams}
/>
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-10 09:12:48 +02:00
<PopUpWarning
isOpen={showWarningDelete}
toggleModal={toggleModalDelete}
content={{
title: `${pluginId}.popUpWarning.title`,
message: `${pluginId}.popUpWarning.bodyMessage.contentType.delete`,
cancel: `${pluginId}.popUpWarning.button.cancel`,
confirm: `${pluginId}.popUpWarning.button.confirm`,
}}
onConfirm={handleConfirmDeleteData}
2019-07-10 09:12:48 +02:00
popUpWarningType="danger"
/>
2019-07-09 19:38:39 +02:00
<PopUpWarning
isOpen={showWarningDeleteAll}
toggleModal={toggleModalDeleteAll}
content={{
2019-07-10 09:12:48 +02:00
title: `${pluginId}.popUpWarning.title`,
message: `${pluginId}.popUpWarning.bodyMessage.contentType.delete${
entriesToDelete.length > 1 ? '.all' : ''
}`,
cancel: `${pluginId}.popUpWarning.button.cancel`,
confirm: `${pluginId}.popUpWarning.button.confirm`,
2019-07-09 19:38:39 +02:00
}}
popUpWarningType="danger"
onConfirm={handleConfirmDeleteAllData}
2019-07-09 19:38:39 +02:00
/>
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({
2019-07-10 17:31:49 +02:00
pathname: PropTypes.string.isRequired,
2019-07-08 20:27:38 +02:00
search: PropTypes.string.isRequired,
}).isRequired,
2019-11-21 12:18:08 +01:00
models: PropTypes.array.isRequired,
getDataSucceeded: PropTypes.func.isRequired,
2019-07-09 09:53:50 +02:00
history: PropTypes.shape({
push: PropTypes.func.isRequired,
}).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,
onDeleteDataSucceeded: PropTypes.func.isRequired,
onDeleteSeveralDataSucceeded: 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,
2019-07-10 09:12:48 +02:00
showWarningDelete: PropTypes.bool.isRequired,
2019-07-09 19:38:39 +02:00
showWarningDeleteAll: PropTypes.bool.isRequired,
2019-10-24 17:08:52 +02:00
slug: PropTypes.string.isRequired,
2019-07-10 09:12:48 +02:00
toggleModalDelete: PropTypes.func.isRequired,
2019-07-09 19:38:39 +02:00
toggleModalDeleteAll: PropTypes.func.isRequired,
2019-07-08 17:01:12 +02:00
};
const mapStateToProps = makeSelectListView();
export function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
getDataSucceeded,
2019-07-09 19:38:39 +02:00
onChangeBulk,
onChangeBulkSelectall,
2019-07-09 09:53:50 +02:00
onChangeListLabels,
onDeleteDataSucceeded,
onDeleteSeveralDataSucceeded,
2019-07-09 09:53:50 +02:00
resetListLabels,
2019-07-08 20:27:38 +02:00
resetProps,
2019-07-10 09:12:48 +02:00
toggleModalDelete,
2019-07-09 19:38:39 +02:00
toggleModalDeleteAll,
2019-07-08 17:01:12 +02:00
},
dispatch
);
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
2019-07-08 17:01:12 +02:00
export default compose(withConnect, memo)(ListView);