From f10ea12a372e06d80d136e42ee4630aa0a717076 Mon Sep 17 00:00:00 2001 From: Yilmaz Kurnazcan Date: Fri, 23 Dec 2022 09:37:39 +0100 Subject: [PATCH 1/2] refactor(entity-list): add num of rows preference feature Refs: TOCDEV-5494 Changelog: add feature to save number of rows as user preference in entity list --- .../modal/ModalDisplay/StyledComponents.js | 5 +-- .../components/Table/NavigationCellHeader.js | 45 +++++++++---------- .../Table/NavigationCellHeaderContainer.js | 28 ++++++++++++ .../src/components/Table/SelectRowNums.js | 44 ++++++++++++++++++ .../src/components/Table/StyledComponents.js | 21 ++++++++- .../src/components/Table/navigationCell.js | 2 +- .../src/containers/TableContainer.js | 2 +- .../entity-list/src/modules/list/sagas.js | 8 ++-- .../src/modules/preferences/actions.js | 11 +++++ .../src/modules/preferences/reducer.js | 12 ++++- .../src/modules/preferences/sagas.js | 41 ++++++++++++++++- 11 files changed, 182 insertions(+), 37 deletions(-) create mode 100644 packages/core/entity-list/src/components/Table/NavigationCellHeaderContainer.js create mode 100644 packages/core/entity-list/src/components/Table/SelectRowNums.js diff --git a/packages/core/app-extensions/src/notification/modules/modal/ModalDisplay/StyledComponents.js b/packages/core/app-extensions/src/notification/modules/modal/ModalDisplay/StyledComponents.js index e7c1c50fe4..2b04df5833 100644 --- a/packages/core/app-extensions/src/notification/modules/modal/ModalDisplay/StyledComponents.js +++ b/packages/core/app-extensions/src/notification/modules/modal/ModalDisplay/StyledComponents.js @@ -1,11 +1,11 @@ import styled, {createGlobalStyle} from 'styled-components' -import {Button, scale, StyledScrollbar, StyledTether, theme} from 'tocco-ui' +import {Button, scale, StyledScrollbar, StyledTether, themeSelector} from 'tocco-ui' export const basePadding = scale.space(0.5) export const StyledModalContent = styled.div` position: relative; - background-color: ${theme.color('paper')}; + background-color: ${themeSelector.color('paper')}; padding: ${basePadding}; display: grid; grid-template-rows: [header] auto [body] 1fr; @@ -77,7 +77,6 @@ export const StyledTitleWrapper = styled.div` export const StyledModalBody = styled.div` grid-row-start: body; overflow: hidden auto; - padding-right: ${scale.space(0)}; ${StyledScrollbar} ` diff --git a/packages/core/entity-list/src/components/Table/NavigationCellHeader.js b/packages/core/entity-list/src/components/Table/NavigationCellHeader.js index 5a66a0288f..6295fb5f30 100644 --- a/packages/core/entity-list/src/components/Table/NavigationCellHeader.js +++ b/packages/core/entity-list/src/components/Table/NavigationCellHeader.js @@ -1,25 +1,33 @@ import PropTypes from 'prop-types' import {FormattedMessage} from 'react-intl' -import {connect} from 'react-redux' import {BallMenu, MenuItem} from 'tocco-ui' -import {displayColumnModal, resetSorting, resetPreferences, resetColumns} from '../../modules/preferences/actions' - -const NavigationCellHeader = props => - !props.disablePreferencesMenu ? ( +const NavigationCellHeader = ({ + disablePreferencesMenu, + displayColumnModal, + resetColumns, + sortable, + resetSorting, + resetPreferences, + displayTableRowsModal +}) => + !disablePreferencesMenu ? ( - + - + + + + - {props.sortable && ( - + {sortable && ( + )} - + @@ -31,19 +39,8 @@ NavigationCellHeader.propTypes = { resetPreferences: PropTypes.func.isRequired, resetColumns: PropTypes.func.isRequired, sortable: PropTypes.bool, - disablePreferencesMenu: PropTypes.bool + disablePreferencesMenu: PropTypes.bool, + displayTableRowsModal: PropTypes.func.isRequired } -const mapActionCreators = { - displayColumnModal, - resetSorting, - resetPreferences, - resetColumns -} - -const mapStateToProps = (state, props) => ({ - sortable: state.input.sortable, - disablePreferencesMenu: state.list.disablePreferencesMenu -}) - -export default connect(mapStateToProps, mapActionCreators)(NavigationCellHeader) +export default NavigationCellHeader diff --git a/packages/core/entity-list/src/components/Table/NavigationCellHeaderContainer.js b/packages/core/entity-list/src/components/Table/NavigationCellHeaderContainer.js new file mode 100644 index 0000000000..0f62d4144c --- /dev/null +++ b/packages/core/entity-list/src/components/Table/NavigationCellHeaderContainer.js @@ -0,0 +1,28 @@ +import {connect} from 'react-redux' + +import { + displayColumnModal, + resetSorting, + resetPreferences, + resetColumns, + displayTableRowsModal +} from '../../modules/preferences/actions' +import NavigationCellHeader from './NavigationCellHeader' + +const mapActionCreators = { + displayColumnModal, + resetSorting, + resetPreferences, + resetColumns, + displayTableRowsModal +} + +const mapStateToProps = (state, props) => { + return { + sortable: state.list.sortable, + disablePreferencesMenu: state.list.disablePreferencesMenu, + numOfRows: state.preferences.numOfRows + } +} + +export default connect(mapStateToProps, mapActionCreators)(NavigationCellHeader) diff --git a/packages/core/entity-list/src/components/Table/SelectRowNums.js b/packages/core/entity-list/src/components/Table/SelectRowNums.js new file mode 100644 index 0000000000..89e0fa6ca1 --- /dev/null +++ b/packages/core/entity-list/src/components/Table/SelectRowNums.js @@ -0,0 +1,44 @@ +import PropTypes from 'prop-types' +import {useState} from 'react' +import {EditableValue, Button} from 'tocco-ui' + +import {StyledButtonWrapper, StyledEditableValueWrapper} from './StyledComponents' + +const SelectNumRows = ({onOk, numOfRows}) => { + const options = [ + {key: 1, display: '25'}, + {key: 2, display: '50'}, + {key: 3, display: '100'} + ] + const matchingValue = options.find(option => Number(option.display) === numOfRows) + const [value, setValue] = useState(matchingValue) + + return ( + <> + + + + + + + + ) +} + +SelectNumRows.propTypes = { + onOk: PropTypes.func.isRequired, + numOfRows: PropTypes.number +} + +export default SelectNumRows diff --git a/packages/core/entity-list/src/components/Table/StyledComponents.js b/packages/core/entity-list/src/components/Table/StyledComponents.js index bd5e9d8247..4c5e4e7538 100644 --- a/packages/core/entity-list/src/components/Table/StyledComponents.js +++ b/packages/core/entity-list/src/components/Table/StyledComponents.js @@ -1,5 +1,5 @@ import styled from 'styled-components' -import {themeSelector, declareFont} from 'tocco-ui' +import {themeSelector, declareFont, scale, StyledButton, colorizeBorder} from 'tocco-ui' export const StyledMarkingWrapper = styled.span` ${declareFont()} @@ -13,3 +13,22 @@ export const StyledMarkingWrapper = styled.span` } ${({marked, theme}) => marked && `color: ${theme.colors.secondary};`} ` + +export const StyledButtonWrapper = styled.div` + position: sticky; + bottom: 0; + padding-top: ${scale.space(0)}; + background-color: ${themeSelector.color('paper')}; + display: flex; + justify-content: flex-end; + + ${StyledButton} { + margin-right: 0; + } +` + +export const StyledEditableValueWrapper = styled.div` + border: 1px solid ${colorizeBorder.shade1}; + padding-right: ${scale.space(-1)}; + padding-left: ${scale.space(-1)}; +` diff --git a/packages/core/entity-list/src/components/Table/navigationCell.js b/packages/core/entity-list/src/components/Table/navigationCell.js index d16ff02f66..0f383d912a 100644 --- a/packages/core/entity-list/src/components/Table/navigationCell.js +++ b/packages/core/entity-list/src/components/Table/navigationCell.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import {Icon} from 'tocco-ui' -import NavigationCellHeader from './NavigationCellHeader' +import NavigationCellHeader from './NavigationCellHeaderContainer' const CellRenderer = ({showNavigation, rowData, navigationStrategy, parent}) => showNavigation && navigationStrategy.DetailLinkRelative ? ( diff --git a/packages/core/entity-list/src/containers/TableContainer.js b/packages/core/entity-list/src/containers/TableContainer.js index 397f89d17b..1a47700e67 100644 --- a/packages/core/entity-list/src/containers/TableContainer.js +++ b/packages/core/entity-list/src/containers/TableContainer.js @@ -25,7 +25,7 @@ const mapStateToProps = (state, props) => ({ currentPage: state.list.currentPage, entities: state.list.entities, entityCount: state.list.entityCount, - limit: state.input.limit, + limit: state.preferences.numOfRows || state.input.limit, inProgress: state.list.inProgress, tableSelectionStyle: getTableSelectionStyle(state.input.selectionStyle, getSelectable(getFormDefinition(state))), clickable: getClickable(getFormDefinition(state)), diff --git a/packages/core/entity-list/src/modules/list/sagas.js b/packages/core/entity-list/src/modules/list/sagas.js index 5124564f55..606ff5190b 100644 --- a/packages/core/entity-list/src/modules/list/sagas.js +++ b/packages/core/entity-list/src/modules/list/sagas.js @@ -288,7 +288,7 @@ export function* setLazyDataMarked(entityName, markings) { export function* fetchEntitiesAndAddToStore(page) { const state = yield select(stateSelector) const {entityName, scope, limit} = state.input - const {columns: columnPreferences} = state.preferences + const {columns: columnPreferences, numOfRows} = state.preferences const {entityStore, sorting} = state.list if (!entityStore[page]) { const basicQuery = yield call(getBasicQuery) @@ -299,7 +299,7 @@ export function* fetchEntitiesAndAddToStore(page) { ...basicQuery, page, sorting, - limit, + limit: numOfRows || limit, paths } @@ -340,6 +340,8 @@ export function* delayedPreloadNextPage(page) { export function* preloadNextPage(currentPage) { const {limit} = yield select(inputSelector) + const {numOfRows} = yield select(preferencesSelector) + const actualLimit = numOfRows || limit const list = yield select(listSelector) const {entityStore} = list let {entityCount} = list @@ -350,7 +352,7 @@ export function* preloadNextPage(currentPage) { entityCount = setCountAction.payload.entityCount } - if (currentPage * limit < entityCount && !entityStore[nextPage]) { + if (currentPage * actualLimit < entityCount && !entityStore[nextPage]) { yield call(fetchEntitiesAndAddToStore, nextPage) } } diff --git a/packages/core/entity-list/src/modules/preferences/actions.js b/packages/core/entity-list/src/modules/preferences/actions.js index d788a379c7..e784bf4086 100644 --- a/packages/core/entity-list/src/modules/preferences/actions.js +++ b/packages/core/entity-list/src/modules/preferences/actions.js @@ -9,6 +9,8 @@ export const RESET_COLUMNS = 'preferences/RESET_COLUMNS' export const RESET_PREFERENCES = 'preferences/RESET_PREFERENCES' export const DISPLAY_COLUMN_MODAL = 'preferences/DISPLAY_COLUMN_MODAL' export const SET_PREFERENCES_LOADED = 'preferences/SET_PREFERENCES_LOADED' +export const SET_NUMBER_OF_TABLE_ROWS = 'preferences/SET_NUMBER_OF_TABLE_ROWS' +export const DISPLAY_TABLE_ROWS_MODAL = 'preferences/DISPLAY_TABLE_ROWS_MODAL' export const loadPreferences = () => ({ type: LOAD_PREFERENCES @@ -78,3 +80,12 @@ export const resetColumns = () => ({ type: RESET_COLUMNS, payload: {} }) + +export const displayTableRowsModal = () => ({ + type: DISPLAY_TABLE_ROWS_MODAL +}) + +export const setNumberOfTableRows = numOfRows => ({ + type: SET_NUMBER_OF_TABLE_ROWS, + payload: {numOfRows} +}) diff --git a/packages/core/entity-list/src/modules/preferences/reducer.js b/packages/core/entity-list/src/modules/preferences/reducer.js index f06f97de5b..5ed284890c 100644 --- a/packages/core/entity-list/src/modules/preferences/reducer.js +++ b/packages/core/entity-list/src/modules/preferences/reducer.js @@ -25,7 +25,13 @@ const resetPreferences = state => ({ ...state, positions: {}, sorting: [], - columns: {} + columns: {}, + numOfRows: undefined +}) + +const setNumberOfTableRows = (state, {payload: {numOfRows}}) => ({ + ...state, + numOfRows }) const ACTION_HANDLERS = { @@ -36,7 +42,8 @@ const ACTION_HANDLERS = { [actions.SET_PREFERENCES_LOADED]: reducerUtil.singleTransferReducer('preferencesLoaded'), [actions.RESET_SORTING]: resetSorting, [actions.RESET_COLUMNS]: resetColumns, - [actions.RESET_PREFERENCES]: resetPreferences + [actions.RESET_PREFERENCES]: resetPreferences, + [actions.SET_NUMBER_OF_TABLE_ROWS]: setNumberOfTableRows } const initialState = { @@ -44,6 +51,7 @@ const initialState = { sorting: [], columns: {}, widths: {}, + numOfRows: undefined, preferencesLoaded: false } diff --git a/packages/core/entity-list/src/modules/preferences/sagas.js b/packages/core/entity-list/src/modules/preferences/sagas.js index 92806afdcf..e991504de1 100644 --- a/packages/core/entity-list/src/modules/preferences/sagas.js +++ b/packages/core/entity-list/src/modules/preferences/sagas.js @@ -3,11 +3,12 @@ import {all, call, put, select, take, takeLatest} from 'redux-saga/effects' import {rest, notification} from 'tocco-app-extensions' import ColumnModal from '../../components/ColumnModal' +import SelectNumRows from '../../components/Table/SelectRowNums' import {getTableColumns} from '../../util/api/forms' import * as util from '../../util/preferences' import * as listActions from '../list/actions' import * as listSagas from '../list/sagas' -import {setPositions, setSorting, setColumns, setPreferencesLoaded} from './actions' +import {setPositions, setSorting, setColumns, setPreferencesLoaded, setNumberOfTableRows} from './actions' import * as actions from './actions' export const inputSelector = state => state.input @@ -22,7 +23,8 @@ export default function* sagas() { takeLatest(actions.RESET_SORTING, resetSorting), takeLatest(actions.RESET_COLUMNS, resetColumns), takeLatest(actions.RESET_PREFERENCES, resetPreferences), - takeLatest(actions.DISPLAY_COLUMN_MODAL, displayColumnModal) + takeLatest(actions.DISPLAY_COLUMN_MODAL, displayColumnModal), + takeLatest(actions.DISPLAY_TABLE_ROWS_MODAL, displayTableRowsModal) ]) } @@ -33,6 +35,7 @@ export function* loadPreferences() { yield put(setPositions(util.getPositions(preferences))) yield put(setSorting(util.getSorting(preferences))) yield put(setColumns(util.getColumns(preferences))) + yield put(actions.setNumberOfTableRows(Number(preferences[`${formName}.numOfRows`]))) yield put(setPreferencesLoaded(true)) } @@ -76,6 +79,15 @@ export function* saveSorting() { } } +export function* saveNumberOfTableRows(answerChannel) { + const {numOfRows} = yield take(answerChannel) + const inputState = yield select(inputSelector) + + yield put(setNumberOfTableRows(Number(numOfRows))) + yield call(listSagas.reloadData) + yield call(rest.savePreferences, {[`${inputState.formName}_${inputState.scope}.numOfRows`]: Number(numOfRows)}) +} + export function* resetSorting() { const inputState = yield select(inputSelector) yield all([ @@ -120,6 +132,31 @@ export function* displayColumnModal() { yield saveColumnPreferences(answerChannel, preferencesColumns, formDefinition) } +export function* displayTableRowsModal() { + const {formDefinition} = yield select(listSagas.listSelector) + const answerChannel = yield call(channel) + const {numOfRows: preferencesNumOfRows} = yield select(preferencesSelector) + + yield put( + notification.modal( + `${formDefinition.id}-numOfRows-setting`, + 'client.entity-list.preferences.numOfRows', + null, + ({close}) => { + const onOk = numOfRows => { + close() + answerChannel.put({numOfRows}) + } + + return + }, + true + ) + ) + + yield call(saveNumberOfTableRows, answerChannel) +} + function* saveColumnPreferences(answerChannel, preferencesColumns, formDefinition) { const columns = (yield take(answerChannel)).reduce( (accumulator, item) => ({ From 6e4bdce4730499fc77465832a3011c2c9b45975f Mon Sep 17 00:00:00 2001 From: Yilmaz Kurnazcan Date: Fri, 23 Dec 2022 12:47:06 +0100 Subject: [PATCH 2/2] chore: fix review issues Refs: TOCDEV-5494 --- .../Table/NavigationCellHeaderContainer.js | 3 +-- .../src/components/Table/SelectRowNums.js | 21 +++++++++++-------- .../src/containers/TableContainer.js | 3 ++- .../entity-list/src/modules/list/sagas.js | 12 +++++------ .../src/modules/preferences/sagas.js | 7 ++++--- .../core/entity-list/src/util/preferences.js | 2 ++ 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/packages/core/entity-list/src/components/Table/NavigationCellHeaderContainer.js b/packages/core/entity-list/src/components/Table/NavigationCellHeaderContainer.js index 0f62d4144c..575cef5884 100644 --- a/packages/core/entity-list/src/components/Table/NavigationCellHeaderContainer.js +++ b/packages/core/entity-list/src/components/Table/NavigationCellHeaderContainer.js @@ -20,8 +20,7 @@ const mapActionCreators = { const mapStateToProps = (state, props) => { return { sortable: state.list.sortable, - disablePreferencesMenu: state.list.disablePreferencesMenu, - numOfRows: state.preferences.numOfRows + disablePreferencesMenu: state.list.disablePreferencesMenu } } diff --git a/packages/core/entity-list/src/components/Table/SelectRowNums.js b/packages/core/entity-list/src/components/Table/SelectRowNums.js index 89e0fa6ca1..68ea5b8994 100644 --- a/packages/core/entity-list/src/components/Table/SelectRowNums.js +++ b/packages/core/entity-list/src/components/Table/SelectRowNums.js @@ -1,16 +1,18 @@ import PropTypes from 'prop-types' import {useState} from 'react' +import {injectIntl, FormattedMessage} from 'react-intl' import {EditableValue, Button} from 'tocco-ui' import {StyledButtonWrapper, StyledEditableValueWrapper} from './StyledComponents' -const SelectNumRows = ({onOk, numOfRows}) => { +const SelectNumRows = injectIntl(({intl, onOk, numOfRows}) => { + const msg = id => intl.formatMessage({id}) const options = [ - {key: 1, display: '25'}, - {key: 2, display: '50'}, - {key: 3, display: '100'} + {key: 25, display: '25'}, + {key: 50, display: '50'}, + {key: 100, display: '100'} ] - const matchingValue = options.find(option => Number(option.display) === numOfRows) + const matchingValue = options.find(option => option.key === numOfRows) const [value, setValue] = useState(matchingValue) return ( @@ -22,21 +24,22 @@ const SelectNumRows = ({onOk, numOfRows}) => { events={{onChange: setValue}} options={{ options, - noResultsText: 'no results found', + noResultsText: msg('client.entity-list.preferences.numOfRows.noResultsText'), isLoading: false }} /> - ) -} +}) SelectNumRows.propTypes = { + intl: PropTypes.object.isRequired, onOk: PropTypes.func.isRequired, numOfRows: PropTypes.number } diff --git a/packages/core/entity-list/src/containers/TableContainer.js b/packages/core/entity-list/src/containers/TableContainer.js index 1a47700e67..0bbf659938 100644 --- a/packages/core/entity-list/src/containers/TableContainer.js +++ b/packages/core/entity-list/src/containers/TableContainer.js @@ -6,6 +6,7 @@ import {changePage, refresh, initialize, onRowClick, setSortingInteractive} from import {changePosition, resetSorting, changeWidth} from '../modules/preferences/actions' import {onSelectChange, setSelection} from '../modules/selection/actions' import {getFormDefinition, getClickable, getDisablePreferencesMenu, getSelectable} from '../util/api/forms' +import {getActualLimit} from '../util/preferences' import {getTableSelectionStyle} from '../util/selection' const mapActionCreators = { @@ -25,7 +26,7 @@ const mapStateToProps = (state, props) => ({ currentPage: state.list.currentPage, entities: state.list.entities, entityCount: state.list.entityCount, - limit: state.preferences.numOfRows || state.input.limit, + limit: getActualLimit(state), inProgress: state.list.inProgress, tableSelectionStyle: getTableSelectionStyle(state.input.selectionStyle, getSelectable(getFormDefinition(state))), clickable: getClickable(getFormDefinition(state)), diff --git a/packages/core/entity-list/src/modules/list/sagas.js b/packages/core/entity-list/src/modules/list/sagas.js index 606ff5190b..e91cb174b9 100644 --- a/packages/core/entity-list/src/modules/list/sagas.js +++ b/packages/core/entity-list/src/modules/list/sagas.js @@ -16,6 +16,7 @@ import { getSorting, splitFormId } from '../../util/api/forms' +import {getActualLimit} from '../../util/preferences' import * as preferencesActions from '../preferences/actions' import * as searchFormActions from '../searchForm/actions' import {getSearchFormValues} from '../searchForm/sagas' @@ -287,8 +288,8 @@ export function* setLazyDataMarked(entityName, markings) { export function* fetchEntitiesAndAddToStore(page) { const state = yield select(stateSelector) - const {entityName, scope, limit} = state.input - const {columns: columnPreferences, numOfRows} = state.preferences + const {entityName, scope} = state.input + const {columns: columnPreferences} = state.preferences const {entityStore, sorting} = state.list if (!entityStore[page]) { const basicQuery = yield call(getBasicQuery) @@ -299,7 +300,7 @@ export function* fetchEntitiesAndAddToStore(page) { ...basicQuery, page, sorting, - limit: numOfRows || limit, + limit: getActualLimit(state), paths } @@ -339,9 +340,8 @@ export function* delayedPreloadNextPage(page) { } export function* preloadNextPage(currentPage) { - const {limit} = yield select(inputSelector) - const {numOfRows} = yield select(preferencesSelector) - const actualLimit = numOfRows || limit + const state = yield select(stateSelector) + const actualLimit = getActualLimit(state) const list = yield select(listSelector) const {entityStore} = list let {entityCount} = list diff --git a/packages/core/entity-list/src/modules/preferences/sagas.js b/packages/core/entity-list/src/modules/preferences/sagas.js index e991504de1..d610348935 100644 --- a/packages/core/entity-list/src/modules/preferences/sagas.js +++ b/packages/core/entity-list/src/modules/preferences/sagas.js @@ -35,7 +35,7 @@ export function* loadPreferences() { yield put(setPositions(util.getPositions(preferences))) yield put(setSorting(util.getSorting(preferences))) yield put(setColumns(util.getColumns(preferences))) - yield put(actions.setNumberOfTableRows(Number(preferences[`${formName}.numOfRows`]))) + yield put(setNumberOfTableRows(Number(preferences[`${formName}.numOfRows`]))) yield put(setPreferencesLoaded(true)) } @@ -82,10 +82,11 @@ export function* saveSorting() { export function* saveNumberOfTableRows(answerChannel) { const {numOfRows} = yield take(answerChannel) const inputState = yield select(inputSelector) + const formName = `${inputState.formName}_${inputState.scope}` - yield put(setNumberOfTableRows(Number(numOfRows))) + yield put(setNumberOfTableRows(numOfRows)) yield call(listSagas.reloadData) - yield call(rest.savePreferences, {[`${inputState.formName}_${inputState.scope}.numOfRows`]: Number(numOfRows)}) + yield call(rest.savePreferences, {[`${formName}.numOfRows`]: numOfRows}) } export function* resetSorting() { diff --git a/packages/core/entity-list/src/util/preferences.js b/packages/core/entity-list/src/util/preferences.js index 6e46ee08af..50826d2995 100644 --- a/packages/core/entity-list/src/util/preferences.js +++ b/packages/core/entity-list/src/util/preferences.js @@ -96,3 +96,5 @@ export const getColumnPreferencesToSave = (formName, columns) => }), {} ) + +export const getActualLimit = state => state.preferences.numOfRows || state.input.limit