606 lines
24 KiB
TypeScript
606 lines
24 KiB
TypeScript
import { useState, useEffect, useMemo } from "react";
|
||
import {
|
||
Search,
|
||
Factory,
|
||
LineChart as LineChartIcon,
|
||
X,
|
||
Loader2,
|
||
Building2,
|
||
Calendar,
|
||
Layers,
|
||
ArrowUpRight
|
||
} from "lucide-react";
|
||
import {
|
||
ResponsiveContainer,
|
||
ComposedChart,
|
||
Area,
|
||
XAxis,
|
||
YAxis,
|
||
Tooltip,
|
||
CartesianGrid
|
||
} from "recharts";
|
||
import { API_BASE } from "@/lib/api";
|
||
|
||
const API = API_BASE;
|
||
|
||
export interface ImePricePoint {
|
||
id: number;
|
||
date: string; // YYYY-MM-DD
|
||
high: number;
|
||
low: number;
|
||
mid: number;
|
||
}
|
||
|
||
export interface ImeSymbol {
|
||
id: number;
|
||
title: string;
|
||
product_name: string;
|
||
manufacturer: string;
|
||
is_active: boolean;
|
||
updated_at: string;
|
||
latest_price?: ImePricePoint;
|
||
}
|
||
|
||
export interface ImeSubcategory {
|
||
id: number;
|
||
title: string;
|
||
symbols_count: number;
|
||
symbols: ImeSymbol[];
|
||
}
|
||
|
||
export interface ImeCategory {
|
||
id: number;
|
||
title: string;
|
||
subcategories_count: number;
|
||
total_symbols: number;
|
||
subcategories: ImeSubcategory[];
|
||
}
|
||
|
||
export interface ImeSymbolDetail {
|
||
id: number;
|
||
cat_id: number;
|
||
title: string;
|
||
manufacturer?: string;
|
||
order_no?: number;
|
||
small_desc?: string;
|
||
total_points: number;
|
||
latest_price?: ImePricePoint;
|
||
prices: ImePricePoint[];
|
||
}
|
||
|
||
type Timeframe = "3M" | "6M" | "1Y" | "ENTIRE";
|
||
|
||
export function ImeView() {
|
||
const [categories, setCategories] = useState<ImeCategory[]>([]);
|
||
const [loading, setLoading] = useState<boolean>(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
// Filters
|
||
const [selectedRootId, setSelectedRootId] = useState<number | "ALL">("ALL");
|
||
const [selectedSubId, setSelectedSubId] = useState<number | "ALL">("ALL");
|
||
const [search, setSearch] = useState<string>("");
|
||
|
||
// Modal Detail
|
||
const [selectedSymbol, setSelectedSymbol] = useState<ImeSymbol | null>(null);
|
||
const [symbolDetail, setSymbolDetail] = useState<ImeSymbolDetail | null>(null);
|
||
const [detailLoading, setDetailLoading] = useState<boolean>(false);
|
||
const [timeframe, setTimeframe] = useState<Timeframe>("ENTIRE");
|
||
|
||
// Load enriched categories with embedded latest prices on mount
|
||
useEffect(() => {
|
||
let alive = true;
|
||
setLoading(true);
|
||
fetch(`${API}/api/ime/categories`, { credentials: "include" })
|
||
.then((r) => {
|
||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||
return r.json();
|
||
})
|
||
.then((data: ImeCategory[]) => {
|
||
if (alive) {
|
||
setCategories(data);
|
||
setError(null);
|
||
}
|
||
})
|
||
.catch((err: any) => {
|
||
if (alive) setError(err.message || "خطا در دریافت اطلاعات بورس فلزات ایران");
|
||
})
|
||
.finally(() => {
|
||
if (alive) setLoading(false);
|
||
});
|
||
|
||
return () => {
|
||
alive = false;
|
||
};
|
||
}, []);
|
||
|
||
// Fetch symbol detail & full price history when modal opens
|
||
useEffect(() => {
|
||
if (!selectedSymbol) {
|
||
setSymbolDetail(null);
|
||
return;
|
||
}
|
||
|
||
let alive = true;
|
||
setDetailLoading(true);
|
||
setTimeframe("ENTIRE");
|
||
|
||
fetch(`${API}/api/ime/symbol/${selectedSymbol.id}`, { credentials: "include" })
|
||
.then((r) => {
|
||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||
return r.json();
|
||
})
|
||
.then((data: ImeSymbolDetail) => {
|
||
if (alive) setSymbolDetail(data);
|
||
})
|
||
.catch(() => {
|
||
if (alive) setSymbolDetail(null);
|
||
})
|
||
.finally(() => {
|
||
if (alive) setDetailLoading(false);
|
||
});
|
||
|
||
return () => {
|
||
alive = false;
|
||
};
|
||
}, [selectedSymbol]);
|
||
|
||
// Handle escape key
|
||
useEffect(() => {
|
||
const handleKeyDown = (e: KeyboardEvent) => {
|
||
if (e.key === "Escape") setSelectedSymbol(null);
|
||
};
|
||
window.addEventListener("keydown", handleKeyDown);
|
||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||
}, []);
|
||
|
||
// Active subcategories based on root category selection
|
||
const currentSubcategories = useMemo(() => {
|
||
if (selectedRootId === "ALL") return [];
|
||
const cat = categories.find((c) => c.id === selectedRootId);
|
||
return cat ? cat.subcategories : [];
|
||
}, [categories, selectedRootId]);
|
||
|
||
// Flattened symbols filtered by root, subcategory, and search query
|
||
const filteredSymbols = useMemo(() => {
|
||
let list: Array<ImeSymbol & { rootTitle: string; subTitle: string }> = [];
|
||
|
||
for (const root of categories) {
|
||
if (selectedRootId !== "ALL" && root.id !== selectedRootId) continue;
|
||
|
||
for (const sub of root.subcategories) {
|
||
if (selectedSubId !== "ALL" && sub.id !== selectedSubId) continue;
|
||
|
||
for (const sym of sub.symbols) {
|
||
list.push({
|
||
...sym,
|
||
rootTitle: root.title,
|
||
subTitle: sub.title,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!search.trim()) return list;
|
||
|
||
const q = search.toLowerCase().trim();
|
||
return list.filter(
|
||
(s) =>
|
||
s.title.toLowerCase().includes(q) ||
|
||
s.product_name.toLowerCase().includes(q) ||
|
||
s.manufacturer.toLowerCase().includes(q) ||
|
||
s.rootTitle.toLowerCase().includes(q) ||
|
||
s.subTitle.toLowerCase().includes(q)
|
||
);
|
||
}, [categories, selectedRootId, selectedSubId, search]);
|
||
|
||
// Filtered prices for modal chart by timeframe
|
||
const chartPoints = useMemo(() => {
|
||
if (!symbolDetail?.prices?.length) return [];
|
||
const prices = symbolDetail.prices;
|
||
const total = prices.length;
|
||
|
||
let sliceCount = total;
|
||
if (timeframe === "3M") sliceCount = 15;
|
||
else if (timeframe === "6M") sliceCount = 30;
|
||
else if (timeframe === "1Y") sliceCount = 60;
|
||
else if (timeframe === "ENTIRE") sliceCount = total;
|
||
|
||
const start = Math.max(0, total - sliceCount);
|
||
return prices.slice(start);
|
||
}, [symbolDetail, timeframe]);
|
||
|
||
// Stats calculation for the symbol modal
|
||
const stats = useMemo(() => {
|
||
if (!chartPoints.length) return null;
|
||
const mids = chartPoints.map((p) => p.mid).filter((v): v is number => typeof v === "number" && v > 0);
|
||
if (!mids.length) return null;
|
||
|
||
const max = Math.max(...mids);
|
||
const min = Math.min(...mids);
|
||
const avg = mids.reduce((a, b) => a + b, 0) / mids.length;
|
||
const first = mids[0];
|
||
const last = mids[mids.length - 1];
|
||
const diff = last - first;
|
||
const pct = first > 0 ? (diff / first) * 100 : 0;
|
||
|
||
return { max, min, avg, diff, pct, last };
|
||
}, [chartPoints]);
|
||
|
||
return (
|
||
<main className="mx-auto max-w-6xl p-4 sm:p-6 space-y-6" dir="rtl">
|
||
{/* Header */}
|
||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b border-border/60 pb-5">
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-10 h-10 rounded-xl bg-primary/10 border border-primary/20 grid place-items-center shrink-0">
|
||
<Factory className="w-5 h-5 text-primary" />
|
||
</div>
|
||
<div>
|
||
<h1 className="text-xl sm:text-2xl font-bold tracking-tight text-foreground">
|
||
بورس فلزات ایران (IME)
|
||
</h1>
|
||
<p className="text-xs text-muted-foreground mt-0.5">
|
||
تابلوی معاملات فیزیکی بورس کالا • آخرین نرخهای کشفشده و آرشیو تاریخی
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Global Search */}
|
||
<div className="relative w-full sm:w-72">
|
||
<Search className="absolute right-3 top-2.5 w-4 h-4 text-muted-foreground" />
|
||
<input
|
||
type="text"
|
||
value={search}
|
||
onChange={(e) => setSearch(e.target.value)}
|
||
placeholder="جستجوی نام کالا یا تولیدکننده..."
|
||
className="w-full rounded-xl border border-border/80 bg-background/80 py-1.5 pr-9 pl-3 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{error && (
|
||
<div className="rounded-xl border border-destructive/40 bg-destructive/10 p-4 text-sm text-destructive">
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
{/* Root Category Navigation Tabs */}
|
||
<div className="flex items-center gap-1.5 overflow-x-auto pb-1 no-scrollbar">
|
||
<button
|
||
onClick={() => {
|
||
setSelectedRootId("ALL");
|
||
setSelectedSubId("ALL");
|
||
}}
|
||
className={`px-3.5 py-1.5 text-xs font-semibold rounded-xl whitespace-nowrap transition ${
|
||
selectedRootId === "ALL"
|
||
? "bg-primary text-primary-foreground shadow-sm"
|
||
: "border border-border/60 bg-muted/30 text-muted-foreground hover:text-foreground hover:bg-muted"
|
||
}`}
|
||
>
|
||
همه فلزات
|
||
</button>
|
||
|
||
{categories.map((cat) => (
|
||
<button
|
||
key={cat.id}
|
||
onClick={() => {
|
||
setSelectedRootId(cat.id);
|
||
setSelectedSubId("ALL");
|
||
}}
|
||
className={`px-3.5 py-1.5 text-xs font-semibold rounded-xl whitespace-nowrap transition flex items-center gap-1.5 ${
|
||
selectedRootId === cat.id
|
||
? "bg-primary text-primary-foreground shadow-sm"
|
||
: "border border-border/60 bg-muted/30 text-muted-foreground hover:text-foreground hover:bg-muted"
|
||
}`}
|
||
>
|
||
<span>{cat.title}</span>
|
||
<span className="text-[10px] opacity-70">({cat.total_symbols})</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{/* Subcategory Pills */}
|
||
{currentSubcategories.length > 0 && (
|
||
<div className="flex items-center gap-1.5 overflow-x-auto pb-1 no-scrollbar border-t border-border/40 pt-3">
|
||
<button
|
||
onClick={() => setSelectedSubId("ALL")}
|
||
className={`px-3 py-1 text-[11px] rounded-lg whitespace-nowrap transition ${
|
||
selectedSubId === "ALL"
|
||
? "bg-foreground text-background font-bold shadow-xs"
|
||
: "text-muted-foreground hover:text-foreground hover:bg-muted/60"
|
||
}`}
|
||
>
|
||
همه زیردستهها
|
||
</button>
|
||
|
||
{currentSubcategories.map((sub) => (
|
||
<button
|
||
key={sub.id}
|
||
onClick={() => setSelectedSubId(sub.id)}
|
||
className={`px-3 py-1 text-[11px] rounded-lg whitespace-nowrap transition flex items-center gap-1 ${
|
||
selectedSubId === sub.id
|
||
? "bg-foreground text-background font-bold shadow-xs"
|
||
: "text-muted-foreground hover:text-foreground hover:bg-muted/60"
|
||
}`}
|
||
>
|
||
<span>{sub.title}</span>
|
||
<span className="text-[10px] opacity-60">({sub.symbols_count})</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* Indicator */}
|
||
<div className="flex items-center justify-between text-xs text-muted-foreground px-1">
|
||
<span>
|
||
نمایش <strong className="text-foreground">{filteredSymbols.length.toLocaleString()}</strong> نماد معاملاتی
|
||
</span>
|
||
</div>
|
||
|
||
{/* Loading state */}
|
||
{loading ? (
|
||
<div className="py-20 flex flex-col items-center justify-center gap-3 text-muted-foreground text-sm">
|
||
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
||
<span>در حال دریافت نمادهای بورس فلزات ایران...</span>
|
||
</div>
|
||
) : filteredSymbols.length === 0 ? (
|
||
<div className="py-16 text-center text-muted-foreground text-sm border rounded-2xl border-dashed">
|
||
نمادی با این مشخصات یافت نشد.
|
||
</div>
|
||
) : (
|
||
/* Symbols Cards Grid */
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{filteredSymbols.slice(0, 120).map((sym) => (
|
||
<div
|
||
key={sym.id}
|
||
onClick={() => setSelectedSymbol(sym)}
|
||
className="group cursor-pointer relative overflow-hidden rounded-2xl border border-border/70 bg-gradient-to-b from-card to-card/40 p-5 backdrop-blur-md transition-all duration-200 hover:shadow-xl hover:-translate-y-1 hover:border-primary flex flex-col justify-between"
|
||
>
|
||
<div className="space-y-2">
|
||
<div className="text-sm font-bold text-foreground group-hover:text-primary transition-colors line-clamp-2">
|
||
{sym.product_name || sym.title}
|
||
</div>
|
||
|
||
{sym.manufacturer && (
|
||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||
<Building2 className="w-3.5 h-3.5 text-primary/70 shrink-0" />
|
||
<span className="truncate">{sym.manufacturer}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* LATEST PRICE PROMINENTLY DISPLAYED ON CARD FACE */}
|
||
<div className="mt-4 pt-3 border-t border-border/40 space-y-2">
|
||
<div className="flex items-baseline justify-between">
|
||
<span className="text-xs text-muted-foreground">آخرین قیمت:</span>
|
||
<div className="text-left font-mono">
|
||
{sym.latest_price?.mid ? (
|
||
<div>
|
||
<div className="text-lg font-bold text-primary">
|
||
{sym.latest_price.mid.toLocaleString()} <span className="text-xs font-normal text-muted-foreground">ریال</span>
|
||
</div>
|
||
<div className="text-[11px] text-muted-foreground">
|
||
{(Math.round(sym.latest_price.mid / 10)).toLocaleString()} تومان
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<span className="text-xs text-muted-foreground">ثبت معامله ندارد</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between text-xs pt-1.5 border-t border-border/30">
|
||
<span className="text-[10px] px-2 py-0.5 rounded-md bg-muted/60 text-muted-foreground font-medium">
|
||
{sym.subTitle || sym.rootTitle}
|
||
</span>
|
||
|
||
{sym.latest_price?.date && (
|
||
<span className="text-[10px] text-muted-foreground font-mono">
|
||
{sym.latest_price.date}
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* FULL HISTORICAL MODAL FOR IME SYMBOL */}
|
||
{selectedSymbol && (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-6 bg-black/75 backdrop-blur-md animate-in fade-in duration-200">
|
||
<div
|
||
className="relative w-full max-w-4xl max-h-[92vh] overflow-y-auto rounded-3xl border border-border/80 bg-background/95 p-5 sm:p-7 shadow-2xl backdrop-blur-2xl space-y-5"
|
||
dir="rtl"
|
||
>
|
||
{/* Modal Header */}
|
||
<div className="flex items-start justify-between border-b border-border/60 pb-4">
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-12 h-12 rounded-2xl grid place-items-center border shrink-0 bg-primary/10 border-primary/30">
|
||
<Factory className="w-6 h-6 text-primary" />
|
||
</div>
|
||
<div>
|
||
<h2 className="text-lg sm:text-xl font-bold text-foreground">
|
||
{selectedSymbol.product_name || selectedSymbol.title}
|
||
</h2>
|
||
{selectedSymbol.manufacturer && (
|
||
<div className="text-xs text-muted-foreground flex items-center gap-1 mt-1">
|
||
<Building2 className="w-3.5 h-3.5 text-primary/70" />
|
||
<span>{selectedSymbol.manufacturer}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
onClick={() => setSelectedSymbol(null)}
|
||
className="rounded-xl border border-border/60 bg-muted/40 p-2 text-muted-foreground transition hover:bg-muted hover:text-foreground"
|
||
aria-label="بستن"
|
||
>
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
{/* Quick Stats Pills */}
|
||
{stats && (
|
||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||
<div className="rounded-2xl border border-border/60 bg-card/60 p-3.5">
|
||
<div className="text-[11px] text-muted-foreground">آخرین قیمت کشفشده</div>
|
||
<div className="text-base sm:text-lg font-bold font-mono text-foreground mt-0.5">
|
||
{stats.last.toLocaleString()} ریال
|
||
</div>
|
||
<div className="text-[10px] text-muted-foreground mt-0.5">
|
||
{(Math.round(stats.last / 10)).toLocaleString()} تومان
|
||
</div>
|
||
</div>
|
||
|
||
<div className="rounded-2xl border border-border/60 bg-card/60 p-3.5">
|
||
<div className="text-[11px] text-muted-foreground">بالاترین قیمت دوره</div>
|
||
<div className="text-base sm:text-lg font-bold font-mono text-foreground mt-0.5">
|
||
{stats.max.toLocaleString()} ریال
|
||
</div>
|
||
</div>
|
||
|
||
<div className="rounded-2xl border border-border/60 bg-card/60 p-3.5">
|
||
<div className="text-[11px] text-muted-foreground">پایینترین قیمت دوره</div>
|
||
<div className="text-base sm:text-lg font-bold font-mono text-foreground mt-0.5">
|
||
{stats.min.toLocaleString()} ریال
|
||
</div>
|
||
</div>
|
||
|
||
<div className="rounded-2xl border border-border/60 bg-card/60 p-3.5">
|
||
<div className="text-[11px] text-muted-foreground">تغییر در بازه انتخابی</div>
|
||
<div
|
||
className={`text-base sm:text-lg font-bold font-mono mt-0.5 ${
|
||
stats.diff >= 0 ? "text-emerald-500" : "text-rose-500"
|
||
}`}
|
||
>
|
||
{stats.diff >= 0 ? "+" : ""}
|
||
{stats.diff.toLocaleString()} ({stats.pct.toFixed(1)}%)
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Chart Area */}
|
||
<div className="rounded-2xl border border-border/60 bg-card/40 p-4 sm:p-5 space-y-4">
|
||
{detailLoading ? (
|
||
<div className="h-72 flex flex-col items-center justify-center gap-2 text-muted-foreground text-sm">
|
||
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
||
<span>در حال دریافت سابقه قیمتهای معاملاتی بورس کالا...</span>
|
||
</div>
|
||
) : chartPoints.length === 0 ? (
|
||
<div className="h-72 flex items-center justify-center text-muted-foreground text-sm">
|
||
اطلاعات قیمتی برای این نماد ثبت نشده است.
|
||
</div>
|
||
) : (
|
||
<div className="h-72 sm:h-80 w-full" dir="ltr">
|
||
<ResponsiveContainer width="100%" height="100%">
|
||
<ComposedChart data={chartPoints} margin={{ top: 10, right: 15, left: 10, bottom: 0 }}>
|
||
<defs>
|
||
<linearGradient id="imePriceGrad" x1="0" y1="0" x2="0" y2="1">
|
||
<stop offset="5%" stopColor="#2563eb" stopOpacity={0.3} />
|
||
<stop offset="95%" stopColor="#2563eb" stopOpacity={0.0} />
|
||
</linearGradient>
|
||
</defs>
|
||
<CartesianGrid strokeDasharray="2 2" opacity={0.15} vertical={true} />
|
||
<XAxis
|
||
dataKey="date"
|
||
tick={{ fontSize: 10, fill: "var(--muted-foreground)" }}
|
||
minTickGap={35}
|
||
/>
|
||
<YAxis
|
||
domain={["auto", "auto"]}
|
||
tick={{ fontSize: 10, fill: "var(--muted-foreground)" }}
|
||
tickFormatter={(v) => `${(v / 1000).toLocaleString()}k`}
|
||
orientation="right"
|
||
/>
|
||
<Tooltip
|
||
content={({ active, payload, label }) => {
|
||
if (!active || !payload?.length) return null;
|
||
const pt = payload[0].payload as ImePricePoint;
|
||
return (
|
||
<div className="rounded-xl border border-border/80 bg-card/95 p-3 text-foreground shadow-2xl backdrop-blur-md space-y-1 min-w-[160px]" dir="rtl">
|
||
<div className="text-[11px] text-muted-foreground font-mono pb-1 border-b border-border/40">
|
||
تاریخ معامله: {pt.date}
|
||
</div>
|
||
<div className="flex items-center justify-between text-xs pt-1">
|
||
<span className="text-muted-foreground">قیمت پایانی:</span>
|
||
<span className="font-mono font-bold text-primary">
|
||
{pt.mid.toLocaleString()} ریال
|
||
</span>
|
||
</div>
|
||
<div className="text-[10px] text-muted-foreground">
|
||
معادل {(Math.round(pt.mid / 10)).toLocaleString()} تومان
|
||
</div>
|
||
</div>
|
||
);
|
||
}}
|
||
/>
|
||
<Area
|
||
type="monotone"
|
||
dataKey="mid"
|
||
name="قیمت کشفشده"
|
||
stroke="#2563eb"
|
||
strokeWidth={2}
|
||
fill="url(#imePriceGrad)"
|
||
dot={{ r: 2 }}
|
||
/>
|
||
</ComposedChart>
|
||
</ResponsiveContainer>
|
||
</div>
|
||
)}
|
||
|
||
{/* Timeframe selector bar */}
|
||
<div className="pt-3 border-t border-border/40 flex flex-wrap items-center justify-center gap-2" dir="rtl">
|
||
<button
|
||
onClick={() => setTimeframe("3M")}
|
||
className={`rounded-xl border px-4 py-1.5 text-xs font-semibold transition ${
|
||
timeframe === "3M"
|
||
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
||
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
||
}`}
|
||
>
|
||
۳ ماه
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setTimeframe("6M")}
|
||
className={`rounded-xl border px-4 py-1.5 text-xs font-semibold transition ${
|
||
timeframe === "6M"
|
||
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
||
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
||
}`}
|
||
>
|
||
۶ ماه
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setTimeframe("1Y")}
|
||
className={`rounded-xl border px-4 py-1.5 text-xs font-semibold transition ${
|
||
timeframe === "1Y"
|
||
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
||
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
||
}`}
|
||
>
|
||
۱ سال
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setTimeframe("ENTIRE")}
|
||
className={`rounded-xl border px-5 py-1.5 text-xs font-bold transition ${
|
||
timeframe === "ENTIRE"
|
||
? "border-primary bg-primary text-primary-foreground shadow-md ring-2 ring-primary/20"
|
||
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
||
}`}
|
||
>
|
||
کل دادهها (تاریخچه کامل)
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</main>
|
||
);
|
||
}
|