chore: use monorepo organization

This commit is contained in:
Rafael Taranto 2025-05-12 10:52:54 +01:00
parent deaf7d6ecc
commit a687827f7e
1099 changed files with 8184 additions and 11535 deletions

View file

@ -0,0 +1,34 @@
import classnames from 'classnames'
import React from 'react'
import { Label1 } from 'src/components/typography/index'
const PercentageChart = ({ cashIn, cashOut }) => {
const value = cashIn || cashOut !== 0 ? cashIn : 50
const buildPercentageView = value => {
if (value <= 15) return
return <Label1 className="text-white">{value}%</Label1>
}
const percentageClasses = {
'h-35 rounded-sm flex items-center justify-center': true,
'min-w-2 rounded-xs': value < 5 && value > 0
}
return (
<div className="flex h-35 gap-1">
<div
className={classnames(percentageClasses, 'bg-java')}
style={{ width: `${value}%` }}>
{buildPercentageView(value, 'cashIn')}
</div>
<div
className={classnames(percentageClasses, 'bg-neon')}
style={{ width: `${100 - value}%` }}>
{buildPercentageView(100 - value, 'cashOut')}
</div>
</div>
)
}
export default PercentageChart

View file

@ -0,0 +1,197 @@
import * as d3 from 'd3'
import * as R from 'ramda'
import React, { useEffect, useRef, useCallback } from 'react'
const transactionProfit = R.prop('profit')
const mockPoint = (tx, offsetMs, profit) => {
const date = new Date(new Date(tx.created).getTime() + offsetMs).toISOString()
return { created: date, 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 timeFrameMS = {
Day: 24 * 3600 * 1000,
Week: 7 * 24 * 3600 * 1000,
Month: 30 * 24 * 3600 * 1000
}
const RefLineChart = ({
data: realData,
previousTimeData,
previousProfit,
timeFrame
}) => {
const svgRef = useRef()
const drawGraph = useCallback(() => {
const svg = d3.select(svgRef.current)
const margin = { top: 0, right: 0, bottom: 0, left: 0 }
const width = 336 - margin.left - margin.right
const height = 140 - margin.top - margin.bottom
const massageData = () => {
// if we're viewing transactions for the past day, then we group by hour. If not, we group by day
const method = timeFrame === 'Day' ? formatHour : formatDay
const aggregatedTX = R.values(R.reduceBy(reducer, [], method, realData))
// if no point exists, then return 2 points at y = 0
if (!aggregatedTX.length && !previousTimeData.length) {
const mockPoint1 = { created: new Date().toISOString(), profit: 0 }
const mockPoint2 = mockPoint(mockPoint1, -3600000, 0)
return [[mockPoint1, mockPoint2], true]
}
// if this time period has no txs, but previous time period has, then % change is -100%
if (!aggregatedTX.length && previousTimeData.length) {
const mockPoint1 = {
created: new Date().toISOString(),
profit: 0
}
const mockPoint2 = mockPoint(mockPoint1, -timeFrameMS[timeFrame], 1)
return [[mockPoint1, mockPoint2], false]
}
// if this time period has txs, but previous doesn't, then % change is +100%
if (aggregatedTX.length && !previousTimeData.length) {
const mockPoint1 = {
created: new Date().toISOString(),
profit: 1
}
const mockPoint2 = mockPoint(mockPoint1, -timeFrameMS[timeFrame], 0)
return [[mockPoint1, mockPoint2], false]
}
// if only one point exists, create point on the left - otherwise the line won't be drawn
if (aggregatedTX.length === 1) {
return [
R.append(
{
created: new Date(
Date.now() - timeFrameMS[timeFrame]
).toISOString(),
profit: previousProfit
},
aggregatedTX
),
false
]
}
// the boolean value is for zeroProfit. It makes the line render at y = 0 instead of y = 50% of container height
return [aggregatedTX, false]
}
/* Important step to make the graph look good!
This function groups transactions by either day or hour depending on the time frame
This makes the line look smooth and not all wonky when there are many transactions in a given time
*/
const [data, zeroProfit] = massageData()
// sets width of the graph
svg.attr('width', width)
// background color for the graph
svg
.append('rect')
.attr('x', 0)
.attr('y', -margin.top)
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top)
.attr('fill', 'var(--ghost)')
.attr('transform', `translate(${0},${margin.top})`)
// gradient color for the graph (creates the "url", the color is applied by calling the url, in the area color fill )
svg
.append('linearGradient')
.attr('id', 'area-gradient')
.attr('gradientUnits', 'userSpaceOnUse')
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 0)
.attr('y2', '100%')
.selectAll('stop')
.data([
{ offset: '0%', color: 'var(--zircon)' },
{ offset: '25%', color: 'var(--zircon)' },
{ offset: '100%', color: 'var(--ghost)' }
])
.enter()
.append('stop')
.attr('offset', function (d) {
return d.offset
})
.attr('stop-color', function (d) {
return d.color
})
const g = svg
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`)
const xDomain = d3.extent(data, t => t.created)
const yDomain = zeroProfit ? [0, 0.1] : [0, d3.max(data, t => t.profit)]
const y = d3
.scaleLinear()
// 30 is a margin so that the labels and the percentage change label can fit and not overlay the line path
.range([height, 40])
.domain([0, yDomain[1]])
const x = d3
.scaleTime()
.domain([new Date(xDomain[0]), new Date(xDomain[1])])
.range([0, width])
const line = d3
.line()
.x(function (d) {
return x(new Date(d.created))
})
.y(function (d) {
return y(d.profit)
})
const area = d3
.area()
.x(function (d) {
return x(new Date(d.created))
})
.y0(height)
.y1(function (d) {
return y(d.profit)
})
// area color fill
g.append('path')
.datum(data)
.attr('d', area)
.attr('fill', 'url(#area-gradient)')
// draw the line
g.append('path')
.datum(data)
.attr('d', line)
.attr('fill', 'none')
.attr('stroke-width', '2')
.attr('stroke-linejoin', 'round')
.attr('stroke', 'var(--zodiac)')
}, [realData, timeFrame, previousTimeData, previousProfit])
useEffect(() => {
// first we clear old chart DOM elements on component update
d3.select(svgRef.current).selectAll('*').remove()
drawGraph()
}, [drawGraph, realData])
return (
<>
<svg ref={svgRef} />
</>
)
}
export default RefLineChart

View file

@ -0,0 +1,348 @@
import BigNumber from 'bignumber.js'
import * as d3 from 'd3'
import { getTimezoneOffset } from 'date-fns-tz'
import { add, format, startOfWeek, startOfYear } from 'date-fns/fp'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import { numberToFiatAmount } from 'src/utils/number'
import { MINUTE, DAY, WEEK, MONTH } from 'src/utils/time'
const Graph = ({ data, timeFrame, timezone }) => {
const ref = useRef(null)
const GRAPH_HEIGHT = 250
const GRAPH_WIDTH = 555
const GRAPH_MARGIN = useMemo(
() => ({
top: 20,
right: 3.5,
bottom: 27,
left: 33.5
}),
[]
)
const offset = getTimezoneOffset(timezone)
const NOW = Date.now() + offset
const periodDomains = {
Day: [NOW - DAY, NOW],
Week: [NOW - WEEK, NOW],
Month: [NOW - MONTH, NOW]
}
const dataPoints = useMemo(
() => ({
Day: {
freq: 24,
step: 60 * 60 * 1000,
tick: d3.utcHour.every(4),
labelFormat: '%H:%M'
},
Week: {
freq: 7,
step: 24 * 60 * 60 * 1000,
tick: d3.utcDay.every(1),
labelFormat: '%a %d'
},
Month: {
freq: 30,
step: 24 * 60 * 60 * 1000,
tick: d3.utcDay.every(2),
labelFormat: '%d'
}
}),
[]
)
const filterDay = useCallback(
x => (timeFrame === 'Day' ? x.getUTCHours() === 0 : x.getUTCDate() === 1),
[timeFrame]
)
const getPastAndCurrentDayLabels = useCallback(d => {
const currentDate = new Date(d)
const currentDateDay = currentDate.getUTCDate()
const currentDateWeekday = currentDate.getUTCDay()
const currentDateMonth = currentDate.getUTCMonth()
const previousDate = new Date(currentDate.getTime())
previousDate.setUTCDate(currentDateDay - 1)
const previousDateDay = previousDate.getUTCDate()
const previousDateWeekday = previousDate.getUTCDay()
const previousDateMonth = previousDate.getUTCMonth()
const daysOfWeek = Array.from(Array(7)).map((_, i) =>
format('EEE', add({ days: i }, startOfWeek(new Date())))
)
const months = Array.from(Array(12)).map((_, i) =>
format('LLL', add({ months: i }, startOfYear(new Date())))
)
return {
previous:
currentDateMonth !== previousDateMonth
? months[previousDateMonth]
: `${daysOfWeek[previousDateWeekday]} ${previousDateDay}`,
current:
currentDateMonth !== previousDateMonth
? months[currentDateMonth]
: `${daysOfWeek[currentDateWeekday]} ${currentDateDay}`
}
}, [])
const buildTicks = useCallback(
domain => {
const points = []
const roundDate = d => {
const step = dataPoints[timeFrame].step
return new Date(Math.ceil(d.valueOf() / step) * step)
}
for (let i = 0; i <= dataPoints[timeFrame].freq; i++) {
const stepDate = new Date(NOW - i * dataPoints[timeFrame].step)
if (roundDate(stepDate) > domain[1]) continue
if (stepDate < domain[0]) continue
points.push(roundDate(stepDate))
}
return points
},
[NOW, dataPoints, timeFrame]
)
const x = d3
.scaleUtc()
.domain(periodDomains[timeFrame])
.range([GRAPH_MARGIN.left, GRAPH_WIDTH - GRAPH_MARGIN.right])
const y = d3
.scaleLinear()
.domain([
0,
(d3.max(data, d => new BigNumber(d.fiat).toNumber()) ?? 1000) * 1.05
])
.nice()
.range([GRAPH_HEIGHT - GRAPH_MARGIN.bottom, GRAPH_MARGIN.top])
const buildBackground = useCallback(
g => {
g.append('rect')
.attr('x', 0)
.attr('y', GRAPH_MARGIN.top)
.attr('width', GRAPH_WIDTH)
.attr('height', GRAPH_HEIGHT - GRAPH_MARGIN.top - GRAPH_MARGIN.bottom)
.attr('fill', 'var(--ghost)')
},
[GRAPH_MARGIN]
)
const buildXAxis = useCallback(
g =>
g
.attr(
'transform',
`translate(0, ${GRAPH_HEIGHT - GRAPH_MARGIN.bottom})`
)
.call(
d3
.axisBottom(x)
.ticks(dataPoints[timeFrame].tick)
.tickFormat(d => {
return d3.timeFormat(dataPoints[timeFrame].labelFormat)(
d.getTime() + d.getTimezoneOffset() * MINUTE
)
})
)
.call(g => g.select('.domain').remove()),
[GRAPH_MARGIN, dataPoints, timeFrame, x]
)
const buildYAxis = useCallback(
g =>
g
.attr('transform', `translate(${GRAPH_MARGIN.left}, 0)`)
.call(
d3
.axisLeft(y)
.ticks(5)
.tickFormat(d => {
if (d >= 1000) return numberToFiatAmount(d / 1000) + 'k'
return numberToFiatAmount(d)
})
)
.call(g => g.select('.domain').remove())
.selectAll('text')
.attr('dy', '-0.25rem'),
[GRAPH_MARGIN, y]
)
const buildGrid = useCallback(
g => {
g.attr('stroke', 'var(--zircon2)')
.attr('fill', 'var(--zircon2)')
// Vertical lines
.call(g =>
g
.append('g')
.selectAll('line')
.data(buildTicks(x.domain()))
.join('line')
.attr('x1', d => 0.5 + x(d))
.attr('x2', d => 0.5 + x(d))
.attr('y1', GRAPH_MARGIN.top)
.attr('y2', GRAPH_HEIGHT - GRAPH_MARGIN.bottom)
.attr('stroke-width', 1)
)
// Horizontal lines
.call(g =>
g
.append('g')
.selectAll('line')
.data(d3.axisLeft(y).scale().ticks(5))
.join('line')
.attr('y1', d => 0.5 + y(d))
.attr('y2', d => 0.5 + y(d))
.attr('x1', GRAPH_MARGIN.left)
.attr('x2', GRAPH_WIDTH)
)
// Thick vertical lines
.call(g =>
g
.append('g')
.selectAll('line')
.data(buildTicks(x.domain()).filter(filterDay))
.join('line')
.attr('class', 'dateSeparator')
.attr('x1', d => 0.5 + x(d))
.attr('x2', d => 0.5 + x(d))
.attr('y1', GRAPH_MARGIN.top - 10)
.attr('y2', GRAPH_HEIGHT - GRAPH_MARGIN.bottom)
.attr('stroke-width', 2)
.join('text')
)
// Left side breakpoint label
.call(g => {
const separator = d3?.select('.dateSeparator')?.node()?.getBBox()
if (!separator) return
const breakpoint = buildTicks(x.domain()).filter(filterDay)
const labels = getPastAndCurrentDayLabels(breakpoint)
return g
.append('text')
.attr('x', separator.x - 7)
.attr('y', separator.y)
.attr('text-anchor', 'end')
.attr('dy', '.25em')
.text(labels.previous)
})
// Right side breakpoint label
.call(g => {
const separator = d3?.select('.dateSeparator')?.node()?.getBBox()
if (!separator) return
const breakpoint = buildTicks(x.domain()).filter(filterDay)
const labels = getPastAndCurrentDayLabels(breakpoint)
return g
.append('text')
.attr('x', separator.x + 7)
.attr('y', separator.y)
.attr('text-anchor', 'start')
.attr('dy', '.25em')
.text(labels.current)
})
},
[GRAPH_MARGIN, buildTicks, getPastAndCurrentDayLabels, x, y, filterDay]
)
const formatTicksText = useCallback(
() =>
d3
.selectAll('.tick text')
.style('stroke', 'var(--comet)')
.style('fill', 'var(--comet)')
.style('stroke-width', 0)
.style('font-family', 'var(--museo)'),
[]
)
const formatText = useCallback(
() =>
d3
.selectAll('text')
.style('stroke', 'var(--comet)')
.style('fill', 'var(--comet)')
.style('stroke-width', 0)
.style('font-family', 'var(--museo)'),
[]
)
const formatTicks = useCallback(() => {
d3.selectAll('.tick line')
.style('stroke', 'transparent')
.style('fill', 'transparent')
}, [])
const drawData = useCallback(
g => {
g.selectAll('circle')
.data(data)
.join('circle')
.attr('cx', d => {
const created = new Date(d.created)
return x(created.setTime(created.getTime() + offset))
})
.attr('cy', d => y(new BigNumber(d.fiat).toNumber()))
.attr('fill', d =>
d.txClass === 'cashIn' ? 'var(--java)' : 'var(--neon)'
)
.attr('r', 3.5)
},
[data, offset, x, y]
)
const drawChart = useCallback(() => {
const svg = d3
.select(ref.current)
.attr('viewBox', [0, 0, GRAPH_WIDTH, GRAPH_HEIGHT])
svg.append('g').call(buildBackground)
svg.append('g').call(buildGrid)
svg.append('g').call(buildXAxis)
svg.append('g').call(buildYAxis)
svg.append('g').call(formatTicksText)
svg.append('g').call(formatText)
svg.append('g').call(formatTicks)
svg.append('g').call(drawData)
return svg.node()
}, [
buildBackground,
buildGrid,
buildXAxis,
buildYAxis,
drawData,
formatText,
formatTicks,
formatTicksText
])
useEffect(() => {
d3.select(ref.current).selectAll('*').remove()
drawChart()
}, [drawChart])
return <svg ref={ref} />
}
export default Graph

View file

@ -0,0 +1,12 @@
import React from 'react'
import { Info1, Label1 } from 'src/components/typography/index'
const InfoWithLabel = ({ info, label }) => {
return (
<div className="flex flex-col">
<Info1 className="mb-0">{info}</Info1>
<Label1 className="m-0">{label}</Label1>
</div>
)
}
export default InfoWithLabel

View file

@ -0,0 +1,42 @@
import classnames from 'classnames'
import * as R from 'ramda'
import React, { useState } from 'react'
import { H4 } from 'src/components/typography'
const ranges = ['Month', 'Week', 'Day']
const Nav = ({ handleSetRange, showPicker }) => {
const [clickedItem, setClickedItem] = useState('Day')
const isSelected = R.equals(clickedItem)
const handleClick = range => {
setClickedItem(range)
handleSetRange(range)
}
return (
<div className="flex justify-between items-center">
<H4 noMargin>{'System performance'}</H4>
{showPicker && (
<div className="flex gap-6">
{ranges.map((it, idx) => {
return (
<div
key={idx}
onClick={e => handleClick(e.target.innerText)}
className={classnames({
'cursor-pointer text-comet': true,
'font-bold text-zodiac border-b-zodiac border-b-2':
isSelected(it)
})}>
{it}
</div>
)
})}
</div>
)}
</div>
)
}
export default Nav

View file

@ -0,0 +1,283 @@
import { useQuery, gql } from '@apollo/client'
import BigNumber from 'bignumber.js'
import classnames from 'classnames'
import { isAfter } from 'date-fns/fp'
import * as R from 'ramda'
import React, { useState } from 'react'
import { Info2, Label1, Label2, P } from 'src/components/typography/index'
import PercentDownIcon from 'src/styling/icons/dashboard/down.svg?react'
import PercentNeutralIcon from 'src/styling/icons/dashboard/equal.svg?react'
import PercentUpIcon from 'src/styling/icons/dashboard/up.svg?react'
import { EmptyTable } from 'src/components/table'
import { java, neon } from 'src/styling/variables'
import { fromNamespace } from 'src/utils/config'
import { DAY, WEEK, MONTH } from 'src/utils/time'
import { timezones } from 'src/utils/timezone-list'
import { toTimezone } from 'src/utils/timezones'
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 getFiats = R.map(R.prop('fiat'))
const GET_DATA = gql`
query getData($excludeTestingCustomers: Boolean) {
transactions(excludeTestingCustomers: $excludeTestingCustomers) {
fiatCode
fiat
fixedFee
commissionPercentage
created
txClass
error
profit
dispense
sendConfirmed
}
fiatRates {
code
name
rate
}
config
}
`
const SystemPerformance = () => {
const [selectedRange, setSelectedRange] = useState('Day')
const { data, loading } = useQuery(GET_DATA, {
variables: { excludeTestingCustomers: true }
})
const fiatLocale = fromNamespace('locale')(data?.config).fiatCurrency
const timezone = fromNamespace('locale')(data?.config).timezone
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
if (!getLastTimePeriod) {
return (
t.error === null &&
isAfter(
toTimezone(t.created, timezone),
toTimezone(periodDomains[selectedRange][1], timezone)
) &&
isAfter(
toTimezone(periodDomains[selectedRange][0], timezone),
toTimezone(t.created, timezone)
)
)
}
return (
t.error === null &&
isAfter(
toTimezone(periodDomains[selectedRange][1], timezone),
toTimezone(t.created, timezone)
) &&
isAfter(
toTimezone(t.created, timezone),
toTimezone(periodDomains[selectedRange][0], timezone)
)
)
}
const convertFiatToLocale = item => {
if (item.fiatCode === fiatLocale) return item
const itemRate = R.find(R.propEq('code', item.fiatCode))(data.fiatRates)
const localeRate = R.find(R.propEq('code', fiatLocale))(data.fiatRates)
const multiplier = localeRate.rate / itemRate.rate
return { ...item, fiat: parseFloat(item.fiat) * multiplier }
}
const transactionsToShow = R.map(convertFiatToLocale)(
R.filter(isInRangeAndNoError(false), data?.transactions ?? [])
)
const transactionsLastTimePeriod = R.map(convertFiatToLocale)(
R.filter(isInRangeAndNoError(true), data?.transactions ?? [])
)
const getNumTransactions = () => {
return R.length(transactionsToShow)
}
const getFiatVolume = () =>
new BigNumber(R.sum(getFiats(transactionsToShow))).toFormat(2)
const getProfit = transactions => {
return R.reduce(
(acc, value) => acc.plus(value.profit),
new BigNumber(0),
transactions
)
}
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('txClass', 'cashIn'))(
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(data.transactions)}
handleSetRange={setSelectedRange}
/>
{!loading && R.isEmpty(data.transactions) && (
<EmptyTable className="pt-10" message="No transactions so far" />
)}
{!loading && !R.isEmpty(data.transactions) && (
<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)}
/>
</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

View file

@ -0,0 +1,2 @@
import SystemPerformance from './SystemPerformance'
export default SystemPerformance