TxsTable wip

This commit is contained in:
mmv 2019-06-19 17:24:55 +04:00
parent c66c59c326
commit f126071669
12 changed files with 158 additions and 118 deletions

View File

@ -38,6 +38,7 @@
"axios": "0.19.0",
"bignumber.js": "9.0.0",
"connected-react-router": "^6.3.1",
"date-fns": "^1.30.1",
"final-form": "4.14.1",
"history": "^4.7.2",
"immortal-db": "^1.0.2",

View File

@ -100,6 +100,7 @@ class Layout extends React.Component<Props, State> {
activeTokens,
createTransaction,
fetchTransactions,
transactions,
} = this.props
const { tabIndex } = this.state
@ -151,7 +152,9 @@ class Layout extends React.Component<Props, State> {
createTransaction={createTransaction}
/>
)}
{tabIndex === 1 && <Transactions fetchTransactions={fetchTransactions} safeAddress={address} />}
{tabIndex === 1 && (
<Transactions transactions={transactions} fetchTransactions={fetchTransactions} safeAddress={address} />
)}
</React.Fragment>
)
}

View File

@ -1,8 +1,10 @@
// @flow
import { format } from 'date-fns'
import { List } from 'immutable'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { buildOrderFieldFrom, FIXED, type SortRow } from '~/components/Table/sorting'
import { type SortRow } from '~/components/Table/sorting'
import { type Column } from '~/components/Table/TableHead'
import { getWeb3 } from '~/logic/wallets/getWeb3'
export const TX_TABLE_NONCE_ID = 'nonce'
export const TX_TABLE_TYPE_ID = 'type'
@ -10,51 +12,76 @@ export const TX_TABLE_DATE_ID = 'date'
export const TX_TABLE_AMOUNT_ID = 'amount'
export const TX_TABLE_STATUS_ID = 'status'
type BalanceData = {
asset: Object,
balance: string,
const web3 = getWeb3()
const { toBN, fromWei } = web3.utils
type TxData = {
nonce: number,
type: string,
date: Date,
amount: number | string,
status: string,
}
export type BalanceRow = SortRow<BalanceData>
const formatDate = date => format(date, 'MMM D, YYYY - h:m:s')
export const getBalanceData = (activeTokens: List<Token>): List<BalanceRow> => {
const rows = activeTokens.map((token: Token) => ({
[BALANCE_TABLE_ASSET_ID]: { name: token.name, logoUri: token.logoUri },
[buildOrderFieldFrom(BALANCE_TABLE_ASSET_ID)]: token.name,
[BALANCE_TABLE_BALANCE_ID]: `${token.balance} ${token.symbol}`,
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.balance),
[FIXED]: token.get('symbol') === 'ETH',
export type BalanceRow = SortRow<TxData>
export const getTxTableData = (transactions: List<Transaction>): List<BalanceRow> => {
const rows = transactions.map((tx: Transaction) => ({
[TX_TABLE_NONCE_ID]: tx.nonce,
[TX_TABLE_TYPE_ID]: 'Outgoing transfer',
[TX_TABLE_DATE_ID]: formatDate(tx.isExecuted ? tx.executionDate : tx.submissionDate),
[TX_TABLE_AMOUNT_ID]: Number(tx.value) > 0 ? fromWei(toBN(tx.value), 'ether') : 'n/a',
[TX_TABLE_STATUS_ID]: tx.isExecuted ? 'done' : 'peding',
}))
return rows
}
export const generateColumns = () => {
const assetColumn: Column = {
id: BALANCE_TABLE_ASSET_ID,
order: true,
const nonceColumn: Column = {
id: TX_TABLE_NONCE_ID,
disablePadding: false,
label: 'Asset',
label: 'Nonce',
custom: false,
width: 250,
order: false,
width: 50,
}
const balanceColumn: Column = {
id: BALANCE_TABLE_BALANCE_ID,
align: 'right',
order: true,
const typeColumn: Column = {
id: TX_TABLE_TYPE_ID,
order: false,
disablePadding: false,
label: 'Balance',
label: 'Type',
custom: false,
width: 150,
}
const valueColumn: Column = {
id: TX_TABLE_AMOUNT_ID,
order: false,
disablePadding: false,
label: 'Amount',
custom: false,
width: 100,
}
const dateColumn: Column = {
id: TX_TABLE_DATE_ID,
order: false,
disablePadding: false,
label: 'Date',
custom: false,
}
const actions: Column = {
id: 'actions',
const statusColumn: Column = {
id: TX_TABLE_STATUS_ID,
order: false,
disablePadding: false,
label: '',
custom: true,
}
return List([assetColumn, balanceColumn, actions])
return List([nonceColumn, typeColumn, valueColumn, dateColumn, statusColumn])
}

View File

@ -6,130 +6,64 @@ import { type Token } from '~/logic/tokens/store/model/token'
import CallMade from '@material-ui/icons/CallMade'
import CallReceived from '@material-ui/icons/CallReceived'
import Button from '@material-ui/core/Button'
import Checkbox from '@material-ui/core/Checkbox'
import TableRow from '@material-ui/core/TableRow'
import TableCell from '@material-ui/core/TableCell'
import { withStyles } from '@material-ui/core/styles'
import Col from '~/components/layout/Col'
import Block from '~/components/layout/Block'
import Row from '~/components/layout/Row'
import Paragraph from '~/components/layout/Paragraph'
import Modal from '~/components/Modal'
import { type Column, cellWidth } from '~/components/Table/TableHead'
import Table from '~/components/Table'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import {
getBalanceData, generateColumns, BALANCE_TABLE_ASSET_ID, type BalanceRow, filterByZero,
} from './dataFetcher'
getTxTableData, generateColumns, TX_TABLE_NONCE_ID, type BalanceRow,
} from './columns'
import { styles } from './style'
export const MANAGE_TOKENS_BUTTON_TEST_ID = 'manage-tokens-btn'
export const BALANCE_ROW_TEST_ID = 'balance-row'
type State = {
hideZero: boolean,
showToken: boolean,
showReceive: boolean,
sendFunds: Object,
}
type Props = {
classes: Object,
granted: boolean,
tokens: List<Token>,
activeTokens: List<Token>,
safeAddress: string,
safeName: string,
etherScanLink: string,
ethBalance: string,
createTransaction: Function,
transactions: List<Transaction>,
}
type Action = 'Token' | 'Send' | 'Receive'
class Balances extends React.Component<Props, State> {
state = {
hideZero: false,
showToken: false,
sendFunds: {
isOpen: false,
selectedToken: undefined,
},
showReceive: false,
}
onShow = (action: Action) => () => {
this.setState(() => ({ [`show${action}`]: true }))
}
onHide = (action: Action) => () => {
this.setState(() => ({ [`show${action}`]: false }))
}
showSendFunds = (token: Token) => {
this.setState({
sendFunds: {
isOpen: true,
selectedToken: token,
},
})
}
hideSendFunds = () => {
this.setState({
sendFunds: {
isOpen: false,
selectedToken: undefined,
},
})
}
handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
const { checked } = e.target
this.setState(() => ({ hideZero: checked }))
}
state = {}
render() {
const {
hideZero, showToken, showReceive, sendFunds,
} = this.state
const {
classes,
granted,
tokens,
safeAddress,
activeTokens,
safeName,
etherScanLink,
ethBalance,
createTransaction,
transactions,
} = this.props
const columns = generateColumns()
const autoColumns = columns.filter(c => !c.custom)
const checkboxClasses = {
root: classes.root,
}
const filteredData = filterByZero(getBalanceData(activeTokens), hideZero)
const filteredData = getTxTableData(transactions)
return (
<React.Fragment>
<Block className={classes.container}>
<Table
label="Balances"
defaultOrderBy={BALANCE_TABLE_ASSET_ID}
label="Transactions"
defaultOrderBy={TX_TABLE_NONCE_ID}
columns={columns}
data={filteredData}
size={filteredData.size}
defaultFixed
>
{(sortedData: Array<BalanceRow>) => sortedData.map((row: any, index: number) => (
<TableRow tabIndex={-1} key={index} className={classes.hide} data-testid={BALANCE_ROW_TEST_ID}>
<TableRow tabIndex={-1} key={index} className={classes.row}>
{autoColumns.map((column: Column) => (
<TableCell key={column.id} style={cellWidth(column.width)} align={column.align} component="td">
{column.id === BALANCE_TABLE_ASSET_ID ? <AssetTableCell asset={row[column.id]} /> : row[column.id]}
<TableCell key={column.id} className={classes.cell} style={cellWidth(column.width)} align={column.align} component="td">
{row[column.id]}
</TableCell>
))}
<TableCell component="td">
{/* <TableCell component="td">
<Row align="end" className={classes.actions}>
{granted && (
<Button
@ -155,12 +89,12 @@ class Balances extends React.Component<Props, State> {
Receive
</Button>
</Row>
</TableCell>
</TableCell> */}
</TableRow>
))
}
</Table>
</React.Fragment>
</Block>
)
}
}

View File

@ -0,0 +1,55 @@
// @flow
import { xs, sm, md, lg } from '~/theme/variables'
export const styles = (theme: Object) => ({
container: {
marginTop: lg,
},
root: {
width: '20px',
marginRight: sm,
},
zero: {
letterSpacing: '-0.5px',
},
message: {
margin: `${sm} 0`,
},
actionIcon: {
marginRight: theme.spacing(1),
},
iconSmall: {
fontSize: 16,
},
cell: {
paddingTop: md,
paddingBottom: md,
},
row: {
'&:hover': {
backgroundColor: '#fff3e2',
},
},
actions: {
justifyContent: 'flex-end',
visibility: 'hidden',
},
send: {
minWidth: '0px',
marginRight: sm,
width: '70px',
},
receive: {
minWidth: '0px',
width: '95px',
},
leftIcon: {
marginRight: xs,
},
links: {
textDecoration: 'underline',
'&:hover': {
cursor: 'pointer',
},
},
})

View File

@ -1,12 +1,15 @@
// @flow
import * as React from 'react'
import { List } from 'immutable'
import NoTransactions from '~/routes/safe/components/Transactions/NoTransactions'
import NoTransactions from '~/routes/safe/components/TransactionsNew/NoTransactions'
import TxsTable from '~/routes/safe/components/TransactionsNew/TxsTable'
import { type Transaction } from '~/routes/safe/store/models/transaction'
type Props = {
safeAddress: string,
threshold: number,
fetchTransactions: Function
fetchTransactions: Function,
transactions: List<Transaction>,
}
class Transactions extends React.Component<Props, {}> {
@ -18,9 +21,11 @@ class Transactions extends React.Component<Props, {}> {
render() {
const { transactions, safeName, threshold } = this.props
const hasTransactions = false
const hasTransactions = transactions.size > 0
return <React.Fragment>{hasTransactions ? <div /> : <NoTransactions />}</React.Fragment>
return (
<React.Fragment>{hasTransactions ? <TxsTable transactions={transactions} /> : <NoTransactions />}</React.Fragment>
)
}
}

View File

@ -61,6 +61,7 @@ class SafeView extends React.Component<Props> {
tokens,
createTransaction,
fetchTransactions,
transactions,
} = this.props
return (
@ -75,6 +76,7 @@ class SafeView extends React.Component<Props> {
granted={granted}
createTransaction={createTransaction}
fetchTransactions={fetchTransactions}
transactions={transactions}
/>
</Page>
)

View File

@ -13,8 +13,10 @@ import { type Safe } from '~/routes/safe/store/models/safe'
import { type Owner } from '~/routes/safe/store/models/owner'
import { type GlobalState } from '~/store'
import { sameAddress } from '~/logic/wallets/ethAddresses'
import { safeTransactionsSelector } from '~/routes/safe/store/selectors/index'
import { orderedTokenListSelector, tokensSelector } from '~/logic/tokens/store/selectors'
import { type Token } from '~/logic/tokens/store/model/token'
import { type Transaction } from '~/routes/safe/store/models/transaction'
import { type TokenBalance } from '~/routes/safe/store/models/tokenBalance'
import { safeParamAddressSelector } from '../store/selectors'
import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
@ -27,6 +29,7 @@ export type SelectorProps = {
userAddress: string,
network: string,
safeUrl: string,
transactions: List<Transaction>,
}
export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = createSelector(
@ -95,4 +98,5 @@ export default createStructuredSelector<Object, *>({
userAddress: userAccountSelector,
network: networkSelector,
safeUrl: safeParamAddressSelector,
transactions: safeTransactionsSelector,
})

View File

@ -54,6 +54,8 @@ const buildTransactionFrom = async (safeAddress: string, tx: TxServiceModel, saf
recipient: tx.to,
data: tx.data ? tx.data : EMPTY_DATA,
isExecuted: tx.isExecuted,
submissionDate: tx.submissionDate,
executionDate: tx.executionDate,
})
}

View File

@ -11,6 +11,8 @@ export type TransactionProps = {
recipient: string,
data: string,
isExecuted: boolean,
submissionDate: Date,
executionDate: Date,
}
export const makeTransaction: RecordFactory<TransactionProps> = Record({
@ -21,6 +23,8 @@ export const makeTransaction: RecordFactory<TransactionProps> = Record({
recipient: '',
data: '',
isExecuted: false,
submissionDate: '',
executionDate: '',
})
export type Transaction = RecordOf<TransactionProps>

View File

@ -23,17 +23,15 @@ type TransactionProps = {
transaction: Transaction,
}
const safePropAddressSelector = (state: GlobalState, props: SafeProps) => props.safeAddress
const transactionsSelector = (state: GlobalState): TransactionsState => state[TRANSACTIONS_REDUCER_ID]
const oneTransactionSelector = (state: GlobalState, props: TransactionProps) => props.transaction
export const safeParamAddressSelector = (state: GlobalState, props: RouterProps) => props.match.params[SAFE_PARAM_ADDRESS] || ''
export const safeTransactionsSelector: Selector<GlobalState, SafeProps, List<Transaction>> = createSelector(
export const safeTransactionsSelector: Selector<GlobalState, RouterProps, List<Transaction>> = createSelector(
transactionsSelector,
safePropAddressSelector,
safeParamAddressSelector,
(transactions: TransactionsState, address: string): List<Transaction> => {
if (!transactions) {
return List([])

View File

@ -5713,6 +5713,11 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
date-fns@^1.30.1:
version "1.30.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"