feat(lme): add color legend, dual Y-axis scaling for price vs stocks, and full Persian localization
This commit is contained in:
parent
380605daa4
commit
8ec51d92a8
|
|
@ -35,17 +35,17 @@ FIELD_MAP = {
|
||||||
}
|
}
|
||||||
|
|
||||||
FA_NAMES = {
|
FA_NAMES = {
|
||||||
"Copper": "مس (Copper)",
|
"Copper": "مس",
|
||||||
"Tin": "قلع (Tin)",
|
"Tin": "قلع",
|
||||||
"Lead": "سرب (Lead)",
|
"Lead": "سرب",
|
||||||
"Zinc": "روی (Zinc)",
|
"Zinc": "روی",
|
||||||
"Aluminium": "آلومینیوم (Aluminium)",
|
"Aluminium": "آلومینیوم",
|
||||||
"Nickel": "نیکل (Nickel)",
|
"Nickel": "نیکل",
|
||||||
"WM_Cu_low": "مس کاتد آلمان (WM-Notiz کف)",
|
"WM_Cu_low": "مس کاتد آلمان (کف قیمت)",
|
||||||
"WM_Cu_high": "مس کاتد آلمان (WM-Notiz سقف)",
|
"WM_Cu_high": "مس کاتد آلمان (سقف قیمت)",
|
||||||
"WI_Cu": "مس ویلند (Wieland Copper)",
|
"WI_Cu": "مس ویلند (Wieland)",
|
||||||
"ACI": "شاخص مس پیشرفته (ACI)",
|
"ACI": "شاخص مس پیشرفته (ACI)",
|
||||||
"MB_bronze_94_6": "برنز 94/6",
|
"MB_bronze_94_6": "برنز ۹۴/۶",
|
||||||
"MB_MS_58_1": "برنج MS 58 (مرحله ۱)",
|
"MB_MS_58_1": "برنج MS 58 (مرحله ۱)",
|
||||||
"MB_MS_58_2": "برنج MS 58 (مرحله ۲)",
|
"MB_MS_58_2": "برنج MS 58 (مرحله ۲)",
|
||||||
"MB_MS_63_37": "برنج MS 63/37",
|
"MB_MS_63_37": "برنج MS 63/37",
|
||||||
|
|
@ -57,6 +57,28 @@ FA_NAMES = {
|
||||||
"Ag_processed": "نقره ساختهشده در اروپا",
|
"Ag_processed": "نقره ساختهشده در اروپا",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SERIES_FA_NAMES = {
|
||||||
|
"LME Copper Cash-Settlement": "قیمت تسویه نقدی مس",
|
||||||
|
"LME Copper stock": "موجودی انبار مس",
|
||||||
|
"LME Aluminium Cash-Settlement": "قیمت تسویه نقدی آلومینیوم",
|
||||||
|
"LME Aluminium stock": "موجودی انبار آلومینیوم",
|
||||||
|
"LME Zinc Cash-Settlement": "قیمت تسویه نقدی روی",
|
||||||
|
"LME Zinc stock": "موجودی انبار روی",
|
||||||
|
"LME Nickel Cash-Settlement": "قیمت تسویه نقدی نیکل",
|
||||||
|
"LME Nickel stock": "موجودی انبار نیکل",
|
||||||
|
"LME Lead Cash-Settlement": "قیمت تسویه نقدی سرب",
|
||||||
|
"LME Lead stock": "موجودی انبار سرب",
|
||||||
|
"LME Tin Cash-Settlement": "قیمت تسویه نقدی قلع",
|
||||||
|
"LME Tin stock": "موجودی انبار قلع",
|
||||||
|
"WM_Cu_low": "کف قیمت مس کاتد آلمان",
|
||||||
|
"WM_Cu_high": "سقف قیمت مس کاتد آلمان",
|
||||||
|
"Gold London Fixing": "انس طلای لندن",
|
||||||
|
"Fine Silver in Euro/kg": "نقره خالص در اروپا",
|
||||||
|
"Gold in Euro/kg": "طلای شمش در اروپا",
|
||||||
|
"Advanced Copper Index (ACI)": "شاخص مس پیشرفته (ACI)",
|
||||||
|
"Wieland-Copper": "مس ویلند",
|
||||||
|
}
|
||||||
|
|
||||||
def clean_num(val: str) -> float:
|
def clean_num(val: str) -> float:
|
||||||
if not val:
|
if not val:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
@ -98,7 +120,7 @@ def fetch_raw():
|
||||||
"cash_str": cells[1],
|
"cash_str": cells[1],
|
||||||
"three_months": clean_num(cells[2]),
|
"three_months": clean_num(cells[2]),
|
||||||
"three_months_str": cells[2],
|
"three_months_str": cells[2],
|
||||||
"unit": "USD/mt"
|
"unit": "دلار بر تن"
|
||||||
})
|
})
|
||||||
|
|
||||||
# Table 1: LME Stocks
|
# Table 1: LME Stocks
|
||||||
|
|
@ -118,7 +140,7 @@ def fetch_raw():
|
||||||
"stocks_str": cells[1],
|
"stocks_str": cells[1],
|
||||||
"change": clean_num(cells[2]),
|
"change": clean_num(cells[2]),
|
||||||
"change_str": cells[2],
|
"change_str": cells[2],
|
||||||
"unit": "mt"
|
"unit": "تن"
|
||||||
})
|
})
|
||||||
|
|
||||||
# Combine Prices + Stocks by metal
|
# Combine Prices + Stocks by metal
|
||||||
|
|
@ -142,7 +164,7 @@ def fetch_raw():
|
||||||
"stocks_str": s_data.get("stocks_str", "-"),
|
"stocks_str": s_data.get("stocks_str", "-"),
|
||||||
"stocks_change": s_data.get("change", 0),
|
"stocks_change": s_data.get("change", 0),
|
||||||
"stocks_change_str": s_data.get("change_str", "-"),
|
"stocks_change_str": s_data.get("change_str", "-"),
|
||||||
"unit": "USD/mt"
|
"unit": "دلار بر تن"
|
||||||
})
|
})
|
||||||
|
|
||||||
# Table 2: FX Rates
|
# Table 2: FX Rates
|
||||||
|
|
@ -171,7 +193,7 @@ def fetch_raw():
|
||||||
"field": field,
|
"field": field,
|
||||||
"price_str": cells[1],
|
"price_str": cells[1],
|
||||||
"prev_str": cells[2],
|
"prev_str": cells[2],
|
||||||
"unit": "EUR/100kg"
|
"unit": "یورو بر ۱۰۰ کیلوگرم"
|
||||||
})
|
})
|
||||||
|
|
||||||
# Table 4: Precious Metals
|
# Table 4: Precious Metals
|
||||||
|
|
@ -218,12 +240,7 @@ def get_lme_data(ttl_seconds: int = 180):
|
||||||
return _cache["data"]
|
return _cache["data"]
|
||||||
|
|
||||||
def get_entire_diagram_data(field: str, ttl_seconds: int = 3600):
|
def get_entire_diagram_data(field: str, ttl_seconds: int = 3600):
|
||||||
"""
|
|
||||||
Fetches the ENTIRE multi-year historical dataset for any symbol
|
|
||||||
from the official XML marketdata API.
|
|
||||||
"""
|
|
||||||
field_clean = field.strip()
|
field_clean = field.strip()
|
||||||
# Resolve aliases (e.g. "copper" -> "LME_Cu_cash")
|
|
||||||
field_clean = FIELD_MAP.get(field_clean.lower(), field_clean)
|
field_clean = FIELD_MAP.get(field_clean.lower(), field_clean)
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
@ -245,17 +262,35 @@ def get_entire_diagram_data(field: str, ttl_seconds: int = 3600):
|
||||||
lines = []
|
lines = []
|
||||||
for line in series_elem:
|
for line in series_elem:
|
||||||
raw_name = line.attrib.get('name', field_clean)
|
raw_name = line.attrib.get('name', field_clean)
|
||||||
|
fa_line_name = SERIES_FA_NAMES.get(raw_name, raw_name)
|
||||||
color = line.attrib.get('lineColor', '#D02E00')
|
color = line.attrib.get('lineColor', '#D02E00')
|
||||||
unit = line.attrib.get('yUnit', '')
|
raw_unit = line.attrib.get('yUnit', '')
|
||||||
precision = int(line.attrib.get('yPrecision', '2'))
|
precision = int(line.attrib.get('yPrecision', '2'))
|
||||||
|
|
||||||
|
# Persian unit & type
|
||||||
|
is_stock = "stock" in raw_name.lower() or "bestände" in raw_name.lower() or "انبار" in fa_line_name
|
||||||
|
if is_stock:
|
||||||
|
unit = "تن"
|
||||||
|
series_type = "stocks"
|
||||||
|
color = "#f97316" # vibrant orange for stocks
|
||||||
|
elif "€" in raw_unit or "euro" in raw_unit.lower():
|
||||||
|
unit = "یورو"
|
||||||
|
series_type = "price"
|
||||||
|
color = "#D02E00" # red for primary price
|
||||||
|
elif "$" in raw_unit or "usd" in raw_unit.lower():
|
||||||
|
unit = "دلار"
|
||||||
|
series_type = "price"
|
||||||
|
color = "#D02E00"
|
||||||
|
else:
|
||||||
|
unit = raw_unit
|
||||||
|
series_type = "price"
|
||||||
|
|
||||||
pts = []
|
pts = []
|
||||||
for val in line:
|
for val in line:
|
||||||
raw_x = val.attrib.get('x', '') # YYYY/MM/DD
|
raw_x = val.attrib.get('x', '') # YYYY/MM/DD
|
||||||
date_iso = raw_x.replace('/', '-')
|
date_iso = raw_x.replace('/', '-')
|
||||||
# Also format DD/MM/YYYY for tooltips as shown in user screenshot
|
|
||||||
parts = date_iso.split('-')
|
parts = date_iso.split('-')
|
||||||
date_display = f"{parts[2]}/{parts[1]}/{parts[0]}" if len(parts) == 3 else date_iso
|
date_display = f"{parts[0]}/{parts[1]}/{parts[2]}" if len(parts) == 3 else date_iso
|
||||||
|
|
||||||
y_str = val.attrib.get('y', '0').replace(',', '')
|
y_str = val.attrib.get('y', '0').replace(',', '')
|
||||||
try:
|
try:
|
||||||
|
|
@ -270,7 +305,9 @@ def get_entire_diagram_data(field: str, ttl_seconds: int = 3600):
|
||||||
})
|
})
|
||||||
|
|
||||||
lines.append({
|
lines.append({
|
||||||
"name": raw_name,
|
"name": fa_line_name,
|
||||||
|
"raw_name": raw_name,
|
||||||
|
"series_type": series_type,
|
||||||
"color": color,
|
"color": color,
|
||||||
"unit": unit,
|
"unit": unit,
|
||||||
"precision": precision,
|
"precision": precision,
|
||||||
|
|
@ -290,6 +327,5 @@ def get_entire_diagram_data(field: str, ttl_seconds: int = 3600):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Backward compatibility alias
|
|
||||||
def get_metal_history(metal_key: str, ttl_seconds: int = 300):
|
def get_metal_history(metal_key: str, ttl_seconds: int = 300):
|
||||||
return get_entire_diagram_data(metal_key, ttl_seconds)
|
return get_entire_diagram_data(metal_key, ttl_seconds)
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,11 @@ import {
|
||||||
Globe,
|
Globe,
|
||||||
X,
|
X,
|
||||||
LineChart as LineChartIcon,
|
LineChart as LineChartIcon,
|
||||||
ChevronLeft,
|
|
||||||
ChevronRight,
|
|
||||||
Maximize2,
|
Maximize2,
|
||||||
Loader2,
|
Loader2,
|
||||||
Calendar,
|
Calendar,
|
||||||
Sparkles
|
Eye,
|
||||||
|
EyeOff
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
|
|
@ -25,7 +24,6 @@ import {
|
||||||
YAxis,
|
YAxis,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
Legend
|
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { API_BASE } from "@/lib/api";
|
import { API_BASE } from "@/lib/api";
|
||||||
|
|
||||||
|
|
@ -83,12 +81,14 @@ export interface LmeData {
|
||||||
|
|
||||||
export interface DiagramPoint {
|
export interface DiagramPoint {
|
||||||
x: string; // YYYY-MM-DD
|
x: string; // YYYY-MM-DD
|
||||||
date_display: string; // DD/MM/YYYY
|
date_display: string;
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DiagramLine {
|
export interface DiagramLine {
|
||||||
name: string;
|
name: string;
|
||||||
|
raw_name: string;
|
||||||
|
series_type: "price" | "stocks";
|
||||||
color: string;
|
color: string;
|
||||||
unit: string;
|
unit: string;
|
||||||
precision: number;
|
precision: number;
|
||||||
|
|
@ -105,21 +105,6 @@ export interface DiagramResponse {
|
||||||
|
|
||||||
type Timeframe = "3M" | "6M" | "1Y" | "2Y" | "ENTIRE";
|
type Timeframe = "3M" | "6M" | "1Y" | "2Y" | "ENTIRE";
|
||||||
|
|
||||||
const METAL_COLORS: Record<string, string> = {
|
|
||||||
Copper: "#D02E00",
|
|
||||||
WM_Cu_low: "#D02E00",
|
|
||||||
WM_Cu_high: "#D02E00",
|
|
||||||
Aluminium: "#0ea5e9",
|
|
||||||
Zinc: "#10b981",
|
|
||||||
Nickel: "#8b5cf6",
|
|
||||||
Lead: "#64748b",
|
|
||||||
Tin: "#06b6d4",
|
|
||||||
ACI: "#f59e0b",
|
|
||||||
USD_ozt_London: "#eab308",
|
|
||||||
Au: "#eab308",
|
|
||||||
Ag: "#94a3b8",
|
|
||||||
};
|
|
||||||
|
|
||||||
export function LmeView() {
|
export function LmeView() {
|
||||||
const [data, setData] = useState<LmeData | null>(null);
|
const [data, setData] = useState<LmeData | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
@ -140,6 +125,10 @@ export function LmeView() {
|
||||||
const [timeframe, setTimeframe] = useState<Timeframe>("ENTIRE");
|
const [timeframe, setTimeframe] = useState<Timeframe>("ENTIRE");
|
||||||
const [offsetIndex, setOffsetIndex] = useState<number>(0);
|
const [offsetIndex, setOffsetIndex] = useState<number>(0);
|
||||||
|
|
||||||
|
// Visibility toggles for dual-series charts
|
||||||
|
const [showPrice, setShowPrice] = useState<boolean>(true);
|
||||||
|
const [showStocks, setShowStocks] = useState<boolean>(true);
|
||||||
|
|
||||||
// Load overview catalog
|
// Load overview catalog
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -169,6 +158,8 @@ export function LmeView() {
|
||||||
let alive = true;
|
let alive = true;
|
||||||
setDiagramLoading(true);
|
setDiagramLoading(true);
|
||||||
setOffsetIndex(0);
|
setOffsetIndex(0);
|
||||||
|
setShowPrice(true);
|
||||||
|
setShowStocks(true);
|
||||||
|
|
||||||
fetch(`${API}/api/lme/diagram?field=${encodeURIComponent(selectedSymbol.field)}`, {
|
fetch(`${API}/api/lme/diagram?field=${encodeURIComponent(selectedSymbol.field)}`, {
|
||||||
credentials: "include"
|
credentials: "include"
|
||||||
|
|
@ -199,11 +190,20 @@ export function LmeView() {
|
||||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Line separation: price line vs stock line
|
||||||
|
const priceLine = useMemo(() => {
|
||||||
|
return diagramData?.lines?.find((l) => l.series_type === "price") || diagramData?.lines?.[0];
|
||||||
|
}, [diagramData]);
|
||||||
|
|
||||||
|
const stockLine = useMemo(() => {
|
||||||
|
return diagramData?.lines?.find((l) => l.series_type === "stocks");
|
||||||
|
}, [diagramData]);
|
||||||
|
|
||||||
// Filter main series by timeframe & offset
|
// Filter main series by timeframe & offset
|
||||||
const chartData = useMemo(() => {
|
const chartData = useMemo(() => {
|
||||||
if (!diagramData?.lines?.length) return [];
|
if (!diagramData?.lines?.length) return [];
|
||||||
const mainLine = diagramData.lines[0];
|
const baseLine = priceLine || diagramData.lines[0];
|
||||||
const total = mainLine.points.length;
|
const total = baseLine.points.length;
|
||||||
if (!total) return [];
|
if (!total) return [];
|
||||||
|
|
||||||
let sliceCount = total;
|
let sliceCount = total;
|
||||||
|
|
@ -213,38 +213,36 @@ export function LmeView() {
|
||||||
else if (timeframe === "2Y") sliceCount = 520;
|
else if (timeframe === "2Y") sliceCount = 520;
|
||||||
else if (timeframe === "ENTIRE") sliceCount = total;
|
else if (timeframe === "ENTIRE") sliceCount = total;
|
||||||
|
|
||||||
// Windowing with offsetIndex
|
|
||||||
const maxOffset = Math.max(0, total - sliceCount);
|
const maxOffset = Math.max(0, total - sliceCount);
|
||||||
const effectiveOffset = Math.min(offsetIndex, maxOffset);
|
const effectiveOffset = Math.min(offsetIndex, maxOffset);
|
||||||
const end = total - effectiveOffset;
|
const end = total - effectiveOffset;
|
||||||
const start = Math.max(0, end - sliceCount);
|
const start = Math.max(0, end - sliceCount);
|
||||||
|
|
||||||
const windowPoints = mainLine.points.slice(start, end);
|
const windowPoints = baseLine.points.slice(start, end);
|
||||||
|
|
||||||
// Merge secondary lines (e.g. stock line in LME_Cu_cash)
|
if (stockLine) {
|
||||||
if (diagramData.lines.length > 1) {
|
const stockMap = new Map(stockLine.points.map((p) => [p.x, p.y]));
|
||||||
const secondLine = diagramData.lines[1];
|
|
||||||
const secondMap = new Map(secondLine.points.map((p) => [p.x, p.y]));
|
|
||||||
return windowPoints.map((p) => ({
|
return windowPoints.map((p) => ({
|
||||||
x: p.x,
|
x: p.x,
|
||||||
date_display: p.date_display,
|
date_display: p.date_display,
|
||||||
[mainLine.name]: p.y,
|
priceVal: p.y,
|
||||||
[secondLine.name]: secondMap.get(p.x) ?? null,
|
stockVal: stockMap.get(p.x) ?? null,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return windowPoints.map((p) => ({
|
return windowPoints.map((p) => ({
|
||||||
x: p.x,
|
x: p.x,
|
||||||
date_display: p.date_display,
|
date_display: p.date_display,
|
||||||
[mainLine.name]: p.y,
|
priceVal: p.y,
|
||||||
}));
|
}));
|
||||||
}, [diagramData, timeframe, offsetIndex]);
|
}, [diagramData, priceLine, stockLine, timeframe, offsetIndex]);
|
||||||
|
|
||||||
// Statistics calculation for the active window
|
// Statistics calculation for the active window
|
||||||
const stats = useMemo(() => {
|
const stats = useMemo(() => {
|
||||||
if (!chartData.length || !diagramData?.lines?.[0]) return null;
|
if (!chartData.length) return null;
|
||||||
const mainName = diagramData.lines[0].name;
|
const values = chartData
|
||||||
const values = chartData.map((d: any) => d[mainName]).filter((v): v is number => typeof v === "number" && v > 0);
|
.map((d) => d.priceVal)
|
||||||
|
.filter((v): v is number => typeof v === "number" && v > 0);
|
||||||
if (!values.length) return null;
|
if (!values.length) return null;
|
||||||
|
|
||||||
const max = Math.max(...values);
|
const max = Math.max(...values);
|
||||||
|
|
@ -256,14 +254,14 @@ export function LmeView() {
|
||||||
const pct = first > 0 ? (diff / first) * 100 : 0;
|
const pct = first > 0 ? (diff / first) * 100 : 0;
|
||||||
|
|
||||||
return { max, min, avg, diff, pct, first, last };
|
return { max, min, avg, diff, pct, first, last };
|
||||||
}, [chartData, diagramData]);
|
}, [chartData]);
|
||||||
|
|
||||||
const canStepPrev = useMemo(() => {
|
const canStepPrev = useMemo(() => {
|
||||||
if (timeframe === "ENTIRE") return false;
|
if (timeframe === "ENTIRE") return false;
|
||||||
const total = diagramData?.lines?.[0]?.points?.length || 0;
|
const total = (priceLine || diagramData?.lines?.[0])?.points?.length || 0;
|
||||||
const count = timeframe === "3M" ? 65 : timeframe === "6M" ? 130 : timeframe === "1Y" ? 260 : 520;
|
const count = timeframe === "3M" ? 65 : timeframe === "6M" ? 130 : timeframe === "1Y" ? 260 : 520;
|
||||||
return offsetIndex + count < total;
|
return offsetIndex + count < total;
|
||||||
}, [timeframe, diagramData, offsetIndex]);
|
}, [timeframe, priceLine, diagramData, offsetIndex]);
|
||||||
|
|
||||||
const canStepNext = useMemo(() => {
|
const canStepNext = useMemo(() => {
|
||||||
return offsetIndex > 0 && timeframe !== "ENTIRE";
|
return offsetIndex > 0 && timeframe !== "ENTIRE";
|
||||||
|
|
@ -319,7 +317,7 @@ export function LmeView() {
|
||||||
|
|
||||||
{data?.date && (
|
{data?.date && (
|
||||||
<div className="self-start sm:self-auto rounded-xl border border-border/80 bg-card/60 px-3.5 py-1.5 text-xs text-muted-foreground shadow-sm">
|
<div className="self-start sm:self-auto rounded-xl border border-border/80 bg-card/60 px-3.5 py-1.5 text-xs text-muted-foreground shadow-sm">
|
||||||
تاریخ: <span className="font-semibold text-foreground">{data.date}</span>
|
تاریخ بازار: <span className="font-semibold text-foreground">{data.date}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -389,7 +387,7 @@ export function LmeView() {
|
||||||
field: m.field,
|
field: m.field,
|
||||||
title: m.metal,
|
title: m.metal,
|
||||||
name_fa: m.name_fa,
|
name_fa: m.name_fa,
|
||||||
unit: "USD/mt",
|
unit: "دلار بر تن",
|
||||||
currentPrice: m.cash_str,
|
currentPrice: m.cash_str,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -400,7 +398,7 @@ export function LmeView() {
|
||||||
<div className="text-base font-bold text-foreground group-hover:text-primary transition-colors">
|
<div className="text-base font-bold text-foreground group-hover:text-primary transition-colors">
|
||||||
{m.name_fa}
|
{m.name_fa}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-muted-foreground font-mono mt-0.5">{m.metal} • USD/mt</div>
|
<div className="text-xs text-muted-foreground font-mono mt-0.5">{m.metal} • دلار بر تن</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[11px] px-2 py-0.5 rounded-lg bg-primary/10 text-primary font-medium flex items-center gap-1 group-hover:bg-primary group-hover:text-primary-foreground transition-colors">
|
<span className="text-[11px] px-2 py-0.5 rounded-lg bg-primary/10 text-primary font-medium flex items-center gap-1 group-hover:bg-primary group-hover:text-primary-foreground transition-colors">
|
||||||
<span>نمودار کامل</span>
|
<span>نمودار کامل</span>
|
||||||
|
|
@ -410,13 +408,13 @@ export function LmeView() {
|
||||||
|
|
||||||
<div className="mt-4 grid grid-cols-2 gap-3 pt-3 border-t border-border/40">
|
<div className="mt-4 grid grid-cols-2 gap-3 pt-3 border-t border-border/40">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] text-muted-foreground">تسویه نقدی (Cash)</div>
|
<div className="text-[11px] text-muted-foreground">تسویه نقدی</div>
|
||||||
<div className="text-lg font-bold font-mono text-foreground mt-0.5">
|
<div className="text-lg font-bold font-mono text-foreground mt-0.5">
|
||||||
${m.cash_str}
|
${m.cash_str}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] text-muted-foreground">۳ ماهه (3-Month)</div>
|
<div className="text-[11px] text-muted-foreground">قرارداد ۳ ماهه</div>
|
||||||
<div className="text-lg font-bold font-mono text-foreground mt-0.5">
|
<div className="text-lg font-bold font-mono text-foreground mt-0.5">
|
||||||
${m.three_months_str}
|
${m.three_months_str}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -479,7 +477,7 @@ export function LmeView() {
|
||||||
|
|
||||||
<div className="mt-4 flex items-center justify-between pt-3 border-t border-border/40">
|
<div className="mt-4 flex items-center justify-between pt-3 border-t border-border/40">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[11px] text-muted-foreground">آخرین مظنه (EUR/100kg)</div>
|
<div className="text-[11px] text-muted-foreground">آخرین مظنه (یورو بر ۱۰۰ کیلو)</div>
|
||||||
<div className="text-xl font-bold font-mono text-foreground mt-0.5">
|
<div className="text-xl font-bold font-mono text-foreground mt-0.5">
|
||||||
{s.price_str} €
|
{s.price_str} €
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -565,15 +563,13 @@ export function LmeView() {
|
||||||
{selectedSymbol && (
|
{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="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
|
<div
|
||||||
className="relative w-full max-w-5xl 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-6"
|
className="relative w-full max-w-5xl 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"
|
dir="rtl"
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start justify-between border-b border-border/60 pb-4">
|
<div className="flex items-start justify-between border-b border-border/60 pb-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div
|
<div className="w-12 h-12 rounded-2xl grid place-items-center border shrink-0 bg-primary/10 border-primary/30">
|
||||||
className="w-12 h-12 rounded-2xl grid place-items-center border shrink-0 bg-primary/10 border-primary/30"
|
|
||||||
>
|
|
||||||
<LineChartIcon className="w-6 h-6 text-primary" />
|
<LineChartIcon className="w-6 h-6 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -586,7 +582,7 @@ export function LmeView() {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground mt-0.5">
|
<p className="text-xs text-muted-foreground mt-0.5">
|
||||||
نمودار کامل تاریخی • {diagramData?.total_points ? `${diagramData.total_points.toLocaleString()} روز معاملاتی ثبتشده` : "در حال بارگذاری..."}
|
نمودار کامل تاریخی • {diagramData?.total_points ? `${diagramData.total_points.toLocaleString()} روز کاری ثبتشده` : "در حال بارگذاری..."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -600,29 +596,29 @@ export function LmeView() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Stat Pill */}
|
{/* Quick Stat Pills (100% Persian) */}
|
||||||
{stats && (
|
{stats && (
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
<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="rounded-2xl border border-border/60 bg-card/60 p-3.5">
|
||||||
<div className="text-[11px] text-muted-foreground">آخرین قیمت (Latest)</div>
|
<div className="text-[11px] text-muted-foreground">آخرین قیمت</div>
|
||||||
<div className="text-lg font-bold font-mono text-foreground mt-0.5">
|
<div className="text-lg font-bold font-mono text-foreground mt-0.5">
|
||||||
{stats.last.toLocaleString()} {diagramData?.lines?.[0]?.unit || ""}
|
{stats.last.toLocaleString()} {priceLine?.unit || ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border border-border/60 bg-card/60 p-3.5">
|
<div className="rounded-2xl border border-border/60 bg-card/60 p-3.5">
|
||||||
<div className="text-[11px] text-muted-foreground">بالاترین دوره (Max)</div>
|
<div className="text-[11px] text-muted-foreground">بالاترین قیمت دوره</div>
|
||||||
<div className="text-lg font-bold font-mono text-foreground mt-0.5">
|
<div className="text-lg font-bold font-mono text-foreground mt-0.5">
|
||||||
{stats.max.toLocaleString()} {diagramData?.lines?.[0]?.unit || ""}
|
{stats.max.toLocaleString()} {priceLine?.unit || ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border border-border/60 bg-card/60 p-3.5">
|
<div className="rounded-2xl border border-border/60 bg-card/60 p-3.5">
|
||||||
<div className="text-[11px] text-muted-foreground">پایینترین دوره (Min)</div>
|
<div className="text-[11px] text-muted-foreground">پایینترین قیمت دوره</div>
|
||||||
<div className="text-lg font-bold font-mono text-foreground mt-0.5">
|
<div className="text-lg font-bold font-mono text-foreground mt-0.5">
|
||||||
{stats.min.toLocaleString()} {diagramData?.lines?.[0]?.unit || ""}
|
{stats.min.toLocaleString()} {priceLine?.unit || ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border border-border/60 bg-card/60 p-3.5">
|
<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-[11px] text-muted-foreground">تغییر در بازه انتخابی</div>
|
||||||
<div
|
<div
|
||||||
className={`text-lg font-bold font-mono mt-0.5 ${
|
className={`text-lg font-bold font-mono mt-0.5 ${
|
||||||
stats.diff >= 0 ? "text-emerald-500" : "text-rose-500"
|
stats.diff >= 0 ? "text-emerald-500" : "text-rose-500"
|
||||||
|
|
@ -634,12 +630,59 @@ export function LmeView() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Chart Area */}
|
{/* Chart Container */}
|
||||||
<div className="rounded-2xl border border-border/60 bg-card/40 p-4 sm:p-5">
|
<div className="rounded-2xl border border-border/60 bg-card/40 p-4 sm:p-5 space-y-4">
|
||||||
|
{/* LEGEND & TOGGLE BAR (راهنمای خطوط و رنگها) */}
|
||||||
|
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-border/40 pb-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xs font-semibold text-foreground">راهنمای رنگها:</span>
|
||||||
|
|
||||||
|
{/* Price Line Toggle */}
|
||||||
|
{priceLine && (
|
||||||
|
<button
|
||||||
|
onClick={() => setShowPrice((v) => !v)}
|
||||||
|
className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-xl text-xs font-medium border transition ${
|
||||||
|
showPrice
|
||||||
|
? "bg-[#D02E00]/15 border-[#D02E00]/40 text-[#D02E00]"
|
||||||
|
: "bg-muted/40 border-border/60 text-muted-foreground opacity-50"
|
||||||
|
}`}
|
||||||
|
title="کلیک برای نمایش/مخفیسازی خط قیمت"
|
||||||
|
>
|
||||||
|
<span className="w-2.5 h-2.5 rounded-full bg-[#D02E00] shrink-0" />
|
||||||
|
<span>{priceLine.name} ({priceLine.unit})</span>
|
||||||
|
{showPrice ? <Eye className="w-3 h-3" /> : <EyeOff className="w-3 h-3" />}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Stock Line Toggle (if exists) */}
|
||||||
|
{stockLine && (
|
||||||
|
<button
|
||||||
|
onClick={() => setShowStocks((v) => !v)}
|
||||||
|
className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-xl text-xs font-medium border transition ${
|
||||||
|
showStocks
|
||||||
|
? "bg-[#f97316]/15 border-[#f97316]/40 text-[#f97316]"
|
||||||
|
: "bg-muted/40 border-border/60 text-muted-foreground opacity-50"
|
||||||
|
}`}
|
||||||
|
title="کلیک برای نمایش/مخفیسازی خط موجودی انبار"
|
||||||
|
>
|
||||||
|
<span className="w-2.5 h-2.5 rounded-full bg-[#f97316] shrink-0" />
|
||||||
|
<span>{stockLine.name} ({stockLine.unit})</span>
|
||||||
|
{showStocks ? <Eye className="w-3 h-3" /> : <EyeOff className="w-3 h-3" />}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{stockLine && (
|
||||||
|
<span className="text-[11px] text-muted-foreground font-medium hidden sm:inline">
|
||||||
|
* محور راست: قیمت ({priceLine?.unit}) • محور چپ: موجودی انبار ({stockLine?.unit})
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{diagramLoading ? (
|
{diagramLoading ? (
|
||||||
<div className="h-80 flex flex-col items-center justify-center gap-2 text-muted-foreground text-sm">
|
<div className="h-80 flex flex-col items-center justify-center gap-2 text-muted-foreground text-sm">
|
||||||
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
||||||
<span>در حال بارگذاری دیتای کامل تاریخی (Entire Data)...</span>
|
<span>در حال بارگذاری تمام دادههای تاریخی...</span>
|
||||||
</div>
|
</div>
|
||||||
) : chartData.length === 0 ? (
|
) : chartData.length === 0 ? (
|
||||||
<div className="h-80 flex items-center justify-center text-muted-foreground text-sm">
|
<div className="h-80 flex items-center justify-center text-muted-foreground text-sm">
|
||||||
|
|
@ -648,14 +691,20 @@ export function LmeView() {
|
||||||
) : (
|
) : (
|
||||||
<div className="h-80 sm:h-96 w-full" dir="ltr">
|
<div className="h-80 sm:h-96 w-full" dir="ltr">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<ComposedChart data={chartData} margin={{ top: 10, right: 15, left: 10, bottom: 0 }}>
|
<ComposedChart data={chartData} margin={{ top: 10, right: 15, left: 15, bottom: 0 }}>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="chartGradient" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id="chartGradientPrice" x1="0" y1="0" x2="0" y2="1">
|
||||||
<stop offset="5%" stopColor="#D02E00" stopOpacity={0.25} />
|
<stop offset="5%" stopColor="#D02E00" stopOpacity={0.25} />
|
||||||
<stop offset="95%" stopColor="#D02E00" stopOpacity={0.0} />
|
<stop offset="95%" stopColor="#D02E00" stopOpacity={0.0} />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
<linearGradient id="chartGradientStocks" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="5%" stopColor="#f97316" stopOpacity={0.18} />
|
||||||
|
<stop offset="95%" stopColor="#f97316" stopOpacity={0.0} />
|
||||||
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<CartesianGrid strokeDasharray="2 2" opacity={0.15} vertical={true} />
|
<CartesianGrid strokeDasharray="2 2" opacity={0.15} vertical={true} />
|
||||||
|
|
||||||
|
{/* X Axis */}
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="x"
|
dataKey="x"
|
||||||
tick={{ fontSize: 10, fill: "var(--muted-foreground)" }}
|
tick={{ fontSize: 10, fill: "var(--muted-foreground)" }}
|
||||||
|
|
@ -666,64 +715,112 @@ export function LmeView() {
|
||||||
}}
|
}}
|
||||||
minTickGap={40}
|
minTickGap={40}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Right Y-Axis for Price */}
|
||||||
<YAxis
|
<YAxis
|
||||||
domain={["auto", "auto"]}
|
yAxisId="price"
|
||||||
tick={{ fontSize: 10, fill: "var(--muted-foreground)" }}
|
|
||||||
tickFormatter={(v) => v.toLocaleString()}
|
|
||||||
orientation="right"
|
orientation="right"
|
||||||
|
domain={["auto", "auto"]}
|
||||||
|
tick={{ fontSize: 10, fill: "#D02E00" }}
|
||||||
|
tickFormatter={(v) => v.toLocaleString()}
|
||||||
|
hide={!showPrice}
|
||||||
/>
|
/>
|
||||||
{/* Tooltip styled exactly like Westmetall screenshot */}
|
|
||||||
|
{/* Left Y-Axis for Warehouse Stocks (Scaled properly!) */}
|
||||||
|
{stockLine && (
|
||||||
|
<YAxis
|
||||||
|
yAxisId="stocks"
|
||||||
|
orientation="left"
|
||||||
|
domain={["auto", "auto"]}
|
||||||
|
tick={{ fontSize: 10, fill: "#f97316" }}
|
||||||
|
tickFormatter={(v) => `${(v / 1000).toFixed(0)}k`}
|
||||||
|
hide={!showStocks}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Persian Tooltip */}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={({ active, payload, label }) => {
|
content={({ active, payload, label }) => {
|
||||||
if (!active || !payload?.length) return null;
|
if (!active || !payload?.length) return null;
|
||||||
const pt = payload[0];
|
const pt = payload[0];
|
||||||
const ptData = pt.payload;
|
const ptData = pt.payload;
|
||||||
const lineName = pt.name;
|
|
||||||
const val = pt.value;
|
|
||||||
const unit = diagramData?.lines?.[0]?.unit || "";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-amber-600/40 bg-[#D02E00] px-3.5 py-2.5 text-white shadow-xl">
|
<div className="rounded-xl border border-border/80 bg-card/95 p-3 text-foreground shadow-2xl backdrop-blur-md space-y-1.5 min-w-[170px]" dir="rtl">
|
||||||
<div className="text-[11px] font-semibold tracking-wider font-mono opacity-90">
|
<div className="text-[11px] font-medium text-muted-foreground border-b border-border/40 pb-1 font-mono">
|
||||||
{lineName}
|
تاریخ: {ptData.date_display || label}
|
||||||
</div>
|
|
||||||
<div className="text-[10px] font-mono opacity-80 mt-0.5">
|
|
||||||
{ptData.date_display || label}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm font-bold font-mono mt-1">
|
|
||||||
{typeof val === "number" ? val.toLocaleString(undefined, { minimumFractionDigits: 2 }) : val} {unit}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showPrice && ptData.priceVal !== undefined && (
|
||||||
|
<div className="flex items-center justify-between gap-3 text-xs">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-[#D02E00]" />
|
||||||
|
<span>{priceLine?.name || "قیمت"}:</span>
|
||||||
|
</div>
|
||||||
|
<span className="font-mono font-bold text-[#D02E00]">
|
||||||
|
{Number(ptData.priceVal).toLocaleString(undefined, { minimumFractionDigits: 2 })} {priceLine?.unit || ""}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showStocks && ptData.stockVal !== null && ptData.stockVal !== undefined && (
|
||||||
|
<div className="flex items-center justify-between gap-3 text-xs">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-[#f97316]" />
|
||||||
|
<span>{stockLine?.name || "موجودی انبار"}:</span>
|
||||||
|
</div>
|
||||||
|
<span className="font-mono font-bold text-[#f97316]">
|
||||||
|
{Number(ptData.stockVal).toLocaleString()} {stockLine?.unit || "تن"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{diagramData?.lines?.map((line, idx) => (
|
|
||||||
|
{/* Price Curve */}
|
||||||
|
{showPrice && (
|
||||||
<Area
|
<Area
|
||||||
key={line.name}
|
yAxisId="price"
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey={line.name}
|
dataKey="priceVal"
|
||||||
stroke={line.color || "#D02E00"}
|
name={priceLine?.name || "قیمت"}
|
||||||
strokeWidth={1.8}
|
stroke="#D02E00"
|
||||||
|
strokeWidth={2}
|
||||||
fillOpacity={1}
|
fillOpacity={1}
|
||||||
fill={idx === 0 ? "url(#chartGradient)" : "transparent"}
|
fill="url(#chartGradientPrice)"
|
||||||
dot={false}
|
dot={false}
|
||||||
/>
|
/>
|
||||||
))}
|
)}
|
||||||
|
|
||||||
|
{/* Stock Curve (Mapped to Left Y-Axis!) */}
|
||||||
|
{showStocks && stockLine && (
|
||||||
|
<Line
|
||||||
|
yAxisId="stocks"
|
||||||
|
type="monotone"
|
||||||
|
dataKey="stockVal"
|
||||||
|
name={stockLine.name}
|
||||||
|
stroke="#f97316"
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* TIMEFRAME BAR — Exactly matching user's screenshot */}
|
{/* TIMEFRAME BAR (100% Persian) */}
|
||||||
<div className="mt-4 pt-4 border-t border-border/40 flex flex-wrap items-center justify-center gap-1.5" dir="ltr">
|
<div className="pt-3 border-t border-border/40 flex flex-wrap items-center justify-center gap-2" dir="rtl">
|
||||||
{/* Step Backwards */}
|
{/* Step Backwards */}
|
||||||
<button
|
<button
|
||||||
onClick={() => stepWindow("prev")}
|
onClick={() => stepWindow("prev")}
|
||||||
disabled={!canStepPrev}
|
disabled={!canStepPrev}
|
||||||
className="rounded-lg border border-border/80 bg-background/80 px-2.5 py-1.5 text-xs text-foreground transition hover:bg-accent disabled:opacity-30 disabled:cursor-not-allowed"
|
className="rounded-xl border border-border/80 bg-background/80 px-3 py-1.5 text-xs text-foreground transition hover:bg-accent disabled:opacity-30 disabled:cursor-not-allowed"
|
||||||
title="نمایش دادههای قدیمیتر"
|
title="نمایش دادههای قدیمیتر"
|
||||||
>
|
>
|
||||||
<
|
> قبلی
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* 3 months */}
|
{/* 3 months */}
|
||||||
|
|
@ -732,13 +829,13 @@ export function LmeView() {
|
||||||
setTimeframe("3M");
|
setTimeframe("3M");
|
||||||
setOffsetIndex(0);
|
setOffsetIndex(0);
|
||||||
}}
|
}}
|
||||||
className={`rounded-lg border px-3 py-1.5 text-xs font-medium transition ${
|
className={`rounded-xl border px-3.5 py-1.5 text-xs font-semibold transition ${
|
||||||
timeframe === "3M"
|
timeframe === "3M"
|
||||||
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
||||||
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
3 months
|
۳ ماه
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* 6 months */}
|
{/* 6 months */}
|
||||||
|
|
@ -747,13 +844,13 @@ export function LmeView() {
|
||||||
setTimeframe("6M");
|
setTimeframe("6M");
|
||||||
setOffsetIndex(0);
|
setOffsetIndex(0);
|
||||||
}}
|
}}
|
||||||
className={`rounded-lg border px-3 py-1.5 text-xs font-medium transition ${
|
className={`rounded-xl border px-3.5 py-1.5 text-xs font-semibold transition ${
|
||||||
timeframe === "6M"
|
timeframe === "6M"
|
||||||
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
||||||
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
6 months
|
۶ ماه
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* 1 year */}
|
{/* 1 year */}
|
||||||
|
|
@ -762,13 +859,13 @@ export function LmeView() {
|
||||||
setTimeframe("1Y");
|
setTimeframe("1Y");
|
||||||
setOffsetIndex(0);
|
setOffsetIndex(0);
|
||||||
}}
|
}}
|
||||||
className={`rounded-lg border px-3 py-1.5 text-xs font-medium transition ${
|
className={`rounded-xl border px-3.5 py-1.5 text-xs font-semibold transition ${
|
||||||
timeframe === "1Y"
|
timeframe === "1Y"
|
||||||
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
||||||
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
1 year
|
۱ سال
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* 2 year */}
|
{/* 2 year */}
|
||||||
|
|
@ -777,13 +874,13 @@ export function LmeView() {
|
||||||
setTimeframe("2Y");
|
setTimeframe("2Y");
|
||||||
setOffsetIndex(0);
|
setOffsetIndex(0);
|
||||||
}}
|
}}
|
||||||
className={`rounded-lg border px-3 py-1.5 text-xs font-medium transition ${
|
className={`rounded-xl border px-3.5 py-1.5 text-xs font-semibold transition ${
|
||||||
timeframe === "2Y"
|
timeframe === "2Y"
|
||||||
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
? "border-primary bg-primary text-primary-foreground shadow-sm"
|
||||||
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
2 year
|
۲ سال
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* entire data (ACTIVE HIGHLIGHT) */}
|
{/* entire data (ACTIVE HIGHLIGHT) */}
|
||||||
|
|
@ -792,23 +889,23 @@ export function LmeView() {
|
||||||
setTimeframe("ENTIRE");
|
setTimeframe("ENTIRE");
|
||||||
setOffsetIndex(0);
|
setOffsetIndex(0);
|
||||||
}}
|
}}
|
||||||
className={`rounded-lg border px-3 py-1.5 text-xs font-bold transition ${
|
className={`rounded-xl border px-4 py-1.5 text-xs font-bold transition ${
|
||||||
timeframe === "ENTIRE"
|
timeframe === "ENTIRE"
|
||||||
? "border-primary bg-primary text-primary-foreground shadow-md ring-2 ring-primary/20"
|
? "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"
|
: "border-border/80 bg-background/80 text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
entire data
|
کل دادهها (تاریخچه کامل)
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Step Forwards */}
|
{/* Step Forwards */}
|
||||||
<button
|
<button
|
||||||
onClick={() => stepWindow("next")}
|
onClick={() => stepWindow("next")}
|
||||||
disabled={!canStepNext}
|
disabled={!canStepNext}
|
||||||
className="rounded-lg border border-border/80 bg-background/80 px-2.5 py-1.5 text-xs text-foreground transition hover:bg-accent disabled:opacity-30 disabled:cursor-not-allowed"
|
className="rounded-xl border border-border/80 bg-background/80 px-3 py-1.5 text-xs text-foreground transition hover:bg-accent disabled:opacity-30 disabled:cursor-not-allowed"
|
||||||
title="نمایش دادههای جدیدتر"
|
title="نمایش دادههای جدیدتر"
|
||||||
>
|
>
|
||||||
>
|
بعدی <
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue