164 lines
5.6 KiB
TypeScript
164 lines
5.6 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { XAxis, YAxis, ResponsiveContainer, Tooltip, Area, AreaChart } from "recharts"
|
|
import { useTheme } from "next-themes"
|
|
|
|
interface StockChartProps {
|
|
symbol: string
|
|
}
|
|
|
|
const timeRanges = [
|
|
{ label: "1D", value: "1D" },
|
|
{ label: "5D", value: "5D" },
|
|
{ label: "1M", value: "1M" },
|
|
{ label: "6M", value: "6M" },
|
|
{ label: "YTD", value: "YTD" },
|
|
{ label: "1Y", value: "1Y" },
|
|
{ label: "5Y", value: "5Y" },
|
|
{ label: "MAX", value: "MAX" },
|
|
]
|
|
|
|
// Mock chart data
|
|
const generateChartData = () => {
|
|
const data = []
|
|
const basePrice = 420
|
|
for (let i = 0; i < 100; i++) {
|
|
const time = new Date(Date.now() - (100 - i) * 60 * 60 * 1000).toLocaleTimeString("en-US", {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
})
|
|
const price = basePrice + Math.sin(i / 10) * 20 + (Math.random() - 0.5) * 10
|
|
data.push({
|
|
time,
|
|
price: price.toFixed(2),
|
|
volume: Math.floor(Math.random() * 1000000),
|
|
})
|
|
}
|
|
return data
|
|
}
|
|
|
|
export function StockChart({ symbol }: StockChartProps) {
|
|
const [selectedRange, setSelectedRange] = useState("1D")
|
|
const [chartData] = useState(generateChartData())
|
|
const { theme } = useTheme()
|
|
|
|
const currentPrice = Number.parseFloat(chartData[chartData.length - 1]?.price || "0")
|
|
const previousPrice = Number.parseFloat(chartData[0]?.price || "0")
|
|
const isPositive = currentPrice > previousPrice
|
|
|
|
const isDark = theme === "dark"
|
|
const axisColor = isDark ? "#e5e7eb" : "hsl(var(--muted-foreground))"
|
|
const strokeColor = isDark ? "#60a5fa" : "hsl(var(--primary))"
|
|
const gradientStopColor = isDark ? "#60a5fa" : "hsl(var(--primary))"
|
|
|
|
return (
|
|
<div className="bg-card border border-border rounded-lg p-4">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="flex items-center space-x-4">
|
|
<div className="flex items-center space-x-2">
|
|
{timeRanges.map((range) => (
|
|
<Button
|
|
key={range.value}
|
|
variant={selectedRange === range.value ? "default" : "ghost"}
|
|
size="sm"
|
|
onClick={() => setSelectedRange(range.value)}
|
|
className="h-8 px-3 text-xs"
|
|
>
|
|
{range.label}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<Badge variant="secondary" className="text-xs">
|
|
-US$19.40
|
|
</Badge>
|
|
<Badge variant={isPositive ? "default" : "destructive"} className="text-xs">
|
|
{isPositive ? "+" : ""}
|
|
{(((currentPrice - previousPrice) / previousPrice) * 100).toFixed(2)}%
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="h-80 w-full">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<AreaChart data={chartData}>
|
|
<defs>
|
|
<linearGradient id="priceGradient" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="5%" stopColor={gradientStopColor} stopOpacity={0.4} />
|
|
<stop offset="95%" stopColor={gradientStopColor} stopOpacity={0} />
|
|
</linearGradient>
|
|
</defs>
|
|
<XAxis
|
|
dataKey="time"
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fontSize: 12, fill: axisColor }}
|
|
interval="preserveStartEnd"
|
|
/>
|
|
<YAxis
|
|
axisLine={false}
|
|
tickLine={false}
|
|
tick={{ fontSize: 12, fill: axisColor }}
|
|
domain={["dataMin - 5", "dataMax + 5"]}
|
|
/>
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: "hsl(var(--popover))",
|
|
border: "1px solid hsl(var(--border))",
|
|
borderRadius: "8px",
|
|
color: "hsl(var(--popover-foreground))",
|
|
}}
|
|
formatter={(value: any) => [`$${value}`, "Price"]}
|
|
labelStyle={{ color: "hsl(var(--muted-foreground))" }}
|
|
/>
|
|
<Area type="monotone" dataKey="price" stroke={strokeColor} strokeWidth={2} fill="url(#priceGradient)" />
|
|
</AreaChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
|
|
<div className="mt-4 grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
<div>
|
|
<span className="text-muted-foreground">Prev Close</span>
|
|
<div className="font-medium">$442.79</div>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">52W Range</span>
|
|
<div className="font-medium">$212.41 - $488.54</div>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Market Cap</span>
|
|
<div className="font-medium">$1.37T</div>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Open</span>
|
|
<div className="font-medium">$435.24</div>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">P/E Ratio</span>
|
|
<div className="font-medium">199.71</div>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Dividend Yield</span>
|
|
<div className="font-medium">—</div>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Day Range</span>
|
|
<div className="font-medium">$419.08 - $435.35</div>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">Volume</span>
|
|
<div className="font-medium">96M</div>
|
|
</div>
|
|
<div>
|
|
<span className="text-muted-foreground">EPS</span>
|
|
<div className="font-medium">$2.12</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|