Initial commit: SAPIENS Stock service
This commit is contained in:
163
components/stock-chart.tsx
Normal file
163
components/stock-chart.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user