StockRow.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. 'use client';
  2. // D6 §5.1 — 종목행(StockRow) 열 구성 빌더 + StockDataTable 편의 래퍼.
  3. // 한 줄 완결: 종목명+코드 | 현재가 | 등락률(색+부호) | 거래대금 | 시가총액 | 시장.
  4. // 정렬: 종목명·현재가·등락률·거래대금·시가총액 헤더 클릭 → SSR 재조회(URL sort/order). 기본 시가총액 desc.
  5. // 거래대금/시총 등 우선순위 낮은 열은 mobile drop (data-priority='low').
  6. import Link from 'next/link';
  7. import { useRouter } from 'next/navigation';
  8. import { useCallback } from 'react';
  9. import DataTable, { DataTableColumn } from '@/components/table/DataTable';
  10. import PriceChange from '@/components/table/PriceChange';
  11. import { StockListRow, StockSortKey, StockSortOrder } from '@/types/stock';
  12. import { formatKrwCompact, formatNumber, tradingStatusLabel } from '@/lib/utils/stock';
  13. // 활성 정렬 컬럼만 방향, 나머지 정렬 가능 컬럼은 null(비활성)
  14. function colSort(key: StockSortKey, activeSort: StockSortKey, order: StockSortOrder): StockSortOrder|null {
  15. return key === activeSort ? order : null;
  16. }
  17. export function buildStockColumns(sort: StockSortKey, order: StockSortOrder): DataTableColumn<StockListRow>[] {
  18. return [
  19. {
  20. key: 'name',
  21. header: '종목명',
  22. align: 'left',
  23. priority: 'high',
  24. sort: colSort('name', sort, order),
  25. cellClassName: 'data-table__cell--name',
  26. cell: (row) => {
  27. const status = tradingStatusLabel(row.tradingStatus);
  28. return (
  29. <Link href={`/stock/${row.code}`} className='stock-cell'>
  30. <span className='stock-cell__name'>{row.name}</span>
  31. <span className='stock-cell__code'>{row.code}</span>
  32. {status && (
  33. <span className={`stock-badge stock-badge--${row.tradingStatus.toLowerCase()}`}>{status}</span>
  34. )}
  35. </Link>
  36. );
  37. }
  38. },
  39. {
  40. key: 'price',
  41. header: <>현재가 <small>(전일종가)</small></>,
  42. align: 'right',
  43. priority: 'high',
  44. sort: colSort('price', sort, order),
  45. cell: (row) => formatNumber(row.closePrice)
  46. },
  47. {
  48. key: 'change',
  49. header: '등락률',
  50. align: 'right',
  51. priority: 'high',
  52. sort: colSort('change', sort, order),
  53. cell: (row) => <PriceChange rate={row.changeRate} />
  54. },
  55. {
  56. key: 'value',
  57. header: '거래대금',
  58. align: 'right',
  59. priority: 'low',
  60. sort: colSort('value', sort, order),
  61. cell: (row) => formatKrwCompact(row.tradingValue)
  62. },
  63. {
  64. key: 'cap',
  65. header: '시가총액',
  66. align: 'right',
  67. priority: 'low',
  68. sort: colSort('cap', sort, order),
  69. cell: (row) => formatKrwCompact(row.marketCap)
  70. },
  71. {
  72. key: 'market',
  73. header: '시장',
  74. align: 'center',
  75. priority: 'medium',
  76. cell: (row) => <span className={`stock-badge stock-badge--${row.market.toLowerCase()}`}>{row.market}</span>
  77. }
  78. ];
  79. }
  80. interface StockDataTableProps {
  81. rows: StockListRow[];
  82. sort: StockSortKey;
  83. order: StockSortOrder;
  84. // 정렬 URL 재구성 시 유지할 현재 필터
  85. market?: string;
  86. q?: string;
  87. caption?: string;
  88. emptyMessage?: string;
  89. }
  90. // 종목 목록 편의 래퍼 — 정렬 헤더 클릭 시 sort/order URL 로 SSR 재조회(필터 유지·페이지 1로 리셋)
  91. export default function StockDataTable({ rows, sort, order, market, q, caption, emptyMessage }: StockDataTableProps)
  92. {
  93. const router = useRouter();
  94. const handleSort = useCallback((key: string) => {
  95. const k = key as StockSortKey;
  96. // 같은 컬럼 재클릭 → 방향 토글, 새 컬럼 → 기본 방향(종목명 asc, 숫자열 desc)
  97. const nextOrder: StockSortOrder = k === sort ? (order === 'desc' ? 'asc' : 'desc') : (k === 'name' ? 'asc' : 'desc');
  98. const params = new URLSearchParams();
  99. if (market) {
  100. params.set('market', market);
  101. }
  102. if (q) {
  103. params.set('q', q);
  104. }
  105. params.set('sort', k);
  106. params.set('order', nextOrder);
  107. router.push(`/stock?${params.toString()}`);
  108. }, [router, sort, order, market, q]);
  109. return (
  110. <DataTable<StockListRow>
  111. caption={caption ?? '종목 목록 — 종목명, 현재가(전일 종가), 등락률, 거래대금, 시가총액, 시장'}
  112. columns={buildStockColumns(sort, order)}
  113. rows={rows}
  114. rowKey={(row) => row.code}
  115. onSort={handleSort}
  116. emptyMessage={emptyMessage}
  117. />
  118. );
  119. }