Commit bcc1ae7f authored by Matija Obreza's avatar Matija Obreza
Browse files

TableInfiniteLoader: using loadMoreData={ loadMySubsets }

parent 8c434d65
......@@ -14,6 +14,23 @@ class Page<T> {
public totalElements: number;
public totalPages: number;
public static nextPage(paged?: Page<any>): IPageRequest {
return {
page: paged ? paged.number : 0,
size: paged ? paged.size : 50,
direction: paged ? paged.sort[0].direction : undefined,
properties: paged ? [ paged.sort[0].property ] : undefined,
};
}
public static fromQueryString(qs: any): IPageRequest {
return {
page: 0,
size: undefined,
direction: qs ? qs.d : undefined,
properties: qs ? [ qs.s ] : undefined,
};
}
}
export interface ISort {
......
......@@ -3,9 +3,10 @@
import { DASHBOARD_APPEND_SUBSETS, DASHBOARD_RECEIVE_SUBSET, DASHBOARD_RECEIVE_SUBSETS, DASHBOARD_REMOVE_SUBSET } from 'subsets/constants';
// Model
import FilteredPage, { IPageRequest } from 'model/FilteredPage';
import FilteredPage from 'model/FilteredPage';
import Subset from 'model/subset/Subset';
import SubsetService from 'service/genesys/SubsetService';
import Page from 'model/Page';
const receiveSubsets = (paged: FilteredPage<Subset>, error = null) => ({
......@@ -42,8 +43,8 @@ export const loadSubset = (uuid: string) => (dispatch) => {
});
};
export const loadMySubsets = (page: IPageRequest) => (dispatch, getState) => {
return SubsetService.mySubsets('', page)
export const loadMoreSubsets = (paged?: Page<Subset>) => (dispatch) => {
return SubsetService.mySubsets('', Page.nextPage(paged))
.then((paged) => {
if (paged.number === 0) {
dispatch(receiveSubsets(paged));
......
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {parse} from 'query-string';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { parse } from 'query-string';
// Actions
import {loadMySubsets} from 'subsets/actions/dashboard';
import {applyFilters} from 'subsets/actions/public';
import { loadMoreSubsets } from 'subsets/actions/dashboard';
import { applyFilters } from 'subsets/actions/public';
// Models
import Subset from 'model/subset/Subset';
import FilteredPage from 'model/FilteredPage';
// UI
import SubsetDashboardCard from './c/SubsetDashboardRow';
import MyDataTable from 'ui/common/tables/MyDataTable';
import Page from 'model/Page';
interface IDashboardPageProps extends React.ClassAttributes<any> {
paged: FilteredPage<Subset>;
pagination: any;
listMySubsetsPromise: any;
loadMySubsets: any;
loadMySubsets: (page?: Page<Subset>) => void;
history: any;
location: any;
login: any;
......@@ -43,53 +42,24 @@ class DashboardPage extends React.Component<IDashboardPageProps> {
protected static needs = [
({ search, params: { filterCode } }) => {
const qs = parse(search || '');
const page = { direction: qs.d, properties: null };
if (qs.s) {
page.properties = [ qs.s ];
}
return applyFilters(filterCode || '', page);
return applyFilters(filterCode || '', Page.fromQueryString(qs));
},
];
protected loadNextPage = (page: number, pageSize: number) => {
const {paged, loadMySubsets} = this.props;
loadMySubsets(null, {direction: paged.sort[0].direction || 'ASC', properties: [paged.sort[0].property], page, size: pageSize});
}
private onPaginationChange = (page: number, results: number, sortBy: string, dir?: string) => {
const {loadMySubsets, history, location} = this.props;
const params = new URLSearchParams(location.search);
if (sortBy) {
params.set('s', sortBy);
} else {
params.delete('s');
}
if (dir) {
params.set('d', dir);
} else {
params.delete('d');
}
location.search = params.toString();
history.push(location);
loadMySubsets({page: page || 0, size: results || 20, direction: dir || 'ASC', properties: [sortBy]});
}
constructor(props: IDashboardPageProps, context: any) {
super(props, context);
}
public componentWillMount() {
const {paged, pagination, loadMySubsets} = this.props;
const {paged, loadMySubsets} = this.props;
if (!paged) {
loadMySubsets({page: pagination.page, size: pagination.size, direction: pagination.dir, properties: [pagination.sort]});
loadMySubsets();
}
}
public render() {
const {paged, pagination, login: { authorities: userRoles }, loadMySubsets} = this.props;
const {paged, login: { authorities: userRoles }, loadMySubsets} = this.props;
const renderSubset = (s: Subset, index: number) => {
return <SubsetDashboardCard key={ s.uuid } subset={ s } index={ index } isAdmin={ userRoles.findIndex((role) => role === 'ROLE_ADMINISTRATOR') !== -1 }/>;
......@@ -97,9 +67,7 @@ class DashboardPage extends React.Component<IDashboardPageProps> {
return (
<MyDataTable
onPaginationChange={ this.onPaginationChange }
promiseLoadData={ loadMySubsets }
pagination={ pagination }
loadMoreData={ loadMySubsets }
paged={ paged }
renderTableRow={ renderSubset }
sortOptions={ sortOptions }
......@@ -112,17 +80,11 @@ class DashboardPage extends React.Component<IDashboardPageProps> {
const mapStateToProps = (state, ownProps) => ({
paged: state.subsets.dashboard.paged || undefined,
login: state.login,
pagination: {
page: +parse(ownProps.location.search).p || 0, // current page
size: +parse(ownProps.location.search).l || 20, // page size
sort: parse(ownProps.location.search).s || 'title', // page sorts
dir: parse(ownProps.location.search).d || 'ASC', // page sort directions
},
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
applyFilters,
loadMySubsets,
loadMoreSubsets,
}, dispatch);
......
......@@ -12,7 +12,7 @@ interface IProps<T> extends React.Props<any> {
roughItemHeight?: number; // px of height of element, defaults to 50px, determines when loadNextPage is called
itemRenderer: (item: T, index?: number) => any;
loadingIndicator?: any;
loadPage: (page: number, pageSize: number) => Promise<Page<T>>;
loadMore: (page: Page<T>) => void;
}
export default class TableInfiniteLoader<T> extends React.Component<IProps<T>, any> {
......@@ -22,15 +22,14 @@ export default class TableInfiniteLoader<T> extends React.Component<IProps<T>, a
}
private endOfListVisibilityChange = (isVisible: boolean): void => {
const { paged, loadPage } = this.props;
const { paged, loadMore } = this.props;
// log(`Visibility ${isVisible}`);
if (paged && isVisible) {
// we should load some stuff
if (paged && paged.content && paged.totalElements > paged.content.length) {
log('Calling for next page', paged.number + 1);
loadPage(paged.number + 1, paged.size);
loadMore(paged);
}
}
}
......
import * as React from 'react';
import { translate } from 'react-i18next';
import FastForward from '@material-ui/icons/FastForward';
import FastRewind from '@material-ui/icons/FastRewind';
import PlayArrow from '@material-ui/icons/PlayArrow';
import Button from '@material-ui/core/Button';
import Grid from '@material-ui/core/Grid';
import {StyleRules, withStyles} from '@material-ui/core/styles';
import Select from '@material-ui/core/Select';
import Input from '@material-ui/core/Input';
import MenuItem from '@material-ui/core/MenuItem';
import compose from 'recompose/compose';
import withWidth from '@material-ui/core/withWidth';
import Hidden from '@material-ui/core/Hidden';
import {Breakpoint} from '@material-ui/core/styles/createBreakpoints';
import Number from 'ui/common/Number';
import Page from 'model/Page';
interface IPaginationComponentProps extends React.ClassAttributes<any> {
classes: any;
t: any;
width: Breakpoint;
pageObj: Page<any>;
onChange: (page?: number, results?: number, sortBy?: string, dir?: string) => void;
displayName: string;
sortBy?: string;
sortOptions?: object;
infinite?: boolean;
}
const styles = (theme) => ({
root: {
backgroundColor: '#D4D2C6',
padding: '1px 20px 1px',
fontFamily: 'Roboto',
},
arrowRevert: {
webkitTransform: 'rotate(180deg)',
transform: 'rotate(180deg)',
},
checkedPage: {
width: '50px',
borderBottom: '1px solid black',
display: 'inline-block',
textAlign: 'center',
padding: '0 0 3px',
margin: '0 8px',
position: 'relative',
bottom: '2px',
},
/* tslint:disable */
paginationBut: {
backgroundColor: '#e8e6e0',
width: '30px',
height: '30px',
margin: '10px 5px',
minHeight: 'auto',
'& > span > svg': {
width: '17px',
height: '17px',
},
},
showResults: {
float: 'right',
marginTop: '12px',
marginLeft: '12px',
'& > div > div': {
marginRight: '35px',
[theme.breakpoints.down('sm')]: {
marginRight: '0px',
},
},
'& > div > svg': {
top: '1px',
width: '26px',
height: '26px',
color: '#050708',
[theme.breakpoints.down('sm')]: {
top: '0px',
},
},
'& > div > div:focus': {
backgroundColor: 'transparent',
},
[theme.breakpoints.down('sm')]: {
border: '1px #e8e6e0 solid',
borderRadius: '3px',
padding: '2px 0 0 7px',
margin: '9px 0 0 4px',
'&:before': {
content: 'none',
},
},
},
floatRight: {
float: 'right',
'&:after': {
content: 'none',
},
},
liItem: {
height: '34px',
fontSize: '14px',
'&:focus': {
backgroundColor: '#E8E5E0',
},
'&:hover': {
backgroundColor: '#E8E5E0',
},
},
/* tslint:enable */
textPagination: {
lineHeight: '50px',
},
bold: {
fontWeight: 'bold',
},
floatLeft: {
float: 'left',
paddingRight: '5px',
},
verticalAlign: {
verticalAlign: 'text-bottom',
paddingLeft: '5px',
[theme.breakpoints.down('sm')]: {
padding: '0 5px 0px 2px',
},
},
inline: {
display: 'inline',
},
}) as StyleRules;
const results = [5, 10, 20, 40, 50, 75, 100];
const mobile = ['sm', 'xs'] as Breakpoint[];
class PaginationComponent extends React.Component<IPaginationComponentProps, any> {
public render() {
const { t, classes, displayName, width, pageObj, onChange, sortOptions, infinite } = this.props;
let { sortBy } = this.props;
if (pageObj && pageObj.sort && pageObj && pageObj.sort[0] !== null) {
sortBy = pageObj && pageObj.sort ? pageObj.sort[0].property : null;
if (sortBy && sortOptions) {
// console.log(`Using auto-detected sortBy=${sortBy}`);
sortBy = Object.keys(sortOptions).find((key) => {
const sortOption = sortOptions[key];
// console.log(`Inspecting for key=${key} prop=${pageObj.sort[0].property}`, sortOption);
if (sortOption.property && pageObj.sort[0].property === sortOption.property) {
if (sortOption.direction && pageObj.sort[0].direction !== sortOption.direction) {
// console.log('Direction doesnt match');
return false;
}
// console.log('Matching sort');
return true;
} else if (key === pageObj.sort[0].property) {
if (sortOption.direction && pageObj.sort[0].direction !== sortOption.direction) {
// console.log('Direction doesnt match');
return false;
}
return true;
}
return false;
}) || '';
// console.log(`Sort by is ${sortBy} was ${this.props.sortBy}`);
}
} else {
sortBy = '';
}
const isMobile = mobile.indexOf(width) !== -1;
const fireSortChange = (page, results, sortBy) => {
console.log('Sort change', sortBy, sortOptions[sortBy]);
if (sortBy && sortOptions && sortOptions[sortBy] && typeof sortOptions[sortBy] !== 'string') {
onChange(page, results, sortOptions[sortBy].property || sortBy, sortOptions[sortBy].direction);
} else {
onChange(page, results, sortBy);
}
};
return (
<Grid container spacing={ 0 } className={ classes.root }>
<Grid item xs={ 12 }>
<div className={ `float-left pr-5 ${classes.bold} ${classes.textPagination}` }>
{ t(pageObj && pageObj.totalElements < 10000 ? 'common:paginate.numberOfItems' : 'common:paginate.estimatedNumberOfItems',
{ count: pageObj ? pageObj.totalElements : '', what: t(displayName || 'common:label.item', { count: pageObj ? pageObj.totalElements : 0 }) }) }
</div>
{ ! infinite &&
<Select
value={ pageObj ? pageObj.size : 10 }
onChange={
(e: any) => { // tslint:disable-line
fireSortChange(0, e.target.value, sortBy);
}
}
className={ `float-left pr-5 ${classes.showResults} ${classes.bold}` }
input={ <Input id="results-count"/> }
MenuProps={ {
PaperProps: {
style: {
width: 188,
},
},
} }
>
{
results.map((e, i) => (
<MenuItem key={ i } value={ e } className={ classes.liItem }>
{ isMobile ? e : `Show ${e} results` }
</MenuItem>
))
}
</Select>
}
{
sortOptions && (
<Select
value={ sortBy }
displayEmpty
onChange={
(e: any) => { // tslint:disable-line
fireSortChange(0, pageObj ? pageObj.size : 10, e.target.value);
}
}
className={ `float-right pl-5 ${classes.showResults} ${classes.bold}` }
input={ <Input id="sort-by"/> }
MenuProps={ {
PaperProps: {
style: {
width: 250,
},
},
} }
>
<MenuItem value="" className={ classes.liItem }>{ t('common:label.sortBy') }</MenuItem>
{
Object.keys(sortOptions).map((key, i) => {
if (typeof sortOptions[key] === 'string') {
return (
<MenuItem key={ i } value={ key } className={ classes.liItem }>
{ t(sortOptions[key]) }
</MenuItem>
);
} else {
return (
<MenuItem key={ i } value={ key } className={ classes.liItem }>
{ t(sortOptions[key].label) }
</MenuItem>
);
}
})
}
</Select>
)
}
{ ! infinite &&
<div className={ isMobile ? classes.floatRight : classes.floatLeft }>
<Hidden implementation="css" mdUp className={ isMobile ? classes.inline : '' }>
<span className={ classes.verticalAlign }>
<span className={ classes.bold }>{ pageObj ? pageObj.number + 1 : 0 }</span>/{ pageObj ? pageObj.totalPages : 0 }
</span>
</Hidden>
<Hidden implementation="css" only={ mobile } className={ classes.inline }>
<Button
variant="fab"
color="inherit"
disabled={ ! pageObj || pageObj.first }
className={ classes.paginationBut }
onClick={ fireSortChange.bind(this, 0, pageObj ? pageObj.size : 10, sortBy) }
>
<FastRewind/>
</Button>
</Hidden>
<Button
variant="fab"
color="inherit"
disabled={ ! pageObj || pageObj.first }
className={ classes.paginationBut }
onClick={ fireSortChange.bind(this, pageObj ? pageObj.number - 1 : 0, pageObj ? pageObj.size : 10, sortBy) }
>
<PlayArrow className={ classes.arrowRevert }/>
</Button>
<Hidden implementation="js" only={ mobile } className={ classes.inline }>
<span className={ `${classes.checkedPage} ${classes.bold}` }>
{ pageObj ? pageObj.number + 1 : 0 }
</span>
</Hidden>
<Button
variant="fab"
color="inherit"
disabled={ ! pageObj || pageObj.last }
className={ classes.paginationBut }
onClick={ fireSortChange.bind(this, pageObj ? pageObj.number + 1 : 1, pageObj ? pageObj.size : 10, sortBy) }
>
<PlayArrow/>
</Button>
<Hidden implementation="css" only={ mobile } className={ classes.inline }>
<Button
variant="fab"
color="inherit"
disabled={ ! pageObj || pageObj.last }
className={ classes.paginationBut }
onClick={ fireSortChange.bind(this, pageObj ? pageObj.totalPages - 1 : 0, pageObj ? pageObj.size : 10, sortBy) }
>
<FastForward/>
</Button>
</Hidden>
<Hidden implementation="css" only={ mobile } className={ classes.inline }>
<span className={ classes.verticalAlign }>of <Number value={ pageObj ? pageObj.totalPages : 0 } /> pages</span>
</Hidden>
</div>
}
</Grid>
</Grid>
);
}
}
export default translate()(compose(withStyles(styles), withWidth())(PaginationComponent));
......@@ -4,7 +4,7 @@ import { translate } from 'react-i18next';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import {Table, TableRow, TableCell} from 'ui/common/tables';
import { Table, TableRow, TableCell } from 'ui/common/tables';
import PaginationComponent from 'ui/common/pagination';
import { ScrollToTopOnMount } from 'ui/common/page/scrollers';
import TableInfiniteLoader from 'ui/common/TableInfiniteLoader';
......@@ -15,9 +15,8 @@ import Page from 'model/Page';
interface IMyDataTableProps extends React.Props<any> {
classes?: any;
paged: Page<any>;
onPaginationChange: (page: number, results: number, sortBy: string, dir?: string) => void;
pagination: any;
promiseLoadData: any;
onSortChange: (sortBy: string, dir?: string) => void;
loadMoreData: (page: Page<any>) => void;
renderTableRow: (row, index) => any;
sortOptions?: any;
headerProps?: Array<{ title: any, width: string }>;
......@@ -31,29 +30,28 @@ const defaultSortOptions = {
};
const defaultHeaderProps = [
{title: 'No.', width: '10px'},
{title: 'common:label.title', width: '40%'},
{title: 'common:label.owner', width: null},
{title: 'common:label.created', width: null},
{title: 'common:label.modified', width: null},
{title: 'common:label.status', width: '90px'},
{ title: 'No.', width: '10px' },
{ title: 'common:label.title', width: '40%' },
{ title: 'common:label.owner', width: null },
{ title: 'common:label.created', width: null },
{ title: 'common:label.modified', width: null },
{ title: 'common:label.status', width: '90px' },
];
function MyDataTable({
classes,
paged,
onPaginationChange,
pagination,
promiseLoadData,
onSortChange,
loadMoreData,
renderTableRow,
sortOptions = defaultSortOptions,
headerProps = defaultHeaderProps,
t,
}: IMyDataTableProps) {
const loadNextPage = (page: number, pageSize: number) => {
return promiseLoadData(page, pageSize, pagination.sort, pagination.filter, pagination.dir);
const loadMore = (page: Page<any>) => {
return loadMoreData(page);
};
return (
<div>
......@@ -61,7 +59,7 @@ function MyDataTable({
<Grid container spacing={ 0 }>
{ !paged ? <Loading/> :
<Grid item xs={ 12 } className="back-gray">
<PaginationComponent displayName="records" pageObj={ paged } onChange={ onPaginationChange } sortOptions={ sortOptions } infinite/>
<PaginationComponent displayName="records" pageObj={ paged } onSortChange={ onSortChange } sortOptions={ sortOptions } infinite/>