| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- import './style.scss';
- import '@/app/styles/data-table.scss';
- import { Metadata } from 'next';
- import Link from 'next/link';
- import MarketNav from './_components/MarketNav';
- import DisclosureList from './_components/DisclosureList';
- import { fetchMarketIndices, fetchMarketExchangeRates, fetchMarketInterestRates, fetchDisclosures } from '@/lib/api/market';
- import { MARKET_INDEX_SERIES_LABEL } from '@/types/market';
- import { changeDirection, changeSymbol } from '@/lib/utils/stock';
- import { formatDecimal, formatBpRate, formatRatePercent, tradeDateLabel } from '@/lib/utils/market';
- export const metadata: Metadata = {
- title: '시장 — 개미투자',
- description: '국내 증시 지수·환율·금리·최근 공시 한눈에 (KOSPI/KOSDAQ/KRX·매매기준율·국제금리)'
- };
- // 개요 미니 위젯 노출 개수
- const RATE_MINI_COUNT = 6;
- const FX_MINI_COUNT = 6;
- const DISCLOSURE_MINI_COUNT = 12;
- export default async function MarketOverviewPage()
- {
- // 지수·환율·금리·공시 병렬 조회 (전부 익명 · request-time)
- const [indicesRes, fxRes, ratesRes, disclosuresRes] = await Promise.all([
- fetchMarketIndices(),
- fetchMarketExchangeRates({}),
- fetchMarketInterestRates({ type: 'International' }),
- fetchDisclosures({ page: 1 })
- ]);
- const indices = indicesRes.success ? (indicesRes.data?.list ?? []) : [];
- const fxRows = fxRes.success ? (fxRes.data?.list ?? []) : [];
- const rateRows = ratesRes.success ? (ratesRes.data?.list ?? []) : [];
- const disclosures = disclosuresRes.success ? (disclosuresRes.data?.list ?? []) : [];
- return (
- <div className='market'>
- <div className='market__head'>
- <h1 className='market__title'>시장</h1>
- <span className='market__basis'>전일 종가 기준 (T+1) · 지수/환율/금리/공시</span>
- </div>
- <MarketNav />
- {/* 지수 요약 카드 */}
- <section className='market__section'>
- <div className='market__section-head'>
- <h2 className='market__section-title'>주요 지수</h2>
- </div>
- {indices.length === 0 ? (
- <p className='market__empty'>지수 데이터를 준비 중입니다.</p>
- ) : (
- <div className='market__index-grid'>
- {indices.map(row => {
- const direction = changeDirection(row.flucRateBp);
- return (
- <div key={`${row.series}-${row.name}`} className='market__index-card'>
- <span className='market__index-name'>{MARKET_INDEX_SERIES_LABEL[row.series] ?? row.name} · {row.name}</span>
- <span className='market__index-close'>{formatDecimal(row.close)}</span>
- <span className={`market__index-change market__index-change--${direction}`}>
- <span aria-hidden='true'>{changeSymbol(direction)}</span> {formatDecimal(row.changeVal)} ({formatBpRate(row.flucRateBp)})
- </span>
- </div>
- );
- })}
- </div>
- )}
- </section>
- {/* 환율 + 금리 미니 위젯 */}
- <section className='market__section'>
- <div className='market__widget-row'>
- <div>
- <div className='market__section-head'>
- <h2 className='market__section-title'>환율</h2>
- <span className='market__basis'>{tradeDateLabel(fxRes.data?.tradeDate)}</span>
- </div>
- {fxRows.length === 0 ? (
- <p className='market__empty'>환율 데이터를 준비 중입니다.</p>
- ) : (
- <div className='market__mini-list'>
- {fxRows.slice(0, FX_MINI_COUNT).map(row => (
- <div key={row.currencyUnit} className='market__mini-item'>
- <span className='market__mini-label'>{row.currencyName} ({row.currencyUnit})</span>
- <span className='market__mini-value'>{formatDecimal(row.dealBasRate)}</span>
- </div>
- ))}
- </div>
- )}
- </div>
- <div>
- <div className='market__section-head'>
- <h2 className='market__section-title'>국제 금리</h2>
- <span className='market__basis'>{tradeDateLabel(ratesRes.data?.tradeDate)}</span>
- </div>
- {rateRows.length === 0 ? (
- <p className='market__empty'>금리 데이터를 준비 중입니다.</p>
- ) : (
- <div className='market__mini-list'>
- {rateRows.slice(0, RATE_MINI_COUNT).map((row, index) => (
- <div key={`${row.itemName}-${index}`} className='market__mini-item'>
- <span className='market__mini-label'>{row.itemName}</span>
- <span className='market__mini-value'>{formatRatePercent(row.rate)}</span>
- </div>
- ))}
- </div>
- )}
- </div>
- </div>
- </section>
- {/* 최근 공시 */}
- <section className='market__section'>
- <div className='market__section-head'>
- <h2 className='market__section-title'>최근 공시</h2>
- <Link href='/disclosure' className='market__section-link'>전체 보기</Link>
- </div>
- <DisclosureList rows={disclosures.slice(0, DISCLOSURE_MINI_COUNT)} />
- </section>
- </div>
- );
- }
|