| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- import '../style.scss';
- import '@/app/styles/data-table.scss';
- import { Metadata } from 'next';
- import MarketNav from '../_components/MarketNav';
- import Sparkline from '../_components/Sparkline';
- import MacroTable from '../_components/MacroTable';
- import { fetchMacroIndicators } from '@/lib/api/macro';
- import { MACRO_CODES, MACRO_CODE_LABEL, MacroIndicator } from '@/types/macro';
- import { changeDirection, changeSymbol } from '@/lib/utils/stock';
- import { macroPeriodLabel, formatMacroValue } from '@/lib/utils/market';
- export const metadata: Metadata = {
- title: '거시지표 — 개미투자',
- description: '한국 거시경제지표 대시보드 — 고용률·실업률·취업자수·소비자물가지수·수출입액 추이 (KOSIS 수집)'
- };
- // 지표당 조회할 최근 시점 수 (월간 기준 1년)
- const MACRO_LIMIT = 12;
- // 백엔드는 코드 오름차순 반환 → 화면 고정 순서(MACRO_CODES)로 재정렬 (미정의 코드는 뒤로)
- function sortByCode(list: MacroIndicator[]): MacroIndicator[] {
- const order = new Map<string, number>(MACRO_CODES.map((code, index) => [code, index]));
- return [...list].sort((a, b) => (order.get(a.code) ?? MACRO_CODES.length) - (order.get(b.code) ?? MACRO_CODES.length));
- }
- export default async function MarketMacroPage()
- {
- const res = await fetchMacroIndicators({ codes: [...MACRO_CODES], limit: MACRO_LIMIT });
- const indicators = res.success ? sortByCode(res.data?.list ?? []) : [];
- return (
- <div className='market'>
- <div className='market__head'>
- <h1 className='market__title'>거시지표</h1>
- <span className='market__basis'>고용·물가·수출입 최근 추이 (KOSIS)</span>
- </div>
- <MarketNav />
- {!res.success ? (
- <div className='market__error' role='alert'>
- <strong>거시지표 데이터를 불러오지 못했습니다.</strong>
- 잠시 후 다시 시도해 주세요.
- </div>
- ) : indicators.length === 0 ? (
- <div className='market__empty'>표시할 거시지표 데이터가 없습니다. 수집이 시작되면 자동으로 표시됩니다.</div>
- ) : (
- <>
- {/* 지표별 카드 — 최신값·단위·직전 대비 증감·추이 */}
- <div className='market__series-grid'>
- {indicators.map(indicator => {
- const label = MACRO_CODE_LABEL[indicator.code] ?? indicator.name;
- const previous = indicator.points.length > 1 ? indicator.points[indicator.points.length - 2] : null;
- const delta = indicator.latestValue !== null && previous !== null ? indicator.latestValue - previous.value : null;
- const direction = changeDirection(delta);
- return (
- <div key={indicator.code} className='market__series-card'>
- <div className='market__series-head'>
- <span className='market__series-name' title={indicator.name}>{label}</span>
- <span className='market__cls-badge'>{macroPeriodLabel(indicator.latestPeriod)}</span>
- </div>
- <span className='market__series-latest'>
- {formatMacroValue(indicator.latestValue)}
- {indicator.unit ? <span className='market__series-unit'>{indicator.unit}</span> : null}
- </span>
- <span className={`market__index-change market__index-change--${direction}`}>
- {delta === null ? (
- '직전 대비 —'
- ) : (
- <>
- <span aria-hidden='true'>{changeSymbol(direction)}</span> {formatMacroValue(Math.abs(delta))} <span className='market__series-delta-caption'>직전 대비</span>
- </>
- )}
- </span>
- <Sparkline
- values={indicator.points.map(p => p.value)}
- label={`${label} 추이`}
- />
- <div className='market__series-sub'>
- <span>{macroPeriodLabel(indicator.points[0]?.period)} ~ {macroPeriodLabel(indicator.latestPeriod)}</span>
- <span>{indicator.points.length}개 시점</span>
- </div>
- </div>
- );
- })}
- </div>
- {/* 최근 N기간 매트릭스 */}
- <section className='market__section market__section--macro-table'>
- <div className='market__section-head'>
- <h2 className='market__section-title'>최근 추이</h2>
- <span className='market__basis'>지표별 최근 {MACRO_LIMIT}개 시점</span>
- </div>
- <div className='market__table-wrap'>
- <MacroTable indicators={indicators} maxRows={MACRO_LIMIT} />
- </div>
- </section>
- </>
- )}
- </div>
- );
- }
|