diff --git a/.gitignore b/.gitignore
index 961e938..3a2bf37 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ lib/
.env-*
.idea
*.log
+.history/
\ No newline at end of file
diff --git a/package.json b/package.json
index 1331aab..01c4e98 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@indec/react-commons",
- "version": "5.4.2",
+ "version": "5.5.0",
"description": "Common reactjs components for apps",
"private": false,
"main": "index.js",
diff --git a/src/components/Table/SortIcon/SortIcon.js b/src/components/Table/SortIcon/SortIcon.js
new file mode 100644
index 0000000..3012d54
--- /dev/null
+++ b/src/components/Table/SortIcon/SortIcon.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import {sortDirections} from '@/constants';
+import {Icon} from '@chakra-ui/react';
+import {FaChevronDown, FaChevronUp} from 'react-icons/fa';
+import PropTypes from 'prop-types';
+
+const SortIcon = ({classified, columnKey, ...props}) => classified && classified?.sort === columnKey && (
+
+);
+
+SortIcon.propTypes = {
+ classified: PropTypes.shape({
+ sortBy: PropTypes.oneOf([sortDirections.ASC, sortDirections.DESC]),
+ sort: PropTypes.string
+ }),
+ columnKey: PropTypes.string
+};
+
+SortIcon.defaultProps = {
+ classified: undefined,
+ columnKey: undefined
+};
+
+export default SortIcon;
diff --git a/src/components/Table/SortIcon/index.js b/src/components/Table/SortIcon/index.js
new file mode 100644
index 0000000..09521a4
--- /dev/null
+++ b/src/components/Table/SortIcon/index.js
@@ -0,0 +1,3 @@
+import SortIcon from './SortIcon';
+
+export default SortIcon;
diff --git a/src/components/Table/Table.js b/src/components/Table/Table.js
index 5645bcd..ca756e8 100644
--- a/src/components/Table/Table.js
+++ b/src/components/Table/Table.js
@@ -1,4 +1,4 @@
-import React, {isValidElement} from 'react';
+import React, {useState, isValidElement} from 'react';
import PropTypes from 'prop-types';
import {
Flex,
@@ -9,32 +9,55 @@ import {
Thead,
Tr,
VStack,
- Table as ChakraTable
+ Table as ChakraTable,
+ HStack,
+ Text
} from '@chakra-ui/react';
+import {sortDirections} from '@/constants';
import {LoadingPage, Pagination} from '@/components';
import {buildRows} from '@/utils';
import TableFooter from '@/components/Table/TableFooter';
+import SortIcon from '@/components/Table/SortIcon';
const Table = ({
- name,
+ caption,
columns,
data,
- caption,
- isLoading,
emptyMessage,
- total,
- showDefaultFooter,
- perPage,
+ footer: Footer,
+ isLoading,
+ name,
onSearch,
+ onSort,
+ paginationStyles,
params,
- footer: Footer,
+ perPage,
+ showDefaultFooter,
showPagination,
- paginationStyles,
+ total,
...props
}) => {
const columnsData = Array.isArray(columns) ? columns : [];
const sizeHeader = columnsData.length;
+ const initialClassified = {sort: null, sortBy: null};
+ const [classified, setClassified] = useState(initialClassified);
+
+ const handleSort = ({target: {id}}) => {
+ if (!id) {
+ return;
+ }
+ let sort;
+ if (id === 'action') {
+ sort = initialClassified;
+ } else if (classified?.sort === id && classified?.sortBy === sortDirections.ASC) {
+ sort = {sort: id, sortBy: sortDirections.DESC};
+ } else {
+ sort = {sort: id, sortBy: sortDirections.ASC};
+ }
+ setClassified(sort);
+ onSort(sort);
+ };
return (
@@ -56,10 +79,20 @@ const Table = ({
- {column.label}
+
+ {data.length > 0 && }
+
+ {column.label || ''}
+
+
|
))}
@@ -107,38 +140,38 @@ const Table = ({
};
Table.propTypes = {
- onSearch: PropTypes.func,
+ caption: PropTypes.string,
columns: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
- params: PropTypes.shape({
- skip: PropTypes.number
- }),
- name: PropTypes.string,
data: PropTypes.arrayOf(PropTypes.shape({})),
- caption: PropTypes.string,
- isLoading: PropTypes.bool,
- showDefaultFooter: PropTypes.bool,
emptyMessage: PropTypes.string,
- total: PropTypes.number,
footer: PropTypes.element,
+ isLoading: PropTypes.bool,
+ name: PropTypes.string,
+ onSearch: PropTypes.func,
+ onSort: PropTypes.func,
+ paginationStyles: PropTypes.shape({}),
+ params: PropTypes.shape({skip: PropTypes.number}),
perPage: PropTypes.number,
+ showDefaultFooter: PropTypes.bool,
showPagination: PropTypes.bool,
- paginationStyles: PropTypes.shape({})
+ total: PropTypes.number
};
Table.defaultProps = {
- name: 'table',
- onSearch: () => {},
caption: null,
+ data: [],
+ footer: undefined,
+ emptyMessage: 'No hay resultados',
isLoading: false,
- params: undefined,
+ name: 'table',
+ onSearch: () => {},
+ onSort: undefined,
paginationStyles: undefined,
+ params: undefined,
+ perPage: 0,
showDefaultFooter: true,
showPagination: true,
- data: [],
- total: 0,
- perPage: 0,
- footer: undefined,
- emptyMessage: 'No hay resultados'
+ total: 0
};
export default Table;
diff --git a/src/components/Table/Table.stories.js b/src/components/Table/Table.stories.js
index fc3de26..6858e94 100644
--- a/src/components/Table/Table.stories.js
+++ b/src/components/Table/Table.stories.js
@@ -31,28 +31,42 @@ const getRows = () => users.map(user => {
user.documentId,
user.role,
user.state,
- user.deleted
+ user.status
];
return ({key: user.id, values: rows});
});
export const Basic = args => {
- const spliceRows = getRows().slice(0, 5);
+ const rows = getRows();
const [prevArgs, updateArgs] = useArgs();
const handleSearch = ({target: {id, value}}) => updateArgs(
{...prevArgs, params: {...prevArgs.params, [id]: value}}
);
+ const handleSort = ({sort, sortBy}) => {
+ const orderedData = rows.sort((firstRow, secondRow) => {
+ const selectedColumn = columns.findIndex(column => column.key === sort);
+ if (firstRow.values[selectedColumn] < secondRow.values[selectedColumn]) {
+ return sortBy === 'asc' ? -1 : 1;
+ }
+ if (firstRow.values[selectedColumn] > secondRow.values[selectedColumn]) {
+ return sortBy === 'asc' ? 1 : -1;
+ }
+ return 0;
+ });
+ updateArgs({...prevArgs, data: orderedData});
+ };
return (
', () => {
const {container} = getComponent();
expect(queryByText(container, 'No hay resultados')).toBeNull();
});
+
+ describe('when one column is clicked and is not an action', () => {
+ beforeEach(() => {
+ props.onSort = jest.fn();
+ const {container} = getComponent();
+ const firstColumn = getByTestId(container, 'column-text-name');
+ fireEvent.click(firstColumn);
+ });
+
+ it('should fire `props.onSort` in asc order', () => {
+ expect(props.onSort).toHaveBeenCalledTimes(1);
+ expect(props.onSort).toHaveBeenCalledWith({sort: 'name', sortBy: 'asc'});
+ });
+
+ describe('when the same column is clicked again', () => {
+ beforeEach(() => {
+ const {container} = getComponent();
+ const firstColumn = getByTestId(container, 'column-text-name');
+ fireEvent.click(firstColumn);
+ });
+
+ it('should fire `props.onSort` in desc order', () => {
+ expect(props.onSort).toHaveBeenCalledWith({sort: 'name', sortBy: 'desc'});
+ });
+ });
+ });
+
+ describe('when one column is clicked and is an action', () => {
+ beforeEach(() => {
+ props.onSort = jest.fn();
+ const {container} = getComponent();
+ const secondColumn = getByTestId(container, 'column-text-action');
+ fireEvent.click(secondColumn);
+ });
+
+ it('should fire `props.onSort` with sort and sortBy in null', () => {
+ expect(props.onSort).toHaveBeenCalledTimes(1);
+ expect(props.onSort).toHaveBeenCalledWith({sort: null, sortBy: null});
+ });
+ });
});
describe('when `props.caption` is defined', () => {
diff --git a/src/constants/index.js b/src/constants/index.js
index a2aec92..5fab4e9 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -1,3 +1,4 @@
export {default as headerOptions} from './headerOptions';
export {default as selectActions} from './selectActions';
+export {default as sortDirections} from './sortDirections';
export {default as users} from './users';
diff --git a/src/constants/sortDirections.js b/src/constants/sortDirections.js
new file mode 100644
index 0000000..78f1a07
--- /dev/null
+++ b/src/constants/sortDirections.js
@@ -0,0 +1,4 @@
+export default {
+ ASC: 'asc',
+ DESC: 'desc'
+};