import './style.scss'; import '@/app/styles/stock-common.scss'; import { Metadata } from 'next'; import Link from 'next/link'; import { fetchStockList } from '@/lib/api/stock'; import { fetchTrendingStocks } from '@/lib/api/community'; import { StockMarketName, StockSortKey, StockSortOrder } from '@/types/stock'; import { formatNumber } from '@/lib/utils/stock'; import StockDataTable from '@/components/table/StockRow'; import TrendingStocks from '@/components/community/TrendingStocks'; import Pager from './_components/Pager'; export const metadata: Metadata = { title: '종목 — 개미투자', description: '국내 상장 전 종목 목록 — 전일 종가·등락률·시가총액 (KOSPI/KOSDAQ/KONEX)' }; const MARKET_TABS: { value: ''|StockMarketName; label: string }[] = [ { value: '', label: '전체' }, { value: 'KOSPI', label: 'KOSPI' }, { value: 'KOSDAQ', label: 'KOSDAQ' }, { value: 'KONEX', label: 'KONEX' } ]; const PER_PAGE = 30; type Props = { searchParams: Promise<{ market?: string; q?: string; page?: string; sort?: string; order?: string; }>; }; const SORT_KEYS: readonly StockSortKey[] = ['name', 'price', 'change', 'cap', 'value']; export default async function StockListPage({ searchParams }: Props) { const query = await searchParams; const market = MARKET_TABS.some(c => c.value === query.market) ? (query.market as ''|StockMarketName) : ''; const q = (query.q ?? '').trim(); const page = Math.max(Number(query.page) || 1, 1); // 정렬 — 화이트리스트 검증. 기본 시가총액 내림차순 const sort: StockSortKey = SORT_KEYS.includes(query.sort as StockSortKey) ? (query.sort as StockSortKey) : 'cap'; const order: StockSortOrder = query.order === 'asc' ? 'asc' : 'desc'; // 백엔드 미기동 시에도 페이지 자체는 렌더 — 테이블 영역만 에러 표시 (request-time fetch) const res = await fetchStockList({ q, market, page, perPage: PER_PAGE, sort, order }); const data = res.success ? res.data : null; // 트렌딩 위젯 — 검색·필터 없는 1페이지에서만 노출 (실패해도 페이지는 렌더) const trending = !q && !market && page === 1 ? await fetchTrendingStocks({ count: 10 }) : null; const trendingRows = trending?.success ? (trending.data?.list ?? []) : []; const buildHref = (nextMarket: ''|StockMarketName) => { const params = new URLSearchParams(); if (nextMarket) { params.set('market', nextMarket); } if (q) { params.set('q', q); } // 시장 전환 시 현재 정렬 유지 (기본값이면 생략해 URL 을 깔끔하게) if (sort !== 'cap' || order !== 'desc') { params.set('sort', sort); params.set('order', order); } const qs = params.toString(); return qs ? `/stock?${qs}` : '/stock'; }; return (
전체 {formatNumber(data.total)}종목
{/* D6 §5.1 — 재사용 DataTable(StockRow) 로 렌더 (정렬 헤더 클릭 → SSR 재조회) */}