418 lines
12 KiB
TypeScript
418 lines
12 KiB
TypeScript
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>
|
||
);
|
||
}
|