Meriadec Pillet
7 years ago
committed by
GitHub
27 changed files with 734 additions and 391 deletions
@ -0,0 +1,66 @@ |
|||
// @flow
|
|||
|
|||
import axios from 'axios' |
|||
import moment from 'moment' |
|||
import { getDefaultUnitByCoinType } from '@ledgerhq/currencies' |
|||
|
|||
import get from 'lodash/get' |
|||
|
|||
import db from 'helpers/db' |
|||
|
|||
type InitCounterValues = () => { type: string, payload: Object } |
|||
export const initCounterValues: InitCounterValues = () => ({ |
|||
type: 'UPDATE_COUNTER_VALUES', |
|||
payload: db.get('counterValues'), |
|||
}) |
|||
|
|||
type UpdateCounterValues = Object => { type: string, payload: Object } |
|||
export const updateCounterValues: UpdateCounterValues = payload => ({ |
|||
type: 'DB:UPDATE_COUNTER_VALUES', |
|||
payload, |
|||
}) |
|||
|
|||
type FetchCounterValues = (?number) => (Dispatch<*>, Function) => void |
|||
export const fetchCounterValues: FetchCounterValues = coinType => (dispatch, getState) => { |
|||
const { accounts, counterValues } = getState() |
|||
|
|||
let coinTypes = [] |
|||
|
|||
if (!coinType) { |
|||
coinTypes = [...new Set(accounts.map(a => a.coinType))] |
|||
} else { |
|||
coinTypes = [coinType] |
|||
} |
|||
|
|||
const today = moment().format('YYYY-MM-DD') |
|||
|
|||
const fetchCounterValuesByCoinType = coinType => { |
|||
const { code } = getDefaultUnitByCoinType(coinType) |
|||
const todayCounterValues = get(counterValues, `${code}-USD.${today}`, null) |
|||
|
|||
if (todayCounterValues !== null) { |
|||
return {} |
|||
} |
|||
|
|||
return axios |
|||
.get( |
|||
`https://min-api.cryptocompare.com/data/histoday?&extraParams=ledger-test&fsym=${code}&tsym=USD&allData=1`, |
|||
) |
|||
.then(({ data }) => ({ |
|||
symbol: `${code}-USD`, |
|||
values: data.Data.reduce((result, d) => { |
|||
const date = moment(d.time * 1000).format('YYYY-MM-DD') |
|||
result[date] = d.close |
|||
return result |
|||
}, {}), |
|||
})) |
|||
} |
|||
|
|||
Promise.all(coinTypes.map(fetchCounterValuesByCoinType)).then(result => { |
|||
const newCounterValues = result.reduce((r, v) => { |
|||
r[v.symbol] = v.values |
|||
return r |
|||
}, {}) |
|||
dispatch(updateCounterValues(newCounterValues)) |
|||
}) |
|||
} |
@ -0,0 +1,66 @@ |
|||
// @flow
|
|||
|
|||
import React, { Fragment } from 'react' |
|||
import moment from 'moment' |
|||
|
|||
import { formatCurrencyUnit, getFiatUnit } from '@ledgerhq/currencies' |
|||
|
|||
import type { Accounts } from 'types/common' |
|||
|
|||
import { space } from 'styles/theme' |
|||
|
|||
import { AreaChart } from 'components/base/Chart' |
|||
import Box, { Card } from 'components/base/Box' |
|||
import CalculateBalance from 'components/CalculateBalance' |
|||
|
|||
import BalanceInfos from './BalanceInfos' |
|||
|
|||
type Props = { |
|||
accounts: Accounts, |
|||
selectedTime: string, |
|||
daysCount: number, |
|||
} |
|||
|
|||
const BalanceSummary = ({ accounts, selectedTime, daysCount }: Props) => ( |
|||
<Card flow={3} p={0} py={6}> |
|||
<CalculateBalance |
|||
accounts={accounts} |
|||
daysCount={daysCount} |
|||
render={({ allBalances, totalBalance, sinceBalance }) => ( |
|||
<Fragment> |
|||
<Box px={6}> |
|||
<BalanceInfos |
|||
fiat="USD" |
|||
totalBalance={totalBalance} |
|||
since={selectedTime} |
|||
sinceBalance={sinceBalance} |
|||
/> |
|||
</Box> |
|||
<Box ff="Open Sans" fontSize={4} color="graphite"> |
|||
<AreaChart |
|||
color="#5286f7" |
|||
data={allBalances} |
|||
height={250} |
|||
id="dashboard-chart" |
|||
padding={{ |
|||
top: space[6], |
|||
bottom: space[6], |
|||
left: space[6] * 2, |
|||
right: space[6], |
|||
}} |
|||
strokeWidth={2} |
|||
renderLabels={d => |
|||
formatCurrencyUnit(getFiatUnit('USD'), d.y * 100, { |
|||
showCode: true, |
|||
}) |
|||
} |
|||
renderTickX={t => moment(t).format('MMM. D')} |
|||
/> |
|||
</Box> |
|||
</Fragment> |
|||
)} |
|||
/> |
|||
</Card> |
|||
) |
|||
|
|||
export default BalanceSummary |
@ -0,0 +1,137 @@ |
|||
// @flow
|
|||
|
|||
import { PureComponent } from 'react' |
|||
import { connect } from 'react-redux' |
|||
import moment from 'moment' |
|||
|
|||
import type { MapStateToProps } from 'react-redux' |
|||
import type { Accounts } from 'types/common' |
|||
|
|||
import { getDefaultUnitByCoinType } from '@ledgerhq/currencies' |
|||
|
|||
import first from 'lodash/first' |
|||
import get from 'lodash/get' |
|||
import last from 'lodash/last' |
|||
|
|||
const mapStateToProps: MapStateToProps<*, *, *> = state => ({ |
|||
counterValues: state.counterValues, |
|||
}) |
|||
|
|||
function getAllBalances({ |
|||
accounts, |
|||
counterValues, |
|||
daysCount, |
|||
}: { |
|||
accounts: Accounts, |
|||
counterValues: Object, |
|||
daysCount: number, |
|||
}) { |
|||
const getDate = date => moment(date).format('YYYY-MM-DD') |
|||
const getValue = (balance, unit, d) => |
|||
balance / 10 ** unit.magnitude * counterValues['BTC-USD'][d] |
|||
|
|||
const allBalancesByCoinType = accounts.reduce((result, account) => { |
|||
const { coinType } = account |
|||
|
|||
Object.keys(account.balanceByDay).forEach(k => { |
|||
if (!result[coinType]) { |
|||
result[coinType] = {} |
|||
} |
|||
result[coinType][k] = account.balanceByDay[k] + get(result, `${coinType}.${k}`, 0) |
|||
}) |
|||
|
|||
return result |
|||
}, {}) |
|||
|
|||
const allBalances = Object.keys(allBalancesByCoinType).reduce((result, coinType) => { |
|||
const unit = getDefaultUnitByCoinType(parseInt(coinType, 10)) |
|||
|
|||
const balanceByDay = allBalancesByCoinType[coinType] |
|||
|
|||
const balanceByDayKeys = Object.keys(balanceByDay).sort((a, b) => new Date(b) - new Date(a)) |
|||
|
|||
const lastDay = balanceByDayKeys[0] |
|||
const lastBalance = balanceByDay[lastDay] |
|||
|
|||
let balance = lastBalance |
|||
let index = daysCount |
|||
|
|||
result[lastDay] = getValue(balance, unit, lastDay) |
|||
|
|||
let d = getDate(moment(lastDay).subtract(1, 'days')) |
|||
|
|||
while (index !== 0) { |
|||
result[d] = getValue(balance, unit, d) + (result[d] || 0) |
|||
d = getDate(moment(d).subtract(1, 'days')) |
|||
|
|||
if (balanceByDay[d]) { |
|||
balance = balanceByDay[d] |
|||
} |
|||
|
|||
index-- |
|||
} |
|||
|
|||
return result |
|||
}, {}) |
|||
|
|||
return Object.keys(allBalances) |
|||
.sort() |
|||
.map(k => ({ |
|||
name: k, |
|||
value: allBalances[k], |
|||
})) |
|||
} |
|||
|
|||
function calculateBalance(props) { |
|||
const allBalances = getAllBalances({ |
|||
accounts: props.accounts, |
|||
counterValues: props.counterValues, |
|||
daysCount: props.daysCount, |
|||
}) |
|||
|
|||
return { |
|||
allBalances, |
|||
totalBalance: last(allBalances).value, |
|||
sinceBalance: first(allBalances).value, |
|||
} |
|||
} |
|||
|
|||
type Props = { |
|||
accounts: Accounts, |
|||
counterValues: Object, |
|||
daysCount: number, |
|||
render: Function, |
|||
} |
|||
|
|||
type State = { |
|||
allBalances: Array<Object>, |
|||
totalBalance: number, |
|||
sinceBalance: number, |
|||
} |
|||
|
|||
class CalculateBalance extends PureComponent<Props, State> { |
|||
state = { |
|||
...calculateBalance(this.props), |
|||
} |
|||
|
|||
componentWillReceiveProps(nextProps) { |
|||
const sameAccounts = this.props.accounts === nextProps.accounts |
|||
const sameCounterValues = this.props.counterValues === nextProps.counterValues |
|||
const sameDaysCount = this.props.daysCount === nextProps.daysCount |
|||
|
|||
if (!sameAccounts || !sameCounterValues || !sameDaysCount) { |
|||
this.setState({ |
|||
...calculateBalance(nextProps), |
|||
}) |
|||
} |
|||
} |
|||
|
|||
render() { |
|||
const { render } = this.props |
|||
const { allBalances, totalBalance, sinceBalance } = this.state |
|||
|
|||
return render({ allBalances, totalBalance, sinceBalance }) |
|||
} |
|||
} |
|||
|
|||
export default connect(mapStateToProps)(CalculateBalance) |
@ -0,0 +1,19 @@ |
|||
// @flow
|
|||
|
|||
import { handleActions } from 'redux-actions' |
|||
|
|||
export type CounterValuesState = {} |
|||
|
|||
const state: CounterValuesState = {} |
|||
|
|||
const handlers = { |
|||
UPDATE_COUNTER_VALUES: ( |
|||
state: CounterValuesState, |
|||
{ payload: counterValues }: { payload: CounterValuesState }, |
|||
): CounterValuesState => ({ |
|||
...state, |
|||
...counterValues, |
|||
}), |
|||
} |
|||
|
|||
export default handleActions(handlers, state) |
Loading…
Reference in new issue