Skip to content

Commit 2aac575

Browse files
kanerepmintdart
andauthored
enh: improves the token pnl page (#2174)
* wip(token-pnl): add chart, pnl grid and relative perf to btc, eth, sol * style: tweaks to general component styling to better align * feat: add tooltip to daily pnl grid * style: more changes to the layout and pnl grid shows max 90 days * feat: perf improvements and general styling tweaks * remove redundant component * refactor: use existing multi-series chart component * style: modify text to secondary * fix * fix: remove the additional day * fix: chart xAxis and percentage to fixed 2 * refactor * fix: add correct formatting to the label * use exisiting chart components --------- Co-authored-by: mintdart <[email protected]>
1 parent 3838f3c commit 2aac575

File tree

8 files changed

+693
-248
lines changed

8 files changed

+693
-248
lines changed

src/components/ECharts/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export interface ILineAndBarChartProps {
8282
}
8383
chartOptions?: {
8484
[key: string]: {
85-
[key: string]: Value | Array<Value> | ((params: any) => string)
85+
[key: string]: Value | Array<Value> | ((params: any) => string | number)
8686
}
8787
}
8888
height?: string
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { formattedNum } from '~/utils'
2+
import { formatPercent } from './format'
3+
import type { ComparisonEntry } from './types'
4+
5+
export const ComparisonPanel = ({ entries, activeId }: { entries: ComparisonEntry[]; activeId: string }) => {
6+
if (!entries.length) return null
7+
return (
8+
<div className="rounded-md border border-(--cards-border) bg-(--cards-bg) p-4">
9+
<div className="mb-3 flex items-center justify-between gap-2">
10+
<h3 className="text-base font-semibold">Range Comparison</h3>
11+
<span className="text-xs text-(--text-secondary)">BTC · ETH · SOL</span>
12+
</div>
13+
<div className="grid gap-3 sm:grid-cols-3">
14+
{entries.map((entry) => {
15+
const isPositive = entry.percentChange >= 0
16+
return (
17+
<div
18+
key={entry.id}
19+
className={`flex flex-col gap-1 rounded-md border border-(--cards-border) p-3 transition-colors duration-200 ${
20+
entry.id === activeId ? 'bg-(--bg-surface)' : 'bg-(--cards-bg)'
21+
}`}
22+
>
23+
<div className="flex items-center gap-2">
24+
{entry.image ? (
25+
<img src={entry.image} alt={entry.name} width={20} height={20} className="rounded-full" />
26+
) : null}
27+
<span className="text-sm font-medium tracking-wide uppercase">{entry.symbol}</span>
28+
</div>
29+
<span className={`text-xl font-semibold ${isPositive ? 'text-emerald-500' : 'text-red-500'}`}>
30+
{formatPercent(entry.percentChange)}
31+
</span>
32+
<span className="text-xs text-(--text-secondary)">{`${isPositive ? '+' : ''}$${formattedNum(entry.absoluteChange)}`}</span>
33+
<span className="text-xs text-(--text-secondary)">{`$${formattedNum(entry.startPrice)} → $${formattedNum(entry.endPrice)}`}</span>
34+
</div>
35+
)
36+
})}
37+
</div>
38+
</div>
39+
)
40+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Tooltip } from '~/components/Tooltip'
2+
import { formatDateLabel, formatPercent } from './format'
3+
import type { TimelinePoint } from './types'
4+
5+
export const DailyPnLGrid = ({ timeline }: { timeline: TimelinePoint[] }) => {
6+
if (!timeline.length) return null
7+
const days = timeline.slice(1)
8+
if (!days.length) return null
9+
10+
const displayDays = days.slice(-90)
11+
12+
return (
13+
<div className="overflow-hidden rounded-md border border-(--cards-border) bg-(--cards-bg) p-4">
14+
<div className="mb-3 flex items-center justify-between gap-2">
15+
<h3 className="text-base font-semibold">Daily Change Grid</h3>
16+
<span className="text-xs text-(--text-secondary)">{displayDays.length} days shown</span>
17+
</div>
18+
<div className="no-scrollbar flex max-w-full flex-wrap gap-1.5 pb-1">
19+
{displayDays.map((day) => {
20+
const isPositive = day.percentChange > 0
21+
const isZero = day.percentChange === 0
22+
const alpha = Math.min(0.6, Math.max(0.22, Math.abs(day.percentChange) / 9))
23+
const backgroundColor = isZero
24+
? 'rgba(148, 163, 184, 0.22)'
25+
: isPositive
26+
? `rgba(16, 185, 129, ${alpha})` // emerald-500 with controlled alpha
27+
: `rgba(239, 68, 68, ${alpha})` // red-500 with controlled alpha
28+
return (
29+
<Tooltip
30+
key={day.timestamp}
31+
placement="top"
32+
content={`${formatPercent(day.percentChange)}${formatDateLabel(day.timestamp)}`}
33+
>
34+
<div
35+
className="flex h-8 w-8 shrink-0 items-end justify-center rounded-md border border-white/5 transition-transform duration-200 hover:scale-[0.99]"
36+
style={{ background: backgroundColor }}
37+
>
38+
<span className="sr-only">{`${formatPercent(day.percentChange)} on ${formatDateLabel(day.timestamp)}`}</span>
39+
</div>
40+
</Tooltip>
41+
)
42+
})}
43+
</div>
44+
</div>
45+
)
46+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export const DateInput = ({
2+
label,
3+
value,
4+
onChange,
5+
min,
6+
max,
7+
invalid
8+
}: {
9+
label: string
10+
value: string
11+
onChange: (value: string) => void
12+
min?: string
13+
max?: string
14+
invalid?: boolean
15+
}) => {
16+
return (
17+
<label className="flex flex-col gap-1.5 text-sm">
18+
<span className="font-light text-(--text-secondary)">{label}</span>
19+
<input
20+
type="date"
21+
value={value}
22+
onChange={(event) => onChange(event.target.value)}
23+
min={min}
24+
max={max}
25+
className={`rounded-md border bg-(--bg-input) px-3 py-2.5 text-base text-black outline-0 transition-colors duration-200 focus:border-white/30 focus:ring-0 dark:text-white ${invalid ? 'border-red-500' : 'border-(--form-control-border)'}`}
26+
/>
27+
</label>
28+
)
29+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export const StatsCard = ({
2+
label,
3+
value,
4+
subtle,
5+
variant = 'default'
6+
}: {
7+
label: string
8+
value: string
9+
subtle?: string
10+
variant?: 'default' | 'highlight'
11+
}) => {
12+
const base = 'flex flex-col rounded-md border p-3 transition-colors duration-200'
13+
const containerClass =
14+
variant === 'highlight'
15+
? `${base} border-(--cards-border) bg-gradient-to-b from-white/5 to-transparent backdrop-blur-sm`
16+
: `${base} border-(--cards-border) bg-(--cards-bg)`
17+
return (
18+
<div className={containerClass}>
19+
<span className="text-xs font-light tracking-wide text-(--text-secondary) uppercase">{label}</span>
20+
<span className={variant === 'highlight' ? 'text-xl font-bold' : 'text-lg font-medium'}>{value}</span>
21+
{subtle ? <span className="text-xs text-(--text-secondary)">{subtle}</span> : null}
22+
</div>
23+
)
24+
}

src/containers/TokenPnl/format.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export const formatPercent = (value: number) => {
2+
if (!Number.isFinite(value)) return '0%'
3+
const formatted = value.toFixed(2)
4+
const prefix = value > 0 ? '+' : value < 0 ? '' : ''
5+
return `${prefix}${formatted}%`
6+
}
7+
8+
export const formatDateLabel = (timestamp: number) => {
9+
return new Date(timestamp * 1000).toLocaleDateString(undefined, {
10+
month: 'short',
11+
day: 'numeric',
12+
timeZone: 'UTC'
13+
})
14+
}

0 commit comments

Comments
 (0)