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