page.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import '../style.scss';
  2. import '@/app/styles/data-table.scss';
  3. import { Metadata } from 'next';
  4. import Link from 'next/link';
  5. import MarketNav from '../_components/MarketNav';
  6. import EsgSecuritiesTable from '../_components/EsgSecuritiesTable';
  7. import EsgIndicesTable from '../_components/EsgIndicesTable';
  8. import SriBondsTable from '../_components/SriBondsTable';
  9. import Pager from '../_components/Pager';
  10. import { fetchMarketEsgSecurities, fetchMarketEsgIndices, fetchMarketSriBonds } from '@/lib/api/market';
  11. import { formatNumber } from '@/lib/utils/stock';
  12. import { tradeDateLabel } from '@/lib/utils/market';
  13. export const metadata: Metadata = {
  14. title: 'ESG (사회책임투자) — 개미투자',
  15. description: 'ESG 증권·ESG 지수·SRI 채권 시세 — 사회책임투자 시장 데이터 (전일 종가 기준)'
  16. };
  17. type EsgTab = 'securities'|'indices'|'sri-bonds';
  18. const TABS: { value: EsgTab; label: string }[] = [
  19. { value: 'securities', label: 'ESG 증권' },
  20. { value: 'indices', label: 'ESG 지수' },
  21. { value: 'sri-bonds', label: 'SRI 채권' }
  22. ];
  23. const PER_PAGE = 20;
  24. type Props = {
  25. searchParams: Promise<{
  26. tab?: string;
  27. page?: string;
  28. }>;
  29. };
  30. export default async function MarketEsgPage({ searchParams }: Props)
  31. {
  32. const query = await searchParams;
  33. const tab: EsgTab = TABS.some(c => c.value === query.tab) ? (query.tab as EsgTab) : 'securities';
  34. const page = Math.max(Number(query.page) || 1, 1);
  35. const securitiesRes = tab === 'securities' ? await fetchMarketEsgSecurities({ page }) : null;
  36. const indicesRes = tab === 'indices' ? await fetchMarketEsgIndices({ page }) : null;
  37. const sriBondsRes = tab === 'sri-bonds' ? await fetchMarketSriBonds({ page }) : null;
  38. const res = securitiesRes ?? indicesRes ?? sriBondsRes!;
  39. const data = res.success ? res.data : null;
  40. const tradeDate = securitiesRes?.data?.tradeDate ?? indicesRes?.data?.tradeDate ?? sriBondsRes?.data?.tradeDate;
  41. return (
  42. <div className='market'>
  43. <div className='market__head'>
  44. <h1 className='market__title'>ESG</h1>
  45. <span className='market__basis'>{tradeDateLabel(tradeDate)} · 사회책임투자</span>
  46. </div>
  47. <MarketNav />
  48. <div className='market__filters'>
  49. <div className='market__filter-group' role='group' aria-label='ESG 구분'>
  50. <span className='market__filter-label'>구분</span>
  51. {TABS.map(item => (
  52. <Link
  53. key={item.value}
  54. href={`/market/esg?tab=${item.value}`}
  55. className={`market__tab${tab === item.value ? ' market__tab--active' : ''}`}
  56. {...(tab === item.value ? { 'aria-current': 'page' as const } : {})}
  57. >
  58. {item.label}
  59. </Link>
  60. ))}
  61. </div>
  62. </div>
  63. {!res.success ? (
  64. <div className='market__error' role='alert'>
  65. <strong>ESG 데이터를 불러오지 못했습니다.</strong>
  66. 잠시 후 다시 시도해 주세요.
  67. </div>
  68. ) : !data || data.list.length === 0 ? (
  69. <div className='market__empty'>표시할 항목이 없습니다.</div>
  70. ) : (
  71. <>
  72. <p className='market__total'>전체 {formatNumber(data.total)}건</p>
  73. <div className='market__table-wrap'>
  74. {tab === 'securities' && <EsgSecuritiesTable rows={securitiesRes!.data!.list} />}
  75. {tab === 'indices' && <EsgIndicesTable rows={indicesRes!.data!.list} />}
  76. {tab === 'sri-bonds' && <SriBondsTable rows={sriBondsRes!.data!.list} />}
  77. </div>
  78. <Pager
  79. total={data.total}
  80. page={page}
  81. perPage={PER_PAGE}
  82. basePath='/market/esg'
  83. query={{ tab }}
  84. />
  85. </>
  86. )}
  87. </div>
  88. );
  89. }