Initial commit: SAPIENS Stock service

This commit is contained in:
jungwoo choi
2025-10-22 09:31:15 +09:00
commit f0c0f3b8d6
93 changed files with 8369 additions and 0 deletions

163
components/stock-chart.tsx Normal file
View 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>
)
}