paginates transactions

This commit is contained in:
jessop 2019-09-22 22:47:07 -04:00 committed by Sean Yesmunt
parent 534d84ef88
commit 5078bd6699
8 changed files with 217 additions and 140 deletions

View file

@ -17,6 +17,7 @@ type Props = {
defaultPath?: string, defaultPath?: string,
filters: Array<string>, filters: Array<string>,
onFileCreated?: string => void, onFileCreated?: string => void,
disabled: boolean,
}; };
class FileExporter extends React.PureComponent<Props> { class FileExporter extends React.PureComponent<Props> {
@ -78,9 +79,15 @@ class FileExporter extends React.PureComponent<Props> {
} }
render() { render() {
const { label } = this.props; const { label, disabled } = this.props;
return ( return (
<Button button="primary" icon={ICONS.DOWNLOAD} label={label || __('Export')} onClick={this.handleButtonClick} /> <Button
button="primary"
disabled={disabled}
icon={ICONS.DOWNLOAD}
label={label || __('Export')}
onClick={this.handleButtonClick}
/>
); );
} }
} }

View file

@ -8,6 +8,7 @@ import {
doSetTransactionListFilter, doSetTransactionListFilter,
selectIsFetchingTransactions, selectIsFetchingTransactions,
} from 'lbry-redux'; } from 'lbry-redux';
import { withRouter } from 'react-router';
import TransactionList from './view'; import TransactionList from './view';
const select = state => ({ const select = state => ({
@ -23,7 +24,9 @@ const perform = dispatch => ({
setTransactionFilter: filterSetting => dispatch(doSetTransactionListFilter(filterSetting)), setTransactionFilter: filterSetting => dispatch(doSetTransactionListFilter(filterSetting)),
}); });
export default connect( export default withRouter(
connect(
select, select,
perform perform
)(TransactionList); )(TransactionList)
);

View file

@ -1,71 +1,45 @@
// @flow // @flow
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
import * as MODALS from 'constants/modal_types'; import React from 'react';
import * as React from 'react';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import FileExporter from 'component/common/file-exporter'; import FileExporter from 'component/common/file-exporter';
import { TRANSACTIONS } from 'lbry-redux'; import { TRANSACTIONS } from 'lbry-redux';
import TransactionListItem from './internal/transaction-list-item'; import TransactionListTable from 'component/transactionListTable';
import RefreshTransactionButton from 'component/transactionRefreshButton'; import RefreshTransactionButton from 'component/transactionRefreshButton';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
import Paginate from 'component/common/paginate';
type Props = { type Props = {
emptyMessage: ?string, emptyMessage: ?string,
filterSetting: string, filterSetting: string,
loading: boolean, loading: boolean,
mySupports: {},
myClaims: any, myClaims: any,
openModal: (id: string, { nout: number, txid: string }) => void,
rewards: {},
setTransactionFilter: string => void, setTransactionFilter: string => void,
slim?: boolean, slim?: boolean,
title: string, title: string,
transactions: Array<Transaction>, transactions: Array<Transaction>,
transactionCount?: number,
history: { replace: string => void },
}; };
class TransactionList extends React.PureComponent<Props> { function TransactionList(props: Props) {
constructor(props: Props) { const { emptyMessage, slim, filterSetting, title, transactions, loading, history, transactionCount } = props;
super(props); const PAGE_SIZE = 20;
(this: any).handleFilterChanged = this.handleFilterChanged.bind(this);
(this: any).filterTransaction = this.filterTransaction.bind(this);
(this: any).revokeClaim = this.revokeClaim.bind(this);
(this: any).isRevokeable = this.isRevokeable.bind(this);
}
capitalize(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
handleFilterChanged(event: SyntheticInputEvent<*>) {
this.props.setTransactionFilter(event.target.value);
}
filterTransaction(transaction: Transaction) {
return this.props.filterSetting === TRANSACTIONS.ALL || this.props.filterSetting === transaction.type;
}
isRevokeable(txid: string, nout: number) {
const outpoint = `${txid}:${nout}`;
const { mySupports, myClaims } = this.props;
return !!mySupports[outpoint] || myClaims.has(outpoint);
}
revokeClaim(txid: string, nout: number) {
this.props.openModal(MODALS.CONFIRM_CLAIM_REVOKE, { txid, nout });
}
render() {
const { emptyMessage, rewards, transactions, slim, filterSetting, title, loading } = this.props;
// The shorter "recent transactions" list shouldn't be filtered
const transactionList = slim ? transactions : transactions.filter(this.filterTransaction);
// Flow offers little support for Object.values() typing. // Flow offers little support for Object.values() typing.
// https://github.com/facebook/flow/issues/2221 // https://github.com/facebook/flow/issues/2221
// $FlowFixMe // $FlowFixMe
const transactionTypes: Array<string> = Object.values(TRANSACTIONS); const transactionTypes: Array<string> = Object.values(TRANSACTIONS);
function capitalize(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function handleFilterChanged(event: SyntheticInputEvent<*>) {
props.setTransactionFilter(event.target.value);
history.replace(`#/$/transactions`); //
}
return ( return (
<React.Fragment> <React.Fragment>
<header className="table__header"> <header className="table__header">
@ -82,22 +56,22 @@ class TransactionList extends React.PureComponent<Props> {
</div> </div>
</h2> </h2>
</header> </header>
{!slim && !!transactions.length && ( {!slim && (
<header className="table__header"> <header className="table__header">
<div className="card__actions--between"> <div className="card__actions--between">
<FileExporter <FileExporter
data={transactionList} data={transactions}
label={__('Export')} label={__('Export')}
title={__('Export Transactions')} title={__('Export Transactions')}
filters={['nout']} filters={['nout']}
defaultPath={__('lbry-transactions-history')} defaultPath={__('lbry-transactions-history')}
disabled={!transactions.length}
/> />
<FormField <FormField
type="select" type="select"
name="file-sort" name="file-sort"
value={filterSetting || TRANSACTIONS.ALL} value={filterSetting || TRANSACTIONS.ALL}
onChange={this.handleFilterChanged} onChange={handleFilterChanged}
label={__('Show')} label={__('Show')}
postfix={ postfix={
<Button <Button
@ -110,7 +84,7 @@ class TransactionList extends React.PureComponent<Props> {
> >
{transactionTypes.map(tt => ( {transactionTypes.map(tt => (
<option key={tt} value={tt}> <option key={tt} value={tt}>
{__(`${this.capitalize(tt)}`)} {__(`${capitalize(tt)}`)}
</option> </option>
))} ))}
</FormField> </FormField>
@ -118,39 +92,19 @@ class TransactionList extends React.PureComponent<Props> {
</header> </header>
)} )}
{!loading && !transactionList.length && ( {!loading && !transactions.length && (
<h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2> <h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2>
)} )}
{!!transactionList.length && ( {!!transactions && !!transactions.length && <TransactionListTable transactionList={transactions} />}
<React.Fragment> {!slim && !!transactionCount && (
<table className="table table--transactions"> <Paginate
<thead> onPageChange={page => history.replace(`#/$/transactions?page=${Number(page)}`)}
<tr> totalPages={Math.ceil(transactionCount / PAGE_SIZE)}
<th>{__('Amount')}</th>
<th>{__('Type')} </th>
<th>{__('Details')} </th>
<th>{__('Transaction')}</th>
<th>{__('Date')}</th>
</tr>
</thead>
<tbody>
{transactionList.map(t => (
<TransactionListItem
key={`${t.txid}:${t.nout}`}
transaction={t}
reward={rewards && rewards[t.txid]}
isRevokeable={this.isRevokeable(t.txid, t.nout)}
revokeClaim={this.revokeClaim}
/> />
))}
</tbody>
</table>
</React.Fragment>
)} )}
</React.Fragment> </React.Fragment>
); );
} }
}
export default TransactionList; export default TransactionList;

View file

@ -0,0 +1,27 @@
import { connect } from 'react-redux';
import { selectClaimedRewardsByTransactionId } from 'lbryinc';
import { doOpenModal } from 'redux/actions/app';
import {
selectAllMyClaimsByOutpoint,
selectSupportsByOutpoint,
selectTransactionListFilter,
selectIsFetchingTransactions,
} from 'lbry-redux';
import TransactionListTable from './view';
const select = state => ({
rewards: selectClaimedRewardsByTransactionId(state),
mySupports: selectSupportsByOutpoint(state),
myClaims: selectAllMyClaimsByOutpoint(state),
filterSetting: selectTransactionListFilter(state),
loading: selectIsFetchingTransactions(state),
});
const perform = dispatch => ({
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
});
export default connect(
select,
perform
)(TransactionListTable);

View file

@ -0,0 +1,65 @@
// @flow
import * as MODALS from 'constants/modal_types';
import React from 'react';
import TransactionListItem from './internal/transaction-list-item';
type Props = {
emptyMessage: ?string,
loading: boolean,
mySupports: {},
myClaims: any,
openModal: (id: string, { nout: number, txid: string }) => void,
rewards: {},
transactionList: Array<any>,
};
// class TransactionList extends React.PureComponent<Props> {
function TransactionListTable(props: Props) {
const { emptyMessage, rewards, loading, transactionList } = props;
function isRevokeable(txid: string, nout: number) {
const outpoint = `${txid}:${nout}`;
const { mySupports, myClaims } = props;
return !!mySupports[outpoint] || myClaims.has(outpoint);
}
function revokeClaim(txid: string, nout: number) {
props.openModal(MODALS.CONFIRM_CLAIM_REVOKE, { txid, nout });
}
return (
<React.Fragment>
{!loading && !transactionList.length && (
<h2 className="main--empty empty">{emptyMessage || __('No transactions.')}</h2>
)}
{!!transactionList.length && (
<React.Fragment>
<table className="table table--transactions">
<thead>
<tr>
<th>{__('Amount')}</th>
<th>{__('Type')} </th>
<th>{__('Details')} </th>
<th>{__('Transaction')}</th>
<th>{__('Date')}</th>
</tr>
</thead>
<tbody>
{transactionList.map(t => (
<TransactionListItem
key={`${t.txid}:${t.nout}`}
transaction={t}
reward={rewards && rewards[t.txid]}
isRevokeable={isRevokeable(t.txid, t.nout)}
revokeClaim={revokeClaim}
/>
))}
</tbody>
</table>
</React.Fragment>
)}
</React.Fragment>
);
}
export default TransactionListTable;

View file

@ -1,17 +1,33 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doFetchTransactions, selectTransactionItems, doFetchClaimListMine } from 'lbry-redux'; import {
doFetchTransactions,
doFetchClaimListMine,
makeSelectFilteredTransactionsForPage,
selectFilteredTransactionCount,
} from 'lbry-redux';
import { withRouter } from 'react-router';
import TransactionHistoryPage from './view'; import TransactionHistoryPage from './view';
const select = state => ({ const select = (state, props) => {
transactions: selectTransactionItems(state), const { search } = props.location;
}); const urlParams = new URLSearchParams(search);
const page = Number(urlParams.get('page')) || 1;
return {
page,
filteredTransactionPage: makeSelectFilteredTransactionsForPage(page)(state),
filteredTransactionsCount: selectFilteredTransactionCount(state),
};
};
const perform = dispatch => ({ const perform = dispatch => ({
fetchTransactions: () => dispatch(doFetchTransactions()), fetchTransactions: () => dispatch(doFetchTransactions()),
fetchMyClaims: () => dispatch(doFetchClaimListMine()), fetchMyClaims: () => dispatch(doFetchClaimListMine()),
}); });
export default connect( export default withRouter(
connect(
select, select,
perform perform
)(TransactionHistoryPage); )(TransactionHistoryPage)
);

View file

@ -9,7 +9,8 @@ type Props = {
fetchMyClaims: () => void, fetchMyClaims: () => void,
fetchTransactions: () => void, fetchTransactions: () => void,
fetchingTransactions: boolean, fetchingTransactions: boolean,
transactions: Array<{}>, filteredTransactionPage: Array<{}>,
filteredTransactionsCount: number,
}; };
class TransactionHistoryPage extends React.PureComponent<Props> { class TransactionHistoryPage extends React.PureComponent<Props> {
@ -21,7 +22,7 @@ class TransactionHistoryPage extends React.PureComponent<Props> {
} }
render() { render() {
const { transactions } = this.props; const { filteredTransactionPage, filteredTransactionsCount } = this.props;
return ( return (
<Page> <Page>
@ -31,7 +32,11 @@ class TransactionHistoryPage extends React.PureComponent<Props> {
'card--disabled': IS_WEB, 'card--disabled': IS_WEB,
})} })}
> >
<TransactionList transactions={transactions} title={__('Transaction History')} /> <TransactionList
transactions={filteredTransactionPage}
transactionCount={filteredTransactionsCount}
title={__('Transaction History')}
/>
</section> </section>
</Page> </Page>
); );