diff --git a/lib/forex.js b/lib/forex.js new file mode 100644 index 00000000..22dc903a --- /dev/null +++ b/lib/forex.js @@ -0,0 +1,5 @@ +const axios = require('axios') + +const getFiatRates = () => axios.get('https://bitpay.com/api/rates').then(response => response.data) + +module.exports = { getFiatRates } diff --git a/lib/new-admin/graphql/schema.js b/lib/new-admin/graphql/schema.js index 3f322338..26202868 100644 --- a/lib/new-admin/graphql/schema.js +++ b/lib/new-admin/graphql/schema.js @@ -18,12 +18,11 @@ const couponManager = require('../../coupons') const serverVersion = require('../../../package.json').version const transactions = require('../transactions') const funding = require('../funding') +const forex = require('../../forex') const supervisor = require('../supervisor') const serverLogs = require('../server-logs') const pairing = require('../pairing') - const plugins = require('../../plugins') -const ticker = require('../../ticker') const { accounts: accountsConfig, @@ -240,6 +239,8 @@ const typeDefs = gql` created: Date age: Float deviceTime: Date + } + type Rate { code: String name: String @@ -268,25 +269,22 @@ const typeDefs = gql` until: Date limit: Int offset: Int - id: ID + deviceId: ID ): [Transaction] transactionsCsv(from: Date, until: Date, limit: Int, offset: Int): String accounts: JSONObject config: JSONObject blacklist: [Blacklist] # userTokens: [UserToken] -<<<<<<< HEAD coupons: [Coupon] + cryptoRates: JSONObject + fiatRates: [Rate] } type SupportLogsResponse { id: ID! timestamp: Date! deviceId: ID -======= - rates: JSONObject - btcRates(to: String, from: String): [Rate] ->>>>>>> 9d88b4f... Feat: make dashboard and machine profile page } enum MachineAction { @@ -356,15 +354,26 @@ const resolvers = { serverLogs.getServerLogs(from, until, limit, offset), serverLogsCsv: (...[, { from, until, limit, offset }]) => serverLogs.getServerLogs(from, until, limit, offset).then(parseAsync), - transactions: (...[, { from, until, limit, offset, id }]) => - transactions.batch(from, until, limit, offset, id), + transactions: (...[, { from, until, limit, offset, deviceId }]) => + transactions.batch(from, until, limit, offset, deviceId), transactionsCsv: (...[, { from, until, limit, offset }]) => transactions.batch(from, until, limit, offset).then(parseAsync), config: () => settingsLoader.loadLatestConfigOrNone(), accounts: () => settingsLoader.loadAccounts(), blacklist: () => blacklist.getBlacklist(), // userTokens: () => tokenManager.getTokenList() - coupons: () => couponManager.getAvailableCoupons() + coupons: () => couponManager.getAvailableCoupons(), + cryptoRates: () => + settingsLoader.loadLatest().then(settings => { + const pi = plugins(settings) + return pi.getRawRates().then(r => { + return { + withCommissions: pi.buildRates(r), + withoutCommissions: pi.buildRatesNoCommission(r) + } + }) + }), + fiatRates: () => forex.getFiatRates() }, Mutation: { machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, newName }]) => machineAction({ deviceId, action, cashbox, cassette1, cassette2, newName }), diff --git a/lib/new-admin/transactions.js b/lib/new-admin/transactions.js index 84a4169f..c7208961 100644 --- a/lib/new-admin/transactions.js +++ b/lib/new-admin/transactions.js @@ -24,19 +24,8 @@ function addNames (txs) { const camelize = _.mapKeys(_.camelCase) -function batch ( - from = new Date(0).toISOString(), - until = new Date().toISOString(), - limit = null, - offset = 0, - id = null -) { - const packager = _.flow( - _.flatten, - _.orderBy(_.property('created'), ['desc']), - _.map(camelize), - addNames - ) +function batch (from = new Date(0).toISOString(), until = new Date().toISOString(), limit = null, offset = 0, id = null) { + const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize), addNames) const cashInSql = `select 'cashIn' as tx_class, txs.*, c.phone as customer_phone, @@ -47,7 +36,7 @@ function batch ( c.front_camera_path as customer_front_camera_path, c.id_card_photo_path as customer_id_card_photo_path, ((not txs.send_confirmed) and (txs.created <= now() - interval $1)) as expired - from cash_in_txs as txs + from cash_in_txs as txs left outer join customers c on txs.customer_id = c.id where txs.created >= $2 and txs.created <= $3 ${ id !== null ? `and txs.device_id = $6` : `` @@ -65,7 +54,7 @@ function batch ( c.front_camera_path as customer_front_camera_path, c.id_card_photo_path as customer_id_card_photo_path, (extract(epoch from (now() - greatest(txs.created, txs.confirmed_at))) * 1000) >= $1 as expired - from cash_out_txs txs + from cash_out_txs txs inner join cash_out_actions actions on txs.id = actions.tx_id and actions.action = 'provisionAddress' left outer join customers c on txs.customer_id = c.id diff --git a/lib/plugins.js b/lib/plugins.js index cf4d4a8a..cf449c0c 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -35,7 +35,8 @@ const PONG_TTL = '1 week' const tradesQueues = {} function plugins (settings, deviceId) { - function buildRatesNoCommission (tickers) { + + function internalBuildRates (tickers, withCommission = true) { const localeConfig = configManager.getLocale(deviceId, settings.config) const cryptoCodes = localeConfig.cryptoCurrencies @@ -43,30 +44,7 @@ function plugins (settings, deviceId) { cryptoCodes.forEach((cryptoCode, i) => { const rateRec = tickers[i] - - if (!rateRec) return - - if (Date.now() - rateRec.timestamp > STALE_TICKER) - return logger.warn('Stale rate for ' + cryptoCode) - const rate = rateRec.rates - rates[cryptoCode] = { - cashIn: rate.ask.round(5), - cashOut: rate.bid.round(5) - } - }) - - return rates - } - - function buildRates (tickers) { - const localeConfig = configManager.getLocale(deviceId, settings.config) - const cryptoCodes = localeConfig.cryptoCurrencies - - const rates = {} - - cryptoCodes.forEach((cryptoCode, i) => { const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) - const rateRec = tickers[i] if (!rateRec) return @@ -78,15 +56,26 @@ function plugins (settings, deviceId) { if (Date.now() - rateRec.timestamp > STALE_TICKER) return logger.warn('Stale rate for ' + cryptoCode) const rate = rateRec.rates - rates[cryptoCode] = { + + withCommission ? rates[cryptoCode] = { cashIn: rate.ask.mul(cashInCommission).round(5), cashOut: cashOutCommission && rate.bid.div(cashOutCommission).round(5) + } : rates[cryptoCode] = { + cashIn: rate.ask.round(5), + cashOut: rate.bid.round(5) } }) - return rates } + function buildRatesNoCommission (tickers) { + return internalBuildRates(tickers, false) + } + + function buildRates (tickers) { + return internalBuildRates(tickers, true) + } + function getNotificationConfig () { return configManager.getGlobalNotifications(settings.config) } diff --git a/lib/ticker.js b/lib/ticker.js index ef710443..85fc3266 100644 --- a/lib/ticker.js +++ b/lib/ticker.js @@ -2,7 +2,6 @@ const mem = require('mem') const configManager = require('./new-config-manager') const ph = require('./plugin-helper') const logger = require('./logger') -const axios = require('axios') const lastRate = {} @@ -40,26 +39,4 @@ const getRates = mem(_getRates, { cacheKey: (settings, fiatCode, cryptoCode) => JSON.stringify([fiatCode, cryptoCode]) }) -const getBtcRates = (to = null, from = 'USD') => { - // if to !== null, then we return only the rates with from (default USD) and to (so an array with 2 items) - return axios.get('https://bitpay.com/api/rates').then(response => { - const fxRates = response.data - if (to === null) { - return fxRates - } - const toRate = fxRates.find(o => o.code === to) - const fromRate = fxRates.find(o => o.code === from) - - let res = [] - if (toRate && to !== from) { - res = [...res, toRate] - } - if (fromRate) { - res = [...res, fromRate] - } - - return res - }) -} - -module.exports = { getBtcRates, getRates } +module.exports = { getRates } diff --git a/new-lamassu-admin/src/components/CollapsibleCard.js b/new-lamassu-admin/src/components/CollapsibleCard.js new file mode 100644 index 00000000..0814ffce --- /dev/null +++ b/new-lamassu-admin/src/components/CollapsibleCard.js @@ -0,0 +1,44 @@ +import Grid from '@material-ui/core/Grid' +import { makeStyles } from '@material-ui/core/styles' +import PropTypes from 'prop-types' +import React from 'react' + +import { white } from 'src/styling/variables' + +const cardState = Object.freeze({ + DEFAULT: 'default', + SHRUNK: 'shrunk', + EXPANDED: 'expanded' +}) + +const styles = { + card: { + wordWrap: 'break-word', + boxShadow: '0 0 4px 0 rgba(0, 0, 0, 0.08)', + borderRadius: 12, + padding: 24, + backgroundColor: white + } +} + +const useStyles = makeStyles(styles) + +const CollapsibleCard = ({ className, state, shrunkComponent, children }) => { + const classes = useStyles() + return ( +
+ +
+ {state === cardState.SHRUNK ? shrunkComponent : children} +
+
+
+ ) +} + +CollapsibleCard.propTypes = { + shrunkComponent: PropTypes.node.isRequired +} + +export default CollapsibleCard +export { cardState } diff --git a/new-lamassu-admin/src/pages/Dashboard/Alerts/Alerts.js b/new-lamassu-admin/src/pages/Dashboard/Alerts/Alerts.js index 65b58dd5..6ee74b27 100644 --- a/new-lamassu-admin/src/pages/Dashboard/Alerts/Alerts.js +++ b/new-lamassu-admin/src/pages/Dashboard/Alerts/Alerts.js @@ -1,12 +1,12 @@ import Button from '@material-ui/core/Button' import Grid from '@material-ui/core/Grid' import { makeStyles } from '@material-ui/core/styles' -import React, { useState, useEffect } from 'react' +import React from 'react' +import { cardState as cardState_ } from 'src/components/CollapsibleCard' import { Label1, H4 } from 'src/components/typography' -import styles from '../Dashboard.styles' - +import styles from './Alerts.styles' import AlertsTable from './AlertsTable' const NUM_TO_RENDER = 3 @@ -23,99 +23,54 @@ const data = { const useStyles = makeStyles(styles) -const Alerts = ({ cardState, setRightSideState }) => { +const Alerts = ({ onReset, onExpand, size }) => { const classes = useStyles() - const [showAllItems, setShowAllItems] = useState(false) - const [showExpandButton, setShowExpandButton] = useState(false) - const [numToRender, setNumToRender] = useState(NUM_TO_RENDER) - useEffect(() => { - if (showAllItems) { - setShowExpandButton(false) - setNumToRender(data?.alerts.length) - } else if (data && data?.alerts.length > numToRender) { - setShowExpandButton(true) - } - if (cardState.cardSize === 'small' || cardState.cardSize === 'default') { - setShowAllItems(false) - setNumToRender(NUM_TO_RENDER) - } - }, [cardState.cardSize, numToRender, showAllItems]) + const showAllItems = size === cardState_.EXPANDED - const reset = () => { - setRightSideState({ - systemStatus: { cardSize: 'default', buttonName: 'Show less' }, - alerts: { cardSize: 'default', buttonName: 'Show less' } - }) - setShowAllItems(false) - setNumToRender(NUM_TO_RENDER) - } - - const showAllClick = () => { - setShowExpandButton(false) - setShowAllItems(true) - setRightSideState({ - systemStatus: { cardSize: 'small', buttonName: 'Show machines' }, - alerts: { cardSize: 'big', buttonName: 'Show less' } - }) - } + const alertsLength = () => (data ? data.alerts.length : 0) return ( <> -
-

{`Alerts ${ - data ? `(${data.alerts.length})` : 0 - }`}

- {(showAllItems || cardState.cardSize === 'small') && ( - <> - - - - +
+

{`Alerts (${alertsLength()})`}

+ {showAllItems && ( + + + )}
- {cardState.cardSize !== 'small' && ( - <> - - - - {showExpandButton && ( - <> - - - - - )} - + <> + + + + {!showAllItems && ( + <> + + + + + )} - - )} + + ) } diff --git a/new-lamassu-admin/src/pages/Dashboard/Alerts/Alerts.styles.js b/new-lamassu-admin/src/pages/Dashboard/Alerts/Alerts.styles.js index d7040367..9e4e7b95 100644 --- a/new-lamassu-admin/src/pages/Dashboard/Alerts/Alerts.styles.js +++ b/new-lamassu-admin/src/pages/Dashboard/Alerts/Alerts.styles.js @@ -6,6 +6,15 @@ import { } from 'src/styling/variables' const styles = { + container: { + display: 'flex', + justifyContent: 'space-between' + }, + centerLabel: { + textAlign: 'center', + marginBottom: 0, + marginTop: 0 + }, label: { margin: 0, color: offColor diff --git a/new-lamassu-admin/src/pages/Dashboard/Alerts/AlertsTable.js b/new-lamassu-admin/src/pages/Dashboard/Alerts/AlertsTable.js index 6d16de92..2e1413d4 100644 --- a/new-lamassu-admin/src/pages/Dashboard/Alerts/AlertsTable.js +++ b/new-lamassu-admin/src/pages/Dashboard/Alerts/AlertsTable.js @@ -1,35 +1,21 @@ -import { withStyles } from '@material-ui/core' import List from '@material-ui/core/List' import ListItem from '@material-ui/core/ListItem' import ListItemText from '@material-ui/core/ListItemText' +import * as R from 'ramda' import React from 'react' -import styles from './Alerts.styles' -// const useStyles = makeStyles(styles) - -const StyledListItem = withStyles(() => ({ - root: { - ...styles.root - } -}))(ListItem) - const AlertsTable = ({ numToRender, alerts }) => { - // const classes = useStyles() - + const alertsToRender = R.slice(0, numToRender, alerts) return ( - <> - - {alerts.map((alert, idx) => { - if (idx < numToRender) { - return ( - - - - ) - } else return null - })} - - + + {alertsToRender.map((alert, idx) => { + return ( + + + + ) + })} + ) } diff --git a/new-lamassu-admin/src/pages/Dashboard/Dashboard.js b/new-lamassu-admin/src/pages/Dashboard/Dashboard.js index 4e5e8b10..dd875662 100644 --- a/new-lamassu-admin/src/pages/Dashboard/Dashboard.js +++ b/new-lamassu-admin/src/pages/Dashboard/Dashboard.js @@ -1,5 +1,6 @@ import Grid from '@material-ui/core/Grid' import { makeStyles } from '@material-ui/core/styles' +import classnames from 'classnames' import React from 'react' import TitleSection from 'src/components/layout/TitleSection' @@ -19,13 +20,17 @@ const Dashboard = () => { <>
-
+
- Cash-out + Cash-out
-
+
- Cash-in + Cash-in
diff --git a/new-lamassu-admin/src/pages/Dashboard/Dashboard.styles.js b/new-lamassu-admin/src/pages/Dashboard/Dashboard.styles.js index d60f9646..faf0ec9e 100644 --- a/new-lamassu-admin/src/pages/Dashboard/Dashboard.styles.js +++ b/new-lamassu-admin/src/pages/Dashboard/Dashboard.styles.js @@ -3,6 +3,21 @@ import { spacer, white, primaryColor } from 'src/styling/variables' const { label1 } = typographyStyles const styles = { + headerLabels: { + display: 'flex', + flexDirection: 'row' + }, + headerLabelContainerMargin: { + marginRight: 24 + }, + headerLabelContainer: { + display: 'flex', + alignItems: 'center' + }, + headerLabelSpan: { + extend: label1, + marginLeft: 6 + }, root: { flexGrow: 1, marginBottom: 108 @@ -17,37 +32,12 @@ const styles = { padding: 24, backgroundColor: white }, - h4: { - margin: 0, - marginRight: spacer * 8 + leftSideMargin: { + marginRight: 24 }, - label: { - color: primaryColor, - minHeight: 0, - minWidth: 0, - padding: 0, - textTransform: 'none', - '&:hover': { - backgroundColor: 'transparent' - } - }, - actionButton: { - marginTop: -4 - }, - headerLabels: { + container: { display: 'flex', - flexDirection: 'row', - '& div': { - display: 'flex', - alignItems: 'center' - }, - '& > div:first-child': { - marginRight: 24 - }, - '& span': { - extend: label1, - marginLeft: 6 - } + justifyContent: 'space-between' }, button: { color: primaryColor, @@ -58,6 +48,15 @@ const styles = { '&:hover': { backgroundColor: 'transparent' } + }, + upperButtonLabel: { + textAlign: 'center', + marginBottom: 0, + marginTop: 16, + marginLeft: spacer + }, + alertsCard: { + marginBottom: 16 } } diff --git a/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.js b/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.js index 0aa3b446..1f9b2034 100644 --- a/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.js +++ b/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.js @@ -1,20 +1,21 @@ import { useQuery } from '@apollo/react-hooks' import { makeStyles } from '@material-ui/core' import Grid from '@material-ui/core/Grid' +import BigNumber from 'bignumber.js' +import classnames from 'classnames' import gql from 'graphql-tag' import * as R from 'ramda' -import React, { useState, useEffect } from 'react' +import React, { useState } from 'react' import { Label2 } from 'src/components/typography' import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg' -import { white, spacer } from 'src/styling/variables' import { fromNamespace } from 'src/utils/config' import styles from './Footer.styles' const GET_DATA = gql` query getData { - rates + cryptoRates cryptoCurrencies { code display @@ -26,23 +27,20 @@ const GET_DATA = gql` } } ` +BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_HALF_UP }) const useStyles = makeStyles(styles) const Footer = () => { const { data, loading } = useQuery(GET_DATA) const [expanded, setExpanded] = useState(false) - const [canExpand, setCanExpand] = useState(false) const [delayedExpand, setDelayedExpand] = useState(null) - const classes = useStyles() - useEffect(() => { - if (data && data.rates && data.rates.withCommissions) { - const numItems = R.keys(data.rates.withCommissions).length - if (numItems > 4) { - setCanExpand(true) - } - } - }, [data]) + const classes = useStyles({ + bigFooter: R.keys(data?.cryptoRates?.withCommissions).length < 8, + expanded + }) + + const canExpand = R.keys(data?.cryptoRates.withCommissions ?? []).length > 4 const wallets = fromNamespace('wallets')(data?.config) @@ -56,49 +54,51 @@ const Footer = () => { const tickerName = data.accountsConfig[tickerIdx].display const cashInNoCommission = parseFloat( - R.path(['rates', 'withoutCommissions', key, 'cashIn'])(data) + R.path(['cryptoRates', 'withoutCommissions', key, 'cashIn'])(data) ) const cashOutNoCommission = parseFloat( - R.path(['rates', 'withoutCommissions', key, 'cashOut'])(data) + R.path(['cryptoRates', 'withoutCommissions', key, 'cashOut'])(data) ) - // check https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round - // to see reason for this implementation. It makes 1.005 round to 1.01 and not 1 - // const monetaryValue = +(Math.round(askBidAvg + 'e+2') + 'e-2') - const avgOfAskBid = +( - Math.round((cashInNoCommission + cashOutNoCommission) / 2 + 'e+2') + 'e-2' + const avgOfAskBid = new BigNumber( + (cashInNoCommission + cashOutNoCommission) / 2 ) - const cashIn = +( - Math.round( - parseFloat(R.path(['rates', 'withCommissions', key, 'cashIn'])(data)) + - 'e+2' - ) + 'e-2' + .decimalPlaces(2) + .toNumber() + const cashIn = new BigNumber( + parseFloat( + R.path(['cryptoRates', 'withCommissions', key, 'cashIn'])(data) + ) ) - const cashOut = +( - Math.round( - parseFloat(R.path(['rates', 'withCommissions', key, 'cashOut'])(data)) + - 'e+2' - ) + 'e-2' + .decimalPlaces(2) + .toNumber() + const cashOut = new BigNumber( + parseFloat( + R.path(['cryptoRates', 'withCommissions', key, 'cashOut'])(data) + ) ) + .decimalPlaces(2) + .toNumber() const localeFiatCurrency = data.config.locale_fiatCurrency + const localeLanguage = data.config.locale_languages[0] return ( - + {data.cryptoCurrencies[idx].display}
-
+
{` ${cashIn.toLocaleString( - 'en-US' + localeLanguage )} ${localeFiatCurrency}`}
-
+
{` ${cashOut.toLocaleString( - 'en-US' + localeLanguage )} ${localeFiatCurrency}`}
@@ -106,70 +106,38 @@ const Footer = () => { className={ classes.tickerLabel }>{`${tickerName}: ${avgOfAskBid.toLocaleString( - 'en-US' + localeLanguage )} ${localeFiatCurrency}`} ) } - const makeFooterExpandedClass = () => { - return { - height: - R.keys(data.rates.withCommissions).length < 8 - ? spacer * 12 * 2 + spacer * 2 - : spacer * 12 * 3 + spacer * 3, - maxHeight: '50vh', - position: 'fixed', - left: 0, - bottom: 0, - width: '100vw', - backgroundColor: white, - textAlign: 'left' - } - } - - const expand = () => { - if (canExpand) { - setExpanded(true) - } - } - - const shrink = () => { - setExpanded(false) - } const handleMouseEnter = () => { - setDelayedExpand( - setTimeout(() => { - expand() - }, 300) - ) + setDelayedExpand(setTimeout(() => canExpand && setExpanded(true), 300)) } const handleMouseLeave = () => { clearTimeout(delayedExpand) - shrink() + setExpanded(false) } return ( - <> -
-
- {!loading && data && ( - - - {R.keys(data.rates.withCommissions).map(key => - renderFooterItem(key) - )} - +
+
+ {!loading && data && ( + + + {R.keys(data.cryptoRates.withCommissions).map(key => + renderFooterItem(key) + )} - )} -
+ + )}
- +
) } diff --git a/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.styles.js b/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.styles.js index 31032dbd..e6a9b657 100644 --- a/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.styles.js +++ b/new-lamassu-admin/src/pages/Dashboard/Footer/Footer.styles.js @@ -1,75 +1,39 @@ -import typographyStyles from 'src/components/typography/styles' -import { - backgroundColor, - offColor, - errorColor, - primaryColor, - white, - spacer -} from 'src/styling/variables' -const { label1 } = typographyStyles +import { offColor, white, spacer } from 'src/styling/variables' const styles = { label: { color: offColor }, - tickerLabel: { - color: offColor, - marginTop: -5 - }, - row: { - backgroundColor: backgroundColor, - borderBottom: 'none' - }, - header: { + headerLabels: { + whiteSpace: 'pre', display: 'flex', - alignItems: 'center', - whiteSpace: 'pre' + flexDirection: 'row', + marginTop: -20 }, - error: { - color: errorColor + headerLabel: { + display: 'flex', + alignItems: 'center' }, - button: { - color: primaryColor, - minHeight: 0, - minWidth: 0, - padding: 0, - textTransform: 'none', - '&:hover': { - backgroundColor: 'transparent' - } + txOutMargin: { + marginLeft: spacer * 3 }, - statusHeader: { - marginLeft: 2 - }, - table: { - maxHeight: 440, - '&::-webkit-scrollbar': { - width: 7 - }, - '&::-webkit-scrollbar-thumb': { - backgroundColor: offColor, - borderRadius: 5 - } - }, - tableBody: { - overflow: 'auto' - }, - h4: { - marginTop: 0 - }, - root: { - flexGrow: 1 - }, - footer: { + footer: ({ expanded, bigFooter }) => ({ + height: expanded + ? bigFooter + ? spacer * 12 * 2 + spacer * 2 + : spacer * 12 * 3 + spacer * 3 + : spacer * 12, position: 'fixed', left: 0, bottom: 0, width: '100vw', backgroundColor: white, textAlign: 'left', - height: spacer * 12, boxShadow: '0px -1px 10px 0px rgba(50, 50, 50, 0.1)' + }), + tickerLabel: { + color: offColor, + marginTop: -5 }, content: { width: 1200, @@ -77,26 +41,12 @@ const styles = { backgroundColor: white, marginTop: 4 }, - headerLabels: { - whiteSpace: 'pre', - display: 'flex', - flexDirection: 'row', - '& div': { - display: 'flex', - alignItems: 'center' - }, - '& > div:first-child': { - marginRight: 24 - }, - '& span': { - extend: label1, - marginLeft: 6 - }, - marginTop: -20 - }, footerContainer: { marginLeft: spacer * 5, marginBottom: spacer * 2 + }, + footerItemContainer: { + marginBottom: 18 } } diff --git a/new-lamassu-admin/src/pages/Dashboard/LeftSide.js b/new-lamassu-admin/src/pages/Dashboard/LeftSide.js index 5c0c956a..59188cdb 100644 --- a/new-lamassu-admin/src/pages/Dashboard/LeftSide.js +++ b/new-lamassu-admin/src/pages/Dashboard/LeftSide.js @@ -7,20 +7,18 @@ import SystemPerformance from './SystemPerformance' const useStyles = makeStyles(styles) -const RightSide = () => { +const LeftSide = () => { const classes = useStyles() return ( - <> - - -
- -
-
+ + +
+ +
- +
) } -export default RightSide +export default LeftSide diff --git a/new-lamassu-admin/src/pages/Dashboard/RightSide.js b/new-lamassu-admin/src/pages/Dashboard/RightSide.js index 5345c0d4..4a8ab0a4 100644 --- a/new-lamassu-admin/src/pages/Dashboard/RightSide.js +++ b/new-lamassu-admin/src/pages/Dashboard/RightSide.js @@ -1,49 +1,88 @@ +import Button from '@material-ui/core/Button' import Grid from '@material-ui/core/Grid' import { makeStyles } from '@material-ui/core/styles' import React, { useState } from 'react' +import CollapsibleCard, { cardState } from 'src/components/CollapsibleCard' +import { H4, Label1 } from 'src/components/typography' + // import Alerts from './Alerts' import styles from './Dashboard.styles' import SystemStatus from './SystemStatus' + const useStyles = makeStyles(styles) -const RightSide = () => { +const ShrunkCard = ({ title, buttonName, onUnshrink }) => { const classes = useStyles() + return ( +
+

{title}

+ <> + + + + +
+ ) +} - const [rightSideState, setRightSide] = useState({ - alerts: { - cardSize: 'default', - buttonName: 'Show less' - }, - systemStatus: { - cardSize: 'default', - buttonName: 'Show less' - } - }) +const RightSide = () => { + // const classes = useStyles() + const [systemStatusSize, setSystemStatusSize] = useState(cardState.DEFAULT) + // const [alertsSize, setAlertsSize] = useState(cardState.DEFAULT) - const setRightSideState = newState => { - setRightSide(newState) + const onReset = () => { + // setAlertsSize(cardState.DEFAULT) + setSystemStatusSize(cardState.DEFAULT) } return ( <> - {/* -
- -
-
*/} - -
- + { + setAlertsSize(cardState.EXPANDED) + setSystemStatusSize(cardState.SHRUNK) + }} + onReset={onReset} + size={alertsSize} + /> + */} + -
-
+ }> + { + setSystemStatusSize(cardState.EXPANDED) + // setAlertsSize(cardState.SHRUNK) + }} + onReset={onReset} + size={systemStatusSize} + /> +
) diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/PercentageChart.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/PercentageChart.js index 00ae0ff1..bd2873da 100644 --- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/PercentageChart.js +++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/PercentageChart.js @@ -1,4 +1,5 @@ import { makeStyles } from '@material-ui/core' +import classnames from 'classnames' import React from 'react' import { ReactComponent as CashIn } from 'src/styling/icons/direction/cash-in.svg' @@ -30,58 +31,46 @@ const styles = { fontFamily: fontSecondary, fontWeight: 700, color: fontColor - } + }, + cashIn: ({ value }) => ({ + width: `${value}%`, + marginRight: 4 + }), + cashOut: ({ value }) => ({ + width: `${100 - value}%` + }) } const useStyles = makeStyles(styles) -const PercentageChart = () => { - const classes = useStyles() - - const value = 50 +const PercentageChart = ({ cashIn, cashOut }) => { + const value = cashIn || cashOut !== 0 ? cashIn : 50 + const classes = useStyles({ value }) const buildPercentageView = (value, direction) => { - switch (direction) { - case 'cashIn': - if (value > 20) { - return ( - <> - - {` ${value}%`} - - ) - } - return null - case 'cashOut': - if (value > 20) { - return ( - <> - - {` ${value}%`} - - ) - } - return null - default: - return null + const Operation = direction === 'cashIn' ? CashIn : CashOut + if (value > 25) { + return ( + <> + + {value > 25 && {` ${value}%`}} + + ) + } + if (value >= 10) { + return } } return ( - <> -
-
- {buildPercentageView(value, 'cashIn')} -
-
- {buildPercentageView(100 - value, 'cashOut')} -
+
+
+ {buildPercentageView(value, 'cashIn')}
- +
+ {buildPercentageView(100 - value, 'cashOut')} +
+
) } diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefLineChart.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefLineChart.js index bf0f4d48..29d92d4f 100644 --- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefLineChart.js +++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefLineChart.js @@ -1,80 +1,66 @@ import * as d3 from 'd3' import * as R from 'ramda' -import React, { useEffect, useRef, useCallback, useState } from 'react' +import React, { useEffect, useRef, useCallback } from 'react' import { backgroundColor, zircon, primaryColor } from 'src/styling/variables' +const transactionProfit = tx => { + const cashInFee = tx.cashInFee ? Number.parseFloat(tx.cashInFee) : 0 + const commission = + Number.parseFloat(tx.commissionPercentage) * Number.parseFloat(tx.fiat) + return commission + cashInFee +} + +const mockPoint = tx => { + const date = new Date(tx.created) + date.setHours(date.getHours() - 1) + return { created: date.toISOString(), profit: tx.profit } +} + +// if we're viewing transactions for the past day, then we group by hour. If not, we group by day +const formatDay = ({ created }) => + new Date(created).toISOString().substring(0, 10) +const formatHour = ({ created }) => + new Date(created).toISOString().substring(0, 13) + +const reducer = (acc, tx) => { + const currentProfit = acc.profit || 0 + return { ...tx, profit: currentProfit + transactionProfit(tx) } +} + const RefLineChart = ({ data: realData, timeFrame }) => { const svgRef = useRef() - // this variable will flip to true if there's no data points or the profit is zero - // this will force the line graph to touch the x axis instead of centering, - // centering is bad because it gives the impression that there could be negative values - // so, if this is true the y domain should be [0, 0.1] - const [zeroProfit, setZeroProfit] = useState(false) - const drawGraph = useCallback(() => { const svg = d3.select(svgRef.current) const margin = { top: 0, right: 0, bottom: 0, left: 0 } const width = 336 - margin.left - margin.right const height = 128 - margin.top - margin.bottom - const transactionProfit = tx => { - let cashInFee = 0 - if (tx.cashInFee) { - cashInFee = Number.parseFloat(tx.cashInFee) - } - const commission = - Number.parseFloat(tx.commissionPercentage) * Number.parseFloat(tx.fiat) - return commission + cashInFee - } - const massageData = () => { - const methods = { - day: function(obj) { - return new Date(obj.created).toISOString().substring(0, 10) - }, - hour: function(obj) { - return new Date(obj.created).toISOString().substring(0, 13) - } - } + // if we're viewing transactions for the past day, then we group by hour. If not, we group by day + const method = timeFrame === 'Day' ? formatHour : formatDay - const method = timeFrame === 'Day' ? 'hour' : 'day' - const f = methods[method] - const groupedTx = R.values(R.groupBy(f)(realData)) - let aggregatedTX = groupedTx.map(list => { - const temp = { ...list[0], profit: transactionProfit(list[0]) } - if (list.length > 1) { - for (let i = 1; i < list.length; i++) { - temp.profit += transactionProfit(list[i]) - } - } - return temp - }) - - // if no point exists, then create a (0,0) point + const aggregatedTX = R.values(R.reduceBy(reducer, [], method, realData)) + // if no point exists, then return 2 points at y = 0 if (aggregatedTX.length === 0) { - setZeroProfit(true) - aggregatedTX = [{ created: new Date().toISOString(), profit: 0 }] - } else { - setZeroProfit(false) + const mockPoint1 = { created: new Date().toISOString(), profit: 0 } + const mockPoint2 = mockPoint(mockPoint1) + return [[mockPoint1, mockPoint2], true] } - // create point on the left if only one point exists, otherwise line won't be drawn + // if only one point exists, create point on the left - otherwise the line won't be drawn if (aggregatedTX.length === 1) { - const temp = { ...aggregatedTX[0] } - const date = new Date(temp.created) - date.setHours(date.getHours() - 1) - temp.created = date.toISOString() - aggregatedTX = [...aggregatedTX, temp] + return [R.append(mockPoint(aggregatedTX[0]), aggregatedTX), false] } - return aggregatedTX + // the boolean value is for zeroProfit. It makes the line render at y = 0 instead of y = 50% of container height + return [aggregatedTX, false] } /* Important step to make the graph look good! - This function groups transactions by either day or hour depending on the time grame + This function groups transactions by either day or hour depending on the time frame This makes the line look smooth and not all wonky when there are many transactions in a given time */ - const data = massageData() + const [data, zeroProfit] = massageData() // sets width of the graph svg.attr('width', width) @@ -162,7 +148,7 @@ const RefLineChart = ({ data: realData, timeFrame }) => { .attr('stroke-width', '2') .attr('stroke-linejoin', 'round') .attr('stroke', primaryColor) - }, [realData, timeFrame, zeroProfit]) + }, [realData, timeFrame]) useEffect(() => { // first we clear old chart DOM elements on component update diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js index e63bdc32..4d7da250 100644 --- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js +++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js @@ -18,21 +18,17 @@ const RefScatterplot = ({ data: realData, timeFrame }) => { // finds maximum value for the Y axis. Minimum value is 100. If value is multiple of 1000, add 100 // (this is because the Y axis looks best with multiples of 100) const findMaxY = () => { - if (realData.length === 0) { - return 100 - } - let maxY = d3.max(realData, t => parseFloat(t.fiat)) - maxY = 100 * Math.ceil(maxY / 100) - if (maxY < 100) { - return 100 - } else if (maxY % 1000 === 0) { - return maxY + 100 - } + if (realData.length === 0) return 100 + const maxvalueTx = + 100 * Math.ceil(d3.max(realData, t => parseFloat(t.fiat)) / 100) + const maxY = Math.max(100, maxvalueTx) + if (maxY % 1000 === 0) return maxY + 100 return maxY } // changes values of arguments in some d3 function calls to make the graph labels look good according to the selected time frame const findXAxisSettings = () => { + // case 'Day' or default const res = { nice: null, ticks: 4, @@ -41,11 +37,8 @@ const RefScatterplot = ({ data: realData, timeFrame }) => { timeRange: [50, 500] } switch (timeFrame) { - case 'Day': - return res case 'Week': return { - ...res, nice: 7, ticks: 7, subtractDays: 7, @@ -54,7 +47,6 @@ const RefScatterplot = ({ data: realData, timeFrame }) => { } case 'Month': return { - ...res, nice: 6, ticks: 6, subtractDays: 30, @@ -133,10 +125,8 @@ const RefScatterplot = ({ data: realData, timeFrame }) => { .ticks(xAxisSettings.ticks) .tickSize(0) .tickFormat(d3.timeFormat(xAxisSettings.timeFormat)) - // .tickFormat(d3.timeFormat('%H:%M')) ) .selectAll('text') - // .attr('dx', '4em') .attr('dy', '1.5em') // this is for the x axis line. It is the same color as the horizontal grid lines g.append('g') diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/Scatterplot.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/Scatterplot.js deleted file mode 100644 index ef08a169..00000000 --- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/Scatterplot.js +++ /dev/null @@ -1,134 +0,0 @@ -/*eslint-disable*/ -import { scaleLinear, scaleTime, max, axisLeft, axisBottom, select } from 'd3' -import React, { useMemo } from 'react' -import moment from 'moment' - -const data = [ - [0, '2020-11-08T18:00:05.664Z'], - [40.01301, '2020-11-09T11:17:05.664Z'] -] - -const marginTop = 10 -const marginRight = 30 -const marginBottom = 30 -const marginLeft = 60 -const width = 510 - marginLeft - marginRight -const height = 141 - marginTop - marginBottom - -const Scatterplot = ({ data: realData }) => { - const x = scaleTime() - .domain([ - moment() - .add(-1, 'day') - .valueOf(), - moment().valueOf() - ]) - .range([0, width]) - .nice() - - const y = scaleLinear() - .domain([0, 1000]) - .range([height, 0]) - .nice() - - // viewBox="0 0 540 141" - return ( - <> - - - - {axisLeft(y)} - {/* */} - - - - - ) -} - -const XAxis = ({ - range = [10, 500], - transform, - scale: xScale, - numTicks = 7 -}) => { - const ticks = useMemo(() => { - return xScale.ticks(numTicks).map(value => ({ - value, - xOffset: xScale(value) - })) - }, [range.join('-')]) - - return ( - - {ticks.map(({ value, xOffset }) => ( - - - {value.getHours()} - - - ))} - - ) -} - -const YAxis = ({ - range = [10, 500], - transform, - scale: xScale, - numTicks = 7 -}) => { - const ticks = useMemo(() => { - return xScale.ticks(numTicks).map(value => ({ - value, - xOffset: xScale(value) - })) - }, [range.join('-')]) - - return ( - - {ticks.map(({ value, xOffset }) => ( - - - {value} - - - ))} - - ) -} - -const RenderCircles = ({ data, scale }) => { - let renderCircles = data.map((item, idx) => { - return ( - - ) - }) - return {renderCircles} -} - -export default Scatterplot diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Nav.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Nav.js index 216c98fe..04c9bd7a 100644 --- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Nav.js +++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Nav.js @@ -1,21 +1,20 @@ -// import Button from '@material-ui/core/Button' import { makeStyles } from '@material-ui/core/styles' import classnames from 'classnames' +import * as R from 'ramda' import React, { useState } from 'react' import { H4 } from 'src/components/typography' import styles from './SystemPerformance.styles' + const useStyles = makeStyles(styles) +const ranges = ['Month', 'Week', 'Day'] const Nav = ({ handleSetRange }) => { const classes = useStyles() const [clickedItem, setClickedItem] = useState('Day') - const isSelected = innerText => { - return innerText === clickedItem - } - + const isSelected = R.equals(clickedItem) const handleClick = range => { setClickedItem(range) handleSetRange(range) @@ -26,34 +25,21 @@ const Nav = ({ handleSetRange }) => {

{'System performance'}

-
-
handleClick(e.target.innerText)} - className={ - isSelected('Month') - ? classnames(classes.newHighlightedLabel, classes.navButton) - : classnames(classes.label, classes.navButton) - }> - Month -
-
handleClick(e.target.innerText)} - className={ - isSelected('Week') - ? classnames(classes.newHighlightedLabel, classes.navButton) - : classnames(classes.label, classes.navButton) - }> - Week -
-
handleClick(e.target.innerText)}> - Day -
+
+ {ranges.map((it, idx) => { + return ( +
handleClick(e.target.innerText)} + className={ + isSelected(it) + ? classnames(classes.newHighlightedLabel, classes.navButton) + : classnames(classes.label, classes.navButton) + }> + {it} +
+ ) + })}
) diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js index 08ddf06a..fd23dce7 100644 --- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js +++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js @@ -1,10 +1,11 @@ import { useQuery } from '@apollo/react-hooks' import Grid from '@material-ui/core/Grid' import { makeStyles } from '@material-ui/core/styles' +import BigNumber from 'bignumber.js' import gql from 'graphql-tag' import moment from 'moment' import * as R from 'ramda' -import React, { useState, useEffect } from 'react' +import React, { useState } from 'react' import { Label2 } from 'src/components/typography/index' import { ReactComponent as TriangleDown } from 'src/styling/icons/arrow/triangle_down.svg' @@ -18,18 +19,31 @@ import InfoWithLabel from './InfoWithLabel' import Nav from './Nav' import styles from './SystemPerformance.styles' -const isNotProp = R.curry(R.compose(R.isNil, R.prop)) +BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_HALF_UP }) + const getFiats = R.map(R.prop('fiat')) -const getProps = propName => R.map(R.prop(propName)) const useStyles = makeStyles(styles) +const mapToFee = R.map(R.prop('cashInFee')) + const getDateSecondsAgo = (seconds = 0, startDate = null) => { - if (startDate) { - return moment(startDate).subtract(seconds, 'second') - } - return moment().subtract(seconds, 'second') + const date = startDate ? moment(startDate) : moment() + return date.subtract(seconds, 'second') } -// const now = moment() +const ranges = { + Day: { + left: getDateSecondsAgo(2 * 24 * 3600, moment()), + right: getDateSecondsAgo(24 * 3600, moment()) + }, + Week: { + left: getDateSecondsAgo(14 * 24 * 3600, moment()), + right: getDateSecondsAgo(7 * 24 * 3600, moment()) + }, + Month: { + left: getDateSecondsAgo(60 * 24 * 3600, moment()), + right: getDateSecondsAgo(30 * 24 * 3600, moment()) + } +} const GET_DATA = gql` query getData { @@ -42,7 +56,7 @@ const GET_DATA = gql` txClass error } - btcRates { + fiatRates { code name rate @@ -51,134 +65,72 @@ const GET_DATA = gql` } ` +const reducer = (acc, it) => + (acc += + Number.parseFloat(it.commissionPercentage) * Number.parseFloat(it.fiat)) + const SystemPerformance = () => { const classes = useStyles() - const [selectedRange, setSelectedRange] = useState('Day') - const [transactionsToShow, setTransactionsToShow] = useState([]) - const [transactionsLastTimePeriod, setTransactionsLastTimePeriod] = useState( - [] - ) - const { data, loading } = useQuery(GET_DATA) - const fiatLocale = fromNamespace('locale')(data?.config).fiatCurrency - useEffect(() => { - const isInRange = (getLastTimePeriod = false) => t => { - const now = moment() - switch (selectedRange) { - case 'Day': - if (getLastTimePeriod) { - return ( - t.error === null && - moment(t.created).isBetween( - getDateSecondsAgo(2 * 24 * 3600, now), - getDateSecondsAgo(24 * 3600, now) - ) - ) - } - return ( - t.error === null && - moment(t.created).isBetween(getDateSecondsAgo(24 * 3600, now), now) - ) - case 'Week': - if (getLastTimePeriod) { - return ( - t.error === null && - moment(t.created).isBetween( - getDateSecondsAgo(14 * 24 * 3600, now), - getDateSecondsAgo(7 * 24 * 3600, now) - ) - ) - } - return ( - t.error === null && - moment(t.created).isBetween( - getDateSecondsAgo(7 * 24 * 3600, now), - now - ) - ) - case 'Month': - if (getLastTimePeriod) { - return ( - t.error === null && - moment(t.created).isBetween( - getDateSecondsAgo(60 * 24 * 3600, now), - getDateSecondsAgo(30 * 24 * 3600, now) - ) - ) - } - return ( - t.error === null && - moment(t.created).isBetween( - getDateSecondsAgo(30 * 24 * 3600, now), - now - ) - ) - default: - return t.error === null && true - } + const isInRangeAndNoError = getLastTimePeriod => t => { + if (t.error !== null) return false + if (!getLastTimePeriod) { + return ( + t.error === null && + moment(t.created).isBetween(ranges[selectedRange].right, moment()) + ) } - - const convertFiatToLocale = item => { - if (item.fiatCode === fiatLocale) return item - const itemRate = R.find(R.propEq('code', item.fiatCode))(data.btcRates) - const localeRate = R.find(R.propEq('code', fiatLocale))(data.btcRates) - const multiplier = localeRate.rate / itemRate.rate - return { ...item, fiat: parseFloat(item.fiat) * multiplier } - } - - setTransactionsToShow( - R.map(convertFiatToLocale)( - R.filter(isInRange(false), data?.transactions ?? []) + return ( + t.error === null && + moment(t.created).isBetween( + ranges[selectedRange].left, + ranges[selectedRange].right ) ) - setTransactionsLastTimePeriod( - R.map(convertFiatToLocale)( - R.filter(isInRange(true), data?.transactions ?? []) - ) - ) - }, [data, fiatLocale, selectedRange]) - - const handleSetRange = range => { - setSelectedRange(range) } + const convertFiatToLocale = item => { + if (item.fiatCode === fiatLocale) return item + const itemRate = R.find(R.propEq('code', item.fiatCode))(data.fiatRates) + const localeRate = R.find(R.propEq('code', fiatLocale))(data.fiatRates) + const multiplier = localeRate.rate / itemRate.rate + return { ...item, fiat: parseFloat(item.fiat) * multiplier } + } + + const transactionsToShow = R.map(convertFiatToLocale)( + R.filter(isInRangeAndNoError(false), data?.transactions ?? []) + ) + const transactionsLastTimePeriod = R.map(convertFiatToLocale)( + R.filter(isInRangeAndNoError(true), data?.transactions ?? []) + ) + const getNumTransactions = () => { - return R.length(R.filter(isNotProp('error'), transactionsToShow)) + return R.length(transactionsToShow) } - const getFiatVolume = () => { - // for explanation check https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round - return +( - Math.round( - R.sum(getFiats(R.filter(isNotProp('error'), transactionsToShow))) + - 'e+2' - ) + 'e-2' - ) - } + const getFiatVolume = () => + new BigNumber(R.sum(getFiats(transactionsToShow))) + .decimalPlaces(2) + .toNumber() - const getProfit = (transactions = transactionsToShow) => { - const cashInFees = R.sum( - getProps('cashInFee')(R.filter(isNotProp('error'), transactions)) - ) - let commissionFees = 0 - transactions.forEach(t => { - if (t.error === null) { - commissionFees += - Number.parseFloat(t.commissionPercentage) * Number.parseFloat(t.fiat) - } - }) - return +(Math.round(commissionFees + cashInFees + 'e+2') + 'e-2') + const getProfit = transactions => { + const cashInFees = R.sum(mapToFee(transactions)) + const commissionFees = R.reduce(reducer, 0, transactions) + + return new BigNumber(commissionFees + cashInFees) + .decimalPlaces(2) + .toNumber() } const getPercentChange = () => { const thisTimePeriodProfit = getProfit(transactionsToShow) const previousTimePeriodProfit = getProfit(transactionsLastTimePeriod) - if (previousTimePeriodProfit === 0) { - return 100 - } + + if (previousTimePeriodProfit === 0) return 100 + return Math.round( (100 * (thisTimePeriodProfit - previousTimePeriodProfit)) / Math.abs(previousTimePeriodProfit) @@ -186,36 +138,17 @@ const SystemPerformance = () => { } const getDirectionPercent = () => { - const directions = { - cashIn: 0, - cashOut: 0, - length: 0 + const [cashIn, cashOut] = R.partition(R.propEq('txClass', 'cashIn'))( + transactionsToShow + ) + const totalLength = cashIn.length + cashOut.length + if (totalLength === 0) { + return { cashIn: 0, cashOut: 0 } } - transactionsToShow.forEach(t => { - if (t.error === null) { - switch (t.txClass) { - case 'cashIn': - directions.cashIn += 1 - directions.length += 1 - break - case 'cashOut': - directions.cashOut += 1 - directions.length += 1 - break - default: - break - } - } - }) + return { - cashIn: - directions.length > 0 - ? Math.round((directions.cashIn / directions.length) * 100) - : 0, - cashOut: - directions.length > 0 - ? Math.round((directions.cashOut / directions.length) * 100) - : 0 + cashIn: Math.round((cashIn.length / totalLength) * 100), + cashOut: Math.round((cashOut.length / totalLength) * 100) } } @@ -223,7 +156,7 @@ const SystemPerformance = () => { return ( <> -