288 lines
9.3 KiB
JavaScript
288 lines
9.3 KiB
JavaScript
import { useQuery, gql } from '@apollo/client'
|
|
import BigNumber from 'bignumber.js'
|
|
import classnames from 'classnames'
|
|
import * as R from 'ramda'
|
|
import React, { useState } from 'react'
|
|
import { Info2, Label1, Label2, P } from '../../../components/typography/index'
|
|
import PercentDownIcon from '../../../styling/icons/dashboard/down.svg?react'
|
|
import PercentNeutralIcon from '../../../styling/icons/dashboard/equal.svg?react'
|
|
import PercentUpIcon from '../../../styling/icons/dashboard/up.svg?react'
|
|
|
|
import { EmptyTable } from '../../../components/table'
|
|
import { java, neon } from '../../../styling/variables'
|
|
import { fromNamespace } from '../../../utils/config'
|
|
import { DAY, WEEK, MONTH } from '../../../utils/time'
|
|
import { timezones } from '../../../utils/timezone-list'
|
|
|
|
import PercentageChart from './Graphs/PercentageChart'
|
|
import LineChart from './Graphs/RefLineChart'
|
|
import Scatterplot from './Graphs/RefScatterplot'
|
|
import InfoWithLabel from './InfoWithLabel'
|
|
import Nav from './Nav'
|
|
|
|
BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_HALF_UP })
|
|
|
|
const sumBNBy = by =>
|
|
R.reduce((acc, value) => acc.plus(by(value)), new BigNumber(0))
|
|
|
|
const getProfit = sumBNBy(tx => tx.profit)
|
|
|
|
const GET_DATA = gql`
|
|
query getData($excludeTestingCustomers: Boolean, $from: DateTimeISO) {
|
|
transactions(
|
|
excludeTestingCustomers: $excludeTestingCustomers
|
|
from: $from
|
|
) {
|
|
fiatCode
|
|
fiat
|
|
created
|
|
txClass
|
|
error
|
|
profit
|
|
dispense
|
|
sendConfirmed
|
|
}
|
|
fiatRates {
|
|
code
|
|
name
|
|
rate
|
|
}
|
|
config
|
|
}
|
|
`
|
|
|
|
const twoMonthsAgo = new Date()
|
|
twoMonthsAgo.setMonth(twoMonthsAgo.getMonth() - 2)
|
|
|
|
const SystemPerformance = () => {
|
|
const [selectedRange, setSelectedRange] = useState('Day')
|
|
|
|
const { data, loading } = useQuery(GET_DATA, {
|
|
variables: {
|
|
excludeTestingCustomers: true,
|
|
from: twoMonthsAgo.toISOString(),
|
|
},
|
|
})
|
|
const fiatLocale = fromNamespace('locale')(data?.config).fiatCurrency
|
|
const timezone = fromNamespace('locale')(data?.config).timezone
|
|
const allTransactions = data?.transactions ?? []
|
|
|
|
const NOW = Date.now()
|
|
|
|
const periodDomains = {
|
|
Day: [NOW - DAY, NOW],
|
|
Week: [NOW - WEEK, NOW],
|
|
Month: [NOW - MONTH, NOW],
|
|
}
|
|
|
|
const isInRangeAndNoError = getLastTimePeriod => t => {
|
|
if (t.error !== null) return false
|
|
if (t.txClass === 'cashOut' && !t.dispense) return false
|
|
if (t.txClass === 'cashIn' && !t.sendConfirmed) return false
|
|
|
|
const createdTimestamp = new Date(t.created).getTime()
|
|
const [rangeStart, rangeEnd] = periodDomains[selectedRange]
|
|
|
|
if (getLastTimePeriod) {
|
|
const duration = rangeEnd - rangeStart
|
|
return (
|
|
createdTimestamp >= rangeStart - duration &&
|
|
createdTimestamp < rangeStart
|
|
)
|
|
}
|
|
|
|
return createdTimestamp >= rangeStart && createdTimestamp <= rangeEnd
|
|
}
|
|
|
|
const convertFiatToLocale = item => {
|
|
if (item.fiatCode === fiatLocale)
|
|
return {
|
|
...item,
|
|
fiat: new BigNumber(item.fiat),
|
|
profit: new BigNumber(item.profit),
|
|
}
|
|
const itemRate = R.find(R.propEq(item.fiatCode, 'code'))(data.fiatRates)
|
|
const localeRate = R.find(R.propEq(fiatLocale, 'code'))(data.fiatRates)
|
|
const multiplier = localeRate.rate / itemRate.rate
|
|
return {
|
|
...item,
|
|
fiat: new BigNumber(item.fiat).times(multiplier),
|
|
profit: new BigNumber(item.profit).times(multiplier),
|
|
}
|
|
}
|
|
|
|
const transactionsToShow = R.map(convertFiatToLocale)(
|
|
R.filter(isInRangeAndNoError(false), allTransactions),
|
|
)
|
|
const transactionsLastTimePeriod = R.map(convertFiatToLocale)(
|
|
R.filter(isInRangeAndNoError(true), allTransactions),
|
|
)
|
|
|
|
const getNumTransactions = () => {
|
|
return R.length(transactionsToShow)
|
|
}
|
|
|
|
const getFiatVolume = () =>
|
|
sumBNBy(tx => tx.fiat)(transactionsToShow).toFormat(2)
|
|
|
|
const getPercentChange = () => {
|
|
const thisTimePeriodProfit = getProfit(transactionsToShow)
|
|
const previousTimePeriodProfit = getProfit(transactionsLastTimePeriod)
|
|
|
|
if (thisTimePeriodProfit.eq(previousTimePeriodProfit)) return 0
|
|
if (previousTimePeriodProfit.eq(0)) return 100
|
|
|
|
return thisTimePeriodProfit
|
|
.minus(previousTimePeriodProfit)
|
|
.times(100)
|
|
.div(previousTimePeriodProfit)
|
|
.toNumber()
|
|
}
|
|
|
|
const getDirectionPercent = () => {
|
|
const [cashIn, cashOut] = R.partition(R.propEq('cashIn', 'txClass'))(
|
|
transactionsToShow,
|
|
)
|
|
const totalLength = cashIn.length + cashOut.length
|
|
if (totalLength === 0) {
|
|
return { cashIn: 0, cashOut: 0 }
|
|
}
|
|
|
|
return {
|
|
cashIn: Math.round((cashIn.length / totalLength) * 100),
|
|
cashOut: Math.round((cashOut.length / totalLength) * 100),
|
|
}
|
|
}
|
|
|
|
const percentChange = getPercentChange()
|
|
|
|
const percentageClasses = {
|
|
'text-tomato': percentChange < 0,
|
|
'text-spring4': percentChange > 0,
|
|
'text-comet': percentChange === 0,
|
|
'flex items-center justify-center gap-1': true,
|
|
}
|
|
|
|
const getPercentageIcon = () => {
|
|
const className = 'w-4 h-4'
|
|
if (percentChange === 0) return <PercentNeutralIcon className={className} />
|
|
if (percentChange > 0) return <PercentUpIcon className={className} />
|
|
return <PercentDownIcon className={className} />
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Nav
|
|
showPicker={!loading && !R.isEmpty(allTransactions)}
|
|
handleSetRange={setSelectedRange}
|
|
/>
|
|
{!loading && R.isEmpty(allTransactions) && (
|
|
<EmptyTable
|
|
className="pt-10"
|
|
message="No transactions during the last month"
|
|
/>
|
|
)}
|
|
{!loading && !R.isEmpty(allTransactions) && (
|
|
<div className="flex flex-col gap-12">
|
|
<div className="flex gap-16">
|
|
<InfoWithLabel info={getNumTransactions()} label={'transactions'} />
|
|
<InfoWithLabel
|
|
info={getFiatVolume()}
|
|
label={`${data?.config.locale_fiatCurrency} volume`}
|
|
/>
|
|
</div>
|
|
<div className="h-62">
|
|
<div className="flex justify-between mb-4">
|
|
<Label2 noMargin>Transactions</Label2>
|
|
<div className="flex items-center">
|
|
<P noMargin>
|
|
{timezones[timezone]?.short ?? timezones[timezone]?.long}{' '}
|
|
timezone
|
|
</P>
|
|
<span className="h-4 w-[1px] bg-comet2 mr-4 ml-8" />
|
|
<div className="flex flex-row gap-4">
|
|
<div className="flex items-center">
|
|
<svg width={8} height={8}>
|
|
<rect width={8} height={8} rx={4} fill={java} />
|
|
</svg>
|
|
<Label1 noMargin className="ml-2">
|
|
In
|
|
</Label1>
|
|
</div>
|
|
<div className="flex items-center">
|
|
<svg width={8} height={8}>
|
|
<rect width={8} height={8} rx={4} fill={neon} />
|
|
</svg>
|
|
<Label1 noMargin className="ml-2">
|
|
Out
|
|
</Label1>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Scatterplot
|
|
timeFrame={selectedRange}
|
|
data={transactionsToShow}
|
|
timezone={timezone}
|
|
/>
|
|
</div>
|
|
<div className="flex h-62">
|
|
<div className="flex-2">
|
|
<Label2 noMargin className="mb-4">
|
|
Profit from commissions
|
|
</Label2>
|
|
<div className="flex justify-between mt-6 mr-7 -mb-8 ml-4 relative">
|
|
<Info2 noMargin>
|
|
{`${getProfit(transactionsToShow).toFormat(2)} ${
|
|
data?.config.locale_fiatCurrency
|
|
}`}
|
|
</Info2>
|
|
<Info2 noMargin className={classnames(percentageClasses)}>
|
|
{getPercentageIcon()}
|
|
{`${new BigNumber(percentChange).toFormat(2)}%`}
|
|
</Info2>
|
|
</div>
|
|
<LineChart
|
|
timeFrame={selectedRange}
|
|
data={transactionsToShow}
|
|
previousTimeData={transactionsLastTimePeriod}
|
|
previousProfit={getProfit(
|
|
transactionsLastTimePeriod,
|
|
).toNumber()}
|
|
/>
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<Label2 noMargin>Direction</Label2>
|
|
<div className="flex flex-row gap-4">
|
|
<div className="flex items-center">
|
|
<svg width={8} height={8}>
|
|
<rect width={8} height={8} rx={2} fill={java} />
|
|
</svg>
|
|
<Label1 noMargin className="ml-2">
|
|
In
|
|
</Label1>
|
|
</div>
|
|
<div className="flex items-center">
|
|
<svg width={8} height={8}>
|
|
<rect width={8} height={8} rx={2} fill={neon} />
|
|
</svg>
|
|
<Label1 noMargin className="ml-2">
|
|
Out
|
|
</Label1>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<PercentageChart
|
|
cashIn={getDirectionPercent().cashIn}
|
|
cashOut={getDirectionPercent().cashOut}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default SystemPerformance
|