import { React, PropTypes, classnames } from '@gravityforms/libraries';
import { spacerClasses, uniqueId } from '@gravityforms/utils';
import Heading from '../../elements/Heading';
import GridColumns from './GridColumns';
import GridControls from './GridControls';
import GridEmpty from './GridEmpty';
import GridRow from './GridRow';
import useStore from './store';
import { getModules } from './utils';
const { forwardRef, useState } = React;
/**
* @module DataGrid
* @description Renders a complex Data Grid component.
*
* @since 3.3.0
*
* @param {object} props Component props.
* @param {object} props.columns Array of column objects. Supply: component, key, props, sortable (optional), hideAt (optional). Key is used to match data keys for cells.
* @param {object} props.columnRowAttributes Custom attributes for the column row.
* @param {string|Array|object} props.columnRowClasses Custom classes for the column row.
* @param {object} props.columnStyleProps Style props for the column.
* @param {object} props.customAttributes Custom attributes for the component
* @param {string|Array|object} props.customClasses Custom classes for the component.
* @param {object} props.data The data for the component.
* @param {number} props.dataPerPage The number of data rows to show per page.
* @param {object} props.dataRowAttributes Custom attributes for the data row.
* @param {string|Array|object} props.dataRowClasses Custom classes for the data row.
* @param {string} props.dataRowMinHeight The minimum height for the data row.
* @param {JSX.Element} props.EmptyImage The image to show when there is no data.
* @param {boolean} props.emptyMessageAttributes Custom attributes for the empty message.
* @param {string|Array|object} props.emptyMessageClasses Custom classes for the empty message.
* @param {boolean} props.equalGrid Whether the grid should be equal.
* @param {string|Array|object} props.gridControlWrapperClasses Custom classes for the grid control wrapper.
* @param {Function} props.handleGridClicks Passed handler for grid clicks if the field type supports it.
* @param {boolean} props.highlightHover Whether the grid should highlight a row on hover.
* @param {boolean} props.highlightSelected Whether the grid should highlight a selected row.
* @param {object} props.i18n Language strings for the component.
* @param {string} props.i18n.emptyMessageI18n The empty message for the data grid.
* @param {string} props.i18n.emptyTitleI18n The empty title for the data grid.
* @param {string} props.i18n.gridHeadingI18n The heading for the data grid.
* @param {string} props.id The ID for the component.
* @param {boolean} props.isLoading Whether the data grid is loading.
* @param {boolean} props.maintainHeight Whether the grid should maintain height at all times.
* @param {Array} props.modules The modules for the data grid.
* @param {object} props.moduleAttributes Custom attributes for the modules.
* @param {Function} props.setGridData Passed handler for field changes if the field type supports it.
* @param {boolean} props.showColumns Whether to show the column row(s).
* @param {boolean} props.showColumnsInFooter Whether the column row should be in the footer as well.
* @param {boolean} props.sortable Whether the grid should be sortable.
* @param {string|number|Array|object} props.spacing The spacing for the component, as a string, number, array, or object.
* @param {object} props.titleAttributes Custom attributes for the title.
* @param {string|Array|object} props.titleClasses Custom classes for the title.
* @param {boolean} props.useAjax Whether to use ajax for the data grid.
* @param {object|null} ref Ref to the component.
*
* @return {JSX.Element} The DataGrid component.
*
* @example
* import DataGrid from '@gravityforms/components/react/admin/modules/DataGrid';
*
* return <DataGrid />;
*
*/
const DataGrid = forwardRef( ( props, ref ) => {
const {
columns = [],
// columnRowAttributes = {},
// columnRowClasses = [],
// columnStyleProps = {},
customAttributes = {},
customClasses = [],
data = [],
dataPerPage = 20,
// dataRowAttributes = {},
// dataRowClasses = [],
// dataRowMinHeight = '71px',
// EmptyImage = null,
// emptyMessageAttributes = {},
// emptyMessageClasses = [],
equalGrid = false,
gridControlWrapperClasses = [],
// handleGridClicks = () => {},
highlightHover = true,
highlightSelected = true,
i18n = {},
id = '',
isLoading = false,
maintainHeight = false,
modules = [],
// moduleAttributes = {},
// setGridData = () => {},
// showColumns = true,
// showColumnsInFooter = true,
sortable = false,
spacing = '',
titleAttributes = {},
titleClasses = [],
useAjax = false,
} = props;
const loadingData = useStore( ( state ) => state.loadingData );
const moduleState = useStore( ( state ) => state.moduleState );
const [ sortColumn, setSortColumn ] = useState( null ); /* eslint-disable-line no-unused-vars */
const [ sortDirection, setSortDirection ] = useState( null ); /* eslint-disable-line no-unused-vars */
const state = {
sortColumn,
setSortColumn,
sortDirection,
setSortDirection,
};
const propsWithState = {
...props,
...state,
};
const { gridHeadingI18n = '' } = i18n;
const gridId = id || uniqueId( 'data-grid-' );
const {
BulkActions,
Filters,
Pagination,
} = getModules( modules, [ 'BulkActions', 'Filters', 'Pagination' ] );
const componentProps = {
className: classnames( {
'gform-data-grid': true,
'gform-data-grid--highlight-hover': highlightHover,
'gform-data-grid--highlight-selected': highlightSelected,
'gform-data-grid--equal-grid': equalGrid,
'gform-data-grid--loading': loadingData,
'gform-data-grid--empty': data.length === 0,
...spacerClasses( spacing ),
}, customClasses ),
ref,
id: gridId,
...customAttributes,
};
const titleProps = {
customClasses: classnames( {
'gform-data-grid__title': true,
}, titleClasses ),
size: 'text-lg',
tagName: 'h3',
weight: 'medium',
...titleAttributes,
};
const columnStyles = columns.map( ( column, index ) =>
column.hideAt
? `@media (max-width: ${ column.hideAt }px) { #${ gridId } .gform-data-grid__column-${ index } { display: none; } }`
: ''
).join( '\n' );
let displayedData = [];
const sortedData = [ ...data ];
if ( data.length === 0 ) {
displayedData = Array.from( { length: dataPerPage } ).map( () => ( {} ) );
} else {
if ( sortable && sortColumn ) {
sortedData.sort( ( a, b ) => {
if ( a[ sortColumn ] < b[ sortColumn ] ) {
return sortDirection === 'asc' ? -1 : 1;
}
if ( a[ sortColumn ] > b[ sortColumn ] ) {
return sortDirection === 'asc' ? 1 : -1;
}
return 0;
} );
}
if ( ! useAjax ) {
const currentPage = moduleState?.currentPage || 0;
displayedData = sortedData.slice( currentPage * dataPerPage, ( currentPage + 1 ) * dataPerPage );
} else {
displayedData = data;
}
if ( maintainHeight && displayedData.length < dataPerPage ) {
const fillerRows = Array.from( { length: dataPerPage - displayedData.length } ).map( () => ( {} ) );
displayedData = [ ...displayedData, ...fillerRows ];
}
}
const gridLocked = Boolean( data.length === 0 || loadingData || isLoading );
return (
<>
<style>{ columnStyles }</style>
<div { ...componentProps }>
{ gridHeadingI18n && <Heading { ...titleProps }>{ gridHeadingI18n }</Heading> }
<GridControls
displayedData={ displayedData }
gridLocked={ gridLocked }
wrapperClasses={ gridControlWrapperClasses }
{ ...propsWithState }
/>
{ Filters && <Filters.ActiveFilters { ...propsWithState } /> }
<GridColumns
location="header"
displayedData={ displayedData }
gridLocked={ gridLocked }
{ ...propsWithState }
id={ gridId }
/>
{ BulkActions && <BulkActions.BulkSelectNotice gridLocked={ gridLocked } { ...propsWithState } /> }
<div className="gform-data-grid__data">
{ displayedData.map( ( row, rowIndex ) => (
<GridRow
key={ `${ gridId }-gform-data-grid__data-row--${ rowIndex }` }
row={ row }
rowIndex={ rowIndex }
displayedData={ displayedData }
gridLocked={ gridLocked }
{ ...propsWithState }
id={ gridId }
/>
) ) }
<GridEmpty { ...propsWithState } />
</div>
<GridColumns
location="footer"
displayedData={ displayedData }
gridLocked={ gridLocked }
{ ...propsWithState }
id={ gridId }
/>
{ Pagination && <Pagination.Pagination { ...propsWithState } /> }
</div>
</>
);
} );
DataGrid.propTypes = {
columns: PropTypes.arrayOf(
PropTypes.shape( {
key: PropTypes.string.isRequired,
component: PropTypes.string.isRequired,
props: PropTypes.object,
sortable: PropTypes.bool,
hideAt: PropTypes.number,
} )
),
columnRowAttributes: PropTypes.object,
columnRowClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
columnStyleProps: PropTypes.object,
customAttributes: PropTypes.object,
customClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
data: PropTypes.arrayOf( PropTypes.object ),
dataPerPage: PropTypes.number,
dataRowAttributes: PropTypes.object,
dataRowClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
dataRowMinHeight: PropTypes.string,
EmptyImage: PropTypes.oneOfType( [
PropTypes.node,
PropTypes.func,
PropTypes.object,
] ),
emptyMessageAttributes: PropTypes.object,
emptyMessageClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
equalGrid: PropTypes.bool,
gridControlWrapperClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
handleGridClicks: PropTypes.func,
highlightHover: PropTypes.bool,
highlightSelected: PropTypes.bool,
i18n: PropTypes.object,
id: PropTypes.string,
isLoading: PropTypes.bool,
maintainHeight: PropTypes.bool,
modules: PropTypes.arrayOf( PropTypes.object ),
moduleAttributes: PropTypes.object,
setGridData: PropTypes.func,
showColumns: PropTypes.bool,
showColumnsInFooter: PropTypes.bool,
sortable: PropTypes.bool,
spacing: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.number,
PropTypes.array,
PropTypes.object,
] ),
titleAttributes: PropTypes.object,
titleClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
useAjax: PropTypes.bool,
};
DataGrid.displayName = 'DataGrid';
export default DataGrid;