mirror of
https://github.com/strapi/strapi.git
synced 2025-09-26 08:52:26 +00:00
Merge pull request #10857 from strapi/migrations/admin-users-filters
Migrations/admin users filters
This commit is contained in:
commit
d4c94aa637
@ -54,10 +54,6 @@ describe('<ListPage />', () => {
|
|||||||
} = render(App);
|
} = render(App);
|
||||||
|
|
||||||
expect(firstChild).toMatchInlineSnapshot(`
|
expect(firstChild).toMatchInlineSnapshot(`
|
||||||
.c37 {
|
|
||||||
font-family: Lato;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c5 {
|
.c5 {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
@ -404,6 +400,10 @@ describe('<ListPage />', () => {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c37 {
|
||||||
|
font-family: Lato;
|
||||||
|
}
|
||||||
|
|
||||||
<main
|
<main
|
||||||
aria-labelledby="title"
|
aria-labelledby="title"
|
||||||
class="c0"
|
class="c0"
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* This component is temporary
|
||||||
|
* FIXME migrate to the common one
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Button } from '@strapi/parts/Button';
|
||||||
|
import { Dialog, DialogBody, DialogFooter } from '@strapi/parts/Dialog';
|
||||||
|
import { Row } from '@strapi/parts/Row';
|
||||||
|
import { Stack } from '@strapi/parts/Stack';
|
||||||
|
import { Text } from '@strapi/parts/Text';
|
||||||
|
import { AlertWarningIcon, DeleteIcon } from '@strapi/icons';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
const ConfirmDialog = ({ isConfirmButtonLoading, onConfirm, onToggle, show }) => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
if (!show) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
onClose={onToggle}
|
||||||
|
title={formatMessage({
|
||||||
|
id: 'Settings.webhooks.confirmation',
|
||||||
|
defaultMessage: 'Confirmation',
|
||||||
|
})}
|
||||||
|
labelledBy="confirmation"
|
||||||
|
describedBy="confirm-description"
|
||||||
|
>
|
||||||
|
<DialogBody icon={<AlertWarningIcon />}>
|
||||||
|
<Stack size={2}>
|
||||||
|
<Row justifyContent="center">
|
||||||
|
<Text id="confirm-description">
|
||||||
|
{formatMessage({
|
||||||
|
id: 'Settings.webhooks.confirmation.delete',
|
||||||
|
defaultMessage: 'Are you sure you want to delete this?',
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</Row>
|
||||||
|
</Stack>
|
||||||
|
</DialogBody>
|
||||||
|
<DialogFooter
|
||||||
|
startAction={
|
||||||
|
<Button onClick={onToggle} variant="tertiary">
|
||||||
|
{formatMessage({ id: 'app.components.Button.cancel', defaultMessage: 'Cancel' })}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
endAction={
|
||||||
|
<Button
|
||||||
|
onClick={onConfirm}
|
||||||
|
variant="danger-light"
|
||||||
|
startIcon={<DeleteIcon />}
|
||||||
|
id="confirm-delete"
|
||||||
|
loading={isConfirmButtonLoading}
|
||||||
|
>
|
||||||
|
{formatMessage({ id: 'app.components.Button.confirm', defaultMessage: 'Confirm' })}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfirmDialog.defaultProps = {
|
||||||
|
isConfirmButtonLoading: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfirmDialog.propTypes = {
|
||||||
|
isConfirmButtonLoading: PropTypes.bool,
|
||||||
|
onConfirm: PropTypes.func.isRequired,
|
||||||
|
onToggle: PropTypes.func.isRequired,
|
||||||
|
show: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmDialog;
|
@ -3,12 +3,14 @@ import PropTypes from 'prop-types';
|
|||||||
import { BaseCheckbox, Box, IconButton, Tbody, Td, Text, Tr, Row } from '@strapi/parts';
|
import { BaseCheckbox, Box, IconButton, Tbody, Td, Text, Tr, Row } from '@strapi/parts';
|
||||||
import { EditIcon, DeleteIcon } from '@strapi/icons';
|
import { EditIcon, DeleteIcon } from '@strapi/icons';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
const TableRows = ({
|
const TableRows = ({
|
||||||
canUpdate,
|
canUpdate,
|
||||||
canDelete,
|
canDelete,
|
||||||
headers,
|
headers,
|
||||||
entriesToDelete,
|
entriesToDelete,
|
||||||
|
onClickDelete,
|
||||||
onSelectRow,
|
onSelectRow,
|
||||||
withMainAction,
|
withMainAction,
|
||||||
withBulkActions,
|
withBulkActions,
|
||||||
@ -18,6 +20,7 @@ const TableRows = ({
|
|||||||
push,
|
push,
|
||||||
location: { pathname },
|
location: { pathname },
|
||||||
} = useHistory();
|
} = useHistory();
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tbody>
|
<Tbody>
|
||||||
@ -55,7 +58,7 @@ const TableRows = ({
|
|||||||
{canUpdate && (
|
{canUpdate && (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => push(`${pathname}/${data.id}`)}
|
onClick={() => push(`${pathname}/${data.id}`)}
|
||||||
label="Edit"
|
label={formatMessage({ id: 'app.utils.edit', defaultMessage: 'Edit' })}
|
||||||
noBorder
|
noBorder
|
||||||
icon={<EditIcon />}
|
icon={<EditIcon />}
|
||||||
/>
|
/>
|
||||||
@ -63,8 +66,8 @@ const TableRows = ({
|
|||||||
{canDelete && (
|
{canDelete && (
|
||||||
<Box paddingLeft={1}>
|
<Box paddingLeft={1}>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => console.log('delete')}
|
onClick={() => onClickDelete(data.id)}
|
||||||
label="Delete"
|
label={formatMessage({ id: 'app.utils.delete', defaultMessage: 'Delete' })}
|
||||||
noBorder
|
noBorder
|
||||||
icon={<DeleteIcon />}
|
icon={<DeleteIcon />}
|
||||||
/>
|
/>
|
||||||
@ -83,6 +86,8 @@ const TableRows = ({
|
|||||||
TableRows.defaultProps = {
|
TableRows.defaultProps = {
|
||||||
canDelete: false,
|
canDelete: false,
|
||||||
canUpdate: false,
|
canUpdate: false,
|
||||||
|
onClickDelete: () => {},
|
||||||
|
onSelectRow: () => {},
|
||||||
rows: [],
|
rows: [],
|
||||||
withBulkActions: false,
|
withBulkActions: false,
|
||||||
withMainAction: false,
|
withMainAction: false,
|
||||||
@ -93,7 +98,8 @@ TableRows.propTypes = {
|
|||||||
canUpdate: PropTypes.bool,
|
canUpdate: PropTypes.bool,
|
||||||
entriesToDelete: PropTypes.array.isRequired,
|
entriesToDelete: PropTypes.array.isRequired,
|
||||||
headers: PropTypes.array.isRequired,
|
headers: PropTypes.array.isRequired,
|
||||||
onSelectRow: PropTypes.func.isRequired,
|
onClickDelete: PropTypes.func,
|
||||||
|
onSelectRow: PropTypes.func,
|
||||||
rows: PropTypes.array,
|
rows: PropTypes.array,
|
||||||
withBulkActions: PropTypes.bool,
|
withBulkActions: PropTypes.bool,
|
||||||
withMainAction: PropTypes.bool,
|
withMainAction: PropTypes.bool,
|
||||||
|
@ -1,13 +1,38 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Table as TableCompo } from '@strapi/parts';
|
import { Box, Row, Button, Table as TableCompo, Subtitle } from '@strapi/parts';
|
||||||
import { EmptyBodyTable, useQueryParams } from '@strapi/helper-plugin';
|
import { EmptyBodyTable, useQueryParams } from '@strapi/helper-plugin';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { DeleteIcon } from '@strapi/icons';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import ConfirmDialog from '../ConfirmDialog';
|
||||||
import TableHead from './TableHead';
|
import TableHead from './TableHead';
|
||||||
import TableRows from './TableRows';
|
import TableRows from './TableRows';
|
||||||
|
|
||||||
const Table = ({ canDelete, canUpdate, headers, rows, withBulkActions, withMainAction }) => {
|
const BlockActions = styled(Row)`
|
||||||
|
& > * + * {
|
||||||
|
margin-left: ${({ theme }) => theme.spaces[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-left: ${({ pullRight }) => (pullRight ? 'auto' : undefined)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Table = ({
|
||||||
|
canDelete,
|
||||||
|
canUpdate,
|
||||||
|
headers,
|
||||||
|
isLoading,
|
||||||
|
onConfirmDeleteAll,
|
||||||
|
rows,
|
||||||
|
withBulkActions,
|
||||||
|
withMainAction,
|
||||||
|
}) => {
|
||||||
const [entriesToDelete, setEntriesToDelete] = useState([]);
|
const [entriesToDelete, setEntriesToDelete] = useState([]);
|
||||||
|
const [showConfirmDeleteAll, setShowConfirmDeleteAll] = useState(false);
|
||||||
|
const [showConfirmDelete, setShowConfirmDelete] = useState(false);
|
||||||
|
const [isConfirmButtonLoading, setIsConfirmButtonLoading] = useState(false);
|
||||||
const [{ query }] = useQueryParams();
|
const [{ query }] = useQueryParams();
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
const ROW_COUNT = rows.length + 1;
|
const ROW_COUNT = rows.length + 1;
|
||||||
const COL_COUNT = headers.length + (withBulkActions ? 1 : 0) + (withMainAction ? 1 : 0);
|
const COL_COUNT = headers.length + (withBulkActions ? 1 : 0) + (withMainAction ? 1 : 0);
|
||||||
const hasFilters = query.filters !== undefined;
|
const hasFilters = query.filters !== undefined;
|
||||||
@ -21,6 +46,28 @@ const Table = ({ canDelete, canUpdate, headers, rows, withBulkActions, withMainA
|
|||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const handleConfirmDeleteAll = async () => {
|
||||||
|
try {
|
||||||
|
setIsConfirmButtonLoading(true);
|
||||||
|
await onConfirmDeleteAll(entriesToDelete);
|
||||||
|
handleToggleConfirmDeleteAll();
|
||||||
|
} catch (err) {
|
||||||
|
setIsConfirmButtonLoading(false);
|
||||||
|
handleToggleConfirmDeleteAll();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmDelete = async () => {
|
||||||
|
try {
|
||||||
|
setIsConfirmButtonLoading(true);
|
||||||
|
await onConfirmDeleteAll(entriesToDelete);
|
||||||
|
handleToggleConfirmDelete();
|
||||||
|
} catch (err) {
|
||||||
|
setIsConfirmButtonLoading(false);
|
||||||
|
handleToggleConfirmDelete();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelectAll = () => {
|
const handleSelectAll = () => {
|
||||||
if (!areAllEntriesSelected) {
|
if (!areAllEntriesSelected) {
|
||||||
setEntriesToDelete(rows.map(row => row.id));
|
setEntriesToDelete(rows.map(row => row.id));
|
||||||
@ -29,6 +76,23 @@ const Table = ({ canDelete, canUpdate, headers, rows, withBulkActions, withMainA
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleToggleConfirmDeleteAll = () => {
|
||||||
|
setShowConfirmDeleteAll(prev => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleConfirmDelete = () => {
|
||||||
|
if (showConfirmDelete) {
|
||||||
|
setEntriesToDelete([]);
|
||||||
|
}
|
||||||
|
setShowConfirmDelete(prev => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickDelete = id => {
|
||||||
|
setEntriesToDelete([id]);
|
||||||
|
|
||||||
|
handleToggleConfirmDelete();
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelectRow = ({ name, value }) => {
|
const handleSelectRow = ({ name, value }) => {
|
||||||
setEntriesToDelete(prev => {
|
setEntriesToDelete(prev => {
|
||||||
if (value) {
|
if (value) {
|
||||||
@ -40,35 +104,79 @@ const Table = ({ canDelete, canUpdate, headers, rows, withBulkActions, withMainA
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableCompo colCount={COL_COUNT} rowCount={ROW_COUNT}>
|
<>
|
||||||
<TableHead
|
{entriesToDelete.length > 0 && (
|
||||||
areAllEntriesSelected={areAllEntriesSelected}
|
<Box>
|
||||||
entriesToDelete={entriesToDelete}
|
<Box paddingBottom={4}>
|
||||||
headers={headers}
|
<Row justifyContent="space-between">
|
||||||
onSelectAll={handleSelectAll}
|
<BlockActions>
|
||||||
withMainAction={withMainAction}
|
<Subtitle textColor="neutral600">
|
||||||
withBulkActions={withBulkActions}
|
{formatMessage(
|
||||||
/>
|
{
|
||||||
{!rows.length ? (
|
id: 'content-manager.components.TableDelete.label',
|
||||||
<EmptyBodyTable colSpan={COL_COUNT} content={content} />
|
defaultMessage: '{number, plural, one {# entry} other {# entries}} selected',
|
||||||
) : (
|
},
|
||||||
<TableRows
|
{ number: entriesToDelete.length }
|
||||||
canDelete={canDelete}
|
)}
|
||||||
canUpdate={canUpdate}
|
</Subtitle>
|
||||||
|
<Button
|
||||||
|
onClick={handleToggleConfirmDeleteAll}
|
||||||
|
startIcon={<DeleteIcon />}
|
||||||
|
size="L"
|
||||||
|
variant="danger-light"
|
||||||
|
>
|
||||||
|
{formatMessage({ id: 'app.utils.delete', defaultMessage: 'Delete' })}
|
||||||
|
</Button>
|
||||||
|
</BlockActions>
|
||||||
|
</Row>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<TableCompo colCount={COL_COUNT} rowCount={ROW_COUNT}>
|
||||||
|
<TableHead
|
||||||
|
areAllEntriesSelected={areAllEntriesSelected}
|
||||||
entriesToDelete={entriesToDelete}
|
entriesToDelete={entriesToDelete}
|
||||||
headers={headers}
|
headers={headers}
|
||||||
onSelectRow={handleSelectRow}
|
onSelectAll={handleSelectAll}
|
||||||
rows={rows}
|
|
||||||
withBulkActions={withBulkActions}
|
|
||||||
withMainAction={withMainAction}
|
withMainAction={withMainAction}
|
||||||
|
withBulkActions={withBulkActions}
|
||||||
/>
|
/>
|
||||||
)}
|
{!rows.length || isLoading ? (
|
||||||
</TableCompo>
|
<EmptyBodyTable colSpan={COL_COUNT} content={content} isLoading={isLoading} />
|
||||||
|
) : (
|
||||||
|
<TableRows
|
||||||
|
canDelete={canDelete}
|
||||||
|
canUpdate={canUpdate}
|
||||||
|
entriesToDelete={entriesToDelete}
|
||||||
|
headers={headers}
|
||||||
|
onClickDelete={handleClickDelete}
|
||||||
|
onSelectRow={handleSelectRow}
|
||||||
|
rows={rows}
|
||||||
|
withBulkActions={withBulkActions}
|
||||||
|
withMainAction={withMainAction}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableCompo>
|
||||||
|
<ConfirmDialog
|
||||||
|
isConfirmButtonLoading={isConfirmButtonLoading}
|
||||||
|
onConfirm={handleConfirmDeleteAll}
|
||||||
|
onToggle={handleToggleConfirmDeleteAll}
|
||||||
|
show={showConfirmDeleteAll}
|
||||||
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
isConfirmButtonLoading={isConfirmButtonLoading}
|
||||||
|
onConfirm={handleConfirmDelete}
|
||||||
|
onToggle={handleToggleConfirmDelete}
|
||||||
|
show={showConfirmDelete}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Table.defaultProps = {
|
Table.defaultProps = {
|
||||||
headers: [],
|
headers: [],
|
||||||
|
isLoading: false,
|
||||||
|
onConfirmDeleteAll: () => {},
|
||||||
rows: [],
|
rows: [],
|
||||||
withBulkActions: false,
|
withBulkActions: false,
|
||||||
withMainAction: false,
|
withMainAction: false,
|
||||||
@ -78,6 +186,8 @@ Table.propTypes = {
|
|||||||
canDelete: PropTypes.bool.isRequired,
|
canDelete: PropTypes.bool.isRequired,
|
||||||
canUpdate: PropTypes.bool.isRequired,
|
canUpdate: PropTypes.bool.isRequired,
|
||||||
headers: PropTypes.array,
|
headers: PropTypes.array,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
|
onConfirmDeleteAll: PropTypes.func,
|
||||||
rows: PropTypes.array,
|
rows: PropTypes.array,
|
||||||
withBulkActions: PropTypes.bool,
|
withBulkActions: PropTypes.bool,
|
||||||
withMainAction: PropTypes.bool,
|
withMainAction: PropTypes.bool,
|
||||||
|
@ -55,74 +55,6 @@ describe('DynamicTable', () => {
|
|||||||
const { container, getByText } = render(app);
|
const { container, getByText } = render(app);
|
||||||
|
|
||||||
expect(container.firstChild).toMatchInlineSnapshot(`
|
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||||
.c24 {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1.25;
|
|
||||||
color: #666687;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c19 {
|
|
||||||
background: #ffffff;
|
|
||||||
padding: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c21 {
|
|
||||||
padding-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c23 {
|
|
||||||
padding-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c20 {
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -webkit-flex;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: flex;
|
|
||||||
-webkit-flex-direction: column;
|
|
||||||
-ms-flex-direction: column;
|
|
||||||
flex-direction: column;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c22 svg {
|
|
||||||
height: 5.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c16 tr:last-of-type {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c17 {
|
|
||||||
border-bottom: 1px solid #eaeaef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c17 td,
|
|
||||||
.c17 th {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c17 td:first-of-type,
|
|
||||||
.c17 th:first-of-type {
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c18 {
|
|
||||||
vertical-align: middle;
|
|
||||||
text-align: left;
|
|
||||||
color: #666687;
|
|
||||||
outline-offset: -4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c18 input {
|
|
||||||
vertical-align: sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c12 {
|
.c12 {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
@ -316,6 +248,74 @@ describe('DynamicTable', () => {
|
|||||||
height: 0.25rem;
|
height: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c24 {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.25;
|
||||||
|
color: #666687;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c19 {
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c21 {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c23 {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c20 {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c22 svg {
|
||||||
|
height: 5.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c16 tr:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c17 {
|
||||||
|
border-bottom: 1px solid #eaeaef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c17 td,
|
||||||
|
.c17 th {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c17 td:first-of-type,
|
||||||
|
.c17 th:first-of-type {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c18 {
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: left;
|
||||||
|
color: #666687;
|
||||||
|
outline-offset: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c18 input {
|
||||||
|
vertical-align: sub;
|
||||||
|
}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="c0"
|
class="c0"
|
||||||
>
|
>
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Tag } from '@strapi/parts';
|
||||||
|
import { Close } from '@strapi/icons';
|
||||||
|
import { useQueryParams } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
|
const FilterList = () => {
|
||||||
|
const [{ query }, setQuery] = useQueryParams();
|
||||||
|
|
||||||
|
const handleClick = filter => {
|
||||||
|
const nextFilters = query.filters.$and.filter(f => {
|
||||||
|
const name = Object.keys(filter)[0];
|
||||||
|
const filterType = Object.keys(filter[name])[0];
|
||||||
|
const value = filter[name][filterType];
|
||||||
|
|
||||||
|
return f[name]?.[filterType] !== value;
|
||||||
|
});
|
||||||
|
|
||||||
|
setQuery({ filters: { $and: nextFilters }, page: 1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
query.filters?.$and.map((filter, i) => {
|
||||||
|
const name = Object.keys(filter)[0];
|
||||||
|
const filterType = Object.keys(filter[name])[0];
|
||||||
|
const value = filter[name][filterType];
|
||||||
|
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
<Box key={`${name}-${i}`} padding={1} onClick={() => handleClick(filter)}>
|
||||||
|
<Tag icon={<Close />}>
|
||||||
|
{name} {filterType} {value}
|
||||||
|
</Tag>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}) || null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilterList;
|
@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Select, Field, Stack, FieldInput, Option } from '@strapi/parts';
|
||||||
|
|
||||||
|
const Inputs = ({ onChange, type, value }) => {
|
||||||
|
if (type === 'boolean') {
|
||||||
|
return (
|
||||||
|
<Select onChange={onChange} value={value}>
|
||||||
|
<Option value="true">true</Option>
|
||||||
|
<Option value="false">false</Option>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO improve
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Field>
|
||||||
|
<Stack>
|
||||||
|
<FieldInput onChange={({ target: { value } }) => onChange(value)} value={value} size="S" />
|
||||||
|
</Stack>
|
||||||
|
</Field>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Inputs.defaultProps = {
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
Inputs.propTypes = {
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.any,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Inputs;
|
@ -0,0 +1,132 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Button, Box, Popover, Stack, Select, Option, FocusTrap } from '@strapi/parts';
|
||||||
|
import { AddIcon } from '@strapi/icons';
|
||||||
|
import { useQueryParams } from '@strapi/helper-plugin';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import Inputs from './Inputs';
|
||||||
|
import getFilterList from './utils/getFilterList';
|
||||||
|
|
||||||
|
const FullWidthButton = styled(Button)`
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FilterPopover = ({ displayedFilters, isVisible, onToggle, source }) => {
|
||||||
|
const [{ query }, setQuery] = useQueryParams();
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const [modifiedData, setModifiedData] = useState({
|
||||||
|
name: displayedFilters[0].name,
|
||||||
|
filter: getFilterList(displayedFilters[0])[0].value,
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChangeFilterField = value => {
|
||||||
|
const nextField = displayedFilters.find(f => f.name === value);
|
||||||
|
const {
|
||||||
|
fieldSchema: { type },
|
||||||
|
} = nextField;
|
||||||
|
|
||||||
|
setModifiedData({ name: value, filter: '$eq', value: type === 'boolean' ? 'true' : '' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const hasFilter =
|
||||||
|
query?.filters?.$and.find(filter => {
|
||||||
|
return (
|
||||||
|
filter[modifiedData.name] &&
|
||||||
|
filter[modifiedData.name]?.[modifiedData.filter] === modifiedData.value
|
||||||
|
);
|
||||||
|
}) !== undefined;
|
||||||
|
|
||||||
|
if (modifiedData.value && !hasFilter) {
|
||||||
|
const filters = [
|
||||||
|
...(query?.filters?.$and || []),
|
||||||
|
{ [modifiedData.name]: { [modifiedData.filter]: modifiedData.value } },
|
||||||
|
];
|
||||||
|
|
||||||
|
setQuery({ filters: { $and: filters }, page: 1 });
|
||||||
|
}
|
||||||
|
onToggle();
|
||||||
|
};
|
||||||
|
|
||||||
|
const appliedFilter = displayedFilters.find(filter => filter.name === modifiedData.name);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover source={source} padding={3} spacingTop={1}>
|
||||||
|
<FocusTrap onEscape={onToggle}>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<Stack size={1} style={{ minWidth: 184 }}>
|
||||||
|
<Box>
|
||||||
|
<Select
|
||||||
|
aria-label="Select field"
|
||||||
|
name="name"
|
||||||
|
size="S"
|
||||||
|
onChange={handleChangeFilterField}
|
||||||
|
value={modifiedData.name}
|
||||||
|
>
|
||||||
|
{displayedFilters.map(filter => {
|
||||||
|
return (
|
||||||
|
<Option key={filter.name} value={filter.name}>
|
||||||
|
{filter.metadatas.label}
|
||||||
|
</Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Select
|
||||||
|
aria-label="Select filter"
|
||||||
|
name="filter"
|
||||||
|
size="S"
|
||||||
|
value={modifiedData.filter}
|
||||||
|
onChange={val => setModifiedData(prev => ({ ...prev, filter: val }))}
|
||||||
|
>
|
||||||
|
{getFilterList(appliedFilter).map(option => {
|
||||||
|
return (
|
||||||
|
<Option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Inputs
|
||||||
|
{...appliedFilter.fieldSchema}
|
||||||
|
value={modifiedData.value}
|
||||||
|
onChange={value => setModifiedData(prev => ({ ...prev, value }))}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<FullWidthButton variant="secondary" startIcon={<AddIcon />} type="submit">
|
||||||
|
{formatMessage({ id: 'app.utils.add-filter', defaultMessage: 'Add filter' })}
|
||||||
|
</FullWidthButton>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
</FocusTrap>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
FilterPopover.propTypes = {
|
||||||
|
displayedFilters: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
metadatas: PropTypes.shape({ label: PropTypes.string }),
|
||||||
|
fieldSchema: PropTypes.shape({ type: PropTypes.string }),
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
isVisible: PropTypes.bool.isRequired,
|
||||||
|
onToggle: PropTypes.func.isRequired,
|
||||||
|
source: PropTypes.shape({ current: PropTypes.instanceOf(Element) }).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilterPopover;
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Depending on the selected field find the possible filters to apply
|
||||||
|
* @param {Object} fieldSchema.type the type of the filter
|
||||||
|
* @returns {Object[]}
|
||||||
|
*/
|
||||||
|
const getFilterList = ({ fieldSchema: { type } }) => {
|
||||||
|
// TODO needs to be improved for the CM
|
||||||
|
switch (type) {
|
||||||
|
case 'email':
|
||||||
|
case 'string': {
|
||||||
|
return [
|
||||||
|
{ label: 'is', value: '$eq' },
|
||||||
|
{ label: 'is not', value: '$ne' },
|
||||||
|
{ label: 'contains', value: '$contains' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return [
|
||||||
|
{ label: 'is', value: '$eq' },
|
||||||
|
{ label: 'is not', value: '$ne' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getFilterList;
|
@ -0,0 +1,54 @@
|
|||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { Button, Box } from '@strapi/parts';
|
||||||
|
import { FilterIcon } from '@strapi/icons';
|
||||||
|
import FilterList from './FilterList';
|
||||||
|
import FilterPopover from './FilterPopover';
|
||||||
|
|
||||||
|
const Filters = ({ displayedFilters }) => {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const buttonRef = useRef();
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
setIsVisible(prev => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box padding={1}>
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
ref={buttonRef}
|
||||||
|
startIcon={<FilterIcon />}
|
||||||
|
onClick={handleToggle}
|
||||||
|
size="S"
|
||||||
|
>
|
||||||
|
{formatMessage({ id: 'app.utils.filters', defaultMessage: 'Filters' })}
|
||||||
|
</Button>
|
||||||
|
{isVisible && (
|
||||||
|
<FilterPopover
|
||||||
|
displayedFilters={displayedFilters}
|
||||||
|
isVisible={isVisible}
|
||||||
|
onToggle={handleToggle}
|
||||||
|
source={buttonRef}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<FilterList />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Filters.propTypes = {
|
||||||
|
displayedFilters: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
metadatas: PropTypes.shape({ label: PropTypes.string }),
|
||||||
|
fieldSchema: PropTypes.shape({ type: PropTypes.string }),
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Filters;
|
@ -23,7 +23,7 @@ const PageSize = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
<Select onChange={handleChange} value={pageSize}>
|
<Select aria-label="Entries per page" onChange={handleChange} value={pageSize}>
|
||||||
<Option value="10">10</Option>
|
<Option value="10">10</Option>
|
||||||
<Option value="20">20</Option>
|
<Option value="20">20</Option>
|
||||||
<Option value="50">50</Option>
|
<Option value="50">50</Option>
|
||||||
|
@ -60,7 +60,13 @@ const Pagination = ({ pagination: { pageCount } }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (activePage === 2 && pageCount >= 3) {
|
if (activePage === 2 && pageCount >= 3) {
|
||||||
firstLinksToCreate = pageCount === 5 ? [2, 3, 4] : [2, 3];
|
if (pageCount === 5) {
|
||||||
|
firstLinksToCreate = [2, 3, 4];
|
||||||
|
} else if (pageCount === 3) {
|
||||||
|
firstLinksToCreate = [2];
|
||||||
|
} else {
|
||||||
|
firstLinksToCreate = [2, 3];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activePage === 4 && pageCount >= 3) {
|
if (activePage === 4 && pageCount >= 3) {
|
||||||
@ -71,15 +77,15 @@ const Pagination = ({ pagination: { pageCount } }) => {
|
|||||||
lastLinksToCreate = [pageCount - 1];
|
lastLinksToCreate = [pageCount - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activePage === pageCount - 2 && pageCount >= 3) {
|
if (activePage === pageCount - 2 && pageCount > 3) {
|
||||||
lastLinksToCreate = [activePage + 1, activePage, activePage - 1];
|
lastLinksToCreate = [activePage + 1, activePage, activePage - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activePage === pageCount - 3 && pageCount >= 3 && activePage > 5) {
|
if (activePage === pageCount - 3 && pageCount > 3 && activePage > 5) {
|
||||||
lastLinksToCreate = [activePage + 2, activePage + 1, activePage, activePage - 1];
|
lastLinksToCreate = [activePage + 2, activePage + 1, activePage, activePage - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activePage === pageCount - 1 && pageCount >= 3) {
|
if (activePage === pageCount - 1 && pageCount > 3) {
|
||||||
lastLinksToCreate = [activePage, activePage - 1];
|
lastLinksToCreate = [activePage, activePage - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +105,11 @@ const Pagination = ({ pagination: { pageCount } }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (![1, 2].includes(activePage) && activePage < pageCount - 3) {
|
if (
|
||||||
|
![1, 2].includes(activePage) &&
|
||||||
|
activePage <= pageCount - 3 &&
|
||||||
|
firstLinks.length + lastLinks.length < 6
|
||||||
|
) {
|
||||||
const middleLinksToCreate = [activePage - 1, activePage, activePage + 1];
|
const middleLinksToCreate = [activePage - 1, activePage, activePage + 1];
|
||||||
|
|
||||||
middleLinksToCreate.forEach(number => {
|
middleLinksToCreate.forEach(number => {
|
||||||
|
@ -283,6 +283,7 @@ exports[`DynamicTable renders and matches the snapshot 1`] = `
|
|||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
|
aria-label="Entries per page"
|
||||||
aria-labelledby="select-1-label select-1-content"
|
aria-labelledby="select-1-label select-1-content"
|
||||||
class="c6"
|
class="c6"
|
||||||
id="select-1"
|
id="select-1"
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useQueryParams } from '@strapi/helper-plugin';
|
||||||
|
import { SearchIcon } from '@strapi/icons';
|
||||||
|
import { IconButton } from '@strapi/parts/IconButton';
|
||||||
|
import { TextInput } from '@strapi/parts/TextInput';
|
||||||
|
|
||||||
|
const Search = () => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [{ query }, setQuery] = useQueryParams();
|
||||||
|
const [value, setValue] = useState(query._q || '');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
if (value) {
|
||||||
|
setQuery({ _q: value, page: 1 });
|
||||||
|
} else {
|
||||||
|
setQuery({ _q: '' }, 'remove');
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
return () => clearTimeout(handler);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
setIsOpen(prev => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
return (
|
||||||
|
<TextInput
|
||||||
|
onBlur={() => setIsOpen(false)}
|
||||||
|
name="search"
|
||||||
|
onChange={({ target: { value } }) => setValue(value)}
|
||||||
|
type="text"
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <IconButton icon={<SearchIcon />} label="Search" onClick={handleToggle} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Search;
|
@ -1,42 +1,36 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
CustomContentLayout,
|
CustomContentLayout,
|
||||||
LoadingIndicatorPage,
|
|
||||||
useRBAC,
|
useRBAC,
|
||||||
SettingsPageTitle,
|
SettingsPageTitle,
|
||||||
useNotification,
|
useNotification,
|
||||||
useFocusWhenNavigate,
|
useFocusWhenNavigate,
|
||||||
} from '@strapi/helper-plugin';
|
} from '@strapi/helper-plugin';
|
||||||
import { Button, HeaderLayout, Main } from '@strapi/parts';
|
import { Button, Box, HeaderLayout, Main, Row } from '@strapi/parts';
|
||||||
import { Mail } from '@strapi/icons';
|
import { Mail } from '@strapi/icons';
|
||||||
import {
|
import { useLocation } from 'react-router-dom';
|
||||||
// useHistory,
|
|
||||||
useLocation,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useQuery } from 'react-query';
|
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import adminPermissions from '../../../permissions';
|
import adminPermissions from '../../../permissions';
|
||||||
import DynamicTable from './DynamicTable';
|
import DynamicTable from './DynamicTable';
|
||||||
|
import Filters from './Filters';
|
||||||
|
import Search from './Search';
|
||||||
import PaginationFooter from './PaginationFooter';
|
import PaginationFooter from './PaginationFooter';
|
||||||
import fetchData from './utils/api';
|
import { deleteData, fetchData } from './utils/api';
|
||||||
|
import displayedFilters from './utils/displayedFilters';
|
||||||
import tableHeaders from './utils/tableHeaders';
|
import tableHeaders from './utils/tableHeaders';
|
||||||
|
|
||||||
const ListPage = () => {
|
const ListPage = () => {
|
||||||
const {
|
const {
|
||||||
allowedActions: { canCreate, canDelete, canRead, canUpdate },
|
allowedActions: { canCreate, canDelete, canRead, canUpdate },
|
||||||
} = useRBAC(adminPermissions.settings.users);
|
} = useRBAC(adminPermissions.settings.users);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const toggleNotification = useNotification();
|
const toggleNotification = useNotification();
|
||||||
|
|
||||||
// const [isWarningDeleteAllOpened, setIsWarningDeleteAllOpened] = useState(false);
|
|
||||||
// const [isModalOpened, setIsModalOpened] = useState(false);
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
// const query = useQuery();
|
|
||||||
// const { push } = useHistory();
|
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
|
|
||||||
const { status, data, isFetching } = useQuery(['projects', search], () => fetchData(search), {
|
const { status, data, isFetching } = useQuery(['users', search], () => fetchData(search), {
|
||||||
enabled: canRead,
|
enabled: canRead,
|
||||||
keepPreviousData: true,
|
keepPreviousData: true,
|
||||||
retry: false,
|
retry: false,
|
||||||
@ -53,181 +47,21 @@ const ListPage = () => {
|
|||||||
|
|
||||||
const total = get(data, 'pagination.total', 0);
|
const total = get(data, 'pagination.total', 0);
|
||||||
|
|
||||||
// const filters = useMemo(() => {
|
const deleteAllMutation = useMutation(ids => deleteData(ids), {
|
||||||
// return getFilters(search);
|
onSuccess: async () => {
|
||||||
// }, [search]);
|
await queryClient.invalidateQueries(['users', search]);
|
||||||
|
},
|
||||||
// const [
|
onError: err => {
|
||||||
// {
|
if (err?.response?.data?.data) {
|
||||||
// // data,
|
toggleNotification({ type: 'warning', message: err.response.data.data });
|
||||||
// // dataToDelete,
|
} else {
|
||||||
// // isLoading,
|
toggleNotification({
|
||||||
// pagination: { total },
|
type: 'warning',
|
||||||
// // shouldRefetchData,
|
message: { id: 'notification.error', defaultMessage: 'An error occured' },
|
||||||
// // showModalConfirmButtonLoading,
|
});
|
||||||
// },
|
}
|
||||||
// dispatch,
|
},
|
||||||
// ] = useReducer(reducer, initialState, init);
|
});
|
||||||
// const pageSize = parseInt(query.get('pageSize') || 10, 10);
|
|
||||||
// const page = parseInt(query.get('page') || 0, 10);
|
|
||||||
// const sort = decodeURIComponent(query.get('sort'));
|
|
||||||
// const _q = decodeURIComponent(query.get('_q') || '');
|
|
||||||
// const getDataRef = useRef();
|
|
||||||
|
|
||||||
// const listRef = useRef();
|
|
||||||
|
|
||||||
// getDataRef.current = async () => {
|
|
||||||
// if (!canRead) {
|
|
||||||
// dispatch({
|
|
||||||
// type: 'UNSET_IS_LOADING',
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// // Show the loading state and reset the state
|
|
||||||
// dispatch({
|
|
||||||
// type: 'GET_DATA',
|
|
||||||
// });
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// const {
|
|
||||||
// data: { results, pagination },
|
|
||||||
// } = await request(`/admin/users${search}`, { method: 'GET' });
|
|
||||||
|
|
||||||
// dispatch({
|
|
||||||
// type: 'GET_DATA_SUCCEEDED',
|
|
||||||
// data: results,
|
|
||||||
// pagination,
|
|
||||||
// });
|
|
||||||
// } catch (err) {
|
|
||||||
// console.error(err.response);
|
|
||||||
// toggleNotification({
|
|
||||||
// type: 'warning',
|
|
||||||
// message: { id: 'notification.error' },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (!isLoadingForPermissions) {
|
|
||||||
// getDataRef.current();
|
|
||||||
// }
|
|
||||||
// }, [search, isLoadingForPermissions]);
|
|
||||||
|
|
||||||
// const handleChangeDataToDelete = ids => {
|
|
||||||
// dispatch({
|
|
||||||
// type: 'ON_CHANGE_DATA_TO_DELETE',
|
|
||||||
// dataToDelete: ids,
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleChangeFilter = ({ filter, name, value }) => {
|
|
||||||
// const filterName = `${name}${filter}`;
|
|
||||||
|
|
||||||
// updateSearchParams(filterName, encodeURIComponent(value), true);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleChangeFooterParams = ({ target: { name, value } }) => {
|
|
||||||
// let paramName = name.split('.')[1].replace('_', '');
|
|
||||||
|
|
||||||
// if (paramName === 'limit') {
|
|
||||||
// paramName = 'pageSize';
|
|
||||||
// }
|
|
||||||
|
|
||||||
// updateSearchParams(paramName, value);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleChangeSort = ({ target: { name, value } }) => {
|
|
||||||
// updateSearchParams(name, value);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleClickDeleteFilter = ({ target: { name } }) => {
|
|
||||||
// const currentSearch = new URLSearchParams(search);
|
|
||||||
|
|
||||||
// currentSearch.delete(name);
|
|
||||||
|
|
||||||
// push({ search: currentSearch.toString() });
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleClickDelete = useCallback(id => {
|
|
||||||
// handleToggleModal();
|
|
||||||
|
|
||||||
// dispatch({
|
|
||||||
// type: 'ON_CHANGE_DATA_TO_DELETE',
|
|
||||||
// dataToDelete: [id],
|
|
||||||
// });
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
// const handleCloseModal = () => {
|
|
||||||
// // Refetch data
|
|
||||||
// getDataRef.current();
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleClosedModalDelete = () => {
|
|
||||||
// if (shouldRefetchData) {
|
|
||||||
// getDataRef.current();
|
|
||||||
// } else {
|
|
||||||
// // Empty the selected ids when the modal closes
|
|
||||||
// dispatch({
|
|
||||||
// type: 'RESET_DATA_TO_DELETE',
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Reset the list's reducer dataToDelete state using a ref so we don't need an effect
|
|
||||||
// listRef.current.resetDataToDelete();
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleConfirmDeleteData = useCallback(async () => {
|
|
||||||
// dispatch({
|
|
||||||
// type: 'ON_DELETE_USERS',
|
|
||||||
// });
|
|
||||||
|
|
||||||
// let shouldDispatchSucceededAction = false;
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// await request('/admin/users/batch-delete', {
|
|
||||||
// method: 'POST',
|
|
||||||
// body: {
|
|
||||||
// ids: dataToDelete,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// shouldDispatchSucceededAction = true;
|
|
||||||
// } catch (err) {
|
|
||||||
// const errorMessage = get(err, 'response.payload.data', 'An error occured');
|
|
||||||
|
|
||||||
// toggleNotification({
|
|
||||||
// type: 'warning',
|
|
||||||
// message: errorMessage,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Only dispatch the action once
|
|
||||||
// if (shouldDispatchSucceededAction) {
|
|
||||||
// dispatch({
|
|
||||||
// type: 'ON_DELETE_USERS_SUCCEEDED',
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// handleToggleModal();
|
|
||||||
// }, [dataToDelete, toggleNotification]);
|
|
||||||
|
|
||||||
// const handleToggle = () => setIsModalOpened(prev => !prev);
|
|
||||||
|
|
||||||
// const handleToggleModal = () => setIsWarningDeleteAllOpened(prev => !prev);
|
|
||||||
|
|
||||||
// const updateSearchParams = (name, value, shouldDeleteSearch = false) => {
|
|
||||||
// const currentSearch = new URLSearchParams(search);
|
|
||||||
// // Update the currentSearch
|
|
||||||
// currentSearch.set(name, value);
|
|
||||||
|
|
||||||
// if (shouldDeleteSearch) {
|
|
||||||
// currentSearch.delete('_q');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// push({
|
|
||||||
// search: currentSearch.toString(),
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
// This can be improved but we need to show an something to the user
|
// This can be improved but we need to show an something to the user
|
||||||
const isLoading =
|
const isLoading =
|
||||||
@ -266,16 +100,26 @@ const ListPage = () => {
|
|||||||
{ number: total }
|
{ number: total }
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<CustomContentLayout action={createAction} canRead={canRead}>
|
<CustomContentLayout canRead={canRead}>
|
||||||
{status === 'error' && <div>TODO: An error occurred</div>}
|
{status === 'error' && <div>TODO: An error occurred</div>}
|
||||||
{canRead && isLoading ? (
|
{canRead && (
|
||||||
<LoadingIndicatorPage />
|
<>
|
||||||
) : (
|
<Box paddingBottom={4}>
|
||||||
|
<Row style={{ flexWrap: 'wrap' }}>
|
||||||
|
<Search />
|
||||||
|
<Filters displayedFilters={displayedFilters} />
|
||||||
|
</Row>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{canRead && (
|
||||||
<>
|
<>
|
||||||
<DynamicTable
|
<DynamicTable
|
||||||
canCreate={canCreate}
|
canCreate={canCreate}
|
||||||
canDelete={canDelete}
|
canDelete={canDelete}
|
||||||
canUpdate={canUpdate}
|
canUpdate={canUpdate}
|
||||||
|
isLoading={isLoading}
|
||||||
|
onConfirmDeleteAll={deleteAllMutation.mutateAsync}
|
||||||
headers={tableHeaders}
|
headers={tableHeaders}
|
||||||
rows={data?.results}
|
rows={data?.results}
|
||||||
withBulkActions
|
withBulkActions
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
/* eslint-disable consistent-return */
|
|
||||||
import produce from 'immer';
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
data: [],
|
|
||||||
dataToDelete: [],
|
|
||||||
isLoading: true,
|
|
||||||
pagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
pageCount: 0,
|
|
||||||
total: 0,
|
|
||||||
},
|
|
||||||
showModalConfirmButtonLoading: false,
|
|
||||||
shouldRefetchData: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const reducer = (state, action) =>
|
|
||||||
produce(state, draftState => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'GET_DATA': {
|
|
||||||
return initialState;
|
|
||||||
}
|
|
||||||
case 'GET_DATA_SUCCEEDED': {
|
|
||||||
draftState.data = action.data;
|
|
||||||
draftState.isLoading = false;
|
|
||||||
draftState.pagination = action.pagination;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'ON_CHANGE_DATA_TO_DELETE': {
|
|
||||||
draftState.dataToDelete = action.dataToDelete;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'ON_DELETE_USERS': {
|
|
||||||
draftState.showModalConfirmButtonLoading = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'ON_DELETE_USERS_SUCCEEDED': {
|
|
||||||
draftState.shouldRefetchData = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'RESET_DATA_TO_DELETE': {
|
|
||||||
draftState.shouldRefetchData = false;
|
|
||||||
draftState.dataToDelete = [];
|
|
||||||
draftState.showModalConfirmButtonLoading = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'UNSET_IS_LOADING': {
|
|
||||||
draftState.isLoading = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return draftState;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export { initialState, reducer };
|
|
File diff suppressed because one or more lines are too long
@ -1,182 +0,0 @@
|
|||||||
import { reducer } from '../reducer';
|
|
||||||
|
|
||||||
describe('ADMIN | CONTAINERS | USERS | ListPage | reducer', () => {
|
|
||||||
describe('DEFAULT_ACTION', () => {
|
|
||||||
it('should return the initialState', () => {
|
|
||||||
const initialState = {
|
|
||||||
test: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(initialState, {})).toEqual(initialState);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET_DATA', () => {
|
|
||||||
it('should return the initialState', () => {
|
|
||||||
const initialState = {
|
|
||||||
test: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const action = {
|
|
||||||
type: 'GET_DATA',
|
|
||||||
};
|
|
||||||
|
|
||||||
const expected = {
|
|
||||||
data: [],
|
|
||||||
dataToDelete: [],
|
|
||||||
isLoading: true,
|
|
||||||
pagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
pageCount: 0,
|
|
||||||
total: 0,
|
|
||||||
},
|
|
||||||
showModalConfirmButtonLoading: false,
|
|
||||||
shouldRefetchData: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(initialState, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET_DATA_SUCCEEDED', () => {
|
|
||||||
it('Should set the data correctly', () => {
|
|
||||||
const action = {
|
|
||||||
type: 'GET_DATA_SUCCEEDED',
|
|
||||||
data: [1, 2, 3],
|
|
||||||
pagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
pageCount: 5,
|
|
||||||
total: 20,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const initialState = {
|
|
||||||
data: [],
|
|
||||||
dataToDelete: [],
|
|
||||||
isLoading: true,
|
|
||||||
pagination: {},
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
data: [1, 2, 3],
|
|
||||||
dataToDelete: [],
|
|
||||||
isLoading: false,
|
|
||||||
pagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
pageCount: 5,
|
|
||||||
total: 20,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(initialState, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ON_CHANGE_DATA_TO_DELETE', () => {
|
|
||||||
it('should change the data correctly', () => {
|
|
||||||
const initialState = {
|
|
||||||
data: [],
|
|
||||||
dataToDelete: [],
|
|
||||||
isLoading: true,
|
|
||||||
};
|
|
||||||
const action = {
|
|
||||||
type: 'ON_CHANGE_DATA_TO_DELETE',
|
|
||||||
dataToDelete: [1, 2],
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
data: [],
|
|
||||||
dataToDelete: [1, 2],
|
|
||||||
isLoading: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(initialState, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ON_DELETE_USERS', () => {
|
|
||||||
it('should set the showModalConfirmButtonLoading to true', () => {
|
|
||||||
const initialState = {
|
|
||||||
data: [1],
|
|
||||||
dataToDelete: [1],
|
|
||||||
showModalConfirmButtonLoading: false,
|
|
||||||
};
|
|
||||||
const action = {
|
|
||||||
type: 'ON_DELETE_USERS',
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
data: [1],
|
|
||||||
dataToDelete: [1],
|
|
||||||
showModalConfirmButtonLoading: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(initialState, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ON_DELETE_USERS_SUCCEEDED', () => {
|
|
||||||
it('should set the shouldRefetchData to true', () => {
|
|
||||||
const initialState = {
|
|
||||||
data: [1],
|
|
||||||
dataToDelete: [1],
|
|
||||||
showModalConfirmButtonLoading: true,
|
|
||||||
shouldRefetchData: false,
|
|
||||||
};
|
|
||||||
const action = {
|
|
||||||
type: 'ON_DELETE_USERS_SUCCEEDED',
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
data: [1],
|
|
||||||
dataToDelete: [1],
|
|
||||||
showModalConfirmButtonLoading: true,
|
|
||||||
shouldRefetchData: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(initialState, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('RESET_DATA_TO_DELETE', () => {
|
|
||||||
it('should empty the dataToDelete array', () => {
|
|
||||||
const initialState = {
|
|
||||||
data: [1],
|
|
||||||
dataToDelete: [1],
|
|
||||||
showModalConfirmButtonLoading: true,
|
|
||||||
shouldRefetchData: true,
|
|
||||||
};
|
|
||||||
const action = {
|
|
||||||
type: 'RESET_DATA_TO_DELETE',
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
data: [1],
|
|
||||||
dataToDelete: [],
|
|
||||||
showModalConfirmButtonLoading: false,
|
|
||||||
shouldRefetchData: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(initialState, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('UNSET_IS_LOADING', () => {
|
|
||||||
it('should set the isLoading to false', () => {
|
|
||||||
const action = {
|
|
||||||
type: 'UNSET_IS_LOADING',
|
|
||||||
};
|
|
||||||
const initialState = {
|
|
||||||
data: [],
|
|
||||||
dataToDelete: [],
|
|
||||||
isLoading: true,
|
|
||||||
pagination: {},
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
data: [],
|
|
||||||
dataToDelete: [],
|
|
||||||
isLoading: false,
|
|
||||||
pagination: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(reducer(initialState, action)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,15 +1,15 @@
|
|||||||
import { axiosInstance } from '../../../../core/utils';
|
import { axiosInstance } from '../../../../core/utils';
|
||||||
|
|
||||||
const fetchData = async search => {
|
const fetchData = async search => {
|
||||||
try {
|
const {
|
||||||
const {
|
data: { data },
|
||||||
data: { data },
|
} = await axiosInstance.get(`/admin/users${search}`);
|
||||||
} = await axiosInstance.get(`/admin/users${search}`);
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (err) {
|
|
||||||
throw new Error(err);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default fetchData;
|
const deleteData = async ids => {
|
||||||
|
await axiosInstance.post('/admin/users/batch-delete', { ids });
|
||||||
|
};
|
||||||
|
|
||||||
|
export { deleteData, fetchData };
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
const displayedFilters = [
|
||||||
|
{
|
||||||
|
name: 'firstname',
|
||||||
|
metadatas: { label: 'Firstname' },
|
||||||
|
fieldSchema: { type: 'string' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lastname',
|
||||||
|
metadatas: { label: 'Lastname' },
|
||||||
|
fieldSchema: { type: 'string' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
metadatas: { label: 'Email' },
|
||||||
|
fieldSchema: { type: 'email' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'username',
|
||||||
|
metadatas: { label: 'Username' },
|
||||||
|
fieldSchema: { type: 'string' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'isActive',
|
||||||
|
metadatas: { label: 'Active user' },
|
||||||
|
fieldSchema: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default displayedFilters;
|
@ -1,30 +0,0 @@
|
|||||||
const getFilters = search => {
|
|
||||||
const query = new URLSearchParams(search);
|
|
||||||
const filters = [];
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (let [key, queryValue] of query.entries()) {
|
|
||||||
if (!['sort', 'pageSize', 'page', '_q'].includes(key)) {
|
|
||||||
const splitted = key.split('_');
|
|
||||||
let filterName;
|
|
||||||
let filterType;
|
|
||||||
|
|
||||||
// Filter type === '=')
|
|
||||||
if (splitted.length === 1) {
|
|
||||||
filterType = '=';
|
|
||||||
filterName = key;
|
|
||||||
} else {
|
|
||||||
filterType = `_${splitted[1]}`;
|
|
||||||
filterName = splitted[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = decodeURIComponent(queryValue);
|
|
||||||
|
|
||||||
filters.push({ displayName: filterName, name: key, filter: filterType, value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getFilters;
|
|
@ -1,29 +0,0 @@
|
|||||||
import getFilters from '../getFilters';
|
|
||||||
|
|
||||||
describe('ADMIN | CONTAINERS | USERS | ListPage | utils | getFilters', () => {
|
|
||||||
it('should return an empty array if there is not filter', () => {
|
|
||||||
const search = '_q=test&sort=firstname&page=1&pageSize=1';
|
|
||||||
|
|
||||||
expect(getFilters(search)).toHaveLength(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle the = filter correctly ', () => {
|
|
||||||
const search = 'sort=firstname&page=1&pageSize=1&firstname=test&firstname_ne=something';
|
|
||||||
const expected = [
|
|
||||||
{
|
|
||||||
displayName: 'firstname',
|
|
||||||
name: 'firstname',
|
|
||||||
filter: '=',
|
|
||||||
value: 'test',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'firstname',
|
|
||||||
name: 'firstname_ne',
|
|
||||||
filter: '_ne',
|
|
||||||
value: 'something',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(getFilters(search)).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
@ -420,8 +420,7 @@
|
|||||||
"content-manager.components.SettingsViewWrapper.pluginHeader.title": "Configure the view - {name}",
|
"content-manager.components.SettingsViewWrapper.pluginHeader.title": "Configure the view - {name}",
|
||||||
"content-manager.components.TableDelete.delete": "Delete all",
|
"content-manager.components.TableDelete.delete": "Delete all",
|
||||||
"content-manager.components.TableDelete.deleteSelected": "Delete selected",
|
"content-manager.components.TableDelete.deleteSelected": "Delete selected",
|
||||||
"content-manager.components.TableDelete.entries.plural": "{number} entries selected",
|
"content-manager.components.TableDelete.label": "{number, plural, one {# entry} other {# entries}} selected",
|
||||||
"content-manager.components.TableDelete.entries.singular": "{number} entry selected",
|
|
||||||
"content-manager.components.TableEmpty.withFilters": "There are no {contentType} with the applied filters...",
|
"content-manager.components.TableEmpty.withFilters": "There are no {contentType} with the applied filters...",
|
||||||
"content-manager.components.TableEmpty.withSearch": "There are no {contentType} corresponding to the search ({search})...",
|
"content-manager.components.TableEmpty.withSearch": "There are no {contentType} corresponding to the search ({search})...",
|
||||||
"content-manager.components.TableEmpty.withoutFilter": "There are no {contentType}...",
|
"content-manager.components.TableEmpty.withoutFilter": "There are no {contentType}...",
|
||||||
|
@ -19,7 +19,7 @@ import EmptyBodyTable from './index';
|
|||||||
|
|
||||||
This component is used to display an empty state in a table.
|
This component is used to display an empty state in a table.
|
||||||
|
|
||||||
## Usage
|
## Usage default
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story name="base">
|
<Story name="base">
|
||||||
@ -31,6 +31,18 @@ This component is used to display an empty state in a table.
|
|||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
## Usage loading
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story name="loading">
|
||||||
|
<Box>
|
||||||
|
<Table colCount={1} rowCount={2} footer={undefined}>
|
||||||
|
<EmptyBodyTable isLoading />
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
## Props
|
## Props
|
||||||
|
|
||||||
<ArgsTable of={EmptyBodyTable} />
|
<ArgsTable of={EmptyBodyTable} />
|
||||||
|
@ -1,10 +1,25 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tbody, Tr, Td } from '@strapi/parts/Table';
|
import { Tbody, Tr, Td } from '@strapi/parts/Table';
|
||||||
import styled from 'styled-components';
|
import { Box, Row, Loader } from '@strapi/parts';
|
||||||
import EmptyStateLayout from '../EmptyStateLayout';
|
import EmptyStateLayout from '../EmptyStateLayout';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const EmptyBodyTable = ({ colSpan, ...rest }) => {
|
const EmptyBodyTable = ({ colSpan, isLoading, ...rest }) => {
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Tbody>
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={colSpan}>
|
||||||
|
<Row justifyContent="center">
|
||||||
|
<Box padding={11} background="neutral0">
|
||||||
|
<Loader />
|
||||||
|
</Box>
|
||||||
|
</Row>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
</Tbody>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Tbody>
|
<Tbody>
|
||||||
<Tr>
|
<Tr>
|
||||||
@ -20,8 +35,8 @@ EmptyBodyTable.defaultProps = {
|
|||||||
action: undefined,
|
action: undefined,
|
||||||
colSpan: 1,
|
colSpan: 1,
|
||||||
content: undefined,
|
content: undefined,
|
||||||
|
|
||||||
icon: undefined,
|
icon: undefined,
|
||||||
|
isLoading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
EmptyBodyTable.propTypes = {
|
EmptyBodyTable.propTypes = {
|
||||||
@ -33,6 +48,7 @@ EmptyBodyTable.propTypes = {
|
|||||||
values: PropTypes.object,
|
values: PropTypes.object,
|
||||||
}),
|
}),
|
||||||
icon: PropTypes.oneOf(['document', 'media', 'permissions']),
|
icon: PropTypes.oneOf(['document', 'media', 'permissions']),
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EmptyBodyTable;
|
export default EmptyBodyTable;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user