steelforesight/src/pages/Scanner/Scanner.tsx

418 lines
12 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { marketPrices, globalComparison } from '@/data/market';
import type { MarketPrice } from '@/data/market';
const riskAlerts = [
{
title: 'فشار صادرات چین',
desc: 'افزایش ۱۸٪ صادرات ارزان چین فشار قیمتی روی بازار آسیا',
level: 'high',
},
{
title: 'محدودیت انرژی داخلی',
desc: 'کاهش تولید زمستانی به دلیل محدودیت گاز صنعتی',
level: 'medium',
},
{
title: 'فرصت بازار عراق',
desc: 'کاهش واردات ترکیه، فرصت افزایش سهم بازار برای ایران',
level: 'low',
},
] as const;
type AlertLevel = 'high' | 'medium' | 'low';
const alertBorderColor: Record<AlertLevel, string> = {
high: 'var(--red)',
medium: 'var(--amber)',
low: 'var(--green-ink)',
};
const alertLabelColor: Record<AlertLevel, string> = {
high: 'var(--red)',
medium: 'var(--amber)',
low: 'var(--green-ink)',
};
const alertLabel: Record<AlertLevel, string> = {
high: 'بالا',
medium: 'متوسط',
low: 'فرصت',
};
function formatNumber(n: number): string {
return n.toLocaleString('fa-IR');
}
function PriceTicker({ item }: { item: MarketPrice }) {
const isUp = item.trend === 'up';
const isDown = item.trend === 'down';
const trendArrow = isUp ? '▲' : isDown ? '▼' : '—';
const trendColor = isUp ? 'var(--green-ink)' : isDown ? 'var(--red)' : 'var(--ink-5)';
const changePrefix = item.change > 0 ? '+' : '';
return (
<div
style={{
padding: '24px 20px',
background: 'var(--paper)',
}}
>
{/* Product name */}
<div
style={{
fontSize: '13px',
fontWeight: 700,
color: 'var(--ink-3)',
marginBottom: '10px',
letterSpacing: '0.03em',
}}
>
{item.name}
</div>
{/* Price */}
<div
style={{
fontSize: '34px',
fontWeight: 900,
fontVariantNumeric: 'tabular-nums',
fontFeatureSettings: '"tnum"',
color: 'var(--ink)',
lineHeight: 1,
marginBottom: '6px',
fontFamily: 'monospace, Vazir, sans-serif',
}}
>
{formatNumber(item.value)}
</div>
{/* Unit */}
<div
style={{
fontSize: '11px',
color: 'var(--ink-5)',
marginBottom: '14px',
}}
>
{item.unit}
</div>
{/* Change + trend */}
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<span style={{ fontSize: '22px', color: trendColor, lineHeight: 1 }}>
{trendArrow}
</span>
<span
style={{
fontSize: '13px',
fontWeight: 700,
color: trendColor,
}}
>
{changePrefix}{formatNumber(item.change)}
</span>
<span
style={{
fontSize: '12px',
fontWeight: 600,
color: trendColor,
background: isUp
? 'rgba(34,197,94,0.08)'
: isDown
? 'rgba(220,38,38,0.08)'
: 'transparent',
padding: '2px 7px',
}}
>
{changePrefix}{item.changePercent.toFixed(1)}٪
</span>
</div>
</div>
);
}
export default function Scanner() {
// Use first 6 prices for the grid
const gridPrices = marketPrices.slice(0, 6);
// For CSS bar chart: find max price in globalComparison for proportional width
// Filter to USD-only for a fair comparison (exclude IRR)
const usdPrices = globalComparison.filter((g) => g.currency === 'USD');
const maxUSD = Math.max(...usdPrices.map((g) => g.price));
return (
<div
dir="rtl"
style={{
fontFamily: 'Vazir, sans-serif',
background: 'var(--paper)',
color: 'var(--ink)',
minHeight: '100vh',
}}
>
{/* Page Header */}
<div
style={{
borderBottom: '4px double var(--ink)',
padding: '40px 48px 32px',
}}
>
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<div
style={{
fontSize: '11px',
letterSpacing: '0.12em',
color: 'var(--ink-5)',
fontWeight: 600,
marginBottom: '12px',
}}
>
اندیشکده فولاد آینده دادههای بازار
</div>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-end',
flexWrap: 'wrap',
gap: '16px',
}}
>
<div>
<h1
style={{
fontSize: '36px',
fontWeight: 900,
margin: '0 0 8px',
lineHeight: 1.3,
}}
>
اسکنر بازار جهانی فولاد
</h1>
<p
style={{
fontSize: '15px',
color: 'var(--ink-4)',
margin: 0,
}}
>
دادههای لحظهای قیمتهای بازار داخلی و جهانی
</p>
</div>
<span
style={{
fontSize: '12px',
color: 'var(--ink-4)',
border: '1px solid var(--rule-thin)',
padding: '4px 12px',
background: 'var(--paper-2)',
whiteSpace: 'nowrap',
}}
>
بهروز شده: بهمن ۱۴۰۳
</span>
</div>
</div>
</div>
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '40px 48px 64px' }}>
{/* Price Ticker Grid */}
<div style={{ marginBottom: '56px' }}>
<div
style={{
display: 'flex',
alignItems: 'baseline',
gap: '12px',
marginBottom: '20px',
}}
>
<span style={{ display: 'inline-block', width: '4px', height: '20px', background: 'var(--ink)' }} />
<h2 style={{ fontSize: '18px', fontWeight: 800, margin: 0 }}>قیمتهای لحظهای بازار</h2>
</div>
<div
style={{
border: '2px solid var(--ink)',
background: 'var(--rule-thin)',
gap: 1,
}}
className="grid grid-cols-3 max-lg:grid-cols-2 max-sm:grid-cols-1"
>
{gridPrices.map((item) => (
<PriceTicker
key={item.id}
item={item}
/>
))}
</div>
</div>
{/* Global Comparison Bar Chart */}
<div style={{ marginBottom: '56px' }}>
<div
style={{
borderTop: '3px solid var(--ink)',
borderBottom: '1px solid var(--rule-thin)',
padding: '16px 0',
marginBottom: '28px',
display: 'flex',
alignItems: 'baseline',
gap: '16px',
}}
>
<h2 style={{ fontSize: '18px', fontWeight: 800, margin: 0 }}>مقایسه جهانی قیمت میلگرد</h2>
<span style={{ fontSize: '12px', color: 'var(--ink-5)' }}>دلار / تن بازار صادراتی</span>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
{usdPrices.map((entry) => {
const barPct = Math.round((entry.price / maxUSD) * 100);
return (
<div key={entry.country} style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
{/* Flag + Country */}
<div
style={{
width: '110px',
display: 'flex',
alignItems: 'center',
gap: '8px',
flexShrink: 0,
}}
>
<span style={{ fontSize: '20px', lineHeight: 1 }}>{entry.flag}</span>
<span style={{ fontSize: '14px', fontWeight: 600, color: 'var(--ink)' }}>
{entry.country}
</span>
</div>
{/* Bar */}
<div
style={{
flex: 1,
height: '28px',
background: 'var(--paper-3)',
position: 'relative',
overflow: 'hidden',
}}
>
<div
style={{
position: 'absolute',
top: 0,
right: 0,
height: '100%',
width: `${barPct}%`,
background: entry.country === 'چین' ? '#7f1d1d' : 'var(--ink)',
transition: 'width 0.4s ease',
}}
/>
</div>
{/* Value */}
<div
style={{
width: '90px',
textAlign: 'left',
flexShrink: 0,
}}
>
<span
style={{
fontSize: '16px',
fontWeight: 800,
fontVariantNumeric: 'tabular-nums',
color: 'var(--ink)',
}}
>
${entry.price}
</span>
<span style={{ fontSize: '11px', color: 'var(--ink-5)', marginRight: '4px' }}>/تن</span>
</div>
</div>
);
})}
</div>
</div>
{/* Active Risk Cards */}
<div>
<div
style={{
borderTop: '3px solid var(--red)',
borderBottom: '1px solid var(--rule-thin)',
padding: '16px 0',
marginBottom: '24px',
display: 'flex',
alignItems: 'baseline',
gap: '16px',
}}
>
<h2 style={{ fontSize: '18px', fontWeight: 800, margin: 0 }}>ریسکهای بازار فعال</h2>
<span style={{ fontSize: '12px', color: 'var(--ink-5)' }}>هشدارهای جاری</span>
</div>
<div
className="grid grid-cols-3 max-lg:grid-cols-2 max-sm:grid-cols-1 gap-4"
>
{riskAlerts.map((alert) => {
const level = alert.level as AlertLevel;
return (
<div
key={alert.title}
style={{
background: 'var(--paper)',
border: '1px solid var(--rule-thin)',
borderRight: `4px solid ${alertBorderColor[level]}`,
padding: '20px',
}}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '10px',
}}
>
<h3
style={{
fontSize: '15px',
fontWeight: 700,
margin: 0,
color: 'var(--ink)',
}}
>
{alert.title}
</h3>
<span
style={{
fontSize: '11px',
fontWeight: 700,
color: alertLabelColor[level],
border: `1px solid ${alertLabelColor[level]}`,
padding: '2px 8px',
}}
>
{alertLabel[level]}
</span>
</div>
<p
style={{
fontSize: '13px',
lineHeight: 1.8,
color: 'var(--ink-4)',
margin: 0,
}}
>
{alert.desc}
</p>
</div>
);
})}
</div>
</div>
</div>
</div>
);
}