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:
parent
5572fb0eb1
commit
ae7eaca10c
43 changed files with 818 additions and 1578 deletions
5
lib/forex.js
Normal file
5
lib/forex.js
Normal 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 }
|
||||||
|
|
@ -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 }),
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
|
||||||
|
|
|
||||||
44
new-lamassu-admin/src/components/CollapsibleCard.js
Normal file
44
new-lamassu-admin/src/components/CollapsibleCard.js
Normal 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 }
|
||||||
|
|
@ -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>
|
||||||
)}
|
</>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)}
|
/>
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}>
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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 = ({
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,13 @@ export default createMuiTheme({
|
||||||
color: fontColor
|
color: fontColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
MuiListItem: {
|
||||||
|
root: {
|
||||||
|
'&:nth-of-type(odd)': {
|
||||||
|
backgroundColor: backgroundColor
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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' &&
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue