fix(ime/lme): eliminate duplicate titles and double prices, convert all calendars and dates to Persian Jalali
This commit is contained in:
parent
9a36873dbe
commit
aec46eb0e6
|
|
@ -33,20 +33,20 @@ HEADERS = {
|
||||||
def clean_persian_title(title: str) -> tuple[str, str]:
|
def clean_persian_title(title: str) -> tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
Removes English symbol codes (e.g. FTKC-BSG017OO-00, KHSS-DRIBRI-00)
|
Removes English symbol codes (e.g. FTKC-BSG017OO-00, KHSS-DRIBRI-00)
|
||||||
and returns (clean_title, manufacturer).
|
and returns (product_name, manufacturer).
|
||||||
|
Avoids duplicate manufacturer names.
|
||||||
"""
|
"""
|
||||||
if not title:
|
if not title:
|
||||||
return "", ""
|
return "", ""
|
||||||
parts = [p.strip() for p in title.split(" - ") if p.strip()]
|
parts = [p.strip() for p in title.split(" - ") if p.strip()]
|
||||||
if len(parts) >= 3:
|
if len(parts) >= 3:
|
||||||
if re.search(r'^[A-Za-z0-9_-]+$', parts[1]):
|
product = parts[0]
|
||||||
product = parts[0]
|
manufacturer = parts[2]
|
||||||
manufacturer = parts[2]
|
return product, manufacturer
|
||||||
return f"{product} ({manufacturer})", manufacturer
|
|
||||||
elif len(parts) == 2:
|
elif len(parts) == 2:
|
||||||
if re.search(r'^[A-Za-z0-9_-]+$', parts[1]):
|
if re.search(r'^[A-Za-z0-9_-]+$', parts[1]):
|
||||||
return parts[0], ""
|
return parts[0], ""
|
||||||
return f"{parts[0]} ({parts[1]})", parts[1]
|
return parts[0], parts[1]
|
||||||
|
|
||||||
cleaned = re.sub(r'\b[A-Za-z0-9]{3,}-[A-Za-z0-9_-]+\b', '', title)
|
cleaned = re.sub(r'\b[A-Za-z0-9]{3,}-[A-Za-z0-9_-]+\b', '', title)
|
||||||
cleaned = re.sub(r'\s*-\s*-+\s*', ' - ', cleaned).strip(" -")
|
cleaned = re.sub(r'\s*-\s*-+\s*', ' - ', cleaned).strip(" -")
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import {
|
||||||
CartesianGrid
|
CartesianGrid
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { API_BASE } from "@/lib/api";
|
import { API_BASE } from "@/lib/api";
|
||||||
|
import { toJalali } from "@/lib/metals-data";
|
||||||
|
|
||||||
const API = API_BASE;
|
const API = API_BASE;
|
||||||
|
|
||||||
|
|
@ -355,7 +356,7 @@ export function ImeView() {
|
||||||
onClick={() => setSelectedSymbol(sym)}
|
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"
|
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="space-y-1.5">
|
||||||
<div className="text-sm font-bold text-foreground group-hover:text-primary transition-colors line-clamp-2">
|
<div className="text-sm font-bold text-foreground group-hover:text-primary transition-colors line-clamp-2">
|
||||||
{sym.product_name || sym.title}
|
{sym.product_name || sym.title}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -368,22 +369,18 @@ export function ImeView() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* LATEST PRICE PROMINENTLY DISPLAYED ON CARD FACE */}
|
{/* LATEST PRICE: Clean, prominent single price in Tomans */}
|
||||||
<div className="mt-4 pt-3 border-t border-border/40 space-y-2">
|
<div className="mt-4 pt-3 border-t border-border/40 space-y-2">
|
||||||
<div className="flex items-baseline justify-between">
|
<div className="flex items-baseline justify-between">
|
||||||
<span className="text-xs text-muted-foreground">آخرین قیمت:</span>
|
<span className="text-xs text-muted-foreground">آخرین نرخ کشفشده:</span>
|
||||||
<div className="text-left font-mono">
|
<div className="text-left font-mono">
|
||||||
{sym.latest_price?.mid ? (
|
{sym.latest_price?.mid ? (
|
||||||
<div>
|
<span className="text-base sm:text-lg font-bold text-primary">
|
||||||
<div className="text-lg font-bold text-primary">
|
{(Math.round(sym.latest_price.mid / 10)).toLocaleString()}{" "}
|
||||||
{sym.latest_price.mid.toLocaleString()} <span className="text-xs font-normal text-muted-foreground">ریال</span>
|
<span className="text-xs font-normal text-muted-foreground">تومان/کیلو</span>
|
||||||
</div>
|
</span>
|
||||||
<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>
|
<span className="text-xs text-muted-foreground">بدون معامله اخیر</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -395,7 +392,7 @@ export function ImeView() {
|
||||||
|
|
||||||
{sym.latest_price?.date && (
|
{sym.latest_price?.date && (
|
||||||
<span className="text-[10px] text-muted-foreground font-mono">
|
<span className="text-[10px] text-muted-foreground font-mono">
|
||||||
{sym.latest_price.date}
|
{toJalali(sym.latest_price.date)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -446,24 +443,24 @@ export function ImeView() {
|
||||||
<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 className="text-base sm:text-lg font-bold font-mono text-foreground mt-0.5">
|
<div className="text-base sm:text-lg font-bold font-mono text-foreground mt-0.5">
|
||||||
{stats.last.toLocaleString()} ریال
|
{(Math.round(stats.last / 10)).toLocaleString()} تومان
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-muted-foreground mt-0.5">
|
<div className="text-[10px] text-muted-foreground mt-0.5">
|
||||||
{(Math.round(stats.last / 10)).toLocaleString()} تومان
|
{stats.last.toLocaleString()} ریال
|
||||||
</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 className="text-base sm:text-lg font-bold font-mono text-foreground mt-0.5">
|
<div className="text-base sm:text-lg font-bold font-mono text-foreground mt-0.5">
|
||||||
{stats.max.toLocaleString()} ریال
|
{(Math.round(stats.max / 10)).toLocaleString()} تومان
|
||||||
</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 className="text-base sm:text-lg font-bold font-mono text-foreground mt-0.5">
|
<div className="text-base sm:text-lg font-bold font-mono text-foreground mt-0.5">
|
||||||
{stats.min.toLocaleString()} ریال
|
{(Math.round(stats.min / 10)).toLocaleString()} تومان
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -475,7 +472,7 @@ export function ImeView() {
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{stats.diff >= 0 ? "+" : ""}
|
{stats.diff >= 0 ? "+" : ""}
|
||||||
{stats.diff.toLocaleString()} ({stats.pct.toFixed(1)}%)
|
{(Math.round(stats.diff / 10)).toLocaleString()} تومان ({stats.pct.toFixed(1)}%)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -506,12 +503,17 @@ export function ImeView() {
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="date"
|
dataKey="date"
|
||||||
tick={{ fontSize: 10, fill: "var(--muted-foreground)" }}
|
tick={{ fontSize: 10, fill: "var(--muted-foreground)" }}
|
||||||
|
tickFormatter={(val) => {
|
||||||
|
const jal = toJalali(val);
|
||||||
|
const parts = jal.split("/");
|
||||||
|
return parts.length === 3 ? `${parts[1]}/${parts[2]}` : jal;
|
||||||
|
}}
|
||||||
minTickGap={35}
|
minTickGap={35}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
domain={["auto", "auto"]}
|
domain={["auto", "auto"]}
|
||||||
tick={{ fontSize: 10, fill: "var(--muted-foreground)" }}
|
tick={{ fontSize: 10, fill: "var(--muted-foreground)" }}
|
||||||
tickFormatter={(v) => `${(v / 1000).toLocaleString()}k`}
|
tickFormatter={(v) => `${(v / 10000).toLocaleString()}k ت`}
|
||||||
orientation="right"
|
orientation="right"
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
|
@ -521,16 +523,16 @@ export function ImeView() {
|
||||||
return (
|
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="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">
|
<div className="text-[11px] text-muted-foreground font-mono pb-1 border-b border-border/40">
|
||||||
تاریخ معامله: {pt.date}
|
تاریخ معامله: {toJalali(pt.date)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between text-xs pt-1">
|
<div className="flex items-center justify-between text-xs pt-1">
|
||||||
<span className="text-muted-foreground">قیمت پایانی:</span>
|
<span className="text-muted-foreground">قیمت پایانی:</span>
|
||||||
<span className="font-mono font-bold text-primary">
|
<span className="font-mono font-bold text-primary">
|
||||||
{pt.mid.toLocaleString()} ریال
|
{(Math.round(pt.mid / 10)).toLocaleString()} تومان
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-muted-foreground">
|
<div className="text-[10px] text-muted-foreground font-mono">
|
||||||
معادل {(Math.round(pt.mid / 10)).toLocaleString()} تومان
|
({pt.mid.toLocaleString()} ریال)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { toJalali } from "@/lib/metals-data";
|
||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
|
|
@ -678,8 +679,9 @@ export function LmeView() {
|
||||||
tick={{ fontSize: 10, fill: "var(--muted-foreground)" }}
|
tick={{ fontSize: 10, fill: "var(--muted-foreground)" }}
|
||||||
tickFormatter={(val) => {
|
tickFormatter={(val) => {
|
||||||
if (!val) return "";
|
if (!val) return "";
|
||||||
const p = val.split("-");
|
const jal = toJalali(val);
|
||||||
return p.length === 3 ? `${p[0]}/${p[1]}` : val;
|
const p = jal.split("/");
|
||||||
|
return p.length === 3 ? `${p[0]}/${p[1]}` : jal;
|
||||||
}}
|
}}
|
||||||
minTickGap={40}
|
minTickGap={40}
|
||||||
/>
|
/>
|
||||||
|
|
@ -716,7 +718,7 @@ export function LmeView() {
|
||||||
return (
|
return (
|
||||||
<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="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-medium text-muted-foreground border-b border-border/40 pb-1 font-mono">
|
<div className="text-[11px] font-medium text-muted-foreground border-b border-border/40 pb-1 font-mono">
|
||||||
تاریخ: {ptData.date_display || label}
|
تاریخ: {toJalali(ptData.date || label)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showPrice && ptData.priceVal !== undefined && (
|
{showPrice && ptData.priceVal !== undefined && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue