From d9866483e711c04d87dd19c84409867b1f11ad22 Mon Sep 17 00:00:00 2001 From: pochretien Date: Wed, 18 Dec 2019 00:20:24 -0500 Subject: [PATCH 01/32] update table server example with the new workflow --- packages/react-vapor/docs/Reducers.ts | 6 +- .../examples/TableHOCServerExample.tsx | 193 ++++++++++++ .../examples/TableHOCServerExamples.tsx | 286 ------------------ .../table-hoc/tests/TableHOCUtils.spec.ts | 2 +- .../tests/TableWithPagination.spec.tsx | 2 +- .../tests/TableWithPredicate.spec.tsx | 2 +- .../tests/TableWithUrlState.spec.tsx | 2 +- .../table-hoc/utils/TableHOCExampleUtils.tsx | 74 +++++ .../table-hoc/{ => utils}/TableHOCUtils.ts | 32 +- .../react-vapor/src/utils/tests/TestUtils.tsx | 2 +- 10 files changed, 296 insertions(+), 305 deletions(-) create mode 100644 packages/react-vapor/src/components/table-hoc/examples/TableHOCServerExample.tsx delete mode 100644 packages/react-vapor/src/components/table-hoc/examples/TableHOCServerExamples.tsx create mode 100644 packages/react-vapor/src/components/table-hoc/utils/TableHOCExampleUtils.tsx rename packages/react-vapor/src/components/table-hoc/{ => utils}/TableHOCUtils.ts (71%) diff --git a/packages/react-vapor/docs/Reducers.ts b/packages/react-vapor/docs/Reducers.ts index 37606592b4..ae01eb9cd4 100644 --- a/packages/react-vapor/docs/Reducers.ts +++ b/packages/react-vapor/docs/Reducers.ts @@ -4,10 +4,7 @@ import { IListBoxExampleCompositeState, listBoxExampleReducer, } from '../src/components/listBox/examples/ListBoxExampleReducer'; -import { - IExampleServerTableState, - TableHOCServerExampleReducer, -} from '../src/components/table-hoc/examples/TableHOCServerExamples'; +import {IExampleServerTableState} from '../src/components/table-hoc/examples/TableHOCServerExample'; import {IReactVaporState} from '../src/ReactVapor'; import {ReactVaporReducers} from '../src/ReactVaporReducers'; @@ -19,5 +16,4 @@ export interface IReactVaporExampleState extends IReactVaporState { export const Reducers: Redux.Reducer = Redux.combineReducers({ ...ReactVaporReducers, listBoxExampleState: listBoxExampleReducer, - tableHOCExample: TableHOCServerExampleReducer, }); diff --git a/packages/react-vapor/src/components/table-hoc/examples/TableHOCServerExample.tsx b/packages/react-vapor/src/components/table-hoc/examples/TableHOCServerExample.tsx new file mode 100644 index 0000000000..8cce451b2c --- /dev/null +++ b/packages/react-vapor/src/components/table-hoc/examples/TableHOCServerExample.tsx @@ -0,0 +1,193 @@ +import * as $ from 'jquery'; +import * as moment from 'moment'; +import * as React from 'react'; +import {connect} from 'react-redux'; +import {RouteComponentProps, withRouter} from 'react-router'; +import * as _ from 'underscore'; + +import {ExampleComponent} from '../../../../docs/src/components/ComponentsInterface'; +import {withServerSideProcessing} from '../../../hoc/withServerSideProcessing/withServerSideProcessing'; +import {IDispatch, IThunkAction} from '../../../utils/ReduxUtils'; +import {IReactVaporTestState} from '../../../utils/tests/TestUtils'; +import {LastUpdated} from '../../lastUpdated/LastUpdated'; +import {Section} from '../../section/Section'; +import {TableWithPaginationActions} from '../actions/TableWithPaginationActions'; +import {TableHeaderWithSort} from '../TableHeaderWithSort'; +import {TableHOC} from '../TableHOC'; +import {TableRowHeader} from '../TableRowHeader'; +import {TableRowNumberHeader} from '../TableRowNumberHeader'; +import {tableWithActions} from '../TableWithActions'; +import {tableWithBlankSlate} from '../TableWithBlankSlate'; +import {tableWithDatePicker} from '../TableWithDatePicker'; +import {tableWithFilter} from '../TableWithFilter'; +import {tableWithNewPagination} from '../TableWithNewPagination'; +import {tableWithPredicate} from '../TableWithPredicate'; +import {tableWithSort} from '../TableWithSort'; +import {tableWithUrlState} from '../TableWithUrlState'; +import {TableHOCExampleUtils} from '../utils/TableHOCExampleUtils'; +import {ITableHOCCompositeState, TableHOCUtils} from '../utils/TableHOCUtils'; + +type TableHOCServerProps = RouteComponentProps & ReturnType; + +export interface IExampleRowData { + city: string; + email: string; + + username: string; + dateOfBirth: Date; + id: string; +} + +export interface IExampleServerTableState { + data: IExampleRowData[]; + isLoading: boolean; +} + +interface TableHOCServerExamplesState { + data: {users: [any]; count: number}; + isLoading: boolean; +} + +export const TableHOCServerExample: ExampleComponent = () => ; + +TableHOCServerExample.title = 'TableHOC server'; + +// start-print +export const TableHOCServerExampleId = 'complex-example'; + +const renderHeader = (isLoading: boolean) => ( + + + + + City + + + Email + + + Username + + Date of Birth + + + +); + +const mapDispatchToProps = (dispatch: IDispatch) => ({ + fetch: () => dispatch(TableHOCServerActions.fetchData()), +}); + +class TableExampleDisconnected extends React.PureComponent { + state: TableHOCServerExamplesState = { + data: null, + isLoading: true, + }; + + private fetch = _.debounce(() => { + this.setState({...this.state, isLoading: true}); + window.setTimeout( + () => + this.props.fetch().done((data: any) => { + this.setState({data, isLoading: false}); + }), + 500 + ); + }, 40); + + private onUpdate = () => { + this.fetch(); + }; + + private updateUrl = (query: string) => { + this.props.history.push({search: query}); + }; + + componentDidMount() { + this.fetch(); + } + + render() { + return ( +
+ + Please note that the backend service doesn't support dates but we still make a request for every + change in the date range. + + + + +
+ ); + } +} + +const ServerTableComposed = _.compose( + withServerSideProcessing, + tableWithUrlState, + tableWithBlankSlate({title: 'No data fetched from the server'}), + tableWithPredicate(TableHOCExampleUtils.tablePredicates[0]), + tableWithPredicate(TableHOCExampleUtils.tablePredicates[1]), + tableWithFilter({ + placeholder: 'Filter all', + blankSlate: { + title: 'No results found', + }, + }), + tableWithSort(), + tableWithDatePicker({...(TableHOCExampleUtils.tableDatePickerConfig as any)}), + tableWithNewPagination({perPageNumbers: [3, 5, 10]}), + tableWithActions() +)(TableHOC); + +const TableHOCServer = connect(undefined, mapDispatchToProps)(withRouter(TableExampleDisconnected)); + +const fetchData = (): IThunkAction => (dispatch: IDispatch, getState: () => IReactVaporTestState) => { + const compositeState: ITableHOCCompositeState = TableHOCUtils.getCompositeState( + TableHOCServerExampleId, + getState() + ); + const [from, to] = _.map(compositeState.dateLimits, (limit) => limit && limit.toISOString()); + const params: any = { + _page: compositeState.pageNb + 1, + _limit: compositeState.perPage, + _sort: compositeState.sortKey, + _order: compositeState.sortAscending ? 'asc' : 'desc', + q: compositeState.filter || undefined, + from, + to, + }; + _.each(compositeState.predicates, (predicate: {id: string; value: string}) => { + params[predicate.id] = predicate.value; + }); + return $.get('https://jsonplaceholder.typicode.com/users', params).then((response: any[], status, request) => { + const count = request.getResponseHeader('x-total-count'); + const users = _.map(response, (user: any) => ({ + city: user.address.city, + username: user.username, + email: user.email, + dateOfBirth: moment() + .subtract(user.address.city.length, 'years') + .toDate(), // fake a year of birth + })); + dispatch(TableWithPaginationActions.setCount(TableHOCServerExampleId, count as any)); + return { + count, + users, + }; + }); +}; + +export const TableHOCServerActions = { + fetchData, +}; diff --git a/packages/react-vapor/src/components/table-hoc/examples/TableHOCServerExamples.tsx b/packages/react-vapor/src/components/table-hoc/examples/TableHOCServerExamples.tsx deleted file mode 100644 index 1363e383e6..0000000000 --- a/packages/react-vapor/src/components/table-hoc/examples/TableHOCServerExamples.tsx +++ /dev/null @@ -1,286 +0,0 @@ -import * as $ from 'jquery'; -import * as moment from 'moment'; -import * as React from 'react'; -import {connect} from 'react-redux'; -import {RouteComponentProps, withRouter} from 'react-router'; -import * as _ from 'underscore'; - -import {ExampleComponent} from '../../../../docs/src/components/ComponentsInterface'; -import {withServerSideProcessing} from '../../../hoc/withServerSideProcessing/withServerSideProcessing'; -import {DateUtils} from '../../../utils/DateUtils'; -import {IDispatch, IReduxAction, IThunkAction} from '../../../utils/ReduxUtils'; -import {IReactVaporTestState} from '../../../utils/tests/TestUtils'; -import {SELECTION_BOXES_LONG} from '../../datePicker/examples/DatePickerExamplesCommon'; -import {LastUpdated} from '../../lastUpdated/LastUpdated'; -import {turnOffLoading} from '../../loading/LoadingActions'; -import {Section} from '../../section/Section'; -import {TableWithPaginationActions} from '../actions/TableWithPaginationActions'; -import {TableHeaderWithSort} from '../TableHeaderWithSort'; -import {TableHOC} from '../TableHOC'; -import {ITableHOCCompositeState, TableHOCUtils} from '../TableHOCUtils'; -import {TableRowConnected} from '../TableRowConnected'; -import {TableRowNumberColumn} from '../TableRowNumberColumn'; -import {TableRowNumberHeader} from '../TableRowNumberHeader'; -import {tableWithActions} from '../TableWithActions'; -import {tableWithBlankSlate} from '../TableWithBlankSlate'; -import {tableWithDatePicker} from '../TableWithDatePicker'; -import {tableWithFilter} from '../TableWithFilter'; -import {tableWithPagination} from '../TableWithPagination'; -import {tableWithPredicate} from '../TableWithPredicate'; -import {tableWithSort} from '../TableWithSort'; -import {tableWithUrlState} from '../TableWithUrlState'; - -type TableHOCServerProps = RouteComponentProps & - ReturnType & - ReturnType; - -export interface IExampleRowData { - city: string; - email: string; - - username: string; - dateOfBirth: Date; - id: string; -} - -export interface IExampleServerTableState { - data: IExampleRowData[]; - isLoading: boolean; -} - -interface ISetExampleDataPayload { - data: any[]; -} - -interface ISetExampleIsLoadingPayload { - isLoading: boolean; -} - -type IExamplePayload = ISetExampleDataPayload | ISetExampleIsLoadingPayload; - -export const TableHOCServerExamples: ExampleComponent = () => ; - -TableHOCServerExamples.title = 'TableHOC server'; - -// start-print -export const TableHOCServerExampleId = 'complex-example'; - -const tableActions = (username: string) => [ - { - primary: true, - icon: 'edit', - name: 'edit', - enabled: true, - trigger: () => alert(username), - callOnDoubleClick: true, - }, -]; - -const generateRows = (allData: IExampleRowData[]) => - allData.map((data: IExampleRowData, i: number) => ( - 👋}} - > - - {data.city} - {data.email.toLowerCase()} - {data.username.toLowerCase()} - {data.dateOfBirth.toLocaleDateString()} - - )); - -const renderHeader = () => ( - - - - - City - - - Email - - - Username - - Date of Birth - {/* Empty th for the collapsible */} - - -); - -const tablePredicates = [ - { - id: 'address.city', - prepend: City:, - values: [ - {displayValue: 'All', value: '', selected: true}, - {displayValue: 'Lebsackbury', value: 'Lebsackbury'}, - ], - }, - { - id: 'username', - prepend: Username:, - values: [ - {displayValue: 'All', value: '', selected: true}, - {displayValue: 'bret', value: 'Bret'}, - ], - }, -]; - -const tableDatePickerConfig = { - datesSelectionBoxes: SELECTION_BOXES_LONG, - years: [...DateUtils.getPreviousYears(25), DateUtils.currentYear.toString()], - initialDateRange: [ - moment() - .subtract(25, 'years') - .toDate(), - moment().toDate(), - ], -}; - -const mapStateToProps = (state: IReactVaporTestState) => ({ - isLoading: state.tableHOCExample.isLoading, - serverData: state.tableHOCExample.data, -}); -const mapDispatchToProps = (dispatch: IDispatch) => ({ - fetch: _.debounce(() => dispatch(TableHOCServerActions.fetchData()), 400), -}); - -const TableExampleDisconnected: React.FunctionComponent = (props) => { - const onUpdate = () => { - props.fetch(); - }; - - const updateUrl = (query: string) => { - props.history.push({search: query}); - }; - - return ( -
- - Please note that the backend service doesn't support dates but we still make a request for every change - in the date range. - - - - -
- ); -}; - -const ServerTableComposed = _.compose( - withServerSideProcessing, - tableWithUrlState, - tableWithBlankSlate({title: 'No data fetched from the server'}), - tableWithPredicate({...tablePredicates[0]}), - tableWithPredicate({...tablePredicates[1]}), - tableWithFilter(), - tableWithDatePicker({...(tableDatePickerConfig as any)}), - tableWithSort(), - tableWithPagination({perPageNumbers: [3, 5, 10]}), - tableWithActions() -)(TableHOC); - -const TableHOCServer = connect( - mapStateToProps, - - mapDispatchToProps -)(withRouter(TableExampleDisconnected)); - -/* ACTIONS */ - -export const TableHOCServerActionsType = { - setData: 'TABLE_HOC_SET_DATA', - setIsLoading: 'TABLE_HOC_SET_IS_LOADING', - fetch: 'TABLE_HOC_FETCH_DATA', -}; - -const setData = (data: any[]): IReduxAction => ({ - type: TableHOCServerActionsType.setData, - payload: {data}, -}); - -const setIsLoading = (isLoading: boolean): IReduxAction => ({ - type: TableHOCServerActionsType.setIsLoading, - payload: {isLoading}, -}); - -const fetchData = (): IThunkAction => (dispatch: IDispatch, getState: () => IReactVaporTestState) => { - const compositeState: ITableHOCCompositeState = TableHOCUtils.getCompositeState( - TableHOCServerExampleId, - getState() - ); - const [from, to] = _.map(compositeState.dateLimits, (limit) => limit && limit.toISOString()); - const params: any = { - _page: compositeState.pageNb + 1, - _limit: compositeState.perPage, - _sort: compositeState.sortKey, - _order: compositeState.sortAscending ? 'asc' : 'desc', - q: compositeState.filter || undefined, - from, - to, - }; - _.each(compositeState.predicates, (predicate: {id: string; value: string}) => { - params[predicate.id] = predicate.value; - }); - dispatch(setIsLoading(true)); - $.get('https://jsonplaceholder.typicode.com/users', params).done((response: any[], status, request) => { - const count = request.getResponseHeader('x-total-count'); - const users = response.map((user: any) => ({ - city: user.address.city, - username: user.username, - email: user.email, - dateOfBirth: moment() - .subtract(user.address.city.length, 'years') - .toDate(), // fake a year of birth - })); - dispatch(setData(users)); - dispatch(turnOffLoading([TableHOCServerExampleId])); - dispatch(TableWithPaginationActions.setCount(TableHOCServerExampleId, count)); - }); -}; - -export const TableHOCServerActions = { - setData, - setIsLoading, - fetchData, -}; - -/* REDUCER */ - -export const TableHOCServerExampleReducer = ( - state: IExampleServerTableState = {data: [], isLoading: true}, - action: IReduxAction -) => { - if (action.type === TableHOCServerActionsType.setData) { - const payload = action.payload as ISetExampleDataPayload; - return { - ...state, - data: [...payload.data], - isLoading: false, - }; - } - if (action.type === TableHOCServerActionsType.setIsLoading) { - const payload = action.payload as ISetExampleIsLoadingPayload; - return { - ...state, - isLoading: payload.isLoading, - }; - } - return state; -}; diff --git a/packages/react-vapor/src/components/table-hoc/tests/TableHOCUtils.spec.ts b/packages/react-vapor/src/components/table-hoc/tests/TableHOCUtils.spec.ts index 9349148946..dbac4fea7e 100644 --- a/packages/react-vapor/src/components/table-hoc/tests/TableHOCUtils.spec.ts +++ b/packages/react-vapor/src/components/table-hoc/tests/TableHOCUtils.spec.ts @@ -1,4 +1,4 @@ -import {TableHOCUtils} from '../TableHOCUtils'; +import {TableHOCUtils} from '../utils/TableHOCUtils'; describe('TableHOCUtils', () => { const defaultProps = {id: 'some-id', componentId: 'some-componentId', tableId: 'some-tableId'}; diff --git a/packages/react-vapor/src/components/table-hoc/tests/TableWithPagination.spec.tsx b/packages/react-vapor/src/components/table-hoc/tests/TableWithPagination.spec.tsx index 13a28a2d6a..a43255f396 100644 --- a/packages/react-vapor/src/components/table-hoc/tests/TableWithPagination.spec.tsx +++ b/packages/react-vapor/src/components/table-hoc/tests/TableWithPagination.spec.tsx @@ -8,8 +8,8 @@ import {turnOffLoading} from '../../loading/LoadingActions'; import {NavigationConnected} from '../../navigation/NavigationConnected'; import {TableWithPaginationActions} from '../actions/TableWithPaginationActions'; import {ITableHOCProps, TableHOC} from '../TableHOC'; -import {TableHOCUtils} from '../TableHOCUtils'; import {ITableWithPaginationProps, tableWithPagination} from '../TableWithPagination'; +import {TableHOCUtils} from '../utils/TableHOCUtils'; describe('Table HOC', () => { describe('TableWithPagination', () => { diff --git a/packages/react-vapor/src/components/table-hoc/tests/TableWithPredicate.spec.tsx b/packages/react-vapor/src/components/table-hoc/tests/TableWithPredicate.spec.tsx index 4f695e4da8..ccc87c6d72 100644 --- a/packages/react-vapor/src/components/table-hoc/tests/TableWithPredicate.spec.tsx +++ b/packages/react-vapor/src/components/table-hoc/tests/TableWithPredicate.spec.tsx @@ -4,8 +4,8 @@ import * as _ from 'underscore'; import {withServerSideProcessing} from '../../../hoc/withServerSideProcessing/withServerSideProcessing'; import {ITableHOCProps, TableHOC} from '../TableHOC'; -import {TableHOCUtils} from '../TableHOCUtils'; import {tableWithPredicate} from '../TableWithPredicate'; +import {TableHOCUtils} from '../utils/TableHOCUtils'; describe('Table HOC', () => { describe('TableWithPredicate', () => { diff --git a/packages/react-vapor/src/components/table-hoc/tests/TableWithUrlState.spec.tsx b/packages/react-vapor/src/components/table-hoc/tests/TableWithUrlState.spec.tsx index 6e9a877e31..53368b8f75 100644 --- a/packages/react-vapor/src/components/table-hoc/tests/TableWithUrlState.spec.tsx +++ b/packages/react-vapor/src/components/table-hoc/tests/TableWithUrlState.spec.tsx @@ -16,13 +16,13 @@ import {changePage} from '../../navigation/pagination/NavigationPaginationAction import {changePerPage} from '../../navigation/perPage/NavigationPerPageActions'; import {TableHeaderActions} from '../actions/TableHeaderActions'; import {ITableHOCOwnProps, TableHOC} from '../TableHOC'; -import {TableHOCUtils} from '../TableHOCUtils'; import {tableWithDatePicker} from '../TableWithDatePicker'; import {tableWithFilter} from '../TableWithFilter'; import {tableWithPagination} from '../TableWithPagination'; import {tableWithPredicate} from '../TableWithPredicate'; import {tableWithSort} from '../TableWithSort'; import {tableWithUrlState} from '../TableWithUrlState'; +import {TableHOCUtils} from '../utils/TableHOCUtils'; describe('Table HOC', () => { describe('tableWithUrlState', () => { diff --git a/packages/react-vapor/src/components/table-hoc/utils/TableHOCExampleUtils.tsx b/packages/react-vapor/src/components/table-hoc/utils/TableHOCExampleUtils.tsx new file mode 100644 index 0000000000..314443a167 --- /dev/null +++ b/packages/react-vapor/src/components/table-hoc/utils/TableHOCExampleUtils.tsx @@ -0,0 +1,74 @@ +import * as moment from 'moment'; +import * as React from 'react'; +import {DateUtils} from '../../../utils/DateUtils'; +import {SELECTION_BOXES_LONG} from '../../datePicker/examples/DatePickerExamplesCommon'; +import {IExampleRowData, TableHOCServerExampleId} from '../examples/TableHOCServerExample'; +import {TableRowConnected} from '../TableRowConnected'; +import {TableRowNumberColumn} from '../TableRowNumberColumn'; + +const generateRows = (allData: IExampleRowData[]) => + allData.map((data: IExampleRowData, i: number) => ( + 👋}} + > + + {data.city} + {data.email.toLowerCase()} + {data.username.toLowerCase()} + {data.dateOfBirth.toLocaleDateString()} + + )); + +const tableActions = (username: string) => [ + { + primary: true, + icon: 'edit', + name: 'edit', + enabled: true, + trigger: () => alert(username), + callOnDoubleClick: true, + }, +]; + +const tablePredicates = [ + { + id: 'address.city', + prepend: City:, + values: [ + {displayValue: 'All', value: '', selected: true}, + {displayValue: 'Lebsackbury', value: 'Lebsackbury'}, + ], + }, + { + id: 'username', + prepend: Username:, + values: [ + {displayValue: 'All', value: '', selected: true}, + {displayValue: 'bret', value: 'Bret'}, + ], + }, +]; + +const tableDatePickerConfig = { + datesSelectionBoxes: SELECTION_BOXES_LONG, + years: [...DateUtils.getPreviousYears(25), DateUtils.currentYear.toString()], + initialDateRange: [ + moment() + .subtract(25, 'years') + .toDate(), + moment().toDate(), + ], +}; + +export const TableHOCExampleUtils = { + generateRows, + tableActions, + tablePredicates, + tableDatePickerConfig, +}; diff --git a/packages/react-vapor/src/components/table-hoc/TableHOCUtils.ts b/packages/react-vapor/src/components/table-hoc/utils/TableHOCUtils.ts similarity index 71% rename from packages/react-vapor/src/components/table-hoc/TableHOCUtils.ts rename to packages/react-vapor/src/components/table-hoc/utils/TableHOCUtils.ts index 140acb6e67..3477fcaf83 100644 --- a/packages/react-vapor/src/components/table-hoc/TableHOCUtils.ts +++ b/packages/react-vapor/src/components/table-hoc/utils/TableHOCUtils.ts @@ -1,10 +1,17 @@ import * as _ from 'underscore'; -import {IReactVaporState} from '../../ReactVapor'; -import {DatePickerSelectors} from '../datePicker/DatePickerSelectors'; -import {IFilterState} from '../filterBox/FilterBoxReducers'; -import {IListBoxState} from '../listBox/ListBoxReducers'; -import {ITableWithSortState} from './reducers/TableWithSortReducers'; +import {IReactVaporState} from '../../../ReactVapor'; +import {DatePickerSelectors} from '../../datePicker/DatePickerSelectors'; +import {IFilterState} from '../../filterBox/FilterBoxReducers'; +import {FlatSelectSelectors} from '../../flatSelect/FlatSelectSelectors'; +import {IListBoxState} from '../../listBox/ListBoxReducers'; +import {PER_PAGE_NUMBERS} from '../../navigation/perPage/NavigationPerPage'; +import {PaginationUtils} from '../../pagination/PaginationUtils'; +import {ITableWithSortState} from '../reducers/TableWithSortReducers'; + +export interface ITableLoading { + isLoading?: boolean; +} export interface ITableHOCPredicateValue { id: string; @@ -28,8 +35,15 @@ const getCompositeState = (id: string, state: IReactVaporState): ITableHOCCompos state.tableHOCHeader, (v: ITableWithSortState) => v.tableId === id && _.isBoolean(v.isAsc) ); - const paginationState = _.findWhere(state.paginationComposite, {id: getPaginationId(id)}); - const perPageState = _.findWhere(state.perPageComposite, {id}); + + const perPage = + parseInt( + FlatSelectSelectors.getSelectedOptionId(state, { + id: PaginationUtils.getPaginationPerPageId(id), + }), + 10 + ) || PER_PAGE_NUMBERS[1]; + const filter: IFilterState = _.findWhere(state.filters, {id}); const predicates = getTablePredicates(id, state); @@ -42,8 +56,8 @@ const getCompositeState = (id: string, state: IReactVaporState): ITableHOCCompos sortAscending: (tableSort && tableSort.isAsc) || null, // pagination - perPage: perPageState && perPageState.perPage, - pageNb: paginationState && paginationState.pageNb, + perPage, + pageNb: _.findWhere(state.paginationComposite, {id: getPaginationId(id)})?.pageNb, // filter filter: filter && filter.filterText, diff --git a/packages/react-vapor/src/utils/tests/TestUtils.tsx b/packages/react-vapor/src/utils/tests/TestUtils.tsx index e0b7d3e146..6d541dd0d3 100644 --- a/packages/react-vapor/src/utils/tests/TestUtils.tsx +++ b/packages/react-vapor/src/utils/tests/TestUtils.tsx @@ -7,7 +7,7 @@ import * as _ from 'underscore'; import createMockStore, {MockStoreEnhanced} from 'redux-mock-store'; import {ISvgProps} from '../../components/svg/Svg'; -import {IExampleServerTableState} from '../../components/table-hoc/examples/TableHOCServerExamples'; +import {IExampleServerTableState} from '../../components/table-hoc/examples/TableHOCServerExample'; import {ITooltipProps} from '../../components/tooltip/Tooltip'; import {IReactVaporState} from '../../ReactVapor'; import {ReactVaporReducers} from '../../ReactVaporReducers'; From b0ec7521dbf3c583217769c45b941380f33ffc57 Mon Sep 17 00:00:00 2001 From: pochretien Date: Wed, 18 Dec 2019 00:34:39 -0500 Subject: [PATCH 02/32] remove old useless interface --- .../src/components/table-hoc/utils/TableHOCUtils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/react-vapor/src/components/table-hoc/utils/TableHOCUtils.ts b/packages/react-vapor/src/components/table-hoc/utils/TableHOCUtils.ts index 3477fcaf83..e594c3589f 100644 --- a/packages/react-vapor/src/components/table-hoc/utils/TableHOCUtils.ts +++ b/packages/react-vapor/src/components/table-hoc/utils/TableHOCUtils.ts @@ -9,10 +9,6 @@ import {PER_PAGE_NUMBERS} from '../../navigation/perPage/NavigationPerPage'; import {PaginationUtils} from '../../pagination/PaginationUtils'; import {ITableWithSortState} from '../reducers/TableWithSortReducers'; -export interface ITableLoading { - isLoading?: boolean; -} - export interface ITableHOCPredicateValue { id: string; value: string; From f7534f92f053277a1f536df5fd3487c0d6b5277e Mon Sep 17 00:00:00 2001 From: pochretien Date: Thu, 19 Dec 2019 12:46:31 -0500 Subject: [PATCH 03/32] Add loading for atom component used in the table --- .../docs/src/components/ComponentsInterface.tsx | 5 +++++ .../src/components/actions/ActionBarReducers.ts | 2 +- .../actions/tests/actionBarsReducers.spec.ts | 2 +- .../src/components/flatSelect/FlatSelect.tsx | 9 ++++++--- .../components/flatSelect/FlatSelectConnected.tsx | 1 - .../components/flatSelect/FlatSelectOption.tsx | 2 ++ .../pagination/NavigationPaginationSelect.tsx | 7 ++++++- .../tests/NavigationPaginationReducers.spec.ts | 2 +- .../src/components/select/SelectConnected.tsx | 15 ++++++++++++++- .../components/select/SingleSelectConnected.tsx | 9 +++++++-- .../table-hoc/reducers/TableRowReducers.ts | 2 +- 11 files changed, 44 insertions(+), 12 deletions(-) diff --git a/packages/react-vapor/docs/src/components/ComponentsInterface.tsx b/packages/react-vapor/docs/src/components/ComponentsInterface.tsx index 5d44eba193..d8d9e76bc7 100644 --- a/packages/react-vapor/docs/src/components/ComponentsInterface.tsx +++ b/packages/react-vapor/docs/src/components/ComponentsInterface.tsx @@ -6,6 +6,11 @@ export interface IComponent { component: ExampleComponent; } +export interface IComponentBehaviour { + isLoading?: boolean; + disabled?: boolean; +} + export type ExampleComponent = React.ComponentType & { description?: string; firstTabLabel?: string; diff --git a/packages/react-vapor/src/components/actions/ActionBarReducers.ts b/packages/react-vapor/src/components/actions/ActionBarReducers.ts index 501d32499d..0048546509 100644 --- a/packages/react-vapor/src/components/actions/ActionBarReducers.ts +++ b/packages/react-vapor/src/components/actions/ActionBarReducers.ts @@ -7,7 +7,7 @@ import {LoadingActions} from '../loading/LoadingActions'; import {PaginationActions} from '../navigation/pagination/NavigationPaginationActions'; import {PerPageActions} from '../navigation/perPage/NavigationPerPageActions'; import {TableHOCRowActionsType} from '../table-hoc/actions/TableHOCRowActions'; -import {TableHOCUtils} from '../table-hoc/TableHOCUtils'; +import {TableHOCUtils} from '../table-hoc/utils/TableHOCUtils'; import {IActionOptions} from './Action'; import {ActionBarActions} from './ActionBarActions'; diff --git a/packages/react-vapor/src/components/actions/tests/actionBarsReducers.spec.ts b/packages/react-vapor/src/components/actions/tests/actionBarsReducers.spec.ts index 6a09f42e41..67f45aa59e 100644 --- a/packages/react-vapor/src/components/actions/tests/actionBarsReducers.spec.ts +++ b/packages/react-vapor/src/components/actions/tests/actionBarsReducers.spec.ts @@ -4,7 +4,7 @@ import {selectListBoxOption} from '../../listBox/ListBoxActions'; import {turnOffLoading, turnOnLoading} from '../../loading/LoadingActions'; import {changePage} from '../../navigation/pagination/NavigationPaginationActions'; import {changePerPage} from '../../navigation/perPage/NavigationPerPageActions'; -import {TableHOCUtils} from '../../table-hoc/TableHOCUtils'; +import {TableHOCUtils} from '../../table-hoc/utils/TableHOCUtils'; import {ActionBarActions, IActionBarPayload, IChangeActionBarActionsPayload} from '../ActionBarActions'; import { actionBarInitialState, diff --git a/packages/react-vapor/src/components/flatSelect/FlatSelect.tsx b/packages/react-vapor/src/components/flatSelect/FlatSelect.tsx index 5eb5510f64..d41f2e8f36 100644 --- a/packages/react-vapor/src/components/flatSelect/FlatSelect.tsx +++ b/packages/react-vapor/src/components/flatSelect/FlatSelect.tsx @@ -6,11 +6,13 @@ import {FlatSelectOption, IFlatSelectOptionProps} from './FlatSelectOption'; export interface IFlatSelectOwnProps { id: string; options: IFlatSelectOptionProps[]; - classes?: string[]; + className?: string; group?: boolean; optionPicker?: boolean; defaultSelectedOptionId?: string; onClick?: (option: IFlatSelectOptionProps) => void; + disabled?: boolean; + classes?: string[] /* @deprecated use className instead */; } export interface IFlatSelectStateProps { @@ -54,7 +56,7 @@ export class FlatSelect extends React.Component { this.props.selectedOptionId && this.props.selectedOptionId === flatSelectOption.id; flatSelectOption.onClick = (option: IFlatSelectOptionProps) => this.handleOnOptionClick(option); - return ; + return ; }); } @@ -65,7 +67,8 @@ export class FlatSelect extends React.Component { 'mod-btn-group': this.props.group, 'mod-option-picker': this.props.optionPicker, }, - this.props.classes + this.props.classes, + this.props.className ); return
{this.getOptions()}
; diff --git a/packages/react-vapor/src/components/flatSelect/FlatSelectConnected.tsx b/packages/react-vapor/src/components/flatSelect/FlatSelectConnected.tsx index 824943da06..eb2e0f2fef 100644 --- a/packages/react-vapor/src/components/flatSelect/FlatSelectConnected.tsx +++ b/packages/react-vapor/src/components/flatSelect/FlatSelectConnected.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import {connect} from 'react-redux'; -import * as _ from 'underscore'; import {IReactVaporState} from '../../ReactVapor'; import {IDispatch, ReduxUtils} from '../../utils/ReduxUtils'; import { diff --git a/packages/react-vapor/src/components/flatSelect/FlatSelectOption.tsx b/packages/react-vapor/src/components/flatSelect/FlatSelectOption.tsx index 92303d0f26..3a65a9af67 100644 --- a/packages/react-vapor/src/components/flatSelect/FlatSelectOption.tsx +++ b/packages/react-vapor/src/components/flatSelect/FlatSelectOption.tsx @@ -12,6 +12,7 @@ export interface IFlatSelectOptionProps { tooltip?: ITooltipProps; selected?: boolean; onClick?: (option: IFlatSelectOptionProps) => void; + disabled?: boolean; } export class FlatSelectOption extends React.Component { @@ -30,6 +31,7 @@ export class FlatSelectOption extends React.Component { + disabled?: boolean; selected: boolean; pageNb: number; onPageClick: (pageNb: number) => void; @@ -8,7 +10,10 @@ export interface INavigationPaginationSelectProps extends React.ClassAttributes< export class NavigationPaginationSelect extends React.Component { render() { - const linkClasses: string = 'flat-select-option' + (this.props.selected ? '' : ' selectable'); + const linkClasses: string = classNames('flat-select-option', { + selectable: !this.props.selected, + disabled: this.props.disabled, + }); return ( + ); + } + const pickerClasses = classNames('select-dropdown dropdown', this.props.selectClasses, { open: this.props.isOpened, 'mod-multi': this.props.multi, diff --git a/packages/react-vapor/src/components/select/SingleSelectConnected.tsx b/packages/react-vapor/src/components/select/SingleSelectConnected.tsx index f54cc42a60..e241207dfc 100644 --- a/packages/react-vapor/src/components/select/SingleSelectConnected.tsx +++ b/packages/react-vapor/src/components/select/SingleSelectConnected.tsx @@ -3,6 +3,7 @@ import * as VaporSVG from 'coveo-styleguide'; import * as React from 'react'; import {keys} from 'ts-transformer-keys'; import * as _ from 'underscore'; +import {IComponentBehaviour} from '../../../docs/src/components/ComponentsInterface'; import {IReactVaporState} from '../../ReactVapor'; import {getReactNodeTextContent} from '../../utils/JSXUtils'; @@ -16,7 +17,7 @@ import {ISelectButtonProps, ISelectOwnProps, ISelectProps, SelectConnected} from import {SelectSelector} from './SelectSelector'; import * as styles from './styles/SingleSelect.scss'; -export interface ISingleSelectOwnProps extends ISelectProps { +export interface ISingleSelectOwnProps extends ISelectProps, IComponentBehaviour { placeholder?: string; toggleClasses?: string; onSelectOptionCallback?: (option: string) => void; @@ -69,7 +70,11 @@ export class SingleSelectConnected extends React.PureComponent< render() { return ( - + {this.props.children} ); diff --git a/packages/react-vapor/src/components/table-hoc/reducers/TableRowReducers.ts b/packages/react-vapor/src/components/table-hoc/reducers/TableRowReducers.ts index 82e3397dcb..aa08d8e5f5 100644 --- a/packages/react-vapor/src/components/table-hoc/reducers/TableRowReducers.ts +++ b/packages/react-vapor/src/components/table-hoc/reducers/TableRowReducers.ts @@ -9,7 +9,7 @@ import { ITableRowToggleCollapsiblePayload, TableHOCRowActionsType, } from '../actions/TableHOCRowActions'; -import {TableHOCUtils} from '../TableHOCUtils'; +import {TableHOCUtils} from '../utils/TableHOCUtils'; export interface ITableRowState { id: string; From 1e248a1d7861f87cb79d0f83ee7b628dcb98c215 Mon Sep 17 00:00:00 2001 From: pochretien Date: Thu, 19 Dec 2019 12:56:50 -0500 Subject: [PATCH 04/32] Update and add loading for each table HOC --- .../loading/components/TableLoading.tsx | 28 +++++--- .../NavigationPaginationReducers.ts | 2 +- .../src/components/table-hoc/TableHOC.tsx | 29 +++++--- .../table-hoc/TableHeaderWithSort.tsx | 9 +++ .../components/table-hoc/TableRowHeader.tsx | 11 ++++ .../table-hoc/TableRowNumberHeader.tsx | 10 ++- .../table-hoc/TableWithBlankSlate.tsx | 16 ++--- .../table-hoc/TableWithDatePicker.tsx | 17 ++++- .../components/table-hoc/TableWithFilter.tsx | 52 ++++++++++++--- .../table-hoc/TableWithPredicate.tsx | 30 +++++++-- .../table-hoc/TableWithUrlState.tsx | 20 +++--- .../examples/TableHOCLoadingExample.tsx | 66 +++++++++++++++++++ .../TableHOCwithBlankSlateExample.tsx | 29 +++----- packages/react-vapor/src/utils/UrlUtils.ts | 2 +- 14 files changed, 243 insertions(+), 78 deletions(-) create mode 100644 packages/react-vapor/src/components/table-hoc/TableRowHeader.tsx create mode 100644 packages/react-vapor/src/components/table-hoc/examples/TableHOCLoadingExample.tsx diff --git a/packages/react-vapor/src/components/loading/components/TableLoading.tsx b/packages/react-vapor/src/components/loading/components/TableLoading.tsx index 6593b196b2..6fe9e66e82 100644 --- a/packages/react-vapor/src/components/loading/components/TableLoading.tsx +++ b/packages/react-vapor/src/components/loading/components/TableLoading.tsx @@ -11,20 +11,30 @@ export const TableLoading = ({ return ( <> - - {_.times(numberOfRow, () => ( - - {_.times(numberOfColumn, () => ( - - ))} - - ))} - +
); }; +export const TableBodyLoading = ({ + numberOfColumn = 4, + numberOfRow = 10, +}: { + numberOfColumn?: number; + numberOfRow?: number; +}) => ( + + {_.times(numberOfRow, (nColumn: number) => ( + + {_.times(numberOfColumn, (nRow: number) => ( + + ))} + + ))} + +); + const TableRowLoading = () => (
diff --git a/packages/react-vapor/src/components/navigation/pagination/NavigationPaginationReducers.ts b/packages/react-vapor/src/components/navigation/pagination/NavigationPaginationReducers.ts index db7207cb44..5dae9e7daf 100644 --- a/packages/react-vapor/src/components/navigation/pagination/NavigationPaginationReducers.ts +++ b/packages/react-vapor/src/components/navigation/pagination/NavigationPaginationReducers.ts @@ -4,8 +4,8 @@ import {IReduxActionsPayload} from '../../../ReactVapor'; import {IReduxAction} from '../../../utils/ReduxUtils'; import {FilterActions} from '../../filterBox/FilterBoxActions'; import {ListBoxActions} from '../../listBox/ListBoxActions'; +import {TableHOCUtils} from '../../table-hoc/utils/TableHOCUtils'; import {TableActions} from '../../tables/TableActions'; -import {TableHOCUtils} from './../../table-hoc/TableHOCUtils'; import {PaginationActions} from './NavigationPaginationActions'; export interface IPaginationState { diff --git a/packages/react-vapor/src/components/table-hoc/TableHOC.tsx b/packages/react-vapor/src/components/table-hoc/TableHOC.tsx index 8c2390688c..c0468f970c 100644 --- a/packages/react-vapor/src/components/table-hoc/TableHOC.tsx +++ b/packages/react-vapor/src/components/table-hoc/TableHOC.tsx @@ -1,8 +1,11 @@ import * as classNames from 'classnames'; import * as React from 'react'; +import * as _ from 'underscore'; import {WithServerSideProcessingProps} from '../../hoc/withServerSideProcessing/withServerSideProcessing'; import {ActionBarConnected} from '../actions/ActionBar'; +import {TableBodyLoading} from '../loading/components/TableLoading'; +import {PER_PAGE_NUMBERS} from '../navigation/perPage/NavigationPerPage'; /** * @deprecated Use WithServerSideProcessingProps directly instead @@ -19,6 +22,7 @@ export interface ITableHOCOwnProps { tableHeader?: React.ReactNode; onUpdate?: () => void; containerClassName?: string; + numberOfColumn?: number; showBorderTop?: boolean; } @@ -33,17 +37,24 @@ export class TableHOC extends React.PureComponent + {this.props.tableHeader} + {this.props.isLoading ? ( + + ) : ( + {this.props.renderBody(this.props.data || [])} + )} + + ); + return ( -
+
{this.renderActions()} - - {this.props.tableHeader} - {this.props.renderBody(this.props.data || [])} -
+ {table} {this.props.children}
); diff --git a/packages/react-vapor/src/components/table-hoc/TableHeaderWithSort.tsx b/packages/react-vapor/src/components/table-hoc/TableHeaderWithSort.tsx index 1e4c6d351b..1e7b67cc21 100644 --- a/packages/react-vapor/src/components/table-hoc/TableHeaderWithSort.tsx +++ b/packages/react-vapor/src/components/table-hoc/TableHeaderWithSort.tsx @@ -10,6 +10,7 @@ import {ITableWithSortState} from './reducers/TableWithSortReducers'; export interface ITableHeaderWithSortOwnProps { id: string; tableId: string; + isLoading?: boolean; isDefault?: boolean; } @@ -63,6 +64,14 @@ export class TableHeaderWithSort extends React.Component< 'admin-sort-descending': this.props.sorted === false, }); + if (this.props.isLoading) { + return ( + +
+ + ); + } + return ( this.props.onSort()}> {this.props.children} diff --git a/packages/react-vapor/src/components/table-hoc/TableRowHeader.tsx b/packages/react-vapor/src/components/table-hoc/TableRowHeader.tsx new file mode 100644 index 0000000000..1f2080c26e --- /dev/null +++ b/packages/react-vapor/src/components/table-hoc/TableRowHeader.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; + +export const TableRowHeader = ({isLoading, children}: {isLoading?: boolean; children?: React.ReactNode}) => { + return isLoading ? ( + +
+ + ) : ( + {children} + ); +}; diff --git a/packages/react-vapor/src/components/table-hoc/TableRowNumberHeader.tsx b/packages/react-vapor/src/components/table-hoc/TableRowNumberHeader.tsx index 8833070295..063480d0df 100644 --- a/packages/react-vapor/src/components/table-hoc/TableRowNumberHeader.tsx +++ b/packages/react-vapor/src/components/table-hoc/TableRowNumberHeader.tsx @@ -1,3 +1,11 @@ import * as React from 'react'; -export const TableRowNumberHeader = () => ; +export const TableRowNumberHeader = ({isLoading}: {isLoading?: boolean}) => { + return isLoading ? ( + +
+ + ) : ( + + ); +}; diff --git a/packages/react-vapor/src/components/table-hoc/TableWithBlankSlate.tsx b/packages/react-vapor/src/components/table-hoc/TableWithBlankSlate.tsx index a73d1e4cf5..a92450d01e 100644 --- a/packages/react-vapor/src/components/table-hoc/TableWithBlankSlate.tsx +++ b/packages/react-vapor/src/components/table-hoc/TableWithBlankSlate.tsx @@ -5,7 +5,8 @@ import * as _ from 'underscore'; import {IReactVaporState} from '../../ReactVapor'; import {ConfigSupplier, HocUtils} from '../../utils/HocUtils'; import {ReduxConnect} from '../../utils/ReduxUtils'; -import {BlankSlate, IBlankSlateProps} from '../blankSlate/BlankSlate'; +import {IBlankSlateProps} from '../blankSlate/BlankSlate'; +import {BlankSlateWithTable} from '../blankSlate/BlankSlatesHOC'; import {ITableHOCOwnProps} from './TableHOC'; import {TableSelectors} from './TableSelectors'; @@ -27,7 +28,6 @@ export const tableWithBlankSlate = (supplier: ConfigSupplier = const isEmpty = TableSelectors.getIsEmpty(state, ownProps); return { isEmpty, - data: isEmpty ? null : ownProps.data, }; }; @@ -36,13 +36,13 @@ export const tableWithBlankSlate = (supplier: ConfigSupplier = render() { const newProps = { ..._.omit(this.props, [...TableWithBlankSlatePropsToOmit]), - renderBody: this.props.isEmpty ? (): any => null : this.props.renderBody, }; - return ( - - {this.props.isEmpty ? : this.props.children} - - ); + + if (_.isNull(this.props.data)) { + newProps.renderBody = () => ; + } + + return {this.props.children}; } } diff --git a/packages/react-vapor/src/components/table-hoc/TableWithDatePicker.tsx b/packages/react-vapor/src/components/table-hoc/TableWithDatePicker.tsx index 7ab0d2b906..a79f786bbf 100644 --- a/packages/react-vapor/src/components/table-hoc/TableWithDatePicker.tsx +++ b/packages/react-vapor/src/components/table-hoc/TableWithDatePicker.tsx @@ -7,10 +7,12 @@ import {WithServerSideProcessingProps} from '../../hoc/withServerSideProcessing/ import {IReactVaporState} from '../../ReactVapor'; import {ConfigSupplier, HocUtils} from '../../utils/HocUtils'; import {ReduxConnect} from '../../utils/ReduxUtils'; +import {UrlUtils} from '../../utils/UrlUtils'; import {IDatePickerDropdownChildrenProps, IDatePickerDropdownOwnProps} from '../datePicker/DatePickerDropdown'; import {DatePickerDropdownConnected} from '../datePicker/DatePickerDropdownConnected'; import {DatePickerSelectors} from '../datePicker/DatePickerSelectors'; import {ITableHOCOwnProps} from './TableHOC'; +import {Params} from './TableWithUrlState'; export interface ITableWithDatePickerConfig extends WithServerSideProcessingProps, @@ -55,9 +57,13 @@ export const tableWithDatePicker = (supplier: ConfigSupplier matchDates(datum, lowerLimit, upperLimit)) : ownProps.data; + + const urlParams = UrlUtils.getSearchParams(); + const lowerDateLimitFromUrl = urlParams[Params.lowerDateLimit] && new Date(urlParams[Params.lowerDateLimit]); + const upperDateLimitFromUrl = urlParams[Params.upperDateLimit] && new Date(urlParams[Params.upperDateLimit]); return { - lowerLimit, - upperLimit, + lowerLimit: lowerLimit || lowerDateLimitFromUrl || config.initialDateRange?.[0], + upperLimit: upperLimit || upperDateLimitFromUrl || config.initialDateRange?.[1], data: config.isServer || ownProps.isServer ? ownProps.data : ownProps.data && filterData(), }; }; @@ -69,7 +75,10 @@ export const tableWithDatePicker = (supplier: ConfigSupplier ); const newActions = [...this.props.actions, datePickerAction]; diff --git a/packages/react-vapor/src/components/table-hoc/TableWithFilter.tsx b/packages/react-vapor/src/components/table-hoc/TableWithFilter.tsx index fe0253e9a2..646039b3ef 100644 --- a/packages/react-vapor/src/components/table-hoc/TableWithFilter.tsx +++ b/packages/react-vapor/src/components/table-hoc/TableWithFilter.tsx @@ -5,21 +5,30 @@ import * as _ from 'underscore'; import {WithServerSideProcessingProps} from '../../hoc/withServerSideProcessing/withServerSideProcessing'; import {IReactVaporState} from '../../ReactVapor'; import {ConfigSupplier, HocUtils} from '../../utils/HocUtils'; -import {ReduxConnect} from '../../utils/ReduxUtils'; +import {IDispatch, ReduxConnect} from '../../utils/ReduxUtils'; +import {UrlUtils} from '../../utils/UrlUtils'; +import {IBlankSlateProps} from '../blankSlate/BlankSlate'; +import {BlankSlateWithTable} from '../blankSlate/BlankSlatesHOC'; +import {filterThrough} from '../filterBox/FilterBoxActions'; import {FilterBoxConnected} from '../filterBox/FilterBoxConnected'; import {FilterBoxSelectors} from '../filterBox/FilterBoxSelectors'; import {ITableHOCOwnProps} from './TableHOC'; +import {Params} from './TableWithUrlState'; export interface ITableWithFilterConfig extends WithServerSideProcessingProps { + blankSlate?: IBlankSlateProps; matchFilter?: (filterValue: string, datum: any) => boolean; placeholder?: string; } export interface ITableWithFilterStateProps { filter: string; + urlFilter: string; } export interface ITableWithFilterDispatchProps { + resetFilter: () => void; + addFilter: (filterText: string) => void; onRender: () => void; } @@ -35,11 +44,9 @@ const defaultMatchFilter = (filter: string, datum: any) => JSON.stringify(_.values(datum).map((v: any) => _.isString(v) && v.toLowerCase())).indexOf(filter.toLowerCase()) !== -1; -type FilterableTableComponent = React.ComponentClass; - -export const tableWithFilter = (supplier: ConfigSupplier = {}) => ( - Component: FilterableTableComponent -): FilterableTableComponent => { +export const tableWithFilter = ( + supplier: ConfigSupplier = {blankSlate: {title: 'No results'}} +) => (Component: React.ComponentClass>) => { const config = HocUtils.supplyConfig(supplier); const mapStateToProps = ( @@ -47,20 +54,26 @@ export const tableWithFilter = (supplier: ConfigSupplier ownProps: ITableWithFilterProps ): ITableWithFilterStateProps | ITableHOCOwnProps => { const filterText = FilterBoxSelectors.getFilterText(state, ownProps); + const matchFilter = config.matchFilter || defaultMatchFilter; const filterData = () => filterText ? _.filter(ownProps.data, (datum: any) => matchFilter(filterText, datum)) : ownProps.data; - + const urlParams = UrlUtils.getSearchParams(); return { filter: filterText, + urlFilter: urlParams[Params.filter], data: ownProps.isServer || config.isServer ? ownProps.data : ownProps.data && filterData(), }; }; - @ReduxConnect(mapStateToProps) + const mapDispatchToProps = (dispatch: IDispatch, ownProps: ITableHOCOwnProps) => ({ + resetFilter: () => dispatch(filterThrough(ownProps.id, '')), + }); + + @ReduxConnect(mapStateToProps, mapDispatchToProps) class TableWithFilter extends React.Component { componentDidUpdate(prevProps: ITableWithFilterProps) { - if (prevProps.filter !== this.props.filter) { + if (prevProps.filter !== this.props.filter && this.props.filter !== this.props.urlFilter) { this.props.onUpdate?.(); } } @@ -76,7 +89,26 @@ export const tableWithFilter = (supplier: ConfigSupplier /> ); const newActions = [...(this.props.actions || []), filterAction]; - const newProps = _.omit(this.props, [...TableWithFilterPropsToOmit]); + const newProps = { + ..._.omit(this.props, [...TableWithFilterPropsToOmit]), + }; + + if (!this.props.data?.length) { + newProps.renderBody = () => ( + this.props.resetFilter(), + }, + ]} + {...HocUtils.supplyConfig(supplier).blankSlate} + /> + ); + } + return ( {this.props.children} diff --git a/packages/react-vapor/src/components/table-hoc/TableWithPredicate.tsx b/packages/react-vapor/src/components/table-hoc/TableWithPredicate.tsx index 0eb4d2fc86..85be78f592 100644 --- a/packages/react-vapor/src/components/table-hoc/TableWithPredicate.tsx +++ b/packages/react-vapor/src/components/table-hoc/TableWithPredicate.tsx @@ -7,12 +7,13 @@ import {WithServerSideProcessingProps} from '../../hoc/withServerSideProcessing/ import {IReactVaporState} from '../../ReactVapor'; import {ConfigSupplier, HocUtils} from '../../utils/HocUtils'; import {ReduxConnect} from '../../utils/ReduxUtils'; +import {UrlUtils} from '../../utils/UrlUtils'; import {IItemBoxProps} from '../itemBox/ItemBox'; import {SelectSelector} from '../select/SelectSelector'; import {SingleSelectConnected} from '../select/SingleSelectConnected'; import * as styles from './styles/TableWithPredicates.scss'; import {ITableHOCOwnProps} from './TableHOC'; -import {TableHOCUtils} from './TableHOCUtils'; +import {TableHOCUtils} from './utils/TableHOCUtils'; export interface ITableWithPredicateConfig extends WithServerSideProcessingProps { id: string; @@ -23,6 +24,7 @@ export interface ITableWithPredicateConfig extends WithServerSideProcessingProps export interface ITableWithPredicateStateProps { predicate: string; + urlPredicate: string[]; } export interface ITableWithPredicateProps @@ -46,24 +48,35 @@ export const tableWithPredicate = (supplier: ConfigSupplier { - const predicate = SelectSelector.getListBoxSelected(state, { - id: TableHOCUtils.getPredicateId(ownProps.id, config.id), - })[0]; + const predicate = + SelectSelector.getListBoxSelected(state, { + id: TableHOCUtils.getPredicateId(ownProps.id, config.id), + })[0] || ''; const matchPredicate = config.matchPredicate || defaultMatchPredicate; const predicateData = () => !ownProps.isServer && !config.isServer && predicate ? _.filter(ownProps.data, (datum: any) => matchPredicate(predicate, datum)) : ownProps.data; + + const urlParams = UrlUtils.getSearchParams(); + const possiblePredicates = TableHOCUtils.getPredicateIds(ownProps.id, state); + return { predicate: predicate, data: ownProps.data && predicateData(), + urlPredicate: Object.keys(urlParams) + .filter((key) => possiblePredicates.includes(key)) + .map((key) => urlParams[key]), }; }; @ReduxConnect(mapStateToProps) class TableWithPredicate extends React.Component { componentDidUpdate(prevProps: ITableWithPredicateProps) { - if (prevProps.predicate !== this.props.predicate) { + if ( + prevProps.predicate !== this.props.predicate && + !_.include(this.props.urlPredicate, this.props.predicate) + ) { this.props.onUpdate?.(); } } @@ -76,7 +89,12 @@ export const tableWithPredicate = (supplier: ConfigSupplier - +
); diff --git a/packages/react-vapor/src/components/table-hoc/TableWithUrlState.tsx b/packages/react-vapor/src/components/table-hoc/TableWithUrlState.tsx index ec08fa63c8..aa8297e3ed 100644 --- a/packages/react-vapor/src/components/table-hoc/TableWithUrlState.tsx +++ b/packages/react-vapor/src/components/table-hoc/TableWithUrlState.tsx @@ -12,7 +12,7 @@ import {changePage} from '../navigation/pagination/NavigationPaginationActions'; import {changePerPage} from '../navigation/perPage/NavigationPerPageActions'; import {TableHeaderActions} from './actions/TableHeaderActions'; import {ITableHOCOwnProps} from './TableHOC'; -import {ITableHOCPredicateValue, TableHOCUtils} from './TableHOCUtils'; +import {ITableHOCPredicateValue, TableHOCUtils} from './utils/TableHOCUtils'; export interface TableWithUrlStateProps { onUpdateUrl: (queryString: string) => void; @@ -23,7 +23,7 @@ enum SortOrderValues { descending = 'desc', } -const Params = { +export const Params = { pageNumber: 'page', pageSize: 'pageSize', sortKey: 'sortBy', @@ -110,16 +110,14 @@ function updateTableStateFromUrl(tableId: string): IThunkAction { Object.keys(urlParams) .filter((key) => possiblePredicates.includes(key)) .forEach((key) => - dispatch( - selectListBoxOption(TableHOCUtils.getPredicateId(tableId, key), false, urlParams[key] as string) - ) + dispatch(selectListBoxOption(TableHOCUtils.getPredicateId(tableId, key), false, urlParams[key])) ); if (urlParams.hasOwnProperty(Params.lowerDateLimit)) { dispatch( changeDatePickerLowerLimit( TableHOCUtils.getDatePickerId(tableId), - new Date(urlParams[Params.lowerDateLimit] as string) + new Date(urlParams[Params.lowerDateLimit]) ) ); } @@ -128,7 +126,7 @@ function updateTableStateFromUrl(tableId: string): IThunkAction { dispatch( changeDatePickerUpperLimit( TableHOCUtils.getDatePickerId(tableId), - new Date(urlParams[Params.upperDateLimit] as string) + new Date(urlParams[Params.upperDateLimit]) ) ); } @@ -138,24 +136,24 @@ function updateTableStateFromUrl(tableId: string): IThunkAction { } if (urlParams.hasOwnProperty(Params.filter)) { - dispatch(filterThrough(tableId, urlParams[Params.filter] as string)); + dispatch(filterThrough(tableId, urlParams[Params.filter])); } if (urlParams.hasOwnProperty(Params.sortKey) && urlParams.hasOwnProperty(Params.sortOrder)) { dispatch( TableHeaderActions.sortTable( - urlParams[Params.sortKey] as string, + urlParams[Params.sortKey], urlParams[Params.sortOrder] === SortOrderValues.ascending ) ); } if (urlParams.hasOwnProperty(Params.pageSize)) { - dispatch(changePerPage(tableId, urlParams[Params.pageSize] as number)); + dispatch(changePerPage(tableId, urlParams[Params.pageSize])); } if (urlParams.hasOwnProperty(Params.pageNumber)) { - dispatch(changePage(TableHOCUtils.getPaginationId(tableId), urlParams[Params.pageNumber] as number)); + dispatch(changePage(TableHOCUtils.getPaginationId(tableId), urlParams[Params.pageNumber])); } }; } diff --git a/packages/react-vapor/src/components/table-hoc/examples/TableHOCLoadingExample.tsx b/packages/react-vapor/src/components/table-hoc/examples/TableHOCLoadingExample.tsx new file mode 100644 index 0000000000..948f98c738 --- /dev/null +++ b/packages/react-vapor/src/components/table-hoc/examples/TableHOCLoadingExample.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import * as _ from 'underscore'; +import {ExampleComponent} from '../../../../docs/src/components/ComponentsInterface'; +import {Section} from '../../section/Section'; +import {TableHOC} from '../TableHOC'; +import {tableWithBlankSlate} from '../TableWithBlankSlate'; +import {tableWithFilter} from '../TableWithFilter'; +import {generateDataWithFacker, generateTableRow} from './TableHOCExamples'; + +export const TableHocLoadingExamples: ExampleComponent = () => ( +
+
+ +
+
+ +
+
+); +TableHocLoadingExamples.title = 'TableHOC loading'; + +const fiveDataRows = generateDataWithFacker(10); + +// start-print + +const TableLoadingExamplesWithDataNull: React.FunctionComponent = () => { + const tableId = 'TableLoadingExamples'; + return ( +
+
+ +
+
+ ); +}; + +const TableLoadingExamplesWithData: React.FunctionComponent = () => { + const tableId = 'TableLoadingExamples'; + return ( +
+
+ +
+
+ ); +}; + +const TableLoadingComposed = _.compose( + tableWithBlankSlate({title: 'No data caused the table to be empty'}), + tableWithFilter({ + matchFilter: (filter: string, data: any) => data.username.toLowerCase().indexOf(filter.toLowerCase()) !== -1, + }), + tableWithBlankSlate({title: 'Filter caused the table to be empty'}) +)(TableHOC); diff --git a/packages/react-vapor/src/components/table-hoc/examples/TableHOCwithBlankSlateExample.tsx b/packages/react-vapor/src/components/table-hoc/examples/TableHOCwithBlankSlateExample.tsx index 4b330bc08f..a1906e6670 100644 --- a/packages/react-vapor/src/components/table-hoc/examples/TableHOCwithBlankSlateExample.tsx +++ b/packages/react-vapor/src/components/table-hoc/examples/TableHOCwithBlankSlateExample.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import {connect} from 'react-redux'; import * as _ from 'underscore'; import {ExampleComponent} from '../../../../docs/src/components/ComponentsInterface'; import {Section} from '../../section/Section'; @@ -24,25 +23,17 @@ export interface IExampleRowData { } // start-print -const mapStateToProps = () => ({ - data: generateDataWithFacker(0), -}); -const TableWithBlankSlateExampleDisconnected: React.FunctionComponent = () => { - const tableId = 'tableWithBlankSlate'; - - return ( -
- -
- ); -}; -const TableWithBlankSlateExample = connect(mapStateToProps)(TableWithBlankSlateExampleDisconnected); +const TableWithBlankSlateExample: React.FunctionComponent = () => ( +
+ +
+); const TableWithBlankSlateComposed = _.compose( tableWithBlankSlate({title: 'No data caused the table to be empty'}), diff --git a/packages/react-vapor/src/utils/UrlUtils.ts b/packages/react-vapor/src/utils/UrlUtils.ts index 77aa88279c..646c697b01 100644 --- a/packages/react-vapor/src/utils/UrlUtils.ts +++ b/packages/react-vapor/src/utils/UrlUtils.ts @@ -21,7 +21,7 @@ function toQueryString(obj: object): string { return QueryString.stringify(obj, {sort: false}); } -function getSearchParams() { +function getSearchParams(): {[key: string]: any} { return UrlUtils.toObject(UrlUtils.getQuery()); } From 4ab8aeb5d262d13994aa0307c5934d4d3acb185d Mon Sep 17 00:00:00 2001 From: pochretien Date: Thu, 19 Dec 2019 13:00:55 -0500 Subject: [PATCH 05/32] Add the new pagination with the new loading --- .../flatSelect/FlatSelectWithPrepend.tsx | 21 +++ .../pagination/PaginationPagesNumber.tsx | 156 ++++++++++++++++++ .../pagination/PaginationPerPage.tsx | 40 +++++ .../components/pagination/PaginationUtils.tsx | 7 + .../components/pagination/TablePagination.tsx | 44 +++++ .../table-hoc/TableWithNewPagination.tsx | 132 +++++++++++++++ .../table-hoc/TableWithPagination.tsx | 13 +- packages/vapor/scss/tables/pagination.scss | 23 +++ 8 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 packages/react-vapor/src/components/flatSelect/FlatSelectWithPrepend.tsx create mode 100644 packages/react-vapor/src/components/pagination/PaginationPagesNumber.tsx create mode 100644 packages/react-vapor/src/components/pagination/PaginationPerPage.tsx create mode 100644 packages/react-vapor/src/components/pagination/PaginationUtils.tsx create mode 100644 packages/react-vapor/src/components/pagination/TablePagination.tsx create mode 100644 packages/react-vapor/src/components/table-hoc/TableWithNewPagination.tsx diff --git a/packages/react-vapor/src/components/flatSelect/FlatSelectWithPrepend.tsx b/packages/react-vapor/src/components/flatSelect/FlatSelectWithPrepend.tsx new file mode 100644 index 0000000000..37de383a23 --- /dev/null +++ b/packages/react-vapor/src/components/flatSelect/FlatSelectWithPrepend.tsx @@ -0,0 +1,21 @@ +import * as classNames from 'classnames'; +import {FunctionComponent} from 'react'; +import * as React from 'react'; +import {IFlatSelectOwnProps} from './FlatSelect'; +import {FlatSelectConnected} from './FlatSelectConnected'; + +export interface IFlatSelectWithPrependProps extends IFlatSelectOwnProps { + prepend?: React.ReactNode; + prependClassName?: string; +} + +export const FlatSelectWithPrepend: FunctionComponent = ({ + prepend, + prependClassName = '', + ...flatSelectProps +}) => ( +
+
{prepend}
+ +
+); diff --git a/packages/react-vapor/src/components/pagination/PaginationPagesNumber.tsx b/packages/react-vapor/src/components/pagination/PaginationPagesNumber.tsx new file mode 100644 index 0000000000..a2a0da9703 --- /dev/null +++ b/packages/react-vapor/src/components/pagination/PaginationPagesNumber.tsx @@ -0,0 +1,156 @@ +import * as classNames from 'classnames'; +import * as React from 'react'; +import {connect} from 'react-redux'; +import * as _ from 'underscore'; +import {IReactVaporState, IReduxActionsPayload} from '../../ReactVapor'; +import {IReduxAction} from '../../utils/ReduxUtils'; +import { + INavigationPaginationDispatchProps, + INavigationPaginationOwnProps, + INavigationPaginationStateProps, +} from '../navigation/pagination/NavigationPagination'; +import {addPagination, changePage, removePagination} from '../navigation/pagination/NavigationPaginationActions'; +import {IPaginationState} from '../navigation/pagination/NavigationPaginationReducers'; +import {NavigationPaginationSelect} from '../navigation/pagination/NavigationPaginationSelect'; +import {Svg} from '../svg/Svg'; + +export interface IPaginationPagesNumberOwnProps { + id?: string; + totalPages: number; + numberOfPagesToShow?: number; + previousLabel?: string; + nextLabel?: string; + loadingIds?: string[]; + hidden?: boolean; + disabled?: boolean; +} + +const mapStateToProps = ( + state: IReactVaporState, + ownProps: INavigationPaginationOwnProps +): INavigationPaginationStateProps => { + const item: IPaginationState = _.findWhere(state.paginationComposite, {id: ownProps.id}); + + return { + currentPage: item ? item.pageNb : 0, + }; +}; + +const mapDispatchToProps = ( + dispatch: (action: IReduxAction) => void, + ownProps: INavigationPaginationOwnProps +): INavigationPaginationDispatchProps => ({ + onRender: () => dispatch(addPagination(ownProps.id)), + onDestroy: () => dispatch(removePagination(ownProps.id)), + onPageClick: (pageNb: number) => dispatch(changePage(ownProps.id, pageNb)), +}); + +export interface INavigationPaginationProps + extends IPaginationPagesNumberOwnProps, + ReturnType, + ReturnType {} + +export const NUMBER_OF_PAGES_SHOWING: number = 7; +export const PREVIOUS_LABEL: string = 'Previous'; +export const NEXT_LABEL: string = 'Next'; + +class PaginationPagesNumberDisconnected extends React.Component { + private handlePageClick = (pageNb: number) => { + if (pageNb >= 0 && this.props.currentPage !== pageNb) { + this.props.onPageClick?.(pageNb); + } + }; + + componentDidUpdate() { + if (this.props.currentPage > this.props.totalPages - 1) { + this.handlePageClick(this.props.totalPages - 1); + } + } + + componentDidMount() { + this.props.onRender?.(); + } + + componentWillUnmount() { + this.props.onDestroy?.(); + } + + render() { + const currentPage: number = this.props.currentPage || 0; + + const showXPages: number = Math.abs((this.props.numberOfPagesToShow || NUMBER_OF_PAGES_SHOWING) - 1); + const previousLabel: string = this.props.previousLabel || PREVIOUS_LABEL; + const nextLabel: string = this.props.nextLabel || NEXT_LABEL; + let start: number = 0; + let end: number = showXPages; + const lastPage: number = this.props.totalPages - 1; + const previousClasses: string = classNames('flat-select-option mod-link', { + disabled: currentPage === 0 || this.props.disabled, + selectable: currentPage !== 0, + hidden: this.props.hidden, + }); + const nextClasses: string = classNames('flat-select-option mod-link', { + disabled: currentPage === lastPage || this.props.disabled, + selectable: currentPage !== lastPage, + hidden: this.props.hidden, + }); + + const pageSelects: JSX.Element[] = []; + + if (!this.props.hidden) { + if (currentPage + showXPages / 2 > lastPage) { + end = lastPage; + start = Math.max(lastPage - showXPages, 0); + } else { + start = Math.max(Math.floor(currentPage - showXPages / 2), 0); + end = Math.min(start + showXPages, lastPage); + } + + _.each(_.range(start, end + 1), (nbr: number): void => { + pageSelects.push( + + ); + }); + } + + return ( +
+ ); + } +} + +export const PaginationPagesNumber = connect(mapStateToProps, mapDispatchToProps)(PaginationPagesNumberDisconnected); diff --git a/packages/react-vapor/src/components/pagination/PaginationPerPage.tsx b/packages/react-vapor/src/components/pagination/PaginationPerPage.tsx new file mode 100644 index 0000000000..e4574925e3 --- /dev/null +++ b/packages/react-vapor/src/components/pagination/PaginationPerPage.tsx @@ -0,0 +1,40 @@ +import * as classNames from 'classnames'; +import * as React from 'react'; +import * as _ from 'underscore'; +import {UrlUtils} from '../../utils/UrlUtils'; +import {FlatSelectWithPrepend} from '../flatSelect/FlatSelectWithPrepend'; +import {Params} from '../table-hoc/TableWithUrlState'; +import {PaginationUtils} from './PaginationUtils'; +import {TablePaginationDefaultValue} from './TablePagination'; + +export interface IPaginationPerPageProps { + id: string; + perPage?: number[]; + defaultPerPageSelected?: number; + label?: React.ReactNode; + disabled?: boolean; + hidden?: boolean; +} + +export const PaginationPerPage = ({ + id, + perPage = TablePaginationDefaultValue.PerPage, + defaultPerPageSelected = UrlUtils.getSearchParams?.()?.[Params.pageSize], + label = TablePaginationDefaultValue.perPageLabel, + disabled, + hidden, +}: IPaginationPerPageProps) => ( + <> + ({ + id: nb?.toString(), + option: {content: nb}, + }))} + defaultSelectedOptionId={defaultPerPageSelected?.toString()} + prependClassName={classNames('items-per-page', {hidden: hidden})} + prepend={label} + disabled={disabled} + /> + +); diff --git a/packages/react-vapor/src/components/pagination/PaginationUtils.tsx b/packages/react-vapor/src/components/pagination/PaginationUtils.tsx new file mode 100644 index 0000000000..5be28473ea --- /dev/null +++ b/packages/react-vapor/src/components/pagination/PaginationUtils.tsx @@ -0,0 +1,7 @@ +const getPaginationPagesNumbersId = (id: string) => `${id}_PagesNumber`; +const getPaginationPerPageId = (id: string) => `${id}_PerPage`; + +export const PaginationUtils = { + getPaginationPagesNumbersId, + getPaginationPerPageId, +}; diff --git a/packages/react-vapor/src/components/pagination/TablePagination.tsx b/packages/react-vapor/src/components/pagination/TablePagination.tsx new file mode 100644 index 0000000000..c1ca25d196 --- /dev/null +++ b/packages/react-vapor/src/components/pagination/TablePagination.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import {TableHOCUtils} from '../table-hoc/utils/TableHOCUtils'; +import {PaginationPagesNumber} from './PaginationPagesNumber'; +import {PaginationPerPage} from './PaginationPerPage'; + +export interface ITablePaginationProps { + id: string; + totalPages: number; + perPageNumbers: number[]; + defaultPerPageSelected: number; + totalEntries: number; + disabled?: boolean; +} + +export const TablePagination = ({ + id, + disabled = false, + totalPages, + perPageNumbers, + defaultPerPageSelected, + totalEntries, +}: ITablePaginationProps) => ( +
+