| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 |
- import './market-movers.scss';
- import { moveDir, formatFlucRate } from '@/types/worldIndex';
- import { type MarketMoversResponse, type MarketMoverGroup, MARKET_LABEL, formatStockPrice, formatMoverCount } from '@/types/marketMover';
- import Sparkline from './Sparkline';
- // 등락 상위 종목 — 상승/하락/상한/하한 4그룹 각 상위 5. 종목명·시장·코드 + 미니 스파크라인 + 현재가·등락률.
- // 상승/하락은 상한·하한 제외(비중복). 등락색 --price-up/down(상승 적/하락 청).
- type Props = {
- data: MarketMoversResponse;
- };
- const GROUPS: { key: 'advances'|'declines'|'limitUp'|'limitDown'; label: string; tone: 'up'|'down' }[] = [
- { key: 'advances', label: '상승', tone: 'up' },
- { key: 'declines', label: '하락', tone: 'down' },
- { key: 'limitUp', label: '상한', tone: 'up' },
- { key: 'limitDown', label: '하한', tone: 'down' }
- ];
- const ARROW = { up: '▲', down: '▼', flat: '–' } as const;
- export default function MarketMovers({ data }: Props)
- {
- const hasAny = data.advances.items.length > 0
- || data.declines.items.length > 0
- || data.limitUp.items.length > 0
- || data.limitDown.items.length > 0;
- if (!hasAny) {
- return null;
- }
- return (
- <section className='mov' aria-label='등락 상위 종목'>
- <div className='mov__head'>
- <h2 className='mov__title'>등락 종목</h2>
- {data.tradeDate && <span className='mov__asof'>{data.tradeDate} 기준</span>}
- </div>
- <div className='mov__grid'>
- {GROUPS.map((g) => (
- <GroupPanel key={g.key} label={g.label} tone={g.tone} group={data[g.key]} />
- ))}
- </div>
- </section>
- );
- }
- function GroupPanel({ label, tone, group }: { label: string; tone: 'up'|'down'; group: MarketMoverGroup })
- {
- return (
- <div className='mov__panel'>
- <div className={`mov__panel-head mov__panel-head--${tone}`}>
- <span className='mov__panel-label'>{label}</span>
- <span className='mov__panel-count'>{formatMoverCount(group.total)}</span>
- </div>
- {group.items.length === 0 ? (
- <p className='mov__empty'>해당 종목 없음</p>
- ) : (
- <ul className='mov__list'>
- {group.items.map((item) => {
- const dir = moveDir(item.flucRateBp);
- return (
- <li key={item.code} className='mov__row'>
- <div className='mov__meta'>
- <span className='mov__name'>{item.name}</span>
- <span className='mov__sub'>{MARKET_LABEL[item.market] ?? ''} · {item.code}</span>
- </div>
- <div className='mov__spark'>
- <Sparkline data={item.spark} dir={dir} />
- </div>
- <div className='mov__nums'>
- <span className='mov__price'>{formatStockPrice(item.close)}</span>
- <span className={`mov__rate mov__rate--${dir}`}>
- <i className='mov__arrow' aria-hidden='true'>{ARROW[dir]}</i>
- {formatFlucRate(item.flucRateBp)}
- </span>
- </div>
- </li>
- );
- })}
- </ul>
- )}
- </div>
- );
- }
|