Skip to content

Commit dffc759

Browse files
authored
Merge pull request #388 from Luluameh/feat/analytics-portfolio-chart
feat: add portfolio performance chart card to Analytics page (#255)
2 parents 4e44d08 + e9381be commit dffc759

File tree

4 files changed

+645
-10
lines changed

4 files changed

+645
-10
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
"use client";
2+
3+
import React, { useState } from "react";
4+
import {
5+
AreaChart,
6+
Area,
7+
XAxis,
8+
YAxis,
9+
Tooltip,
10+
ResponsiveContainer,
11+
CartesianGrid,
12+
} from "recharts";
13+
import { TrendingUp, MoreHorizontal } from "lucide-react";
14+
15+
/* ── Sample data matching the mockup date range ─────────────────────── */
16+
const chartData = [
17+
{ date: "Oct 01", value: 118200 },
18+
{ date: "Oct 03", value: 119800 },
19+
{ date: "Oct 05", value: 119500 },
20+
{ date: "Oct 07", value: 120100 },
21+
{ date: "Oct 09", value: 119900 },
22+
{ date: "Oct 10", value: 120800 },
23+
{ date: "Oct 12", value: 121200 },
24+
{ date: "Oct 14", value: 120600 },
25+
{ date: "Oct 16", value: 121800 },
26+
{ date: "Oct 18", value: 122300 },
27+
{ date: "Oct 20", value: 123100 },
28+
{ date: "Oct 22", value: 123800 },
29+
{ date: "Oct 25", value: 124500 },
30+
{ date: "Oct 27", value: 124200 },
31+
{ date: "Oct 30", value: 124800 },
32+
{ date: "Nov 01", value: 124592 },
33+
];
34+
35+
/* ── Custom Tooltip ─────────────────────────────────────────────────── */
36+
interface TooltipPayloadItem {
37+
value: number;
38+
payload: { date: string; value: number };
39+
}
40+
41+
function CustomTooltip({
42+
active,
43+
payload,
44+
}: {
45+
active?: boolean;
46+
payload?: TooltipPayloadItem[];
47+
label?: string;
48+
}) {
49+
if (!active || !payload || payload.length === 0) return null;
50+
51+
const { date, value } = payload[0].payload;
52+
53+
return (
54+
<div
55+
style={{
56+
background: "rgba(8, 20, 24, 0.95)",
57+
border: "1px solid rgba(0, 242, 254, 0.3)",
58+
borderRadius: 8,
59+
padding: "10px 14px",
60+
boxShadow: "0 8px 32px rgba(0,0,0,0.45)",
61+
}}
62+
>
63+
<p
64+
style={{
65+
margin: 0,
66+
fontSize: 11,
67+
color: "#5e8c96",
68+
marginBottom: 4,
69+
}}
70+
>
71+
{date}, 2023
72+
</p>
73+
<p
74+
style={{
75+
margin: 0,
76+
fontSize: 16,
77+
fontWeight: 700,
78+
color: "#ffffff",
79+
}}
80+
>
81+
$
82+
{value.toLocaleString("en-US", {
83+
minimumFractionDigits: 2,
84+
maximumFractionDigits: 2,
85+
})}
86+
</p>
87+
</div>
88+
);
89+
}
90+
91+
/* ── Custom Active Dot ──────────────────────────────────────────────── */
92+
function ActiveDot(props: { cx?: number; cy?: number }) {
93+
const { cx = 0, cy = 0 } = props;
94+
return (
95+
<g>
96+
{/* Outer glow */}
97+
<circle cx={cx} cy={cy} r={10} fill="rgba(0,242,254,0.15)" />
98+
{/* Ring */}
99+
<circle
100+
cx={cx}
101+
cy={cy}
102+
r={5}
103+
fill="#081418"
104+
stroke="#00f2fe"
105+
strokeWidth={2}
106+
/>
107+
{/* Vertical guide line */}
108+
<line
109+
x1={cx}
110+
y1={cy + 10}
111+
x2={cx}
112+
y2={300}
113+
stroke="rgba(0,242,254,0.25)"
114+
strokeWidth={1}
115+
strokeDasharray="3 3"
116+
/>
117+
</g>
118+
);
119+
}
120+
121+
/* ── Main Component ─────────────────────────────────────────────────── */
122+
export default function PortfolioPerformanceChart() {
123+
const [isMenuOpen, setIsMenuOpen] = useState(false);
124+
125+
return (
126+
<div
127+
className="relative overflow-hidden rounded-2xl border"
128+
style={{
129+
background:
130+
"linear-gradient(180deg, rgba(6,18,20,0.85) 0%, rgba(4,12,14,0.75) 100%)",
131+
borderColor: "rgba(8,120,120,0.12)",
132+
}}
133+
>
134+
{/* ── Header ─────────────────────────────────────────────────── */}
135+
<div className="flex items-start justify-between px-6 pt-6 pb-2">
136+
{/* Left section */}
137+
<div>
138+
<p
139+
className="text-[11px] tracking-[0.15em] uppercase m-0 mb-1"
140+
style={{ color: "#5e8c96" }}
141+
>
142+
Total Portfolio Value
143+
</p>
144+
<div className="flex items-center gap-3 flex-wrap">
145+
<h2
146+
className="text-[32px] font-bold m-0 leading-tight"
147+
style={{ color: "#ffffff" }}
148+
>
149+
$124,592.45
150+
</h2>
151+
<span
152+
className="inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-semibold"
153+
style={{
154+
background: "rgba(0, 242, 254, 0.1)",
155+
color: "#00f2fe",
156+
border: "1px solid rgba(0, 242, 254, 0.15)",
157+
}}
158+
>
159+
<TrendingUp size={12} />
160+
+12.4%
161+
</span>
162+
</div>
163+
</div>
164+
165+
{/* Right – overflow menu */}
166+
<button
167+
onClick={() => setIsMenuOpen(!isMenuOpen)}
168+
className="p-1.5 rounded-lg transition-colors cursor-pointer"
169+
style={{
170+
color: "#5e8c96",
171+
background: "transparent",
172+
border: "none",
173+
}}
174+
onMouseEnter={(e) =>
175+
(e.currentTarget.style.background = "rgba(0,242,254,0.08)")
176+
}
177+
onMouseLeave={(e) =>
178+
(e.currentTarget.style.background = "transparent")
179+
}
180+
aria-label="More options"
181+
>
182+
<MoreHorizontal size={18} />
183+
</button>
184+
</div>
185+
186+
{/* ── Chart ──────────────────────────────────────────────────── */}
187+
<div className="w-full" style={{ height: 260 }}>
188+
<ResponsiveContainer width="100%" height="100%">
189+
<AreaChart
190+
data={chartData}
191+
margin={{ top: 20, right: 24, bottom: 0, left: 24 }}
192+
>
193+
<defs>
194+
<linearGradient id="areaGrad" x1="0" y1="0" x2="0" y2="1">
195+
<stop offset="0%" stopColor="#00f2fe" stopOpacity={0.25} />
196+
<stop offset="60%" stopColor="#00f2fe" stopOpacity={0.06} />
197+
<stop offset="100%" stopColor="#00f2fe" stopOpacity={0} />
198+
</linearGradient>
199+
</defs>
200+
201+
<CartesianGrid
202+
strokeDasharray="3 3"
203+
stroke="rgba(94,140,150,0.07)"
204+
vertical={false}
205+
/>
206+
207+
<XAxis
208+
dataKey="date"
209+
axisLine={false}
210+
tickLine={false}
211+
tick={{ fill: "#3d6a75", fontSize: 11 }}
212+
dy={10}
213+
interval={1}
214+
/>
215+
216+
<YAxis hide domain={["dataMin - 2000", "dataMax + 1000"]} />
217+
218+
<Tooltip
219+
content={<CustomTooltip />}
220+
cursor={false}
221+
/>
222+
223+
<Area
224+
type="monotone"
225+
dataKey="value"
226+
stroke="#00f2fe"
227+
strokeWidth={2}
228+
fill="url(#areaGrad)"
229+
activeDot={<ActiveDot />}
230+
dot={false}
231+
/>
232+
</AreaChart>
233+
</ResponsiveContainer>
234+
</div>
235+
</div>
236+
);
237+
}

frontend/app/dashboard/analytics/page.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from "react";
22
import { MoreHorizontal, PieChart } from "lucide-react";
3+
import PortfolioPerformanceChart from "./PortfolioPerformanceChart";
34

45
export const metadata = { title: "Analytics – Nestera" };
56

@@ -18,11 +19,7 @@ export default function AnalyticsPage() {
1819
</div>
1920
</div>
2021

21-
<div className="bg-linear-to-b from-[rgba(6,18,20,0.45)] to-[rgba(4,12,14,0.35)] border border-[rgba(8,120,120,0.06)] rounded-2xl p-8 text-center">
22-
<p className="text-[#5e8c96] text-sm">
23-
Analytics charts and data will appear here.
24-
</p>
25-
</div>
22+
<PortfolioPerformanceChart />
2623

2724
<div className="grid grid-cols-1 xl:grid-cols-2 gap-6 mt-6">
2825
<article className="rounded-2xl border border-[rgba(8,120,120,0.06)] bg-linear-to-b from-[rgba(6,18,20,0.45)] to-[rgba(4,12,14,0.35)] p-6">
@@ -106,4 +103,4 @@ export default function AnalyticsPage() {
106103
</div>
107104
</div>
108105
);
109-
}
106+
}

0 commit comments

Comments
 (0)