Fix: make percentage chart work properly

Fix: code review

Fix: rework components according to PR requested changes

Fix: fix repeated code buildRatesNoCommission

Also renames id to deviceId in transactions quer

Fix: pr requested changes

Chore: move inline styles to classes

Chore: remove comment

Fix: bad equality !process.env.NODE_ENV === 'production'
This commit is contained in:
Cesar 2020-12-07 19:58:20 +00:00 committed by Josh Harvey
parent 5572fb0eb1
commit ae7eaca10c
43 changed files with 818 additions and 1578 deletions

5
lib/forex.js Normal file
View file

@ -0,0 +1,5 @@
const axios = require('axios')
const getFiatRates = () => axios.get('https://bitpay.com/api/rates').then(response => response.data)
module.exports = { getFiatRates }

View file

@ -18,12 +18,11 @@ const couponManager = require('../../coupons')
const serverVersion = require('../../../package.json').version const serverVersion = require('../../../package.json').version
const transactions = require('../transactions') const transactions = require('../transactions')
const funding = require('../funding') const funding = require('../funding')
const forex = require('../../forex')
const supervisor = require('../supervisor') const supervisor = require('../supervisor')
const serverLogs = require('../server-logs') const serverLogs = require('../server-logs')
const pairing = require('../pairing') const pairing = require('../pairing')
const plugins = require('../../plugins') const plugins = require('../../plugins')
const ticker = require('../../ticker')
const { const {
accounts: accountsConfig, accounts: accountsConfig,
@ -240,6 +239,8 @@ const typeDefs = gql`
created: Date created: Date
age: Float age: Float
deviceTime: Date deviceTime: Date
}
type Rate { type Rate {
code: String code: String
name: String name: String
@ -268,25 +269,22 @@ const typeDefs = gql`
until: Date until: Date
limit: Int limit: Int
offset: Int offset: Int
id: ID deviceId: ID
): [Transaction] ): [Transaction]
transactionsCsv(from: Date, until: Date, limit: Int, offset: Int): String transactionsCsv(from: Date, until: Date, limit: Int, offset: Int): String
accounts: JSONObject accounts: JSONObject
config: JSONObject config: JSONObject
blacklist: [Blacklist] blacklist: [Blacklist]
# userTokens: [UserToken] # userTokens: [UserToken]
<<<<<<< HEAD
coupons: [Coupon] coupons: [Coupon]
cryptoRates: JSONObject
fiatRates: [Rate]
} }
type SupportLogsResponse { type SupportLogsResponse {
id: ID! id: ID!
timestamp: Date! timestamp: Date!
deviceId: ID deviceId: ID
=======
rates: JSONObject
btcRates(to: String, from: String): [Rate]
>>>>>>> 9d88b4f... Feat: make dashboard and machine profile page
} }
enum MachineAction { enum MachineAction {
@ -356,15 +354,26 @@ const resolvers = {
serverLogs.getServerLogs(from, until, limit, offset), serverLogs.getServerLogs(from, until, limit, offset),
serverLogsCsv: (...[, { from, until, limit, offset }]) => serverLogsCsv: (...[, { from, until, limit, offset }]) =>
serverLogs.getServerLogs(from, until, limit, offset).then(parseAsync), serverLogs.getServerLogs(from, until, limit, offset).then(parseAsync),
transactions: (...[, { from, until, limit, offset, id }]) => transactions: (...[, { from, until, limit, offset, deviceId }]) =>
transactions.batch(from, until, limit, offset, id), transactions.batch(from, until, limit, offset, deviceId),
transactionsCsv: (...[, { from, until, limit, offset }]) => transactionsCsv: (...[, { from, until, limit, offset }]) =>
transactions.batch(from, until, limit, offset).then(parseAsync), transactions.batch(from, until, limit, offset).then(parseAsync),
config: () => settingsLoader.loadLatestConfigOrNone(), config: () => settingsLoader.loadLatestConfigOrNone(),
accounts: () => settingsLoader.loadAccounts(), accounts: () => settingsLoader.loadAccounts(),
blacklist: () => blacklist.getBlacklist(), blacklist: () => blacklist.getBlacklist(),
// userTokens: () => tokenManager.getTokenList() // 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: { Mutation: {
machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, newName }]) => machineAction({ deviceId, action, cashbox, cassette1, cassette2, newName }), machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, newName }]) => machineAction({ deviceId, action, cashbox, cassette1, cassette2, newName }),

View file

@ -24,19 +24,8 @@ function addNames (txs) {
const camelize = _.mapKeys(_.camelCase) const camelize = _.mapKeys(_.camelCase)
function batch ( function batch (from = new Date(0).toISOString(), until = new Date().toISOString(), limit = null, offset = 0, id = null) {
from = new Date(0).toISOString(), const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize), addNames)
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.*, const cashInSql = `select 'cashIn' as tx_class, txs.*,
c.phone as customer_phone, c.phone as customer_phone,
@ -47,7 +36,7 @@ function batch (
c.front_camera_path as customer_front_camera_path, c.front_camera_path as customer_front_camera_path,
c.id_card_photo_path as customer_id_card_photo_path, c.id_card_photo_path as customer_id_card_photo_path,
((not txs.send_confirmed) and (txs.created <= now() - interval $1)) as expired ((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 left outer join customers c on txs.customer_id = c.id
where txs.created >= $2 and txs.created <= $3 ${ where txs.created >= $2 and txs.created <= $3 ${
id !== null ? `and txs.device_id = $6` : `` id !== null ? `and txs.device_id = $6` : ``
@ -65,7 +54,7 @@ function batch (
c.front_camera_path as customer_front_camera_path, c.front_camera_path as customer_front_camera_path,
c.id_card_photo_path as customer_id_card_photo_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 (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 inner join cash_out_actions actions on txs.id = actions.tx_id
and actions.action = 'provisionAddress' and actions.action = 'provisionAddress'
left outer join customers c on txs.customer_id = c.id left outer join customers c on txs.customer_id = c.id

View file

@ -35,7 +35,8 @@ const PONG_TTL = '1 week'
const tradesQueues = {} const tradesQueues = {}
function plugins (settings, deviceId) { function plugins (settings, deviceId) {
function buildRatesNoCommission (tickers) {
function internalBuildRates (tickers, withCommission = true) {
const localeConfig = configManager.getLocale(deviceId, settings.config) const localeConfig = configManager.getLocale(deviceId, settings.config)
const cryptoCodes = localeConfig.cryptoCurrencies const cryptoCodes = localeConfig.cryptoCurrencies
@ -43,30 +44,7 @@ function plugins (settings, deviceId) {
cryptoCodes.forEach((cryptoCode, i) => { cryptoCodes.forEach((cryptoCode, i) => {
const rateRec = tickers[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 commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config)
const rateRec = tickers[i]
if (!rateRec) return 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) if (Date.now() - rateRec.timestamp > STALE_TICKER) return logger.warn('Stale rate for ' + cryptoCode)
const rate = rateRec.rates const rate = rateRec.rates
rates[cryptoCode] = {
withCommission ? rates[cryptoCode] = {
cashIn: rate.ask.mul(cashInCommission).round(5), cashIn: rate.ask.mul(cashInCommission).round(5),
cashOut: cashOutCommission && rate.bid.div(cashOutCommission).round(5) cashOut: cashOutCommission && rate.bid.div(cashOutCommission).round(5)
} : rates[cryptoCode] = {
cashIn: rate.ask.round(5),
cashOut: rate.bid.round(5)
} }
}) })
return rates return rates
} }
function buildRatesNoCommission (tickers) {
return internalBuildRates(tickers, false)
}
function buildRates (tickers) {
return internalBuildRates(tickers, true)
}
function getNotificationConfig () { function getNotificationConfig () {
return configManager.getGlobalNotifications(settings.config) return configManager.getGlobalNotifications(settings.config)
} }

View file

@ -2,7 +2,6 @@ const mem = require('mem')
const configManager = require('./new-config-manager') const configManager = require('./new-config-manager')
const ph = require('./plugin-helper') const ph = require('./plugin-helper')
const logger = require('./logger') const logger = require('./logger')
const axios = require('axios')
const lastRate = {} const lastRate = {}
@ -40,26 +39,4 @@ const getRates = mem(_getRates, {
cacheKey: (settings, fiatCode, cryptoCode) => JSON.stringify([fiatCode, cryptoCode]) cacheKey: (settings, fiatCode, cryptoCode) => JSON.stringify([fiatCode, cryptoCode])
}) })
const getBtcRates = (to = null, from = 'USD') => { module.exports = { getRates }
// 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 }

View file

@ -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 (
<div className={className}>
<Grid item>
<div className={classes.card}>
{state === cardState.SHRUNK ? shrunkComponent : children}
</div>
</Grid>
</div>
)
}
CollapsibleCard.propTypes = {
shrunkComponent: PropTypes.node.isRequired
}
export default CollapsibleCard
export { cardState }

View file

@ -1,12 +1,12 @@
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button'
import Grid from '@material-ui/core/Grid' import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles' 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 { Label1, H4 } from 'src/components/typography'
import styles from '../Dashboard.styles' import styles from './Alerts.styles'
import AlertsTable from './AlertsTable' import AlertsTable from './AlertsTable'
const NUM_TO_RENDER = 3 const NUM_TO_RENDER = 3
@ -23,99 +23,54 @@ const data = {
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const Alerts = ({ cardState, setRightSideState }) => { const Alerts = ({ onReset, onExpand, size }) => {
const classes = useStyles() const classes = useStyles()
const [showAllItems, setShowAllItems] = useState(false)
const [showExpandButton, setShowExpandButton] = useState(false)
const [numToRender, setNumToRender] = useState(NUM_TO_RENDER)
useEffect(() => { const showAllItems = size === cardState_.EXPANDED
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 reset = () => { const alertsLength = () => (data ? data.alerts.length : 0)
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' }
})
}
return ( return (
<> <>
<div <div className={classes.container}>
style={{ <H4 className={classes.h4}>{`Alerts (${alertsLength()})`}</H4>
display: 'flex', {showAllItems && (
justifyContent: 'space-between' <Label1 className={classes.upperButtonLabel}>
}}> <Button
<H4 className={classes.h4}>{`Alerts ${ onClick={onReset}
data ? `(${data.alerts.length})` : 0 size="small"
}`}</H4> disableRipple
{(showAllItems || cardState.cardSize === 'small') && ( disableFocusRipple
<> className={classes.button}>
<Label1 {'Show less'}
style={{ </Button>
textAlign: 'center', </Label1>
marginBottom: 0,
marginTop: 0
}}>
<Button
onClick={reset}
size="small"
disableRipple
disableFocusRipple
className={classes.button}>
{cardState.buttonName}
</Button>
</Label1>
</>
)} )}
</div> </div>
{cardState.cardSize !== 'small' && ( <>
<> <Grid container spacing={1}>
<Grid container spacing={1}> <Grid item xs={12}>
<Grid item xs={12}> <AlertsTable
<AlertsTable numToRender={showAllItems ? data?.alerts.length : NUM_TO_RENDER}
numToRender={numToRender} alerts={data?.alerts ?? []}
alerts={data?.alerts ?? []} />
/> {!showAllItems && (
{showExpandButton && ( <>
<> <Label1 className={classes.centerLabel}>
<Label1 style={{ textAlign: 'center', marginBottom: 0 }}> <Button
<Button onClick={() => onExpand('alerts')}
onClick={showAllClick} size="small"
size="small" disableRipple
disableRipple disableFocusRipple
disableFocusRipple className={classes.button}>
className={classes.button}> {`Show all (${data.alerts.length})`}
{`Show all (${data.alerts.length})`} </Button>
</Button> </Label1>
</Label1> </>
</> )}
)}
</Grid>
</Grid> </Grid>
</> </Grid>
)} </>
</> </>
) )
} }

View file

@ -6,6 +6,15 @@ import {
} from 'src/styling/variables' } from 'src/styling/variables'
const styles = { const styles = {
container: {
display: 'flex',
justifyContent: 'space-between'
},
centerLabel: {
textAlign: 'center',
marginBottom: 0,
marginTop: 0
},
label: { label: {
margin: 0, margin: 0,
color: offColor color: offColor

View file

@ -1,35 +1,21 @@
import { withStyles } from '@material-ui/core'
import List from '@material-ui/core/List' import List from '@material-ui/core/List'
import ListItem from '@material-ui/core/ListItem' import ListItem from '@material-ui/core/ListItem'
import ListItemText from '@material-ui/core/ListItemText' import ListItemText from '@material-ui/core/ListItemText'
import * as R from 'ramda'
import React from 'react' 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 AlertsTable = ({ numToRender, alerts }) => {
// const classes = useStyles() const alertsToRender = R.slice(0, numToRender, alerts)
return ( return (
<> <List dense>
<List dense> {alertsToRender.map((alert, idx) => {
{alerts.map((alert, idx) => { return (
if (idx < numToRender) { <ListItem key={idx}>
return ( <ListItemText primary={alert.text} />
<StyledListItem key={idx}> </ListItem>
<ListItemText primary={alert.text} /> )
</StyledListItem> })}
) </List>
} else return null
})}
</List>
</>
) )
} }

View file

@ -1,5 +1,6 @@
import Grid from '@material-ui/core/Grid' import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames'
import React from 'react' import React from 'react'
import TitleSection from 'src/components/layout/TitleSection' import TitleSection from 'src/components/layout/TitleSection'
@ -19,13 +20,17 @@ const Dashboard = () => {
<> <>
<TitleSection title="Dashboard"> <TitleSection title="Dashboard">
<div className={classes.headerLabels}> <div className={classes.headerLabels}>
<div> <div
className={classnames(
classes.headerLabelContainer,
classes.headerLabelContainerMargin
)}>
<TxOutIcon /> <TxOutIcon />
<span>Cash-out</span> <span className={classes.headerLabelSpan}>Cash-out</span>
</div> </div>
<div> <div className={classes.headerLabelContainer}>
<TxInIcon /> <TxInIcon />
<span>Cash-in</span> <span className={classes.headerLabelSpan}>Cash-in</span>
</div> </div>
</div> </div>
</TitleSection> </TitleSection>

View file

@ -3,6 +3,21 @@ import { spacer, white, primaryColor } from 'src/styling/variables'
const { label1 } = typographyStyles const { label1 } = typographyStyles
const styles = { const styles = {
headerLabels: {
display: 'flex',
flexDirection: 'row'
},
headerLabelContainerMargin: {
marginRight: 24
},
headerLabelContainer: {
display: 'flex',
alignItems: 'center'
},
headerLabelSpan: {
extend: label1,
marginLeft: 6
},
root: { root: {
flexGrow: 1, flexGrow: 1,
marginBottom: 108 marginBottom: 108
@ -17,37 +32,12 @@ const styles = {
padding: 24, padding: 24,
backgroundColor: white backgroundColor: white
}, },
h4: { leftSideMargin: {
margin: 0, marginRight: 24
marginRight: spacer * 8
}, },
label: { container: {
color: primaryColor,
minHeight: 0,
minWidth: 0,
padding: 0,
textTransform: 'none',
'&:hover': {
backgroundColor: 'transparent'
}
},
actionButton: {
marginTop: -4
},
headerLabels: {
display: 'flex', display: 'flex',
flexDirection: 'row', justifyContent: 'space-between'
'& div': {
display: 'flex',
alignItems: 'center'
},
'& > div:first-child': {
marginRight: 24
},
'& span': {
extend: label1,
marginLeft: 6
}
}, },
button: { button: {
color: primaryColor, color: primaryColor,
@ -58,6 +48,15 @@ const styles = {
'&:hover': { '&:hover': {
backgroundColor: 'transparent' backgroundColor: 'transparent'
} }
},
upperButtonLabel: {
textAlign: 'center',
marginBottom: 0,
marginTop: 16,
marginLeft: spacer
},
alertsCard: {
marginBottom: 16
} }
} }

View file

@ -1,20 +1,21 @@
import { useQuery } from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import Grid from '@material-ui/core/Grid' import Grid from '@material-ui/core/Grid'
import BigNumber from 'bignumber.js'
import classnames from 'classnames'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState, useEffect } from 'react' import React, { useState } from 'react'
import { Label2 } from 'src/components/typography' import { Label2 } from 'src/components/typography'
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' 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 { 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 { fromNamespace } from 'src/utils/config'
import styles from './Footer.styles' import styles from './Footer.styles'
const GET_DATA = gql` const GET_DATA = gql`
query getData { query getData {
rates cryptoRates
cryptoCurrencies { cryptoCurrencies {
code code
display display
@ -26,23 +27,20 @@ const GET_DATA = gql`
} }
} }
` `
BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_HALF_UP })
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const Footer = () => { const Footer = () => {
const { data, loading } = useQuery(GET_DATA) const { data, loading } = useQuery(GET_DATA)
const [expanded, setExpanded] = useState(false) const [expanded, setExpanded] = useState(false)
const [canExpand, setCanExpand] = useState(false)
const [delayedExpand, setDelayedExpand] = useState(null) const [delayedExpand, setDelayedExpand] = useState(null)
const classes = useStyles()
useEffect(() => { const classes = useStyles({
if (data && data.rates && data.rates.withCommissions) { bigFooter: R.keys(data?.cryptoRates?.withCommissions).length < 8,
const numItems = R.keys(data.rates.withCommissions).length expanded
if (numItems > 4) { })
setCanExpand(true)
} const canExpand = R.keys(data?.cryptoRates.withCommissions ?? []).length > 4
}
}, [data])
const wallets = fromNamespace('wallets')(data?.config) const wallets = fromNamespace('wallets')(data?.config)
@ -56,49 +54,51 @@ const Footer = () => {
const tickerName = data.accountsConfig[tickerIdx].display const tickerName = data.accountsConfig[tickerIdx].display
const cashInNoCommission = parseFloat( const cashInNoCommission = parseFloat(
R.path(['rates', 'withoutCommissions', key, 'cashIn'])(data) R.path(['cryptoRates', 'withoutCommissions', key, 'cashIn'])(data)
) )
const cashOutNoCommission = parseFloat( 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 const avgOfAskBid = new BigNumber(
// to see reason for this implementation. It makes 1.005 round to 1.01 and not 1 (cashInNoCommission + cashOutNoCommission) / 2
// const monetaryValue = +(Math.round(askBidAvg + 'e+2') + 'e-2')
const avgOfAskBid = +(
Math.round((cashInNoCommission + cashOutNoCommission) / 2 + 'e+2') + 'e-2'
) )
const cashIn = +( .decimalPlaces(2)
Math.round( .toNumber()
parseFloat(R.path(['rates', 'withCommissions', key, 'cashIn'])(data)) + const cashIn = new BigNumber(
'e+2' parseFloat(
) + 'e-2' R.path(['cryptoRates', 'withCommissions', key, 'cashIn'])(data)
)
) )
const cashOut = +( .decimalPlaces(2)
Math.round( .toNumber()
parseFloat(R.path(['rates', 'withCommissions', key, 'cashOut'])(data)) + const cashOut = new BigNumber(
'e+2' parseFloat(
) + 'e-2' R.path(['cryptoRates', 'withCommissions', key, 'cashOut'])(data)
)
) )
.decimalPlaces(2)
.toNumber()
const localeFiatCurrency = data.config.locale_fiatCurrency const localeFiatCurrency = data.config.locale_fiatCurrency
const localeLanguage = data.config.locale_languages[0]
return ( return (
<Grid key={key} item xs={3} style={{ marginBottom: 18 }}> <Grid key={key} item xs={3} className={classes.footerItemContainer}>
<Label2 className={classes.label}> <Label2 className={classes.label}>
{data.cryptoCurrencies[idx].display} {data.cryptoCurrencies[idx].display}
</Label2> </Label2>
<div className={classes.headerLabels}> <div className={classes.headerLabels}>
<div> <div className={classes.headerLabel}>
<TxInIcon /> <TxInIcon />
<Label2>{` ${cashIn.toLocaleString( <Label2>{` ${cashIn.toLocaleString(
'en-US' localeLanguage
)} ${localeFiatCurrency}`}</Label2> )} ${localeFiatCurrency}`}</Label2>
</div> </div>
<div> <div className={classnames(classes.headerLabel, classes.txOutMargin)}>
<TxOutIcon /> <TxOutIcon />
<Label2>{` ${cashOut.toLocaleString( <Label2>{` ${cashOut.toLocaleString(
'en-US' localeLanguage
)} ${localeFiatCurrency}`}</Label2> )} ${localeFiatCurrency}`}</Label2>
</div> </div>
</div> </div>
@ -106,70 +106,38 @@ const Footer = () => {
className={ className={
classes.tickerLabel classes.tickerLabel
}>{`${tickerName}: ${avgOfAskBid.toLocaleString( }>{`${tickerName}: ${avgOfAskBid.toLocaleString(
'en-US' localeLanguage
)} ${localeFiatCurrency}`}</Label2> )} ${localeFiatCurrency}`}</Label2>
</Grid> </Grid>
) )
} }
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 = () => { const handleMouseEnter = () => {
setDelayedExpand( setDelayedExpand(setTimeout(() => canExpand && setExpanded(true), 300))
setTimeout(() => {
expand()
}, 300)
)
} }
const handleMouseLeave = () => { const handleMouseLeave = () => {
clearTimeout(delayedExpand) clearTimeout(delayedExpand)
shrink() setExpanded(false)
} }
return ( return (
<> <div
<div onMouseEnter={handleMouseEnter}
onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}
onMouseLeave={handleMouseLeave} className={classes.footer}>
className={classes.footer} <div className={classes.content}>
style={expanded ? makeFooterExpandedClass() : null}> {!loading && data && (
<div className={classes.content}> <Grid container spacing={1}>
{!loading && data && ( <Grid container className={classes.footerContainer}>
<Grid container spacing={1}> {R.keys(data.cryptoRates.withCommissions).map(key =>
<Grid container className={classes.footerContainer}> renderFooterItem(key)
{R.keys(data.rates.withCommissions).map(key => )}
renderFooterItem(key)
)}
</Grid>
</Grid> </Grid>
)} </Grid>
</div> )}
</div> </div>
</> </div>
) )
} }

View file

@ -1,75 +1,39 @@
import typographyStyles from 'src/components/typography/styles' import { offColor, white, spacer } from 'src/styling/variables'
import {
backgroundColor,
offColor,
errorColor,
primaryColor,
white,
spacer
} from 'src/styling/variables'
const { label1 } = typographyStyles
const styles = { const styles = {
label: { label: {
color: offColor color: offColor
}, },
tickerLabel: { headerLabels: {
color: offColor, whiteSpace: 'pre',
marginTop: -5
},
row: {
backgroundColor: backgroundColor,
borderBottom: 'none'
},
header: {
display: 'flex', display: 'flex',
alignItems: 'center', flexDirection: 'row',
whiteSpace: 'pre' marginTop: -20
}, },
error: { headerLabel: {
color: errorColor display: 'flex',
alignItems: 'center'
}, },
button: { txOutMargin: {
color: primaryColor, marginLeft: spacer * 3
minHeight: 0,
minWidth: 0,
padding: 0,
textTransform: 'none',
'&:hover': {
backgroundColor: 'transparent'
}
}, },
statusHeader: { footer: ({ expanded, bigFooter }) => ({
marginLeft: 2 height: expanded
}, ? bigFooter
table: { ? spacer * 12 * 2 + spacer * 2
maxHeight: 440, : spacer * 12 * 3 + spacer * 3
'&::-webkit-scrollbar': { : spacer * 12,
width: 7
},
'&::-webkit-scrollbar-thumb': {
backgroundColor: offColor,
borderRadius: 5
}
},
tableBody: {
overflow: 'auto'
},
h4: {
marginTop: 0
},
root: {
flexGrow: 1
},
footer: {
position: 'fixed', position: 'fixed',
left: 0, left: 0,
bottom: 0, bottom: 0,
width: '100vw', width: '100vw',
backgroundColor: white, backgroundColor: white,
textAlign: 'left', textAlign: 'left',
height: spacer * 12,
boxShadow: '0px -1px 10px 0px rgba(50, 50, 50, 0.1)' boxShadow: '0px -1px 10px 0px rgba(50, 50, 50, 0.1)'
}),
tickerLabel: {
color: offColor,
marginTop: -5
}, },
content: { content: {
width: 1200, width: 1200,
@ -77,26 +41,12 @@ const styles = {
backgroundColor: white, backgroundColor: white,
marginTop: 4 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: { footerContainer: {
marginLeft: spacer * 5, marginLeft: spacer * 5,
marginBottom: spacer * 2 marginBottom: spacer * 2
},
footerItemContainer: {
marginBottom: 18
} }
} }

View file

@ -7,20 +7,18 @@ import SystemPerformance from './SystemPerformance'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const RightSide = () => { const LeftSide = () => {
const classes = useStyles() const classes = useStyles()
return ( return (
<> <Grid item xs={6}>
<Grid item xs={6}> <Grid item className={classes.leftSideMargin}>
<Grid item style={{ marginRight: 24 }}> <div className={classes.card}>
<div className={classes.card}> <SystemPerformance />
<SystemPerformance /> </div>
</div>
</Grid>
</Grid> </Grid>
</> </Grid>
) )
} }
export default RightSide export default LeftSide

View file

@ -1,49 +1,88 @@
import Button from '@material-ui/core/Button'
import Grid from '@material-ui/core/Grid' import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import React, { useState } from 'react' import React, { useState } from 'react'
import CollapsibleCard, { cardState } from 'src/components/CollapsibleCard'
import { H4, Label1 } from 'src/components/typography'
// import Alerts from './Alerts' // import Alerts from './Alerts'
import styles from './Dashboard.styles' import styles from './Dashboard.styles'
import SystemStatus from './SystemStatus' import SystemStatus from './SystemStatus'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const RightSide = () => { const ShrunkCard = ({ title, buttonName, onUnshrink }) => {
const classes = useStyles() const classes = useStyles()
return (
<div className={classes.container}>
<H4 className={classes.h4}>{title}</H4>
<>
<Label1 className={classes.upperButtonLabel}>
<Button
onClick={onUnshrink}
size="small"
disableRipple
disableFocusRipple
className={classes.button}>
{buttonName}
</Button>
</Label1>
</>
</div>
)
}
const [rightSideState, setRightSide] = useState({ const RightSide = () => {
alerts: { // const classes = useStyles()
cardSize: 'default', const [systemStatusSize, setSystemStatusSize] = useState(cardState.DEFAULT)
buttonName: 'Show less' // const [alertsSize, setAlertsSize] = useState(cardState.DEFAULT)
},
systemStatus: {
cardSize: 'default',
buttonName: 'Show less'
}
})
const setRightSideState = newState => { const onReset = () => {
setRightSide(newState) // setAlertsSize(cardState.DEFAULT)
setSystemStatusSize(cardState.DEFAULT)
} }
return ( return (
<> <>
<Grid item xs={6}> <Grid item xs={6}>
{/* <Grid item style={{ marginBottom: 16 }}> {/* <CollapsibleCard
<div className={classes.card}> className={classes.alertsCard}
<Alerts state={alertsSize}
cardState={rightSideState.alerts} shrunkComponent={
setRightSideState={setRightSideState} <ShrunkCard
title={'Alerts'}
buttonName={'Show alerts'}
onUnshrink={onReset}
/> />
</div> }>
</Grid> */} <Alerts
<Grid item> onExpand={() => {
<div className={classes.card}> setAlertsSize(cardState.EXPANDED)
<SystemStatus setSystemStatusSize(cardState.SHRUNK)
cardState={rightSideState.systemStatus} }}
setRightSideState={setRightSideState} onReset={onReset}
size={alertsSize}
/>
</CollapsibleCard> */}
<CollapsibleCard
state={systemStatusSize}
shrunkComponent={
<ShrunkCard
title={'System status'}
buttonName={'Show machines'}
onUnshrink={onReset}
/> />
</div> }>
</Grid> <SystemStatus
onExpand={() => {
setSystemStatusSize(cardState.EXPANDED)
// setAlertsSize(cardState.SHRUNK)
}}
onReset={onReset}
size={systemStatusSize}
/>
</CollapsibleCard>
</Grid> </Grid>
</> </>
) )

View file

@ -1,4 +1,5 @@
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import classnames from 'classnames'
import React from 'react' import React from 'react'
import { ReactComponent as CashIn } from 'src/styling/icons/direction/cash-in.svg' import { ReactComponent as CashIn } from 'src/styling/icons/direction/cash-in.svg'
@ -30,58 +31,46 @@ const styles = {
fontFamily: fontSecondary, fontFamily: fontSecondary,
fontWeight: 700, fontWeight: 700,
color: fontColor color: fontColor
} },
cashIn: ({ value }) => ({
width: `${value}%`,
marginRight: 4
}),
cashOut: ({ value }) => ({
width: `${100 - value}%`
})
} }
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const PercentageChart = () => { const PercentageChart = ({ cashIn, cashOut }) => {
const classes = useStyles() const value = cashIn || cashOut !== 0 ? cashIn : 50
const classes = useStyles({ value })
const value = 50
const buildPercentageView = (value, direction) => { const buildPercentageView = (value, direction) => {
switch (direction) { const Operation = direction === 'cashIn' ? CashIn : CashOut
case 'cashIn': if (value > 25) {
if (value > 20) { return (
return ( <>
<> <Operation />
<CashIn /> {value > 25 && <span className={classes.label}>{` ${value}%`}</span>}
<span className={classes.label}>{` ${value}%`}</span> </>
</> )
) }
} if (value >= 10) {
return null return <Operation />
case 'cashOut':
if (value > 20) {
return (
<>
<CashOut />
<span className={classes.label}>{` ${value}%`}</span>
</>
)
}
return null
default:
return null
} }
} }
return ( return (
<> <div className={classes.wrapper}>
<div className={classes.wrapper}> <div className={classnames(classes.percentageBox, classes.cashIn)}>
<div {buildPercentageView(value, 'cashIn')}
className={classes.percentageBox}
style={{ width: `${value}%`, marginRight: 4 }}>
{buildPercentageView(value, 'cashIn')}
</div>
<div
className={classes.percentageBox}
style={{ width: `${100 - value}%` }}>
{buildPercentageView(100 - value, 'cashOut')}
</div>
</div> </div>
</> <div className={classnames(classes.percentageBox, classes.cashOut)}>
{buildPercentageView(100 - value, 'cashOut')}
</div>
</div>
) )
} }

View file

@ -1,80 +1,66 @@
import * as d3 from 'd3' import * as d3 from 'd3'
import * as R from 'ramda' 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' 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 RefLineChart = ({ data: realData, timeFrame }) => {
const svgRef = useRef() 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 drawGraph = useCallback(() => {
const svg = d3.select(svgRef.current) const svg = d3.select(svgRef.current)
const margin = { top: 0, right: 0, bottom: 0, left: 0 } const margin = { top: 0, right: 0, bottom: 0, left: 0 }
const width = 336 - margin.left - margin.right const width = 336 - margin.left - margin.right
const height = 128 - margin.top - margin.bottom 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 massageData = () => {
const methods = { // if we're viewing transactions for the past day, then we group by hour. If not, we group by day
day: function(obj) { const method = timeFrame === 'Day' ? formatHour : formatDay
return new Date(obj.created).toISOString().substring(0, 10)
},
hour: function(obj) {
return new Date(obj.created).toISOString().substring(0, 13)
}
}
const method = timeFrame === 'Day' ? 'hour' : 'day' const aggregatedTX = R.values(R.reduceBy(reducer, [], method, realData))
const f = methods[method] // if no point exists, then return 2 points at y = 0
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
if (aggregatedTX.length === 0) { if (aggregatedTX.length === 0) {
setZeroProfit(true) const mockPoint1 = { created: new Date().toISOString(), profit: 0 }
aggregatedTX = [{ created: new Date().toISOString(), profit: 0 }] const mockPoint2 = mockPoint(mockPoint1)
} else { return [[mockPoint1, mockPoint2], true]
setZeroProfit(false)
} }
// 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) { if (aggregatedTX.length === 1) {
const temp = { ...aggregatedTX[0] } return [R.append(mockPoint(aggregatedTX[0]), aggregatedTX), false]
const date = new Date(temp.created)
date.setHours(date.getHours() - 1)
temp.created = date.toISOString()
aggregatedTX = [...aggregatedTX, temp]
} }
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! /* 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 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 // sets width of the graph
svg.attr('width', width) svg.attr('width', width)
@ -162,7 +148,7 @@ const RefLineChart = ({ data: realData, timeFrame }) => {
.attr('stroke-width', '2') .attr('stroke-width', '2')
.attr('stroke-linejoin', 'round') .attr('stroke-linejoin', 'round')
.attr('stroke', primaryColor) .attr('stroke', primaryColor)
}, [realData, timeFrame, zeroProfit]) }, [realData, timeFrame])
useEffect(() => { useEffect(() => {
// first we clear old chart DOM elements on component update // first we clear old chart DOM elements on component update

View file

@ -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 // 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) // (this is because the Y axis looks best with multiples of 100)
const findMaxY = () => { const findMaxY = () => {
if (realData.length === 0) { if (realData.length === 0) return 100
return 100 const maxvalueTx =
} 100 * Math.ceil(d3.max(realData, t => parseFloat(t.fiat)) / 100)
let maxY = d3.max(realData, t => parseFloat(t.fiat)) const maxY = Math.max(100, maxvalueTx)
maxY = 100 * Math.ceil(maxY / 100) if (maxY % 1000 === 0) return maxY + 100
if (maxY < 100) {
return 100
} else if (maxY % 1000 === 0) {
return maxY + 100
}
return maxY return maxY
} }
// changes values of arguments in some d3 function calls to make the graph labels look good according to the selected time frame // changes values of arguments in some d3 function calls to make the graph labels look good according to the selected time frame
const findXAxisSettings = () => { const findXAxisSettings = () => {
// case 'Day' or default
const res = { const res = {
nice: null, nice: null,
ticks: 4, ticks: 4,
@ -41,11 +37,8 @@ const RefScatterplot = ({ data: realData, timeFrame }) => {
timeRange: [50, 500] timeRange: [50, 500]
} }
switch (timeFrame) { switch (timeFrame) {
case 'Day':
return res
case 'Week': case 'Week':
return { return {
...res,
nice: 7, nice: 7,
ticks: 7, ticks: 7,
subtractDays: 7, subtractDays: 7,
@ -54,7 +47,6 @@ const RefScatterplot = ({ data: realData, timeFrame }) => {
} }
case 'Month': case 'Month':
return { return {
...res,
nice: 6, nice: 6,
ticks: 6, ticks: 6,
subtractDays: 30, subtractDays: 30,
@ -133,10 +125,8 @@ const RefScatterplot = ({ data: realData, timeFrame }) => {
.ticks(xAxisSettings.ticks) .ticks(xAxisSettings.ticks)
.tickSize(0) .tickSize(0)
.tickFormat(d3.timeFormat(xAxisSettings.timeFormat)) .tickFormat(d3.timeFormat(xAxisSettings.timeFormat))
// .tickFormat(d3.timeFormat('%H:%M'))
) )
.selectAll('text') .selectAll('text')
// .attr('dx', '4em')
.attr('dy', '1.5em') .attr('dy', '1.5em')
// this is for the x axis line. It is the same color as the horizontal grid lines // this is for the x axis line. It is the same color as the horizontal grid lines
g.append('g') g.append('g')

View file

@ -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 (
<>
<svg
width={width + marginLeft + marginRight}
height={height + marginTop + marginBottom}>
<g transform={`translate(${marginLeft},${marginTop})`}>
<XAxis
transform={`translate(0, ${height + marginTop})`}
scale={x}
numTicks={6}
/>
<g>{axisLeft(y)}</g>
{/* <YAxis transform={`translate(0, 0)`} scale={y} numTicks={6} /> */}
<RenderCircles data={data} scale={{ x, y }} />
</g>
</svg>
</>
)
}
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 (
<g transform={transform}>
{ticks.map(({ value, xOffset }) => (
<g key={value} transform={`translate(${xOffset}, 0)`}>
<text
key={value}
style={{
fontSize: '10px',
textAnchor: 'middle',
transform: 'translateY(10px)'
}}>
{value.getHours()}
</text>
</g>
))}
</g>
)
}
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 (
<g transform={transform}>
{ticks.map(({ value, xOffset }) => (
<g key={value} transform={`translate(0, ${xOffset})`}>
<text
key={value}
style={{
fontSize: '10px',
textAnchor: 'middle',
transform: 'translateX(-10px)'
}}>
{value}
</text>
</g>
))}
</g>
)
}
const RenderCircles = ({ data, scale }) => {
let renderCircles = data.map((item, idx) => {
return (
<circle
cx={scale.x(new Date(item[1]))}
cy={scale.y(item[0])}
r="4"
style={{ fill: 'rgba(25, 158, 199, .9)' }}
key={idx}
/>
)
})
return <g>{renderCircles}</g>
}
export default Scatterplot

View file

@ -1,21 +1,20 @@
// import Button from '@material-ui/core/Button'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames' import classnames from 'classnames'
import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import { H4 } from 'src/components/typography' import { H4 } from 'src/components/typography'
import styles from './SystemPerformance.styles' import styles from './SystemPerformance.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const ranges = ['Month', 'Week', 'Day']
const Nav = ({ handleSetRange }) => { const Nav = ({ handleSetRange }) => {
const classes = useStyles() const classes = useStyles()
const [clickedItem, setClickedItem] = useState('Day') const [clickedItem, setClickedItem] = useState('Day')
const isSelected = innerText => { const isSelected = R.equals(clickedItem)
return innerText === clickedItem
}
const handleClick = range => { const handleClick = range => {
setClickedItem(range) setClickedItem(range)
handleSetRange(range) handleSetRange(range)
@ -26,34 +25,21 @@ const Nav = ({ handleSetRange }) => {
<div className={classes.titleAndButtonsContainer}> <div className={classes.titleAndButtonsContainer}>
<H4 className={classes.h4}>{'System performance'}</H4> <H4 className={classes.h4}>{'System performance'}</H4>
</div> </div>
<div style={{ display: 'flex' }}> <div className={classes.navContainer}>
<div {ranges.map((it, idx) => {
onClick={e => handleClick(e.target.innerText)} return (
className={ <div
isSelected('Month') key={idx}
? classnames(classes.newHighlightedLabel, classes.navButton) onClick={e => handleClick(e.target.innerText)}
: classnames(classes.label, classes.navButton) className={
}> isSelected(it)
Month ? classnames(classes.newHighlightedLabel, classes.navButton)
</div> : classnames(classes.label, classes.navButton)
<div }>
onClick={e => handleClick(e.target.innerText)} {it}
className={ </div>
isSelected('Week') )
? classnames(classes.newHighlightedLabel, classes.navButton) })}
: classnames(classes.label, classes.navButton)
}>
Week
</div>
<div
className={
isSelected('Day')
? classnames(classes.newHighlightedLabel, classes.navButton)
: classnames(classes.label, classes.navButton)
}
onClick={e => handleClick(e.target.innerText)}>
Day
</div>
</div> </div>
</div> </div>
) )

View file

@ -1,10 +1,11 @@
import { useQuery } from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
import Grid from '@material-ui/core/Grid' import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import BigNumber from 'bignumber.js'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState, useEffect } from 'react' import React, { useState } from 'react'
import { Label2 } from 'src/components/typography/index' import { Label2 } from 'src/components/typography/index'
import { ReactComponent as TriangleDown } from 'src/styling/icons/arrow/triangle_down.svg' 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 Nav from './Nav'
import styles from './SystemPerformance.styles' 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 getFiats = R.map(R.prop('fiat'))
const getProps = propName => R.map(R.prop(propName))
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const mapToFee = R.map(R.prop('cashInFee'))
const getDateSecondsAgo = (seconds = 0, startDate = null) => { const getDateSecondsAgo = (seconds = 0, startDate = null) => {
if (startDate) { const date = startDate ? moment(startDate) : moment()
return moment(startDate).subtract(seconds, 'second') return date.subtract(seconds, 'second')
}
return moment().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` const GET_DATA = gql`
query getData { query getData {
@ -42,7 +56,7 @@ const GET_DATA = gql`
txClass txClass
error error
} }
btcRates { fiatRates {
code code
name name
rate 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 SystemPerformance = () => {
const classes = useStyles() const classes = useStyles()
const [selectedRange, setSelectedRange] = useState('Day') const [selectedRange, setSelectedRange] = useState('Day')
const [transactionsToShow, setTransactionsToShow] = useState([])
const [transactionsLastTimePeriod, setTransactionsLastTimePeriod] = useState(
[]
)
const { data, loading } = useQuery(GET_DATA) const { data, loading } = useQuery(GET_DATA)
const fiatLocale = fromNamespace('locale')(data?.config).fiatCurrency const fiatLocale = fromNamespace('locale')(data?.config).fiatCurrency
useEffect(() => { const isInRangeAndNoError = getLastTimePeriod => t => {
const isInRange = (getLastTimePeriod = false) => t => { if (t.error !== null) return false
const now = moment() if (!getLastTimePeriod) {
switch (selectedRange) { return (
case 'Day': t.error === null &&
if (getLastTimePeriod) { moment(t.created).isBetween(ranges[selectedRange].right, moment())
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
}
} }
return (
const convertFiatToLocale = item => { t.error === null &&
if (item.fiatCode === fiatLocale) return item moment(t.created).isBetween(
const itemRate = R.find(R.propEq('code', item.fiatCode))(data.btcRates) ranges[selectedRange].left,
const localeRate = R.find(R.propEq('code', fiatLocale))(data.btcRates) ranges[selectedRange].right
const multiplier = localeRate.rate / itemRate.rate
return { ...item, fiat: parseFloat(item.fiat) * multiplier }
}
setTransactionsToShow(
R.map(convertFiatToLocale)(
R.filter(isInRange(false), data?.transactions ?? [])
) )
) )
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 = () => { const getNumTransactions = () => {
return R.length(R.filter(isNotProp('error'), transactionsToShow)) return R.length(transactionsToShow)
} }
const getFiatVolume = () => { const getFiatVolume = () =>
// for explanation check https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round new BigNumber(R.sum(getFiats(transactionsToShow)))
return +( .decimalPlaces(2)
Math.round( .toNumber()
R.sum(getFiats(R.filter(isNotProp('error'), transactionsToShow))) +
'e+2'
) + 'e-2'
)
}
const getProfit = (transactions = transactionsToShow) => { const getProfit = transactions => {
const cashInFees = R.sum( const cashInFees = R.sum(mapToFee(transactions))
getProps('cashInFee')(R.filter(isNotProp('error'), transactions)) const commissionFees = R.reduce(reducer, 0, transactions)
)
let commissionFees = 0 return new BigNumber(commissionFees + cashInFees)
transactions.forEach(t => { .decimalPlaces(2)
if (t.error === null) { .toNumber()
commissionFees +=
Number.parseFloat(t.commissionPercentage) * Number.parseFloat(t.fiat)
}
})
return +(Math.round(commissionFees + cashInFees + 'e+2') + 'e-2')
} }
const getPercentChange = () => { const getPercentChange = () => {
const thisTimePeriodProfit = getProfit(transactionsToShow) const thisTimePeriodProfit = getProfit(transactionsToShow)
const previousTimePeriodProfit = getProfit(transactionsLastTimePeriod) const previousTimePeriodProfit = getProfit(transactionsLastTimePeriod)
if (previousTimePeriodProfit === 0) {
return 100 if (previousTimePeriodProfit === 0) return 100
}
return Math.round( return Math.round(
(100 * (thisTimePeriodProfit - previousTimePeriodProfit)) / (100 * (thisTimePeriodProfit - previousTimePeriodProfit)) /
Math.abs(previousTimePeriodProfit) Math.abs(previousTimePeriodProfit)
@ -186,36 +138,17 @@ const SystemPerformance = () => {
} }
const getDirectionPercent = () => { const getDirectionPercent = () => {
const directions = { const [cashIn, cashOut] = R.partition(R.propEq('txClass', 'cashIn'))(
cashIn: 0, transactionsToShow
cashOut: 0, )
length: 0 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 { return {
cashIn: cashIn: Math.round((cashIn.length / totalLength) * 100),
directions.length > 0 cashOut: Math.round((cashOut.length / totalLength) * 100)
? Math.round((directions.cashIn / directions.length) * 100)
: 0,
cashOut:
directions.length > 0
? Math.round((directions.cashOut / directions.length) * 100)
: 0
} }
} }
@ -223,7 +156,7 @@ const SystemPerformance = () => {
return ( return (
<> <>
<Nav handleSetRange={handleSetRange} /> <Nav handleSetRange={setSelectedRange} />
{!loading && ( {!loading && (
<> <>
<Grid container spacing={2}> <Grid container spacing={2}>
@ -241,7 +174,7 @@ const SystemPerformance = () => {
</Grid> </Grid>
{/* todo new customers */} {/* todo new customers */}
</Grid> </Grid>
<Grid container style={{ marginTop: 30 }}> <Grid container className={classes.gridContainer}>
<Grid item xs={12}> <Grid item xs={12}>
<Label2>Transactions</Label2> <Label2>Transactions</Label2>
<Scatterplot <Scatterplot
@ -250,27 +183,23 @@ const SystemPerformance = () => {
/> />
</Grid> </Grid>
</Grid> </Grid>
<Grid container style={{ marginTop: 30 }}> <Grid container className={classes.gridContainer}>
<Grid item xs={8}> <Grid item xs={8}>
<Label2>Profit from commissions</Label2> <Label2>Profit from commissions</Label2>
<div <div className={classes.profitContainer}>
style={{
display: 'flex',
justifyContent: 'space-between',
margin: '0 26px -30px 16px',
position: 'relative'
}}>
<div className={classes.profitLabel}> <div className={classes.profitLabel}>
{`${getProfit()} ${data?.config.locale_fiatCurrency}`} {`${getProfit(transactionsToShow)} ${
data?.config.locale_fiatCurrency
}`}
</div> </div>
<div <div
className={ className={
percentChange <= 0 ? classes.percentDown : classes.percentUp percentChange <= 0 ? classes.percentDown : classes.percentUp
}> }>
{percentChange <= 0 ? ( {percentChange <= 0 ? (
<TriangleDown style={{ height: 13 }} /> <TriangleDown className={classes.percentDown} />
) : ( ) : (
<TriangleUp style={{ height: 10 }} /> <TriangleUp className={classes.percentUp} />
)}{' '} )}{' '}
{`${percentChange}%`} {`${percentChange}%`}
</div> </div>
@ -281,7 +210,10 @@ const SystemPerformance = () => {
<Label2>Direction</Label2> <Label2>Direction</Label2>
<Grid container> <Grid container>
<Grid item xs> <Grid item xs>
<PercentageChart data={getDirectionPercent()} /> <PercentageChart
cashIn={getDirectionPercent().cashIn}
cashOut={getDirectionPercent().cashOut}
/>
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>

View file

@ -5,8 +5,8 @@ import {
fontSize3, fontSize3,
fontSecondary, fontSecondary,
fontColor, fontColor,
secondaryColorDarker, spring4,
linkSecondaryColor tomato
} from 'src/styling/variables' } from 'src/styling/variables'
const styles = { const styles = {
@ -59,6 +59,9 @@ const styles = {
navButton: { navButton: {
marginLeft: 24 marginLeft: 24
}, },
navContainer: {
display: 'flex'
},
profitLabel: { profitLabel: {
fontSize: fontSize3, fontSize: fontSize3,
fontFamily: fontSecondary, fontFamily: fontSecondary,
@ -69,13 +72,24 @@ const styles = {
fontSize: fontSize3, fontSize: fontSize3,
fontFamily: fontSecondary, fontFamily: fontSecondary,
fontWeight: 700, fontWeight: 700,
color: secondaryColorDarker color: spring4,
height: 10
}, },
percentDown: { percentDown: {
fontSize: fontSize3, fontSize: fontSize3,
fontFamily: fontSecondary, fontFamily: fontSecondary,
fontWeight: 700, fontWeight: 700,
color: linkSecondaryColor color: tomato,
height: 13
},
profitContainer: {
display: 'flex',
justifyContent: 'space-between',
margin: '0 26px -30px 16px',
position: 'relative'
},
gridContainer: {
marginTop: 30
} }
} }

View file

@ -5,6 +5,7 @@ import TableCell from '@material-ui/core/TableCell'
import TableContainer from '@material-ui/core/TableContainer' import TableContainer from '@material-ui/core/TableContainer'
import TableHead from '@material-ui/core/TableHead' import TableHead from '@material-ui/core/TableHead'
import TableRow from '@material-ui/core/TableRow' import TableRow from '@material-ui/core/TableRow'
import classnames from 'classnames'
import React from 'react' import React from 'react'
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'
@ -57,73 +58,70 @@ const MachinesTable = ({ machines, numToRender }) => {
} }
return ( return (
<> <TableContainer className={classes.table}>
<TableContainer className={classes.table}> <Table>
<Table> <TableHead>
<TableHead> <TableRow>
<TableRow> <HeaderCell>
<HeaderCell> <div className={classes.header}>
<div className={classes.header}> <Label2 className={classes.label}>Machines</Label2>
<Label2 className={classes.label}>Machines</Label2> </div>
</div> </HeaderCell>
</HeaderCell> <HeaderCell>
<HeaderCell> <div className={`${classes.header} ${classes.statusHeader}`}>
<div className={`${classes.header} ${classes.statusHeader}`}> <Label2 className={classes.label}>Status</Label2>
<Label2 className={classes.label}>Status</Label2> </div>
</div> </HeaderCell>
</HeaderCell> {/* <HeaderCell>
{/* <HeaderCell>
<div className={classes.header}> <div className={classes.header}>
<TxInIcon /> <TxInIcon />
</div> </div>
</HeaderCell> */} </HeaderCell> */}
<HeaderCell> <HeaderCell>
<div className={classes.header}> <div className={classes.header}>
<TxOutIcon /> <TxOutIcon />
<Label2 className={classes.label}> 1</Label2> <Label2 className={classes.label}> 1</Label2>
</div> </div>
</HeaderCell> </HeaderCell>
<HeaderCell> <HeaderCell>
<div className={classes.header}> <div className={classes.header}>
<TxOutIcon /> <TxOutIcon />
<Label2 className={classes.label}> 2</Label2> <Label2 className={classes.label}> 2</Label2>
</div> </div>
</HeaderCell> </HeaderCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{machines.map((machine, idx) => { {machines.map((machine, idx) => {
if (idx < numToRender) { if (idx < numToRender) {
return ( return (
<TableRow <TableRow
onClick={() => redirect(machine.name)} onClick={() => redirect(machine.name)}
style={{ cursor: 'pointer' }} className={classnames(classes.row, classes.clickableRow)}
key={machine.deviceId + idx} key={machine.deviceId + idx}>
className={classes.row}> <StyledCell align="left">
<StyledCell align="left"> <TL2>{machine.name}</TL2>
<TL2>{machine.name}</TL2> </StyledCell>
</StyledCell> <StyledCell>
<StyledCell> <Status status={machine.statuses[0]} />
<Status status={machine.statuses[0]} /> </StyledCell>
</StyledCell> {/* <StyledCell align="left">
{/* <StyledCell align="left">
{makePercentageText(machine.cashbox)} {makePercentageText(machine.cashbox)}
</StyledCell> */} </StyledCell> */}
<StyledCell align="left"> <StyledCell align="left">
{makePercentageText(machine.cassette1)} {makePercentageText(machine.cassette1)}
</StyledCell> </StyledCell>
<StyledCell align="left"> <StyledCell align="left">
{makePercentageText(machine.cassette2)} {makePercentageText(machine.cassette2)}
</StyledCell> </StyledCell>
</TableRow> </TableRow>
) )
} }
return null return null
})} })}
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
</>
) )
} }

View file

@ -6,6 +6,10 @@ import {
} from 'src/styling/variables' } from 'src/styling/variables'
const styles = { const styles = {
container: {
display: 'flex',
justifyContent: 'space-between'
},
label: { label: {
margin: 0, margin: 0,
color: offColor color: offColor
@ -14,6 +18,9 @@ const styles = {
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
borderBottom: 'none' borderBottom: 'none'
}, },
clickableRow: {
cursor: 'pointer'
},
header: { header: {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@ -32,11 +39,21 @@ const styles = {
backgroundColor: 'transparent' backgroundColor: 'transparent'
} }
}, },
buttonLabel: {
textAlign: 'center',
marginBottom: 0
},
upperButtonLabel: {
textAlign: 'center',
marginBottom: 0,
marginTop: 0
},
statusHeader: { statusHeader: {
marginLeft: 2 marginLeft: 2
}, },
/* table: { /* // temporary class, until alerts are enabled. Delete this table class and uncomment the other one
maxHeight: 440, table: {
height: 463,
'&::-webkit-scrollbar': { '&::-webkit-scrollbar': {
width: 7 width: 7
}, },
@ -45,10 +62,8 @@ const styles = {
borderRadius: 5 borderRadius: 5
} }
}, */ }, */
// temporary, when notifications are enabled delete this one and decomment above
table: { table: {
maxHeight: 465, maxHeight: 440,
minHeight: 465,
'&::-webkit-scrollbar': { '&::-webkit-scrollbar': {
width: 7 width: 7
}, },
@ -62,6 +77,15 @@ const styles = {
}, },
h4: { h4: {
marginTop: 0 marginTop: 0
},
tl2: {
display: 'inline'
},
label1: {
display: 'inline'
},
machinesTableContainer: {
marginTop: 23
} }
} }

View file

@ -1,10 +1,11 @@
import { useQuery } from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
// import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button'
import Grid from '@material-ui/core/Grid' import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import React, { useState, useEffect } from 'react' import React from 'react'
import { cardState as cardState_ } from 'src/components/CollapsibleCard'
// import ActionButton from 'src/components/buttons/ActionButton' // import ActionButton from 'src/components/buttons/ActionButton'
import { H4, TL2, Label1 } from 'src/components/typography' import { H4, TL2, Label1 } from 'src/components/typography'
@ -14,7 +15,7 @@ import styles from './MachinesTable.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
// number of machines in the table to render on page load // number of machines in the table to render on page load
// const NUM_TO_RENDER = 3 const NUM_TO_RENDER = 3
const GET_DATA = gql` const GET_DATA = gql`
query getData { query getData {
@ -38,97 +39,56 @@ const GET_DATA = gql`
} }
` `
const parseUptime = time => { /* const parseUptime = time => {
if (time < 60) return `${time}s` if (time < 60) return `${time}s`
if (time < 3600) return `${Math.floor(time / 60)}m` if (time < 3600) return `${Math.floor(time / 60)}m`
if (time < 86400) return `${Math.floor(time / 60 / 60)}h` if (time < 86400) return `${Math.floor(time / 60 / 60)}h`
return `${Math.floor(time / 60 / 60 / 24)}d` return `${Math.floor(time / 60 / 60 / 24)}d`
} } */
const SystemStatus = ({ cardState, setRightSideState }) => { const SystemStatus = ({ onReset, onExpand, size }) => {
const classes = useStyles() const classes = useStyles()
const { data, loading } = useQuery(GET_DATA) const { data, loading } = useQuery(GET_DATA)
const [showAllItems, setShowAllItems] = useState(false)
// const [showExpandButton, setShowExpandButton] = useState(false)
// const [numToRender, setNumToRender] = useState(NUM_TO_RENDER)
useEffect(() => { const showAllItems = size === cardState_.EXPANDED
/* if (showAllItems) {
setShowExpandButton(false)
setNumToRender(data?.machines.length)
} else if (data && data?.machines.length > numToRender) {
setShowExpandButton(true)
} */
if (cardState.cardSize === 'small' || cardState.cardSize === 'default') {
setShowAllItems(false)
// setNumToRender(NUM_TO_RENDER)
}
}, [cardState.cardSize, data, /* numToRender, */ showAllItems])
/* const reset = () => { // const uptime = data?.uptime ?? [{}]
setShowAllItems(false)
setNumToRender(NUM_TO_RENDER)
setRightSideState({
systemStatus: { cardSize: 'default', buttonName: 'Show less' },
alerts: { cardSize: 'default', buttonName: 'Show less' }
})
}
const showAllClick = () => {
setShowExpandButton(false)
setShowAllItems(true)
setRightSideState({
systemStatus: { cardSize: 'big', buttonName: 'Show less' },
alerts: { cardSize: 'small', buttonName: 'Show alerts' }
})
} */
// placeholder data
if (data) {
data.uptime = [{ time: 1854125, state: 'RUNNING' }]
}
const uptime = data?.uptime ?? [{}]
return ( return (
<> <>
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div className={classes.container}>
<H4 className={classes.h4}>System status</H4>{' '} <H4 className={classes.h4}>System status</H4>{' '}
</div> {showAllItems && (
{/* {(showAllItems || cardState.cardSize === 'small') && ( <Label1 className={classes.upperButtonLabel}>
<> <Button
<Label1 onClick={onReset}
style={{ size="small"
textAlign: 'center', disableRipple
marginBottom: 0, disableFocusRipple
marginTop: 0 className={classes.button}>
}}> {'Show less'}
<Button </Button>
onClick={reset} </Label1>
size="small"
disableRipple
disableFocusRipple
className={classes.button}>
{cardState.buttonName}
</Button>
</Label1>
</>
)} )}
</div> */} </div>
{!loading && cardState.cardSize !== 'small' && ( {!loading && (
<> <>
<Grid container spacing={1}> <Grid container spacing={1}>
{/*
On hold until system uptime is implemented
<Grid item xs={4}> <Grid item xs={4}>
<TL2 style={{ display: 'inline' }}> <TL2 className={classes.tl2}>
{parseUptime(uptime[0].time)} {parseUptime(uptime[0].time)}
</TL2> </TL2>
<Label1 style={{ display: 'inline' }}> System up time</Label1> <Label1 className={classes.label1}> System up time</Label1>
</Grid> */}
<Grid item xs={4}>
<TL2 className={classes.tl2}>{data?.serverVersion}</TL2>
<Label1 className={classes.label1}> server version</Label1>
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={4}>
<TL2 style={{ display: 'inline' }}>{data?.serverVersion}</TL2> {/*
<Label1 style={{ display: 'inline' }}> server version</Label1> On hold until system update features are implemented
</Grid> <ActionButton
<Grid item xs={4}>
{/* <ActionButton
color="primary" color="primary"
className={classes.actionButton} className={classes.actionButton}
onClick={() => console.log('Upgrade button clicked')}> onClick={() => console.log('Upgrade button clicked')}>
@ -136,18 +96,22 @@ const SystemStatus = ({ cardState, setRightSideState }) => {
</ActionButton> */} </ActionButton> */}
</Grid> </Grid>
</Grid> </Grid>
<Grid container spacing={1} style={{ marginTop: 23 }}> <Grid
container
spacing={1}
className={classes.machinesTableContainer}>
<Grid item xs={12}> <Grid item xs={12}>
<MachinesTable <MachinesTable
/* numToRender={numToRender} */ numToRender={
numToRender={Infinity} showAllItems ? data?.machines.length : NUM_TO_RENDER
}
machines={data?.machines ?? []} machines={data?.machines ?? []}
/> />
{/* {showExpandButton && ( {!showAllItems && data.machines.length > NUM_TO_RENDER && (
<> <>
<Label1 style={{ textAlign: 'center', marginBottom: 0 }}> <Label1 className={classes.buttonLabel}>
<Button <Button
onClick={showAllClick} onClick={() => onExpand()}
size="small" size="small"
disableRipple disableRipple
disableFocusRipple disableFocusRipple
@ -156,7 +120,7 @@ const SystemStatus = ({ cardState, setRightSideState }) => {
</Button> </Button>
</Label1> </Label1>
</> </>
)} */} )}
</Grid> </Grid>
</Grid> </Grid>
</> </>

View file

@ -115,23 +115,19 @@ const CashCassettes = ({ machine, config, refetchData }) => {
}) })
} }
return ( return machine.name ? (
<> <EditableTable
{machine.name && ( error={error?.message}
<EditableTable stripeWhen={isCashOutDisabled}
error={error?.message} disableRowEdit={isCashOutDisabled}
stripeWhen={isCashOutDisabled} name="cashboxes"
disableRowEdit={isCashOutDisabled} elements={elements}
name="cashboxes" enableEdit
elements={elements} data={[machine] || []}
enableEdit save={onSave}
data={[machine] || []} validationSchema={ValidationSchema}
save={onSave} />
validationSchema={ValidationSchema} ) : null
/>
)}
</>
)
} }
export default CashCassettes export default CashCassettes

View file

@ -86,17 +86,15 @@ const Commissions = ({ name: SCREEN_KEY, id: deviceId }) => {
return R.values(commissions) return R.values(commissions)
} }
getMachineCommissions() const machineCommissions = getMachineCommissions()
return ( return (
<> <EditableTable
<EditableTable name="overrides"
name="overrides" save={saveOverrides}
save={saveOverrides} data={machineCommissions}
data={getMachineCommissions()} elements={overrides(currency)}
elements={overrides(currency)} />
/>
</>
) )
} }

View file

@ -10,28 +10,24 @@ const useStyles = makeStyles(styles)
const Details = ({ data }) => { const Details = ({ data }) => {
const classes = useStyles() const classes = useStyles()
return ( return (
<> <div className={classes.row}>
<div className={classes.row}> <div className={classes.rowItem}>
<div className={classes.rowItem}> <Label3 className={classes.label3}>Paired at</Label3>
{' '} <P>
<Label3 className={classes.label3}>Paired at</Label3> {data.pairedAt
<P> ? moment(data.pairedAt).format('YYYY-MM-DD HH:mm:ss')
{data.pairedAt : ''}
? moment(data.pairedAt).format('YYYY-MM-DD HH:mm:ss') </P>
: ''}
</P>
</div>
<div className={classes.rowItem}>
<Label3 className={classes.label3}>Machine model</Label3>
<P>{data.model}</P>
</div>
<div className={classes.rowItem}>
{' '}
<Label3 className={classes.label3}>Software version</Label3>
<P>{data.version}</P>
</div>
</div> </div>
</> <div className={classes.rowItem}>
<Label3 className={classes.label3}>Machine model</Label3>
<P>{data.model}</P>
</div>
<div className={classes.rowItem}>
<Label3 className={classes.label3}>Software version</Label3>
<P>{data.version}</P>
</div>
</div>
) )
} }

View file

@ -30,6 +30,25 @@ const MACHINE_ACTION = gql`
} }
` `
const makeLastPing = lastPing => {
if (!lastPing) return null
const now = moment()
const secondsAgo = now.diff(lastPing, 'seconds')
if (secondsAgo < 60) {
return `${secondsAgo} ${secondsAgo === 1 ? 'second' : 'seconds'} ago`
}
if (secondsAgo < 3600) {
const minutes = Math.round(secondsAgo / 60)
return `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago`
}
if (secondsAgo < 3600 * 24) {
const hours = Math.round(secondsAgo / 3600)
return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`
}
const days = Math.round(secondsAgo / 3600 / 24)
return `${days} ${days === 1 ? 'day' : 'days'} ago`
}
const Overview = ({ data, onActionSuccess }) => { const Overview = ({ data, onActionSuccess }) => {
const [action, setAction] = useState('') const [action, setAction] = useState('')
const [confirmActionDialogOpen, setConfirmActionDialogOpen] = useState(false) const [confirmActionDialogOpen, setConfirmActionDialogOpen] = useState(false)
@ -50,24 +69,6 @@ const Overview = ({ data, onActionSuccess }) => {
const confirmActionDialog = action => const confirmActionDialog = action =>
setAction(action) || setConfirmActionDialogOpen(true) setAction(action) || setConfirmActionDialogOpen(true)
const makeLastPing = () => {
const now = moment()
const secondsAgo = now.diff(data.lastPing, 'seconds')
if (secondsAgo < 60) {
return `${secondsAgo} ${secondsAgo === 1 ? 'second' : 'seconds'} ago`
}
if (secondsAgo < 3600) {
const minutes = Math.round(secondsAgo / 60)
return `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago`
}
if (secondsAgo < 3600 * 24) {
const hours = Math.round(secondsAgo / 3600)
return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`
}
const days = Math.round(secondsAgo / 3600 / 24)
return `${days} ${days === 1 ? 'day' : 'days'} ago`
}
return ( return (
<> <>
<div className={classes.row}> <div className={classes.row}>
@ -84,7 +85,7 @@ const Overview = ({ data, onActionSuccess }) => {
<div className={classes.row}> <div className={classes.row}>
<div className={classes.rowItem}> <div className={classes.rowItem}>
<Label3 className={classes.label3}>Last ping</Label3> <Label3 className={classes.label3}>Last ping</Label3>
<P>{data.lastPing ? makeLastPing() : ''}</P> <P>{makeLastPing(data.lastPing)}</P>
</div> </div>
</div> </div>
<div className={classes.row}> <div className={classes.row}>

View file

@ -1,74 +0,0 @@
import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames'
import * as R from 'ramda'
import React, { useState, useEffect } from 'react'
import { CopyToClipboard as ReactCopyToClipboard } from 'react-copy-to-clipboard'
import Popover from 'src/components/Popper'
import { ReactComponent as CopyIcon } from 'src/styling/icons/action/copy/copy.svg'
import { comet } from 'src/styling/variables'
import { cpcStyles } from './Transactions.styles'
const useStyles = makeStyles(cpcStyles)
const CopyToClipboard = ({
className,
buttonClassname,
children,
...props
}) => {
const [anchorEl, setAnchorEl] = useState(null)
useEffect(() => {
if (anchorEl) setTimeout(() => setAnchorEl(null), 3000)
}, [anchorEl])
const classes = useStyles()
const handleClick = event => {
setAnchorEl(anchorEl ? null : event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const open = Boolean(anchorEl)
const id = open ? 'simple-popper' : undefined
return (
<div className={classes.wrapper}>
{children && (
<>
<div className={classnames(classes.address, className)}>
{children}
</div>
<div className={classnames(classes.buttonWrapper, buttonClassname)}>
<ReactCopyToClipboard text={R.replace(/\s/g, '')(children)}>
<button
aria-describedby={id}
onClick={event => handleClick(event)}>
<CopyIcon />
</button>
</ReactCopyToClipboard>
</div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
arrowSize={3}
bgColor={comet}
placement="top">
<div className={classes.popoverContent}>
<div>Copied to clipboard!</div>
</div>
</Popover>
</>
)}
</div>
)
}
export default CopyToClipboard

View file

@ -17,12 +17,11 @@ import {
Td, Td,
Th Th
} from 'src/components/fake-table/Table' } from 'src/components/fake-table/Table'
import styles from 'src/components/tables/DataTable.styles'
import { H4 } from 'src/components/typography' import { H4 } from 'src/components/typography'
import { ReactComponent as ExpandClosedIcon } from 'src/styling/icons/action/expand/closed.svg' import { ReactComponent as ExpandClosedIcon } from 'src/styling/icons/action/expand/closed.svg'
import { ReactComponent as ExpandOpenIcon } from 'src/styling/icons/action/expand/open.svg' import { ReactComponent as ExpandOpenIcon } from 'src/styling/icons/action/expand/open.svg'
import styles from './DataTable.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const Row = ({ const Row = ({

View file

@ -1,45 +0,0 @@
import { zircon } from 'src/styling/variables'
const styles = {
expandButton: {
outline: 'none',
border: 'none',
backgroundColor: 'transparent',
cursor: 'pointer',
padding: 4
},
rowWrapper: {
// workaround to shadows cut by r-virtualized when scroll is visible
padding: 1
},
row: {
border: [[2, 'solid', 'transparent']],
borderRadius: 0
},
expanded: {
border: [[2, 'solid', zircon]],
boxShadow: '0 0 8px 0 rgba(0,0,0,0.08)'
},
before: {
paddingTop: 12
},
after: {
paddingBottom: 12
},
pointer: {
cursor: 'pointer'
},
body: {
flex: [[1, 1, 'auto']]
},
table: ({ width }) => ({
marginBottom: 30,
minHeight: 200,
width,
flex: 1,
display: 'flex',
flexDirection: 'column'
})
}
export default styles

View file

@ -1,195 +0,0 @@
import { makeStyles, Box } from '@material-ui/core'
import BigNumber from 'bignumber.js'
import moment from 'moment'
import React, { memo } from 'react'
import { IDButton } from 'src/components/buttons'
import { Label1 } from 'src/components/typography'
import { ReactComponent as CardIdInverseIcon } from 'src/styling/icons/ID/card/white.svg'
import { ReactComponent as CardIdIcon } from 'src/styling/icons/ID/card/zodiac.svg'
import { ReactComponent as PhoneIdInverseIcon } from 'src/styling/icons/ID/phone/white.svg'
import { ReactComponent as PhoneIdIcon } from 'src/styling/icons/ID/phone/zodiac.svg'
import { ReactComponent as CamIdInverseIcon } from 'src/styling/icons/ID/photo/white.svg'
import { ReactComponent as CamIdIcon } from 'src/styling/icons/ID/photo/zodiac.svg'
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 { URI } from 'src/utils/apollo'
import { toUnit, formatCryptoAddress } from 'src/utils/coin'
import { onlyFirstToUpper } from 'src/utils/string'
import CopyToClipboard from './CopyToClipboard'
import styles from './DetailsCard.styles'
import { getStatus } from './helper'
const useStyles = makeStyles(styles)
const formatAddress = (cryptoCode = '', address = '') =>
formatCryptoAddress(cryptoCode, address).replace(/(.{5})/g, '$1 ')
const Label = ({ children }) => {
const classes = useStyles()
return <Label1 className={classes.label}>{children}</Label1>
}
const DetailsRow = ({ it: tx }) => {
const classes = useStyles()
const fiat = Number.parseFloat(tx.fiat)
const crypto = toUnit(new BigNumber(tx.cryptoAtoms), tx.cryptoCode)
const commissionPercentage = Number.parseFloat(tx.commissionPercentage, 2)
const commission = Number(fiat * commissionPercentage).toFixed(2)
const exchangeRate = Number(fiat / crypto).toFixed(3)
const displayExRate = `1 ${tx.cryptoCode} = ${exchangeRate} ${tx.fiatCode}`
const customer = tx.customerIdCardData && {
name: `${onlyFirstToUpper(
tx.customerIdCardData.firstName
)} ${onlyFirstToUpper(tx.customerIdCardData.lastName)}`,
age: moment().diff(moment(tx.customerIdCardData.dateOfBirth), 'years'),
country: tx.customerIdCardData.country,
idCardNumber: tx.customerIdCardData.documentNumber,
idCardExpirationDate: moment(tx.customerIdCardData.expirationDate).format(
'DD-MM-YYYY'
)
}
return (
<div className={classes.wrapper}>
<div className={classes.row}>
<div className={classes.direction}>
<Label>Direction</Label>
<div>
<span className={classes.txIcon}>
{tx.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />}
</span>
<span>{tx.txClass === 'cashOut' ? 'Cash-out' : 'Cash-in'}</span>
</div>
</div>
<div className={classes.availableIds}>
<Label>Available IDs</Label>
<Box display="flex" flexDirection="row">
{tx.customerPhone && (
<IDButton
className={classes.idButton}
name="phone"
Icon={PhoneIdIcon}
InverseIcon={PhoneIdInverseIcon}>
{tx.customerPhone}
</IDButton>
)}
{tx.customerIdCardPhotoPath && !tx.customerIdCardData && (
<IDButton
popoverClassname={classes.popover}
className={classes.idButton}
name="card"
Icon={CardIdIcon}
InverseIcon={CardIdInverseIcon}>
<img
className={classes.idCardPhoto}
src={`${URI}/id-card-photo/${tx.customerIdCardPhotoPath}`}
alt=""
/>
</IDButton>
)}
{tx.customerIdCardData && (
<IDButton
className={classes.idButton}
name="card"
Icon={CardIdIcon}
InverseIcon={CardIdInverseIcon}>
<div className={classes.idCardDataCard}>
<div>
<div>
<Label>Name</Label>
<div>{customer.name}</div>
</div>
<div>
<Label>Age</Label>
<div>{customer.age}</div>
</div>
<div>
<Label>Country</Label>
<div>{customer.country}</div>
</div>
</div>
<div>
<div>
<Label>ID number</Label>
<div>{customer.idCardNumber}</div>
</div>
<div>
<Label>Expiration date</Label>
<div>{customer.idCardExpirationDate}</div>
</div>
</div>
</div>
</IDButton>
)}
{tx.customerFrontCameraPath && (
<IDButton
name="cam"
Icon={CamIdIcon}
InverseIcon={CamIdInverseIcon}>
<img
src={`${URI}/front-camera-photo/${tx.customerFrontCameraPath}`}
alt=""
/>
</IDButton>
)}
</Box>
</div>
<div className={classes.exchangeRate}>
<Label>Exchange rate</Label>
<div>{crypto > 0 ? displayExRate : '-'}</div>
</div>
<div className={classes.commission}>
<Label>Commission</Label>
<div>
{`${commission} ${tx.fiatCode} (${commissionPercentage * 100} %)`}
</div>
</div>
<div>
<Label>Fixed fee</Label>
<div>
{tx.txClass === 'cashIn'
? `${Number.parseFloat(tx.cashInFee)} ${tx.fiatCode}`
: 'N/A'}
</div>
</div>
</div>
<div className={classes.secondRow}>
<div className={classes.address}>
<Label>Address</Label>
<div>
<CopyToClipboard>
{formatAddress(tx.cryptoCode, tx.toAddress)}
</CopyToClipboard>
</div>
</div>
<div className={classes.transactionId}>
<Label>Transaction ID</Label>
<div>
{tx.txClass === 'cashOut' ? (
'N/A'
) : (
<CopyToClipboard>{tx.txHash}</CopyToClipboard>
)}
</div>
</div>
<div className={classes.sessionId}>
<Label>Session ID</Label>
<CopyToClipboard>{tx.id}</CopyToClipboard>
</div>
</div>
<div className={classes.lastRow}>
<div>
<Label>Transaction status</Label>
<span className={classes.bold}>{getStatus(tx)}</span>
</div>
</div>
</div>
)
}
export default memo(DetailsRow, (prev, next) => prev.id === next.id)

View file

@ -1,86 +0,0 @@
import typographyStyles from 'src/components/typography/styles'
import { offColor } from 'src/styling/variables'
const { p } = typographyStyles
const styles = {
wrapper: {
display: 'flex',
flexDirection: 'column',
marginTop: 24
},
row: {
display: 'flex',
flexDirection: 'row',
marginBottom: 36
},
secondRow: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 36
},
lastRow: {
display: 'flex',
flexDirection: 'row',
marginBottom: 32
},
label: {
color: offColor,
margin: [[0, 0, 6, 0]]
},
txIcon: {
marginRight: 10
},
popover: {
height: 164,
width: 215
},
idButton: {
marginRight: 4
},
idCardDataCard: {
extend: p,
display: 'flex',
padding: [[11, 8]],
// rework this into a proper component
'& > div': {
display: 'flex',
flexDirection: 'column',
'& > div': {
width: 144,
height: 37,
marginBottom: 15,
'&:last-child': {
marginBottom: 0
}
}
}
},
bold: {
fontWeight: 700
},
direction: {
width: 233
},
availableIds: {
width: 232
},
exchangeRate: {
width: 250
},
commission: {
width: 217
},
address: {
width: 280
},
transactionId: {
width: 280
},
sessionId: {
width: 215
}
}
export default styles

View file

@ -1,12 +0,0 @@
import React from 'react'
import { Td } from 'src/components/fake-table/Table'
import { ReactComponent as StripesSvg } from 'src/styling/icons/stripes.svg'
const Stripes = ({ width }) => (
<Td width={width}>
<StripesSvg />
</Td>
)
export default Stripes

View file

@ -6,22 +6,26 @@ import moment from 'moment'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import DetailsRow from 'src/pages/Transactions/DetailsCard'
import { mainStyles } from 'src/pages/Transactions/Transactions.styles'
import { getStatus } from 'src/pages/Transactions/helper'
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' 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 { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
import { toUnit, formatCryptoAddress } from 'src/utils/coin' import { toUnit, formatCryptoAddress } from 'src/utils/coin'
import DataTable from './DataTable' import DataTable from './DataTable'
import DetailsRow from './DetailsCard'
import { mainStyles } from './Transactions.styles'
import { getStatus } from './helper'
const useStyles = makeStyles(mainStyles) const useStyles = makeStyles(mainStyles)
const NUM_LOG_RESULTS = 5 const NUM_LOG_RESULTS = 5
const GET_TRANSACTIONS = gql` const GET_TRANSACTIONS = gql`
query transactions($limit: Int, $from: Date, $until: Date, $id: ID) { query transactions($limit: Int, $from: Date, $until: Date, $deviceId: ID) {
transactions(limit: $limit, from: $from, until: $until, id: $id) { transactions(
limit: $limit
from: $from
until: $until
deviceId: $deviceId
) {
id id
txClass txClass
txHash txHash
@ -61,7 +65,7 @@ const Transactions = ({ id }) => {
{ {
variables: { variables: {
limit: NUM_LOG_RESULTS, limit: NUM_LOG_RESULTS,
id deviceId: id
} }
} }
) )

View file

@ -1,23 +0,0 @@
const getCashOutStatus = it => {
if (it.hasError) return 'Error'
if (it.dispense) return 'Success'
if (it.expired) return 'Expired'
return 'Pending'
}
const getCashInStatus = it => {
if (it.operatorCompleted) return 'Cancelled'
if (it.hasError) return 'Error'
if (it.sendConfirmed) return 'Sent'
if (it.expired) return 'Expired'
return 'Pending'
}
const getStatus = it => {
if (it.class === 'cashOut') {
return getCashOutStatus(it)
}
return getCashInStatus(it)
}
export { getStatus }

View file

@ -1,11 +1,15 @@
import List from '@material-ui/core/List' import List from '@material-ui/core/List'
import ListItem from '@material-ui/core/ListItem' import ListItem from '@material-ui/core/ListItem'
import ListItemText from '@material-ui/core/ListItemText' import ListItemText from '@material-ui/core/ListItemText'
import { makeStyles } from '@material-ui/core/styles'
import React from 'react' import React from 'react'
import styles from './Machines.styles'
const useStyles = makeStyles(styles)
const MachineSidebar = ({ data, getText, getKey, isSelected, selectItem }) => { const MachineSidebar = ({ data, getText, getKey, isSelected, selectItem }) => {
const classes = useStyles()
return ( return (
<List style={{ height: 400, overflowY: 'auto' }}> <List className={classes.sidebarContainer}>
{data.map((item, idx) => { {data.map((item, idx) => {
return ( return (
<ListItem <ListItem
@ -20,8 +24,6 @@ const MachineSidebar = ({ data, getText, getKey, isSelected, selectItem }) => {
})} })}
</List> </List>
) )
/* return data.map(item => <button key={getKey(item)}>{getText(item)}</button>) */
} }
export default MachineSidebar export default MachineSidebar

View file

@ -3,6 +3,7 @@ import Breadcrumbs from '@material-ui/core/Breadcrumbs'
import Grid from '@material-ui/core/Grid' import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import NavigateNextIcon from '@material-ui/icons/NavigateNext' import NavigateNextIcon from '@material-ui/icons/NavigateNext'
import classnames from 'classnames'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
@ -66,59 +67,58 @@ const Machines = () => {
}, [loading, data, location.state]) }, [loading, data, location.state])
return ( return (
<> <Grid container className={classes.grid}>
<Grid container className={classes.grid}> <Grid item xs={3}>
<Grid item xs={3}> <Grid item xs={12}>
<Grid item xs={12}> <div className={classes.breadcrumbsContainer}>
<div style={{ marginTop: 32 }}> <Breadcrumbs separator={<NavigateNextIcon fontSize="small" />}>
<Breadcrumbs separator={<NavigateNextIcon fontSize="small" />}> <Link to="/dashboard" className={classes.breadcrumbLink}>
<Link to="/dashboard" style={{ textDecoration: 'none' }}> <Label3 className={classes.subtitle}>Dashboard</Label3>
<Label3 className={classes.subtitle}>Dashboard</Label3> </Link>
</Link> <TL2 className={classes.subtitle}>{selectedMachine}</TL2>
<TL2 className={classes.subtitle}>{selectedMachine}</TL2> </Breadcrumbs>
</Breadcrumbs> <Overview data={machineInfo} onActionSuccess={refetch} />
<Overview data={machineInfo} onActionSuccess={refetch} />
</div>
</Grid>
<Grid item xs={12}>
<Sidebar
isSelected={R.equals(selectedMachine)}
selectItem={setSelectedMachine}
data={machines}
getText={R.prop('name')}
getKey={R.prop('deviceId')}
/>
</Grid>
</Grid>
<Grid item xs={9}>
<div className={classes.content}>
<div className={classes.detailItem} style={{ marginTop: 24 }}>
<TL1 className={classes.subtitle}>{'Details'}</TL1>
<Details data={machineInfo} />
</div>
<div className={classes.detailItem}>
<TL1 className={classes.subtitle}>{'Cash cassettes'}</TL1>
<Cassettes
refetchData={refetch}
machine={machineInfo}
config={data?.config ?? false}
/>
</div>
<div className={classes.transactionsItem}>
<TL1 className={classes.subtitle}>{'Latest transactions'}</TL1>
<Transactions id={machineInfo?.deviceId ?? null} />
</div>
<div className={classes.detailItem}>
<TL1 className={classes.subtitle}>{'Commissions'}</TL1>
<Commissions
name={'commissions'}
id={machineInfo?.deviceId ?? null}
/>
</div>
</div> </div>
</Grid> </Grid>
<Grid item xs={12}>
<Sidebar
isSelected={R.equals(selectedMachine)}
selectItem={setSelectedMachine}
data={machines}
getText={R.prop('name')}
getKey={R.prop('deviceId')}
/>
</Grid>
</Grid> </Grid>
</> <Grid item xs={9}>
<div className={classes.content}>
<div
className={classnames(classes.detailItem, classes.detailsMargin)}>
<TL1 className={classes.subtitle}>{'Details'}</TL1>
<Details data={machineInfo} />
</div>
<div className={classes.detailItem}>
<TL1 className={classes.subtitle}>{'Cash cassettes'}</TL1>
<Cassettes
refetchData={refetch}
machine={machineInfo}
config={data?.config ?? false}
/>
</div>
<div className={classes.transactionsItem}>
<TL1 className={classes.subtitle}>{'Latest transactions'}</TL1>
<Transactions id={machineInfo?.deviceId ?? null} />
</div>
<div className={classes.detailItem}>
<TL1 className={classes.subtitle}>{'Commissions'}</TL1>
<Commissions
name={'commissions'}
id={machineInfo?.deviceId ?? null}
/>
</div>
</div>
</Grid>
</Grid>
) )
} }

View file

@ -69,6 +69,19 @@ const styles = {
}, },
actionButton: { actionButton: {
marginRight: 8 marginRight: 8
},
breadcrumbsContainer: {
marginTop: 32
},
breadcrumbLink: {
textDecoration: 'none'
},
detailsMargin: {
marginTop: 24
},
sidebarContainer: {
height: 400,
overflowY: 'auto'
} }
} }

View file

@ -51,18 +51,6 @@ const useStyles = makeStyles({
}) })
const tree = [ const tree = [
/* {
key: 'dashboard',
label: 'Dashboard',
route: '/dashboard',
component: Dashboard
}, */
/* {
key: 'machines',
label: 'Machines',
route: '/machines',
component: Machines
}, */
{ {
key: 'transactions', key: 'transactions',
label: 'Transactions', label: 'Transactions',

View file

@ -108,6 +108,13 @@ export default createMuiTheme({
color: fontColor color: fontColor
} }
} }
},
MuiListItem: {
root: {
'&:nth-of-type(odd)': {
backgroundColor: backgroundColor
}
}
} }
} }
}) })

View file

@ -1,7 +1,7 @@
import * as sanctuary from 'sanctuary' import * as sanctuary from 'sanctuary'
const checkOnlyDev = () => { const checkOnlyDev = () => {
if (!process.env.NODE_ENV === 'production') return false if (process.env.NODE_ENV !== 'production') return false
return ( return (
process.env.NODE_ENV === 'development' && process.env.NODE_ENV === 'development' &&